]> git.ipfire.org Git - thirdparty/kernel/stable-queue.git/commitdiff
6.18-stable patches
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 30 Jul 2026 12:25:25 +0000 (14:25 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 30 Jul 2026 12:25:25 +0000 (14:25 +0200)
added patches:
accel-amdxdna-reject-command-submission-on-devices-without-a-submit-op.patch
arm64-dts-qcom-correct-rbr-opp-entry.patch
arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.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
bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch
bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch
cred-add-kernel_cred-helper.patch
cxl-fix-cxl_headerlog_size-to-match-ras-capability-size.patch
cxl-pci-remove-cxl-vh-handling-in-config_pcieaer_cxl-conditional-blocks-from-core-pci.c.patch
cxl-pci-remove-unnecessary-cxl-endpoint-handling-helper-functions.patch
cxl-pci-remove-unnecessary-cxl-rch-handling-helper-functions.patch
dm-avoid-leaking-the-caller-s-thread-keyring-via-the-table-device-file.patch
dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch
dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch
fs-resctrl-fix-use-after-free-during-unmount.patch
fs-resctrl-move-allocation-free-of-closid_num_dirty_rmid.patch
fs-resctrl-move-rmid-initialization-to-first-mount.patch
fs-resctrl-split-l3-dependent-parts-out-of-__mon_event_count.patch
fuse-uring-fix-race-between-registration-and-connection-abortion.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
i3c-mipi-i3c-hci-fix-handling-of-shared-irqs-during-early-initialization.patch
i3c-mipi-i3c-hci-fix-hot-join-nack.patch
kho-add-interfaces-to-unpreserve-folios-page-ranges-and-vmalloc.patch
kho-make-sure-scratch-size-is-always-aligned-by-cma_min_alignment_bytes.patch
landlock-account-all-audit-data-allocations-to-user-space.patch
landlock-fix-formatting.patch
mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.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-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch
net-mana-optimize-irq-affinity-for-low-vcpu-configs.patch
net-mana-validate-the-packet-length-reported-by-the-nic.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-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch
nfsd-pass-nfsd_file-to-nfsd_iter_read.patch
octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch
octeontx2-af-validate-body-pcifunc-in-rvu_mbox_handler_rep_event_notify.patch
ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch
pinctrl-renesas-rzg2l-use-enotsupp-instead-of-eopnotsupp.patch
pmdomain-imx93-blk-ctrl-convert-to-devm_-only.patch
pmdomain-imx93-blk-ctrl-extract-phy-as-shared-domain-for-dsi-csi.patch
remoteproc-xlnx-check-remote-core-state.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
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
vduse-avoid-leaking-information-to-userspace.patch
vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch
vduse-return-internal-vq-group-struct-as-map-token.patch
vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch
x86-fs-resctrl-rename-some-l3-specific-functions.patch
x86-fs-resctrl-rename-struct-rdt_mon_domain-and-rdt_hw_mon_domain.patch
xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch

64 files changed:
queue-6.18/accel-amdxdna-reject-command-submission-on-devices-without-a-submit-op.patch [new file with mode: 0644]
queue-6.18/arm64-dts-qcom-correct-rbr-opp-entry.patch [new file with mode: 0644]
queue-6.18/arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.patch [new file with mode: 0644]
queue-6.18/ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch [new file with mode: 0644]
queue-6.18/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch [new file with mode: 0644]
queue-6.18/audit-use-unsigned-int-instead-of-unsigned.patch [new file with mode: 0644]
queue-6.18/audit-widen-ino-fields-to-u64.patch [new file with mode: 0644]
queue-6.18/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch [new file with mode: 0644]
queue-6.18/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch [new file with mode: 0644]
queue-6.18/cred-add-kernel_cred-helper.patch [new file with mode: 0644]
queue-6.18/cxl-fix-cxl_headerlog_size-to-match-ras-capability-size.patch [new file with mode: 0644]
queue-6.18/cxl-pci-remove-cxl-vh-handling-in-config_pcieaer_cxl-conditional-blocks-from-core-pci.c.patch [new file with mode: 0644]
queue-6.18/cxl-pci-remove-unnecessary-cxl-endpoint-handling-helper-functions.patch [new file with mode: 0644]
queue-6.18/cxl-pci-remove-unnecessary-cxl-rch-handling-helper-functions.patch [new file with mode: 0644]
queue-6.18/dm-avoid-leaking-the-caller-s-thread-keyring-via-the-table-device-file.patch [new file with mode: 0644]
queue-6.18/dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch [new file with mode: 0644]
queue-6.18/dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch [new file with mode: 0644]
queue-6.18/fs-resctrl-fix-use-after-free-during-unmount.patch [new file with mode: 0644]
queue-6.18/fs-resctrl-move-allocation-free-of-closid_num_dirty_rmid.patch [new file with mode: 0644]
queue-6.18/fs-resctrl-move-rmid-initialization-to-first-mount.patch [new file with mode: 0644]
queue-6.18/fs-resctrl-split-l3-dependent-parts-out-of-__mon_event_count.patch [new file with mode: 0644]
queue-6.18/fuse-uring-fix-race-between-registration-and-connection-abortion.patch [new file with mode: 0644]
queue-6.18/gpu-buddy-bail-out-of-try_harder-when-alignment-cannot-be-honoured.patch [new file with mode: 0644]
queue-6.18/gpu-move-drm-buddy-allocator-one-level-up-part-two.patch [new file with mode: 0644]
queue-6.18/i3c-mipi-i3c-hci-fix-handling-of-shared-irqs-during-early-initialization.patch [new file with mode: 0644]
queue-6.18/i3c-mipi-i3c-hci-fix-hot-join-nack.patch [new file with mode: 0644]
queue-6.18/kho-add-interfaces-to-unpreserve-folios-page-ranges-and-vmalloc.patch [new file with mode: 0644]
queue-6.18/kho-make-sure-scratch-size-is-always-aligned-by-cma_min_alignment_bytes.patch [new file with mode: 0644]
queue-6.18/landlock-account-all-audit-data-allocations-to-user-space.patch [new file with mode: 0644]
queue-6.18/landlock-fix-formatting.patch [new file with mode: 0644]
queue-6.18/mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch [new file with mode: 0644]
queue-6.18/mmc-vub300-fix-use-after-free-on-probe-failure.patch [new file with mode: 0644]
queue-6.18/mmc-vub300-rename-probe-error-labels.patch [new file with mode: 0644]
queue-6.18/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch [new file with mode: 0644]
queue-6.18/net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch [new file with mode: 0644]
queue-6.18/net-mana-optimize-irq-affinity-for-low-vcpu-configs.patch [new file with mode: 0644]
queue-6.18/net-mana-validate-the-packet-length-reported-by-the-nic.patch [new file with mode: 0644]
queue-6.18/netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch [new file with mode: 0644]
queue-6.18/netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch [new file with mode: 0644]
queue-6.18/netfilter-nf_tables-remove-register-tracking-infrastructure.patch [new file with mode: 0644]
queue-6.18/netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch [new file with mode: 0644]
queue-6.18/nfsd-pass-nfsd_file-to-nfsd_iter_read.patch [new file with mode: 0644]
queue-6.18/octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch [new file with mode: 0644]
queue-6.18/octeontx2-af-validate-body-pcifunc-in-rvu_mbox_handler_rep_event_notify.patch [new file with mode: 0644]
queue-6.18/ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch [new file with mode: 0644]
queue-6.18/pinctrl-renesas-rzg2l-use-enotsupp-instead-of-eopnotsupp.patch [new file with mode: 0644]
queue-6.18/pmdomain-imx93-blk-ctrl-convert-to-devm_-only.patch [new file with mode: 0644]
queue-6.18/pmdomain-imx93-blk-ctrl-extract-phy-as-shared-domain-for-dsi-csi.patch [new file with mode: 0644]
queue-6.18/remoteproc-xlnx-check-remote-core-state.patch [new file with mode: 0644]
queue-6.18/series
queue-6.18/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch [new file with mode: 0644]
queue-6.18/sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch [new file with mode: 0644]
queue-6.18/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch [new file with mode: 0644]
queue-6.18/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch [new file with mode: 0644]
queue-6.18/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch [new file with mode: 0644]
queue-6.18/thunderbolt-remove-service-debugfs-entries-during-unregister.patch [new file with mode: 0644]
queue-6.18/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch [new file with mode: 0644]
queue-6.18/vduse-avoid-leaking-information-to-userspace.patch [new file with mode: 0644]
queue-6.18/vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch [new file with mode: 0644]
queue-6.18/vduse-return-internal-vq-group-struct-as-map-token.patch [new file with mode: 0644]
queue-6.18/vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch [new file with mode: 0644]
queue-6.18/x86-fs-resctrl-rename-some-l3-specific-functions.patch [new file with mode: 0644]
queue-6.18/x86-fs-resctrl-rename-struct-rdt_mon_domain-and-rdt_hw_mon_domain.patch [new file with mode: 0644]
queue-6.18/xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch [new file with mode: 0644]

diff --git a/queue-6.18/accel-amdxdna-reject-command-submission-on-devices-without-a-submit-op.patch b/queue-6.18/accel-amdxdna-reject-command-submission-on-devices-without-a-submit-op.patch
new file mode 100644 (file)
index 0000000..f4fd3a1
--- /dev/null
@@ -0,0 +1,49 @@
+From stable+bounces-289669-greg=kroah.com@vger.kernel.org Mon Jul 27 21:51:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 15:50:59 -0400
+Subject: accel/amdxdna: reject command submission on devices without a submit op
+To: stable@vger.kernel.org
+Cc: Doruk Tan Ozturk <doruk@0sec.ai>, Lizhi Hou <lizhi.hou@amd.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727195059.1774971-1-sashal@kernel.org>
+
+From: Doruk Tan Ozturk <doruk@0sec.ai>
+
+[ Upstream commit 38953513d7313992676d4136cd425cdb70c6278e ]
+
+amdxdna_cmd_submit() calls xdna->dev_info->ops->cmd_submit()
+unconditionally, but only aie2_dev_ops defines that callback.
+aie4_vf_ops (the AIE4 SR-IOV virtual function) does not, so a user
+AMDXDNA_EXEC_CMD ioctl on an AIE4 device reaches a NULL function-pointer
+call and oopses the kernel. AIE4 submits work through a mapped user queue
+and doorbell, not this ioctl path.
+
+Reject the submission early with -EOPNOTSUPP when the device provides no
+cmd_submit op, so the shared EXEC ioctl is a clean no-op on such devices.
+
+Fixes: aac243092b70 ("accel/amdxdna: Add command execution")
+Cc: stable@vger.kernel.org
+Found by 0sec automated security-research tooling (https://0sec.ai).
+Assisted-by: 0sec:claude-opus-4-8
+Signed-off-by: Doruk Tan Ozturk <doruk@0sec.ai>
+Reviewed-by: Lizhi Hou <lizhi.hou@amd.com>
+Signed-off-by: Lizhi Hou <lizhi.hou@amd.com>
+Link: https://patch.msgid.link/20260713173030.87541-3-doruk@0sec.ai
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/accel/amdxdna/amdxdna_ctx.c |    4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/accel/amdxdna/amdxdna_ctx.c
++++ b/drivers/accel/amdxdna/amdxdna_ctx.c
+@@ -406,6 +406,10 @@ int amdxdna_cmd_submit(struct amdxdna_cl
+       int ret, idx;
+       XDNA_DBG(xdna, "Command BO hdl %d, Arg BO count %d", cmd_bo_hdl, arg_bo_cnt);
++
++      if (!xdna->dev_info->ops->cmd_submit)
++              return -EOPNOTSUPP;
++
+       job = kzalloc(struct_size(job, bos, arg_bo_cnt), GFP_KERNEL);
+       if (!job)
+               return -ENOMEM;
diff --git a/queue-6.18/arm64-dts-qcom-correct-rbr-opp-entry.patch b/queue-6.18/arm64-dts-qcom-correct-rbr-opp-entry.patch
new file mode 100644 (file)
index 0000000..2bbb8ad
--- /dev/null
@@ -0,0 +1,334 @@
+From stable+bounces-288029-greg=kroah.com@vger.kernel.org Wed Jul 22 20:00:57 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 14:00:47 -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: <20260722180048.2184180-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/lemans.dtsi   |    8 ++++----
+ 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 ++++++++--------
+ 11 files changed, 50 insertions(+), 50 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/lemans.dtsi
++++ b/arch/arm64/boot/dts/qcom/lemans.dtsi
+@@ -5146,8 +5146,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>;
+                                       };
+@@ -5234,8 +5234,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>;
+                                       };
+--- a/arch/arm64/boot/dts/qcom/sc7180.dtsi
++++ b/arch/arm64/boot/dts/qcom/sc7180.dtsi
+@@ -3452,8 +3452,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
+@@ -5196,8 +5196,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>;
+                                       };
+@@ -5295,8 +5295,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
+@@ -3303,8 +3303,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>;
+                                       };
+@@ -3385,8 +3385,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>;
+                                       };
+@@ -3461,8 +3461,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
+@@ -4754,8 +4754,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>;
+                                       };
+@@ -4836,8 +4836,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>;
+                                       };
+@@ -4916,8 +4916,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>;
+                                       };
+@@ -4991,8 +4991,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>;
+                                       };
+@@ -6110,8 +6110,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>;
+                                       };
+@@ -6190,8 +6190,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>;
+                                       };
+@@ -6270,8 +6270,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>;
+                                       };
+@@ -6345,8 +6345,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
+@@ -2306,8 +2306,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
+@@ -3939,8 +3939,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
+@@ -4824,8 +4824,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
+@@ -2925,8 +2925,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
+@@ -3483,8 +3483,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
+@@ -5522,8 +5522,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>;
+                                       };
+@@ -5610,8 +5610,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>;
+                                       };
+@@ -5697,8 +5697,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>;
+                                       };
+@@ -5779,8 +5779,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.18/arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.patch b/queue-6.18/arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.patch
new file mode 100644 (file)
index 0000000..6e4d814
--- /dev/null
@@ -0,0 +1,176 @@
+From stable+bounces-288030-greg=kroah.com@vger.kernel.org Wed Jul 22 20:08:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 14:00:48 -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: <20260722180048.2184180-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
+@@ -5522,18 +5522,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>;
+                                       };
+@@ -5574,7 +5574,7 @@
+                                                        <&usb_1_ss1_qmpphy QMP_USB43DP_DP_VCO_DIV_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>;
+@@ -5606,30 +5606,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 {
+@@ -5662,7 +5638,7 @@
+                                                        <&usb_1_ss2_qmpphy QMP_USB43DP_DP_VCO_DIV_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>;
+@@ -5693,30 +5669,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 {
+@@ -5779,19 +5731,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.18/ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch b/queue-6.18/ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch
new file mode 100644 (file)
index 0000000..17dc731
--- /dev/null
@@ -0,0 +1,100 @@
+From stable+bounces-290198-greg=kroah.com@vger.kernel.org Wed Jul 29 02:30:58 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 20:30:49 -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: <20260729003049.888768-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
+@@ -2833,6 +2833,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
+@@ -144,6 +144,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.18/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch b/queue-6.18/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch
new file mode 100644 (file)
index 0000000..aa65d44
--- /dev/null
@@ -0,0 +1,364 @@
+From stable+bounces-282946-greg=kroah.com@vger.kernel.org Tue Jul 21 21:45:51 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 15:36:13 -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: <20260721193613.410665-3-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
+@@ -256,8 +256,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);
+@@ -280,13 +285,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);
+@@ -317,13 +324,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) {
+@@ -1416,7 +1417,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.18/audit-use-unsigned-int-instead-of-unsigned.patch b/queue-6.18/audit-use-unsigned-int-instead-of-unsigned.patch
new file mode 100644 (file)
index 0000000..3ba1499
--- /dev/null
@@ -0,0 +1,202 @@
+From stable+bounces-282945-greg=kroah.com@vger.kernel.org Tue Jul 21 23:14:02 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 15:36:12 -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: <20260721193613.410665-2-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/audit_watch.c       |    2 +-
+ kernel/auditfilter.c       |    8 ++++----
+ kernel/auditsc.c           |    2 +-
+ lib/compat_audit.c         |   12 ++++++------
+ 9 files changed, 23 insertions(+), 23 deletions(-)
+
+--- a/include/linux/audit.h
++++ b/include/linux/audit.h
+@@ -125,8 +125,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
+@@ -2030,7 +2030,7 @@ void audit_log_vformat(struct audit_buff
+                * 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
+@@ -233,7 +233,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/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,
+-                             u64 ino, unsigned invalidating)
++                             u64 ino, unsigned int invalidating)
+ {
+       struct audit_watch *owatch, *nwatch, *nextw;
+       struct audit_krule *r, *nextr;
+--- 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.18/audit-widen-ino-fields-to-u64.patch b/queue-6.18/audit-widen-ino-fields-to-u64.patch
new file mode 100644 (file)
index 0000000..53c9475
--- /dev/null
@@ -0,0 +1,187 @@
+From stable+bounces-282943-greg=kroah.com@vger.kernel.org Tue Jul 21 21:45:45 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 15:36:11 -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: <20260721193613.410665-1-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          |   13 ++++++-------
+ kernel/audit_fsnotify.c |    4 ++--
+ kernel/audit_watch.c    |   12 ++++++------
+ kernel/auditsc.c        |    4 ++--
+ 5 files changed, 17 insertions(+), 18 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
+@@ -76,7 +76,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;
+@@ -225,9 +225,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. */
+@@ -277,16 +277,15 @@ 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);
+ 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
+@@ -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;
+@@ -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;
+--- 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) &&
+@@ -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 invalidating)
+ {
+       struct audit_watch *owatch, *nwatch, *nextw;
+       struct audit_krule *r, *nextr;
+@@ -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
+@@ -886,7 +886,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);
+@@ -1534,7 +1534,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.18/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch b/queue-6.18/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch
new file mode 100644 (file)
index 0000000..bd2b60a
--- /dev/null
@@ -0,0 +1,97 @@
+From stable+bounces-290081-greg=kroah.com@vger.kernel.org Tue Jul 28 19:58:29 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 12:29:46 -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: <20260728162946.99526-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.18/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch b/queue-6.18/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch
new file mode 100644 (file)
index 0000000..e68a965
--- /dev/null
@@ -0,0 +1,165 @@
+From stable+bounces-290080-greg=kroah.com@vger.kernel.org Tue Jul 28 19:24:02 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 12:29:45 -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: <20260728162946.99526-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
+@@ -320,51 +320,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.18/cred-add-kernel_cred-helper.patch b/queue-6.18/cred-add-kernel_cred-helper.patch
new file mode 100644 (file)
index 0000000..21d65cd
--- /dev/null
@@ -0,0 +1,48 @@
+From stable+bounces-289686-greg=kroah.com@vger.kernel.org Mon Jul 27 23:04:43 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 16:59:56 -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: <20260727205958.1929027-1-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
+  */
+@@ -156,6 +158,11 @@ extern struct cred *prepare_exec_creds(v
+ extern int commit_creds(struct cred *);
+ extern void abort_creds(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.18/cxl-fix-cxl_headerlog_size-to-match-ras-capability-size.patch b/queue-6.18/cxl-fix-cxl_headerlog_size-to-match-ras-capability-size.patch
new file mode 100644 (file)
index 0000000..17762e5
--- /dev/null
@@ -0,0 +1,219 @@
+From stable+bounces-288498-greg=kroah.com@vger.kernel.org Thu Jul 23 20:29:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:28:06 -0400
+Subject: cxl: Fix CXL_HEADERLOG_SIZE to match RAS Capability size
+To: stable@vger.kernel.org
+Cc: Terry Bowman <terry.bowman@amd.com>, Alison Schofield <alison.schofield@intel.com>, Dave Jiang <dave.jiang@intel.com>, Ben Cheatham <benjamin.cheatham@amd.com>, Richard Cheng <icheng@nvidia.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723182806.3493205-4-sashal@kernel.org>
+
+From: Terry Bowman <terry.bowman@amd.com>
+
+[ Upstream commit c268f949e219f9e179558e836f457f6c5fbec416 ]
+
+The CXL r4.0 8.2.4.17.7 RAS Capability Structure has total length 0x58
+bytes (CXL_RAS_CAPABILITY_LENGTH); the Header Log occupies the trailing
+64 bytes at offset 0x18.  CXL_HEADERLOG_SIZE was defined as SZ_512,
+eight times the actual on-device size.
+
+header_log_copy() reads CXL_HEADERLOG_SIZE_U32 (128) dwords from the
+RAS capability iomap, overrunning the 88-byte mapping by 448 bytes.
+The cxl_aer_uncorrectable_error trace event memcpy()s CXL_HEADERLOG_SIZE
+(512) bytes from its source.  For the CPER caller the source is
+struct cxl_ras_capability_regs::header_log[16] (64 bytes) embedded in a
+stack-local cxl_cper_prot_err_work_data, so the memcpy reads 448 bytes
+of kernel stack into the trace event ring buffer where userspace can
+read it via tracefs.
+
+Set CXL_HEADERLOG_SIZE to 64 and derive CXL_HEADERLOG_SIZE_U32 from it,
+bringing all iomap readers into agreement on 16 dwords.  Userspace tools
+such as rasdaemon have grown a dependency on the buggy 512-byte (128 u32)
+header_log layout in the cxl_aer_uncorrectable_error trace event.  Add
+CXL_HEADERLOG_TRACE_SIZE_U32 = 128 and use it for the trace event
+__array and its memcpy to preserve that ABI.  Both callers now pass a
+zero-filled u32[CXL_HEADERLOG_TRACE_SIZE_U32] staging buffer with only
+the first CXL_HEADERLOG_SIZE_U32 (16) entries populated from hardware;
+the remaining 112 u32s are zero-padded, keeping the 512-byte trace ring
+buffer layout intact.
+
+[ dj: Replaced 64 with SZ_64 per RichardC ]
+
+Fixes: 36f257e3b0ba ("acpi/ghes, cxl/pci: Process CXL CPER Protocol Errors")
+Fixes: 2905cb5236cb ("cxl/pci: Add (hopeful) error handling support")
+Cc: stable@vger.kernel.org
+Reported-by: Sashiko
+Signed-off-by: Terry Bowman <terry.bowman@amd.com>
+Reviewed-by: Alison Schofield <alison.schofield@intel.com>
+Reviewed-by: Dave Jiang <dave.jiang@intel.com>
+Reviewed-by: Ben Cheatham <benjamin.cheatham@amd.com>
+Reviewed-by: Richard Cheng <icheng@nvidia.com>
+Link: https://patch.msgid.link/20260605180610.2249458-1-terry.bowman@amd.com
+Signed-off-by: Dave Jiang <dave.jiang@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/cxl/core/ras.c   |   27 ++++++++++++++++++++-------
+ drivers/cxl/core/trace.h |   24 ++++++++++++++++--------
+ drivers/cxl/cxl.h        |   14 ++++++++++++--
+ 3 files changed, 48 insertions(+), 17 deletions(-)
+
+--- a/drivers/cxl/core/ras.c
++++ b/drivers/cxl/core/ras.c
+@@ -8,6 +8,10 @@
+ #include <cxlpci.h>
+ #include "trace.h"
++/* Check that UCE header definition is maintained to keep ABI intact  */
++static_assert(CXL_HEADERLOG_TRACE_SIZE_U32 == 128,
++            "rasdaemon ABI requires exactly 128 u32s");
++
+ static void cxl_cper_trace_corr_port_prot_err(struct pci_dev *pdev,
+                                             struct cxl_ras_capability_regs ras_cap)
+ {
+@@ -19,6 +23,7 @@ static void cxl_cper_trace_corr_port_pro
+ static void cxl_cper_trace_uncorr_port_prot_err(struct pci_dev *pdev,
+                                               struct cxl_ras_capability_regs ras_cap)
+ {
++      u32 hl[CXL_HEADERLOG_TRACE_SIZE_U32] = {};
+       u32 status = ras_cap.uncor_status & ~ras_cap.uncor_mask;
+       u32 fe;
+@@ -28,8 +33,8 @@ static void cxl_cper_trace_uncorr_port_p
+       else
+               fe = status;
+-      trace_cxl_port_aer_uncorrectable_error(&pdev->dev, status, fe,
+-                                             ras_cap.header_log);
++      memcpy(hl, ras_cap.header_log, CXL_HEADERLOG_SIZE);
++      trace_cxl_port_aer_uncorrectable_error(&pdev->dev, status, fe, hl);
+ }
+ static void cxl_cper_trace_corr_prot_err(struct cxl_memdev *cxlmd,
+@@ -44,6 +49,7 @@ static void
+ cxl_cper_trace_uncorr_prot_err(struct cxl_memdev *cxlmd,
+                              struct cxl_ras_capability_regs ras_cap)
+ {
++      u32 hl[CXL_HEADERLOG_TRACE_SIZE_U32] = {};
+       u32 status = ras_cap.uncor_status & ~ras_cap.uncor_mask;
+       u32 fe;
+@@ -53,8 +59,15 @@ cxl_cper_trace_uncorr_prot_err(struct cx
+       else
+               fe = status;
+-      trace_cxl_aer_uncorrectable_error(cxlmd, status, fe,
+-                                        ras_cap.header_log);
++      /*
++       * ras_cap.header_log[] holds CXL_HEADERLOG_SIZE_U32 (16) hardware
++       * dwords.  Copy them into the front of a zero-filled
++       * CXL_HEADERLOG_TRACE_SIZE_U32 (128) u32 staging buffer so the trace
++       * event memcpy sees a full 512-byte source and the userspace ABI
++       * (rasdaemon) is preserved.
++       */
++      memcpy(hl, ras_cap.header_log, CXL_HEADERLOG_SIZE);
++      trace_cxl_aer_uncorrectable_error(cxlmd, status, fe, hl);
+ }
+ static int match_memdev_by_parent(struct device *dev, const void *uport)
+@@ -181,12 +194,12 @@ static void header_log_copy(void __iomem
+ {
+       void __iomem *addr;
+       u32 *log_addr;
+-      int i, log_u32_size = CXL_HEADERLOG_SIZE / sizeof(u32);
++      int i;
+       addr = ras_base + CXL_RAS_HEADER_LOG_OFFSET;
+       log_addr = log;
+-      for (i = 0; i < log_u32_size; i++) {
++      for (i = 0; i < CXL_HEADERLOG_SIZE_U32; i++) {
+               *log_addr = readl(addr);
+               log_addr++;
+               addr += sizeof(u32);
+@@ -199,7 +212,7 @@ static void header_log_copy(void __iomem
+  */
+ bool cxl_handle_ras(struct cxl_dev_state *cxlds, void __iomem *ras_base)
+ {
+-      u32 hl[CXL_HEADERLOG_SIZE_U32];
++      u32 hl[CXL_HEADERLOG_TRACE_SIZE_U32] = {};
+       void __iomem *addr;
+       u32 status;
+       u32 fe;
+--- a/drivers/cxl/core/trace.h
++++ b/drivers/cxl/core/trace.h
+@@ -56,7 +56,7 @@ TRACE_EVENT(cxl_port_aer_uncorrectable_e
+               __string(host, dev_name(dev->parent))
+               __field(u32, status)
+               __field(u32, first_error)
+-              __array(u32, header_log, CXL_HEADERLOG_SIZE_U32)
++              __array(u32, header_log, CXL_HEADERLOG_TRACE_SIZE_U32)
+       ),
+       TP_fast_assign(
+               __assign_str(device);
+@@ -64,10 +64,14 @@ TRACE_EVENT(cxl_port_aer_uncorrectable_e
+               __entry->status = status;
+               __entry->first_error = fe;
+               /*
+-               * Embed the 512B headerlog data for user app retrieval and
+-               * parsing, but no need to print this in the trace buffer.
++               * Embed headerlog data for user app retrieval and parsing,
++               * but no need to print in the trace buffer. Only
++               * CXL_HEADERLOG_SIZE_U32 (16) dwords are hardware data;
++               * the remaining entries preserve the 512-byte ABI layout
++               * rasdaemon depends on and are zero-filled by the caller.
+                */
+-              memcpy(__entry->header_log, hl, CXL_HEADERLOG_SIZE);
++              memcpy(__entry->header_log, hl,
++                      CXL_HEADERLOG_TRACE_SIZE_U32 * sizeof(u32));
+       ),
+       TP_printk("device=%s host=%s status: '%s' first_error: '%s'",
+                 __get_str(device), __get_str(host),
+@@ -85,7 +89,7 @@ TRACE_EVENT(cxl_aer_uncorrectable_error,
+               __field(u64, serial)
+               __field(u32, status)
+               __field(u32, first_error)
+-              __array(u32, header_log, CXL_HEADERLOG_SIZE_U32)
++              __array(u32, header_log, CXL_HEADERLOG_TRACE_SIZE_U32)
+       ),
+       TP_fast_assign(
+               __assign_str(memdev);
+@@ -94,10 +98,14 @@ TRACE_EVENT(cxl_aer_uncorrectable_error,
+               __entry->status = status;
+               __entry->first_error = fe;
+               /*
+-               * Embed the 512B headerlog data for user app retrieval and
+-               * parsing, but no need to print this in the trace buffer.
++               * Embed headerlog data for user app retrieval and parsing,
++               * but no need to print in the trace buffer. Only
++               * CXL_HEADERLOG_SIZE_U32 (16) dwords are hardware data;
++               * the remaining entries preserve the 512-byte ABI layout
++               * rasdaemon depends on and are zero-filled by the caller.
+                */
+-              memcpy(__entry->header_log, hl, CXL_HEADERLOG_SIZE);
++              memcpy(__entry->header_log, hl,
++                      CXL_HEADERLOG_TRACE_SIZE_U32 * sizeof(u32));
+       ),
+       TP_printk("memdev=%s host=%s serial=%lld: status: '%s' first_error: '%s'",
+                 __get_str(memdev), __get_str(host), __entry->serial,
+--- a/drivers/cxl/cxl.h
++++ b/drivers/cxl/cxl.h
+@@ -148,8 +148,18 @@ static inline int ways_to_eiw(unsigned i
+ #define CXL_RAS_CAP_CONTROL_FE_MASK GENMASK(5, 0)
+ #define CXL_RAS_HEADER_LOG_OFFSET 0x18
+ #define CXL_RAS_CAPABILITY_LENGTH 0x58
+-#define CXL_HEADERLOG_SIZE SZ_512
+-#define CXL_HEADERLOG_SIZE_U32 SZ_512 / sizeof(u32)
++#define CXL_HEADERLOG_SIZE SZ_64
++#define CXL_HEADERLOG_SIZE_U32 (CXL_HEADERLOG_SIZE / sizeof(u32))
++
++/*
++ * The RAS UCE trace event header array was originally sized at SZ_512/sizeof(u32)
++ * = 128 u32s due to a bug. Userspace tools (rasdaemon) have grown a dependency
++ * on that 512-byte layout. Keep the trace array at 128 u32s to preserve the
++ * ABI; only CXL_HEADERLOG_SIZE_U32 (16) dwords are valid hardware data, the
++ * remainder are zero-filled.
++ */
++#define CXL_HEADERLOG_TRACE_SIZE SZ_512
++#define CXL_HEADERLOG_TRACE_SIZE_U32 (CXL_HEADERLOG_TRACE_SIZE / sizeof(u32))
+ /* CXL 2.0 8.2.8.1 Device Capabilities Array Register */
+ #define CXLDEV_CAP_ARRAY_OFFSET 0x0
diff --git a/queue-6.18/cxl-pci-remove-cxl-vh-handling-in-config_pcieaer_cxl-conditional-blocks-from-core-pci.c.patch b/queue-6.18/cxl-pci-remove-cxl-vh-handling-in-config_pcieaer_cxl-conditional-blocks-from-core-pci.c.patch
new file mode 100644 (file)
index 0000000..b897b30
--- /dev/null
@@ -0,0 +1,585 @@
+From stable+bounces-288499-greg=kroah.com@vger.kernel.org Thu Jul 23 20:35:30 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:28:05 -0400
+Subject: cxl/pci: Remove CXL VH handling in CONFIG_PCIEAER_CXL conditional blocks from core/pci.c
+To: stable@vger.kernel.org
+Cc: Dave Jiang <dave.jiang@intel.com>, Robert Richter <rrichter@amd.com>, Joshua Hahn <joshua.hahnjy@gmail.com>, Jonathan Cameron <jonathan.cameron@huawei.com>, Alison Schofield <alison.schofield@intel.com>, Terry Bowman <terry.bowman@amd.com>, Dan Williams <dan.j.williams@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723182806.3493205-3-sashal@kernel.org>
+
+From: Dave Jiang <dave.jiang@intel.com>
+
+[ Upstream commit 7ff8b1d60881c5f97b5ae426e14d2822917d3b69 ]
+
+Create new config CONFIG_CXL_RAS and put all CXL RAS items behind the
+config. The config will depend on CPER and PCIE AER to build. Move the
+related VH RAS code from core/pci.c to core/ras.c.
+
+Restricted CXL host (RCH) RAS functions will be moved in a future patch.
+
+Cc: Robert Richter <rrichter@amd.com>
+Reviewed-by: Joshua Hahn <joshua.hahnjy@gmail.com>
+Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
+Signed-off-by: Dave Jiang <dave.jiang@intel.com>
+Reviewed-by: Alison Schofield <alison.schofield@intel.com>
+Co-developed-by: Terry Bowman <terry.bowman@amd.com>
+Signed-off-by: Terry Bowman <terry.bowman@amd.com>
+Reviewed-by: Dan Williams <dan.j.williams@intel.com>
+Link: https://patch.msgid.link/20260114182055.46029-8-terry.bowman@amd.com
+Signed-off-by: Dan Williams <dan.j.williams@intel.com>
+Signed-off-by: Dave Jiang <dave.jiang@intel.com>
+Stable-dep-of: c268f949e219 ("cxl: Fix CXL_HEADERLOG_SIZE to match RAS Capability size")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/cxl/Kconfig       |    4 
+ drivers/cxl/core/Makefile |    2 
+ drivers/cxl/core/core.h   |   31 +++++++
+ drivers/cxl/core/pci.c    |  189 ----------------------------------------------
+ drivers/cxl/core/ras.c    |  176 ++++++++++++++++++++++++++++++++++++++++++
+ drivers/cxl/cxl.h         |    8 -
+ drivers/cxl/cxlpci.h      |   16 +++
+ tools/testing/cxl/Kbuild  |    2 
+ 8 files changed, 233 insertions(+), 195 deletions(-)
+
+--- a/drivers/cxl/Kconfig
++++ b/drivers/cxl/Kconfig
+@@ -234,4 +234,8 @@ config CXL_MCE
+       def_bool y
+       depends on X86_MCE && MEMORY_FAILURE
++config CXL_RAS
++      def_bool y
++      depends on ACPI_APEI_GHES && PCIEAER && CXL_PCI
++
+ endif
+--- a/drivers/cxl/core/Makefile
++++ b/drivers/cxl/core/Makefile
+@@ -14,9 +14,9 @@ cxl_core-y += pci.o
+ cxl_core-y += hdm.o
+ cxl_core-y += pmu.o
+ cxl_core-y += cdat.o
+-cxl_core-y += ras.o
+ cxl_core-$(CONFIG_TRACING) += trace.o
+ cxl_core-$(CONFIG_CXL_REGION) += region.o
+ cxl_core-$(CONFIG_CXL_MCE) += mce.o
+ cxl_core-$(CONFIG_CXL_FEATURES) += features.o
+ cxl_core-$(CONFIG_CXL_EDAC_MEM_FEATURES) += edac.o
++cxl_core-$(CONFIG_CXL_RAS) += ras.o
+--- a/drivers/cxl/core/core.h
++++ b/drivers/cxl/core/core.h
+@@ -144,8 +144,39 @@ int cxl_pci_get_bandwidth(struct pci_dev
+ int cxl_port_get_switch_dport_bandwidth(struct cxl_port *port,
+                                       struct access_coordinate *c);
++#ifdef CONFIG_CXL_RAS
+ int cxl_ras_init(void);
+ void cxl_ras_exit(void);
++bool cxl_handle_ras(struct cxl_dev_state *cxlds, void __iomem *ras_base);
++void cxl_handle_cor_ras(struct cxl_dev_state *cxlds, void __iomem *ras_base);
++#else
++static inline int cxl_ras_init(void)
++{
++      return 0;
++}
++
++static inline void cxl_ras_exit(void)
++{
++}
++
++static inline bool cxl_handle_ras(struct cxl_dev_state *cxlds, void __iomem *ras_base)
++{
++      return false;
++}
++static inline void cxl_handle_cor_ras(struct cxl_dev_state *cxlds, void __iomem *ras_base) { }
++#endif /* CONFIG_CXL_RAS */
++
++/* Restricted CXL Host specific RAS functions */
++#ifdef CONFIG_CXL_RAS
++void cxl_dport_map_rch_aer(struct cxl_dport *dport);
++void cxl_disable_rch_root_ints(struct cxl_dport *dport);
++void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds);
++#else
++static inline void cxl_dport_map_rch_aer(struct cxl_dport *dport) { }
++static inline void cxl_disable_rch_root_ints(struct cxl_dport *dport) { }
++static inline void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds) { }
++#endif /* CONFIG_CXL_RAS */
++
+ int cxl_gpf_port_setup(struct cxl_dport *dport);
+ struct cxl_hdm;
+--- a/drivers/cxl/core/pci.c
++++ b/drivers/cxl/core/pci.c
+@@ -711,81 +711,8 @@ err:
+ }
+ EXPORT_SYMBOL_NS_GPL(read_cdat_data, "CXL");
+-static void cxl_handle_cor_ras(struct cxl_dev_state *cxlds,
+-                             void __iomem *ras_base)
+-{
+-      void __iomem *addr;
+-      u32 status;
+-
+-      if (!ras_base)
+-              return;
+-
+-      addr = ras_base + CXL_RAS_CORRECTABLE_STATUS_OFFSET;
+-      status = readl(addr);
+-      if (status & CXL_RAS_CORRECTABLE_STATUS_MASK) {
+-              writel(status & CXL_RAS_CORRECTABLE_STATUS_MASK, addr);
+-              trace_cxl_aer_correctable_error(cxlds->cxlmd, status);
+-      }
+-}
+-
+-/* CXL spec rev3.0 8.2.4.16.1 */
+-static void header_log_copy(void __iomem *ras_base, u32 *log)
+-{
+-      void __iomem *addr;
+-      u32 *log_addr;
+-      int i, log_u32_size = CXL_HEADERLOG_SIZE / sizeof(u32);
+-
+-      addr = ras_base + CXL_RAS_HEADER_LOG_OFFSET;
+-      log_addr = log;
+-
+-      for (i = 0; i < log_u32_size; i++) {
+-              *log_addr = readl(addr);
+-              log_addr++;
+-              addr += sizeof(u32);
+-      }
+-}
+-
+-/*
+- * Log the state of the RAS status registers and prepare them to log the
+- * next error status. Return 1 if reset needed.
+- */
+-static bool cxl_handle_ras(struct cxl_dev_state *cxlds,
+-                         void __iomem *ras_base)
+-{
+-      u32 hl[CXL_HEADERLOG_SIZE_U32];
+-      void __iomem *addr;
+-      u32 status;
+-      u32 fe;
+-
+-      if (!ras_base)
+-              return false;
+-
+-      addr = ras_base + CXL_RAS_UNCORRECTABLE_STATUS_OFFSET;
+-      status = readl(addr);
+-      if (!(status & CXL_RAS_UNCORRECTABLE_STATUS_MASK))
+-              return false;
+-
+-      /* If multiple errors, log header points to first error from ctrl reg */
+-      if (hweight32(status) > 1) {
+-              void __iomem *rcc_addr =
+-                      ras_base + CXL_RAS_CAP_CONTROL_OFFSET;
+-
+-              fe = BIT(FIELD_GET(CXL_RAS_CAP_CONTROL_FE_MASK,
+-                                 readl(rcc_addr)));
+-      } else {
+-              fe = status;
+-      }
+-
+-      header_log_copy(ras_base, hl);
+-      trace_cxl_aer_uncorrectable_error(cxlds->cxlmd, status, fe, hl);
+-      writel(status & CXL_RAS_UNCORRECTABLE_STATUS_MASK, addr);
+-
+-      return true;
+-}
+-
+-#ifdef CONFIG_PCIEAER_CXL
+-
+-static void cxl_dport_map_rch_aer(struct cxl_dport *dport)
++#ifdef CONFIG_CXL_RAS
++void cxl_dport_map_rch_aer(struct cxl_dport *dport)
+ {
+       resource_size_t aer_phys;
+       struct device *host;
+@@ -800,19 +727,7 @@ static void cxl_dport_map_rch_aer(struct
+       }
+ }
+-static void cxl_dport_map_ras(struct cxl_dport *dport)
+-{
+-      struct cxl_register_map *map = &dport->reg_map;
+-      struct device *dev = dport->dport_dev;
+-
+-      if (!map->component_map.ras.valid)
+-              dev_dbg(dev, "RAS registers not found\n");
+-      else if (cxl_map_component_regs(map, &dport->regs.component,
+-                                      BIT(CXL_CM_CAP_CAP_ID_RAS)))
+-              dev_dbg(dev, "Failed to map RAS capability.\n");
+-}
+-
+-static void cxl_disable_rch_root_ints(struct cxl_dport *dport)
++void cxl_disable_rch_root_ints(struct cxl_dport *dport)
+ {
+       void __iomem *aer_base = dport->regs.dport_aer;
+       u32 aer_cmd_mask, aer_cmd;
+@@ -836,28 +751,6 @@ static void cxl_disable_rch_root_ints(st
+       writel(aer_cmd, aer_base + PCI_ERR_ROOT_COMMAND);
+ }
+-/**
+- * cxl_dport_init_ras_reporting - Setup CXL RAS report on this dport
+- * @dport: the cxl_dport that needs to be initialized
+- * @host: host device for devm operations
+- */
+-void cxl_dport_init_ras_reporting(struct cxl_dport *dport, struct device *host)
+-{
+-      dport->reg_map.host = host;
+-      cxl_dport_map_ras(dport);
+-
+-      if (dport->rch) {
+-              struct pci_host_bridge *host_bridge = to_pci_host_bridge(dport->dport_dev);
+-
+-              if (!host_bridge->native_aer)
+-                      return;
+-
+-              cxl_dport_map_rch_aer(dport);
+-              cxl_disable_rch_root_ints(dport);
+-      }
+-}
+-EXPORT_SYMBOL_NS_GPL(cxl_dport_init_ras_reporting, "CXL");
+-
+ /*
+  * Copy the AER capability registers using 32 bit read accesses.
+  * This is necessary because RCRB AER capability is MMIO mapped. Clear the
+@@ -906,7 +799,7 @@ static bool cxl_rch_get_aer_severity(str
+       return false;
+ }
+-static void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds)
++void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds)
+ {
+       struct pci_dev *pdev = to_pci_dev(cxlds->dev);
+       struct aer_capability_regs aer_regs;
+@@ -931,82 +824,8 @@ static void cxl_handle_rdport_errors(str
+       else
+               cxl_handle_ras(cxlds, dport->regs.ras);
+ }
+-
+-#else
+-static void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds) { }
+ #endif
+-void cxl_cor_error_detected(struct pci_dev *pdev)
+-{
+-      struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
+-      struct device *dev = &cxlds->cxlmd->dev;
+-
+-      scoped_guard(device, dev) {
+-              if (!dev->driver) {
+-                      dev_warn(&pdev->dev,
+-                               "%s: memdev disabled, abort error handling\n",
+-                               dev_name(dev));
+-                      return;
+-              }
+-
+-              if (cxlds->rcd)
+-                      cxl_handle_rdport_errors(cxlds);
+-
+-              cxl_handle_cor_ras(cxlds, cxlds->regs.ras);
+-      }
+-}
+-EXPORT_SYMBOL_NS_GPL(cxl_cor_error_detected, "CXL");
+-
+-pci_ers_result_t cxl_error_detected(struct pci_dev *pdev,
+-                                  pci_channel_state_t state)
+-{
+-      struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
+-      struct cxl_memdev *cxlmd = cxlds->cxlmd;
+-      struct device *dev = &cxlmd->dev;
+-      bool ue;
+-
+-      scoped_guard(device, dev) {
+-              if (!dev->driver) {
+-                      dev_warn(&pdev->dev,
+-                               "%s: memdev disabled, abort error handling\n",
+-                               dev_name(dev));
+-                      return PCI_ERS_RESULT_DISCONNECT;
+-              }
+-
+-              if (cxlds->rcd)
+-                      cxl_handle_rdport_errors(cxlds);
+-              /*
+-               * A frozen channel indicates an impending reset which is fatal to
+-               * CXL.mem operation, and will likely crash the system. On the off
+-               * chance the situation is recoverable dump the status of the RAS
+-               * capability registers and bounce the active state of the memdev.
+-               */
+-              ue = cxl_handle_ras(cxlds, cxlds->regs.ras);
+-      }
+-
+-
+-      switch (state) {
+-      case pci_channel_io_normal:
+-              if (ue) {
+-                      device_release_driver(dev);
+-                      return PCI_ERS_RESULT_NEED_RESET;
+-              }
+-              return PCI_ERS_RESULT_CAN_RECOVER;
+-      case pci_channel_io_frozen:
+-              dev_warn(&pdev->dev,
+-                       "%s: frozen state error detected, disable CXL.mem\n",
+-                       dev_name(dev));
+-              device_release_driver(dev);
+-              return PCI_ERS_RESULT_NEED_RESET;
+-      case pci_channel_io_perm_failure:
+-              dev_warn(&pdev->dev,
+-                       "failure state error detected, request disconnect\n");
+-              return PCI_ERS_RESULT_DISCONNECT;
+-      }
+-      return PCI_ERS_RESULT_NEED_RESET;
+-}
+-EXPORT_SYMBOL_NS_GPL(cxl_error_detected, "CXL");
+-
+ static int cxl_flit_size(struct pci_dev *pdev)
+ {
+       if (cxl_pci_flit_256(pdev))
+--- a/drivers/cxl/core/ras.c
++++ b/drivers/cxl/core/ras.c
+@@ -5,6 +5,7 @@
+ #include <linux/aer.h>
+ #include <cxl/event.h>
+ #include <cxlmem.h>
++#include <cxlpci.h>
+ #include "trace.h"
+ static void cxl_cper_trace_corr_port_prot_err(struct pci_dev *pdev,
+@@ -124,3 +125,178 @@ void cxl_ras_exit(void)
+       cxl_cper_unregister_prot_err_work(&cxl_cper_prot_err_work);
+       cancel_work_sync(&cxl_cper_prot_err_work);
+ }
++
++static void cxl_dport_map_ras(struct cxl_dport *dport)
++{
++      struct cxl_register_map *map = &dport->reg_map;
++      struct device *dev = dport->dport_dev;
++
++      if (!map->component_map.ras.valid)
++              dev_dbg(dev, "RAS registers not found\n");
++      else if (cxl_map_component_regs(map, &dport->regs.component,
++                                      BIT(CXL_CM_CAP_CAP_ID_RAS)))
++              dev_dbg(dev, "Failed to map RAS capability.\n");
++}
++
++/**
++ * cxl_dport_init_ras_reporting - Setup CXL RAS report on this dport
++ * @dport: the cxl_dport that needs to be initialized
++ * @host: host device for devm operations
++ */
++void cxl_dport_init_ras_reporting(struct cxl_dport *dport, struct device *host)
++{
++      dport->reg_map.host = host;
++      cxl_dport_map_ras(dport);
++
++      if (dport->rch) {
++              struct pci_host_bridge *host_bridge = to_pci_host_bridge(dport->dport_dev);
++
++              if (!host_bridge->native_aer)
++                      return;
++
++              cxl_dport_map_rch_aer(dport);
++              cxl_disable_rch_root_ints(dport);
++      }
++}
++EXPORT_SYMBOL_NS_GPL(cxl_dport_init_ras_reporting, "CXL");
++
++void cxl_handle_cor_ras(struct cxl_dev_state *cxlds, void __iomem *ras_base)
++{
++      void __iomem *addr;
++      u32 status;
++
++      if (!ras_base)
++              return;
++
++      addr = ras_base + CXL_RAS_CORRECTABLE_STATUS_OFFSET;
++      status = readl(addr);
++      if (status & CXL_RAS_CORRECTABLE_STATUS_MASK) {
++              writel(status & CXL_RAS_CORRECTABLE_STATUS_MASK, addr);
++              trace_cxl_aer_correctable_error(cxlds->cxlmd, status);
++      }
++}
++
++/* CXL spec rev3.0 8.2.4.16.1 */
++static void header_log_copy(void __iomem *ras_base, u32 *log)
++{
++      void __iomem *addr;
++      u32 *log_addr;
++      int i, log_u32_size = CXL_HEADERLOG_SIZE / sizeof(u32);
++
++      addr = ras_base + CXL_RAS_HEADER_LOG_OFFSET;
++      log_addr = log;
++
++      for (i = 0; i < log_u32_size; i++) {
++              *log_addr = readl(addr);
++              log_addr++;
++              addr += sizeof(u32);
++      }
++}
++
++/*
++ * Log the state of the RAS status registers and prepare them to log the
++ * next error status. Return 1 if reset needed.
++ */
++bool cxl_handle_ras(struct cxl_dev_state *cxlds, void __iomem *ras_base)
++{
++      u32 hl[CXL_HEADERLOG_SIZE_U32];
++      void __iomem *addr;
++      u32 status;
++      u32 fe;
++
++      if (!ras_base)
++              return false;
++
++      addr = ras_base + CXL_RAS_UNCORRECTABLE_STATUS_OFFSET;
++      status = readl(addr);
++      if (!(status & CXL_RAS_UNCORRECTABLE_STATUS_MASK))
++              return false;
++
++      /* If multiple errors, log header points to first error from ctrl reg */
++      if (hweight32(status) > 1) {
++              void __iomem *rcc_addr =
++                      ras_base + CXL_RAS_CAP_CONTROL_OFFSET;
++
++              fe = BIT(FIELD_GET(CXL_RAS_CAP_CONTROL_FE_MASK,
++                                 readl(rcc_addr)));
++      } else {
++              fe = status;
++      }
++
++      header_log_copy(ras_base, hl);
++      trace_cxl_aer_uncorrectable_error(cxlds->cxlmd, status, fe, hl);
++      writel(status & CXL_RAS_UNCORRECTABLE_STATUS_MASK, addr);
++
++      return true;
++}
++
++void cxl_cor_error_detected(struct pci_dev *pdev)
++{
++      struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
++      struct device *dev = &cxlds->cxlmd->dev;
++
++      scoped_guard(device, dev) {
++              if (!dev->driver) {
++                      dev_warn(&pdev->dev,
++                               "%s: memdev disabled, abort error handling\n",
++                               dev_name(dev));
++                      return;
++              }
++
++              if (cxlds->rcd)
++                      cxl_handle_rdport_errors(cxlds);
++
++              cxl_handle_cor_ras(cxlds, cxlds->regs.ras);
++      }
++}
++EXPORT_SYMBOL_NS_GPL(cxl_cor_error_detected, "CXL");
++
++pci_ers_result_t cxl_error_detected(struct pci_dev *pdev,
++                                  pci_channel_state_t state)
++{
++      struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
++      struct cxl_memdev *cxlmd = cxlds->cxlmd;
++      struct device *dev = &cxlmd->dev;
++      bool ue;
++
++      scoped_guard(device, dev) {
++              if (!dev->driver) {
++                      dev_warn(&pdev->dev,
++                               "%s: memdev disabled, abort error handling\n",
++                               dev_name(dev));
++                      return PCI_ERS_RESULT_DISCONNECT;
++              }
++
++              if (cxlds->rcd)
++                      cxl_handle_rdport_errors(cxlds);
++              /*
++               * A frozen channel indicates an impending reset which is fatal to
++               * CXL.mem operation, and will likely crash the system. On the off
++               * chance the situation is recoverable dump the status of the RAS
++               * capability registers and bounce the active state of the memdev.
++               */
++              ue = cxl_handle_ras(cxlds, cxlds->regs.ras);
++      }
++
++
++      switch (state) {
++      case pci_channel_io_normal:
++              if (ue) {
++                      device_release_driver(dev);
++                      return PCI_ERS_RESULT_NEED_RESET;
++              }
++              return PCI_ERS_RESULT_CAN_RECOVER;
++      case pci_channel_io_frozen:
++              dev_warn(&pdev->dev,
++                       "%s: frozen state error detected, disable CXL.mem\n",
++                       dev_name(dev));
++              device_release_driver(dev);
++              return PCI_ERS_RESULT_NEED_RESET;
++      case pci_channel_io_perm_failure:
++              dev_warn(&pdev->dev,
++                       "failure state error detected, request disconnect\n");
++              return PCI_ERS_RESULT_DISCONNECT;
++      }
++      return PCI_ERS_RESULT_NEED_RESET;
++}
++EXPORT_SYMBOL_NS_GPL(cxl_error_detected, "CXL");
+--- a/drivers/cxl/cxl.h
++++ b/drivers/cxl/cxl.h
+@@ -781,14 +781,6 @@ struct cxl_dport *devm_cxl_add_rch_dport
+                                        struct device *dport_dev, int port_id,
+                                        resource_size_t rcrb);
+-#ifdef CONFIG_PCIEAER_CXL
+-void cxl_setup_parent_dport(struct device *host, struct cxl_dport *dport);
+-void cxl_dport_init_ras_reporting(struct cxl_dport *dport, struct device *host);
+-#else
+-static inline void cxl_dport_init_ras_reporting(struct cxl_dport *dport,
+-                                              struct device *host) { }
+-#endif
+-
+ struct cxl_decoder *to_cxl_decoder(struct device *dev);
+ struct cxl_root_decoder *to_cxl_root_decoder(struct device *dev);
+ struct cxl_switch_decoder *to_cxl_switch_decoder(struct device *dev);
+--- a/drivers/cxl/cxlpci.h
++++ b/drivers/cxl/cxlpci.h
+@@ -130,7 +130,23 @@ static inline bool cxl_pci_flit_256(stru
+ int devm_cxl_port_enumerate_dports(struct cxl_port *port);
+ struct cxl_dev_state;
+ void read_cdat_data(struct cxl_port *port);
++
++#ifdef CONFIG_CXL_RAS
+ void cxl_cor_error_detected(struct pci_dev *pdev);
+ pci_ers_result_t cxl_error_detected(struct pci_dev *pdev,
+                                   pci_channel_state_t state);
++void cxl_dport_init_ras_reporting(struct cxl_dport *dport, struct device *host);
++#else
++static inline void cxl_cor_error_detected(struct pci_dev *pdev) { }
++
++static inline pci_ers_result_t cxl_error_detected(struct pci_dev *pdev,
++                                                pci_channel_state_t state)
++{
++      return PCI_ERS_RESULT_NONE;
++}
++
++static inline void cxl_dport_init_ras_reporting(struct cxl_dport *dport,
++                                              struct device *host) { }
++#endif
++
+ #endif /* __CXL_PCI_H__ */
+--- a/tools/testing/cxl/Kbuild
++++ b/tools/testing/cxl/Kbuild
+@@ -58,12 +58,12 @@ cxl_core-y += $(CXL_CORE_SRC)/pci.o
+ cxl_core-y += $(CXL_CORE_SRC)/hdm.o
+ cxl_core-y += $(CXL_CORE_SRC)/pmu.o
+ cxl_core-y += $(CXL_CORE_SRC)/cdat.o
+-cxl_core-y += $(CXL_CORE_SRC)/ras.o
+ cxl_core-$(CONFIG_TRACING) += $(CXL_CORE_SRC)/trace.o
+ cxl_core-$(CONFIG_CXL_REGION) += $(CXL_CORE_SRC)/region.o
+ cxl_core-$(CONFIG_CXL_MCE) += $(CXL_CORE_SRC)/mce.o
+ cxl_core-$(CONFIG_CXL_FEATURES) += $(CXL_CORE_SRC)/features.o
+ cxl_core-$(CONFIG_CXL_EDAC_MEM_FEATURES) += $(CXL_CORE_SRC)/edac.o
++cxl_core-$(CONFIG_CXL_RAS) += $(CXL_CORE_SRC)/ras.o
+ cxl_core-y += config_check.o
+ cxl_core-y += cxl_core_test.o
+ cxl_core-y += cxl_core_exports.o
diff --git a/queue-6.18/cxl-pci-remove-unnecessary-cxl-endpoint-handling-helper-functions.patch b/queue-6.18/cxl-pci-remove-unnecessary-cxl-endpoint-handling-helper-functions.patch
new file mode 100644 (file)
index 0000000..30cbb1c
--- /dev/null
@@ -0,0 +1,118 @@
+From stable+bounces-288496-greg=kroah.com@vger.kernel.org Thu Jul 23 20:35:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:28:03 -0400
+Subject: cxl/pci: Remove unnecessary CXL Endpoint handling helper functions
+To: stable@vger.kernel.org
+Cc: Terry Bowman <terry.bowman@amd.com>, Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>, Jonathan Cameron <jonathan.cameron@huawei.com>, Dave Jiang <dave.jiang@intel.com>, Joshua Hahn <joshua.hahnjy@gmail.com>, Dan Williams <dan.j.williams@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723182806.3493205-1-sashal@kernel.org>
+
+From: Terry Bowman <terry.bowman@amd.com>
+
+[ Upstream commit ca3d1a53e62093d17436abd447463da9c0f4e56b ]
+
+The CXL driver's cxl_handle_endpoint_cor_ras()/cxl_handle_endpoint_ras()
+are unnecessary helper functions used only for Endpoints. Remove these
+functions as they are not common for all CXL devices and do not provide
+value for EP handling.
+
+Rename __cxl_handle_ras to cxl_handle_ras() and __cxl_handle_cor_ras()
+to cxl_handle_cor_ras().
+
+Signed-off-by: Terry Bowman <terry.bowman@amd.com>
+Reviewed-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
+Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
+Reviewed-by: Dave Jiang <dave.jiang@intel.com>
+Tested-by: Joshua Hahn <joshua.hahnjy@gmail.com>
+Reviewed-by: Dan Williams <dan.j.williams@intel.com>
+Link: https://patch.msgid.link/20260114182055.46029-5-terry.bowman@amd.com
+Signed-off-by: Dan Williams <dan.j.williams@intel.com>
+Signed-off-by: Dave Jiang <dave.jiang@intel.com>
+Stable-dep-of: c268f949e219 ("cxl: Fix CXL_HEADERLOG_SIZE to match RAS Capability size")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/cxl/core/pci.c |   26 ++++++++------------------
+ 1 file changed, 8 insertions(+), 18 deletions(-)
+
+--- a/drivers/cxl/core/pci.c
++++ b/drivers/cxl/core/pci.c
+@@ -711,8 +711,8 @@ err:
+ }
+ EXPORT_SYMBOL_NS_GPL(read_cdat_data, "CXL");
+-static void __cxl_handle_cor_ras(struct cxl_dev_state *cxlds,
+-                               void __iomem *ras_base)
++static void cxl_handle_cor_ras(struct cxl_dev_state *cxlds,
++                             void __iomem *ras_base)
+ {
+       void __iomem *addr;
+       u32 status;
+@@ -728,11 +728,6 @@ static void __cxl_handle_cor_ras(struct
+       }
+ }
+-static void cxl_handle_endpoint_cor_ras(struct cxl_dev_state *cxlds)
+-{
+-      return __cxl_handle_cor_ras(cxlds, cxlds->regs.ras);
+-}
+-
+ /* CXL spec rev3.0 8.2.4.16.1 */
+ static void header_log_copy(void __iomem *ras_base, u32 *log)
+ {
+@@ -754,8 +749,8 @@ static void header_log_copy(void __iomem
+  * Log the state of the RAS status registers and prepare them to log the
+  * next error status. Return 1 if reset needed.
+  */
+-static bool __cxl_handle_ras(struct cxl_dev_state *cxlds,
+-                                void __iomem *ras_base)
++static bool cxl_handle_ras(struct cxl_dev_state *cxlds,
++                         void __iomem *ras_base)
+ {
+       u32 hl[CXL_HEADERLOG_SIZE_U32];
+       void __iomem *addr;
+@@ -788,11 +783,6 @@ static bool __cxl_handle_ras(struct cxl_
+       return true;
+ }
+-static bool cxl_handle_endpoint_ras(struct cxl_dev_state *cxlds)
+-{
+-      return __cxl_handle_ras(cxlds, cxlds->regs.ras);
+-}
+-
+ #ifdef CONFIG_PCIEAER_CXL
+ static void cxl_dport_map_rch_aer(struct cxl_dport *dport)
+@@ -871,13 +861,13 @@ EXPORT_SYMBOL_NS_GPL(cxl_dport_init_ras_
+ static void cxl_handle_rdport_cor_ras(struct cxl_dev_state *cxlds,
+                                         struct cxl_dport *dport)
+ {
+-      return __cxl_handle_cor_ras(cxlds, dport->regs.ras);
++      return cxl_handle_cor_ras(cxlds, dport->regs.ras);
+ }
+ static bool cxl_handle_rdport_ras(struct cxl_dev_state *cxlds,
+                                      struct cxl_dport *dport)
+ {
+-      return __cxl_handle_ras(cxlds, dport->regs.ras);
++      return cxl_handle_ras(cxlds, dport->regs.ras);
+ }
+ /*
+@@ -974,7 +964,7 @@ void cxl_cor_error_detected(struct pci_d
+               if (cxlds->rcd)
+                       cxl_handle_rdport_errors(cxlds);
+-              cxl_handle_endpoint_cor_ras(cxlds);
++              cxl_handle_cor_ras(cxlds, cxlds->regs.ras);
+       }
+ }
+ EXPORT_SYMBOL_NS_GPL(cxl_cor_error_detected, "CXL");
+@@ -1003,7 +993,7 @@ pci_ers_result_t cxl_error_detected(stru
+                * chance the situation is recoverable dump the status of the RAS
+                * capability registers and bounce the active state of the memdev.
+                */
+-              ue = cxl_handle_endpoint_ras(cxlds);
++              ue = cxl_handle_ras(cxlds, cxlds->regs.ras);
+       }
diff --git a/queue-6.18/cxl-pci-remove-unnecessary-cxl-rch-handling-helper-functions.patch b/queue-6.18/cxl-pci-remove-unnecessary-cxl-rch-handling-helper-functions.patch
new file mode 100644 (file)
index 0000000..968ecd1
--- /dev/null
@@ -0,0 +1,65 @@
+From stable+bounces-288497-greg=kroah.com@vger.kernel.org Thu Jul 23 20:30:03 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:28:04 -0400
+Subject: cxl/pci: Remove unnecessary CXL RCH handling helper functions
+To: stable@vger.kernel.org
+Cc: Terry Bowman <terry.bowman@amd.com>, Alejandro Lucero <alucerop@amd.com>, Dave Jiang <dave.jiang@intel.com>, Jonathan Cameron <jonathan.cameron@huawei.com>, Dan Williams <dan.j.williams@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723182806.3493205-2-sashal@kernel.org>
+
+From: Terry Bowman <terry.bowman@amd.com>
+
+[ Upstream commit eb78ef4d6f0e51243c1ee117f801dbc503e886ab ]
+
+cxl_handle_rdport_cor_ras() and cxl_handle_rdport_ras() are specific
+to Restricted CXL Host (RCH) handling. Improve readability and
+maintainability by replacing these and instead using the common
+cxl_handle_cor_ras() and cxl_handle_ras() functions.
+
+Signed-off-by: Terry Bowman <terry.bowman@amd.com>
+Reviewed-by: Alejandro Lucero <alucerop@amd.com>
+Reviewed-by: Dave Jiang <dave.jiang@intel.com>
+Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
+Reviewed-by: Dan Williams <dan.j.williams@intel.com>
+Link: https://patch.msgid.link/20260114182055.46029-6-terry.bowman@amd.com
+Signed-off-by: Dan Williams <dan.j.williams@intel.com>
+Signed-off-by: Dave Jiang <dave.jiang@intel.com>
+Stable-dep-of: c268f949e219 ("cxl: Fix CXL_HEADERLOG_SIZE to match RAS Capability size")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/cxl/core/pci.c |   16 ++--------------
+ 1 file changed, 2 insertions(+), 14 deletions(-)
+
+--- a/drivers/cxl/core/pci.c
++++ b/drivers/cxl/core/pci.c
+@@ -858,18 +858,6 @@ void cxl_dport_init_ras_reporting(struct
+ }
+ EXPORT_SYMBOL_NS_GPL(cxl_dport_init_ras_reporting, "CXL");
+-static void cxl_handle_rdport_cor_ras(struct cxl_dev_state *cxlds,
+-                                        struct cxl_dport *dport)
+-{
+-      return cxl_handle_cor_ras(cxlds, dport->regs.ras);
+-}
+-
+-static bool cxl_handle_rdport_ras(struct cxl_dev_state *cxlds,
+-                                     struct cxl_dport *dport)
+-{
+-      return cxl_handle_ras(cxlds, dport->regs.ras);
+-}
+-
+ /*
+  * Copy the AER capability registers using 32 bit read accesses.
+  * This is necessary because RCRB AER capability is MMIO mapped. Clear the
+@@ -939,9 +927,9 @@ static void cxl_handle_rdport_errors(str
+       pci_print_aer(pdev, severity, &aer_regs);
+       if (severity == AER_CORRECTABLE)
+-              cxl_handle_rdport_cor_ras(cxlds, dport);
++              cxl_handle_cor_ras(cxlds, dport->regs.ras);
+       else
+-              cxl_handle_rdport_ras(cxlds, dport);
++              cxl_handle_ras(cxlds, dport->regs.ras);
+ }
+ #else
diff --git a/queue-6.18/dm-avoid-leaking-the-caller-s-thread-keyring-via-the-table-device-file.patch b/queue-6.18/dm-avoid-leaking-the-caller-s-thread-keyring-via-the-table-device-file.patch
new file mode 100644 (file)
index 0000000..6fdbaf2
--- /dev/null
@@ -0,0 +1,72 @@
+From stable+bounces-289688-greg=kroah.com@vger.kernel.org Mon Jul 27 23:00:30 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 16:59:58 -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: <20260727205958.1929027-3-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
+@@ -735,7 +735,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.18/dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch b/queue-6.18/dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch
new file mode 100644 (file)
index 0000000..a8aa89a
--- /dev/null
@@ -0,0 +1,55 @@
+From stable+bounces-289272-greg=kroah.com@vger.kernel.org Sun Jul 26 14:47:53 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:47:40 -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: <20260726124741.149009-1-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
+@@ -161,13 +161,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.18/dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch b/queue-6.18/dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch
new file mode 100644 (file)
index 0000000..0066216
--- /dev/null
@@ -0,0 +1,43 @@
+From stable+bounces-289273-greg=kroah.com@vger.kernel.org Sun Jul 26 14:47:47 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:47:41 -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: <20260726124741.149009-2-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
+@@ -166,6 +166,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.18/fs-resctrl-fix-use-after-free-during-unmount.patch b/queue-6.18/fs-resctrl-fix-use-after-free-during-unmount.patch
new file mode 100644 (file)
index 0000000..021bc35
--- /dev/null
@@ -0,0 +1,134 @@
+From stable+bounces-289738-greg=kroah.com@vger.kernel.org Tue Jul 28 01:36:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:20 -0400
+Subject: fs/resctrl: Fix use-after-free during unmount
+To: stable@vger.kernel.org
+Cc: Tony Luck <tony.luck@intel.com>, Sashiko <sashiko-bot@kernel.org>, Reinette Chatre <reinette.chatre@intel.com>, "Borislav Petkov (AMD)" <bp@alien8.de>, Chen Yu <yu.c.chen@intel.com>, stable@kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727233421.2423844-6-sashal@kernel.org>
+
+From: Tony Luck <tony.luck@intel.com>
+
+[ Upstream commit 52fce648607e0d6a76eeb443d78708c49df1c554 ]
+
+During unmount or failure teardown all mon_data structures that contain
+monitoring event file private data are freed after which kernfs nodes are
+removed. However, the RDT_DELETED flag is never set for the statically
+allocated default resource group.
+
+A concurrent reader of an event file associated with the default resource
+group may, after dropping kernfs active protection, block on rdtgroup_mutex
+while unmount proceeds to free the file private data and destroy the kernfs
+node without waiting for the reader.
+
+When the mutex is released, the reader wakes up, observes that RDT_DELETED
+is not set for the default group, and dereferences the already-freed
+file private data.
+
+The scenario can be depicted as follows:
+  CPU0                                      CPU1
+   /*
+    * Default resource group's
+    * monitoring data accessible via
+    * kernfs file with kernfs_node::priv
+    * pointing to a struct mon_data.
+    * User opens the file for reading.
+    */
+   rdtgroup_mondata_show()                 /* arch encounters fatal error */
+    rdtgroup_kn_lock_live()                 resctrl_exit()
+     atomic_inc(&rdtgroup_default.waitcount) cpus_read_lock()
+     kernfs_break_active_protection(kn)      mutex_lock(&rdtgroup_mutex)
+     cpus_read_lock()                        resctrl_fs_teardown()
+     mutex_lock(&rdtgroup_mutex)              rmdir_all_sub()
+                                              mon_put_kn_priv()
+                                               /* Delete all mon_data structures */
+                                              rdtgroup_destroy_root()
+                                               kernfs_destroy_root()
+                                               rdtgroup_default.kn = NULL
+                                             mutex_unlock(&rdtgroup_mutex)
+     /*
+      * rdtgroup_default.flags is empty so
+      * rdtgroup_kn_lock_live() returns
+      * &rdtgroup_default
+      */
+     md = of->kn->priv;
+
+     /* md points to freed mon_data */
+
+Set RDT_DELETED for the default group unconditionally since the flag does
+not lead to the freeing of this statically allocated group.
+
+Do not allow a new resctrl mount if there are any waiters on default group
+of previous mount. A new mount will re-initialize the default group that
+would appear to waiters from previous mount as though the default group is
+accessible causing them to access the mon_data structures from the previous
+mount that have been removed.
+
+Fixes: 2a6566038544 ("x86/resctrl: Expand the width of domid by replacing mon_data_bits")
+Closes: https://sashiko.dev/#/patchset/20260508182143.14592-1-tony.luck%40intel.com?part=2 [1]
+Reported-by: Sashiko <sashiko-bot@kernel.org>
+Signed-off-by: Tony Luck <tony.luck@intel.com>
+Signed-off-by: Reinette Chatre <reinette.chatre@intel.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Reviewed-by: Chen Yu <yu.c.chen@intel.com>
+Cc: <stable@kernel.org>
+Link: https://patch.msgid.link/49a2ca3ca688f27e1a646cf90e1dc69287021127.1783377598.git.reinette.chatre@intel.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/resctrl/rdtgroup.c |   18 ++++++++++++++++--
+ 1 file changed, 16 insertions(+), 2 deletions(-)
+
+--- a/fs/resctrl/rdtgroup.c
++++ b/fs/resctrl/rdtgroup.c
+@@ -581,14 +581,20 @@ unlock:
+  *
+  * On resource group creation via a mkdir, an extra kernfs_node reference is
+  * taken to ensure that the rdtgroup structure remains accessible for the
+- * rdtgroup_kn_unlock() calls where it is removed.
++ * rdtgroup_kn_unlock() calls where it is removed. The default group is
++ * statically allocated: it does not have an extra reference but will have
++ * RDT_DELETED set on unmount to support safe access to its associated files
++ * via rdtgroup_kn_lock_live/rdtgroup_kn_unlock().
+  *
+- * Drop the extra reference here, then free the rdtgroup structure.
++ * For all but the default group: drop the extra reference, then free the
++ * rdtgroup structure.
+  *
+  * Return: void
+  */
+ static void rdtgroup_remove(struct rdtgroup *rdtgrp)
+ {
++      if (rdtgrp == &rdtgroup_default)
++              return;
+       kernfs_put(rdtgrp->kn);
+       kfree(rdtgrp);
+ }
+@@ -2732,6 +2738,12 @@ static int rdt_get_tree(struct fs_contex
+               goto out;
+       }
++      /* Avoid races from pending operations from a previous mount */
++      if (atomic_read(&rdtgroup_default.waitcount) != 0) {
++              ret = -EBUSY;
++              goto out;
++      }
++
+       ret = setup_rmid_lru_list();
+       if (ret)
+               goto out;
+@@ -3094,6 +3106,7 @@ static void resctrl_fs_teardown(void)
+       mon_put_kn_priv();
+       rdt_pseudo_lock_release();
+       rdtgroup_default.mode = RDT_MODE_SHAREABLE;
++      rdtgroup_default.flags = RDT_DELETED;
+       closid_exit();
+       schemata_list_destroy();
+       rdtgroup_destroy_root();
+@@ -4148,6 +4161,7 @@ static int rdtgroup_setup_root(struct rd
+       ctx->kfc.root = rdt_root;
+       rdtgroup_default.kn = kernfs_root_to_node(rdt_root);
++      rdtgroup_default.flags = 0;
+       return 0;
+ }
diff --git a/queue-6.18/fs-resctrl-move-allocation-free-of-closid_num_dirty_rmid.patch b/queue-6.18/fs-resctrl-move-allocation-free-of-closid_num_dirty_rmid.patch
new file mode 100644 (file)
index 0000000..c125e2b
--- /dev/null
@@ -0,0 +1,162 @@
+From stable+bounces-289736-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:18 -0400
+Subject: fs/resctrl: Move allocation/free of closid_num_dirty_rmid[]
+To: stable@vger.kernel.org
+Cc: Tony Luck <tony.luck@intel.com>, "Borislav Petkov (AMD)" <bp@alien8.de>, Reinette Chatre <reinette.chatre@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727233421.2423844-4-sashal@kernel.org>
+
+From: Tony Luck <tony.luck@intel.com>
+
+[ Upstream commit ee7f6af79f0916b6c49e15edd4cba020b3e4c4ac ]
+
+closid_num_dirty_rmid[] and rmid_ptrs[] are allocated together during resctrl
+initialization and freed together during resctrl exit.
+
+Telemetry events are enumerated on resctrl mount so only at resctrl mount will
+the number of RMID supported by all monitoring resources and needed as size
+for rmid_ptrs[] be known.
+
+Separate closid_num_dirty_rmid[] and rmid_ptrs[] allocation and free in
+preparation for rmid_ptrs[] to be allocated on resctrl mount.
+
+Keep the rdtgroup_mutex protection around the allocation and free of
+closid_num_dirty_rmid[] as ARM needs this to guarantee memory ordering.
+
+Signed-off-by: Tony Luck <tony.luck@intel.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Reviewed-by: Reinette Chatre <reinette.chatre@intel.com>
+Link: https://lore.kernel.org/20251217172121.12030-1-tony.luck@intel.com
+Stable-dep-of: 52fce648607e ("fs/resctrl: Fix use-after-free during unmount")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/resctrl/monitor.c |   79 ++++++++++++++++++++++++++++++++-------------------
+ 1 file changed, 51 insertions(+), 28 deletions(-)
+
+--- a/fs/resctrl/monitor.c
++++ b/fs/resctrl/monitor.c
+@@ -865,36 +865,14 @@ void mbm_setup_overflow_handler(struct r
+ static int dom_data_init(struct rdt_resource *r)
+ {
+       u32 idx_limit = resctrl_arch_system_num_rmid_idx();
+-      u32 num_closid = resctrl_arch_get_num_closid(r);
+       struct rmid_entry *entry = NULL;
+       int err = 0, i;
+       u32 idx;
+       mutex_lock(&rdtgroup_mutex);
+-      if (IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID)) {
+-              u32 *tmp;
+-
+-              /*
+-               * If the architecture hasn't provided a sanitised value here,
+-               * this may result in larger arrays than necessary. Resctrl will
+-               * use a smaller system wide value based on the resources in
+-               * use.
+-               */
+-              tmp = kcalloc(num_closid, sizeof(*tmp), GFP_KERNEL);
+-              if (!tmp) {
+-                      err = -ENOMEM;
+-                      goto out_unlock;
+-              }
+-
+-              closid_num_dirty_rmid = tmp;
+-      }
+       rmid_ptrs = kcalloc(idx_limit, sizeof(struct rmid_entry), GFP_KERNEL);
+       if (!rmid_ptrs) {
+-              if (IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID)) {
+-                      kfree(closid_num_dirty_rmid);
+-                      closid_num_dirty_rmid = NULL;
+-              }
+               err = -ENOMEM;
+               goto out_unlock;
+       }
+@@ -930,11 +908,6 @@ static void dom_data_exit(struct rdt_res
+       if (!r->mon_capable)
+               goto out_unlock;
+-      if (IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID)) {
+-              kfree(closid_num_dirty_rmid);
+-              closid_num_dirty_rmid = NULL;
+-      }
+-
+       kfree(rmid_ptrs);
+       rmid_ptrs = NULL;
+@@ -1757,6 +1730,45 @@ ssize_t mbm_L3_assignments_write(struct
+       return ret ?: nbytes;
+ }
++static int closid_num_dirty_rmid_alloc(struct rdt_resource *r)
++{
++      if (IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID)) {
++              u32 num_closid = resctrl_arch_get_num_closid(r);
++              u32 *tmp;
++
++              /* For ARM memory ordering access to closid_num_dirty_rmid */
++              mutex_lock(&rdtgroup_mutex);
++
++              /*
++               * If the architecture hasn't provided a sanitised value here,
++               * this may result in larger arrays than necessary. Resctrl will
++               * use a smaller system wide value based on the resources in
++               * use.
++               */
++              tmp = kcalloc(num_closid, sizeof(*tmp), GFP_KERNEL);
++              if (!tmp) {
++                      mutex_unlock(&rdtgroup_mutex);
++                      return -ENOMEM;
++              }
++
++              closid_num_dirty_rmid = tmp;
++
++              mutex_unlock(&rdtgroup_mutex);
++      }
++
++      return 0;
++}
++
++static void closid_num_dirty_rmid_free(void)
++{
++      if (IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID)) {
++              mutex_lock(&rdtgroup_mutex);
++              kfree(closid_num_dirty_rmid);
++              closid_num_dirty_rmid = NULL;
++              mutex_unlock(&rdtgroup_mutex);
++      }
++}
++
+ /**
+  * resctrl_l3_mon_resource_init() - Initialise global monitoring structures.
+  *
+@@ -1777,10 +1789,16 @@ int resctrl_l3_mon_resource_init(void)
+       if (!r->mon_capable)
+               return 0;
+-      ret = dom_data_init(r);
++      ret = closid_num_dirty_rmid_alloc(r);
+       if (ret)
+               return ret;
++      ret = dom_data_init(r);
++      if (ret) {
++              closid_num_dirty_rmid_free();
++              return ret;
++      }
++
+       if (resctrl_arch_is_evt_configurable(QOS_L3_MBM_TOTAL_EVENT_ID)) {
+               mon_event_all[QOS_L3_MBM_TOTAL_EVENT_ID].configurable = true;
+               resctrl_file_fflags_init("mbm_total_bytes_config",
+@@ -1823,5 +1841,10 @@ void resctrl_l3_mon_resource_exit(void)
+ {
+       struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
++      if (!r->mon_capable)
++              return;
++
++      closid_num_dirty_rmid_free();
++
+       dom_data_exit(r);
+ }
diff --git a/queue-6.18/fs-resctrl-move-rmid-initialization-to-first-mount.patch b/queue-6.18/fs-resctrl-move-rmid-initialization-to-first-mount.patch
new file mode 100644 (file)
index 0000000..9578632
--- /dev/null
@@ -0,0 +1,181 @@
+From stable+bounces-289737-greg=kroah.com@vger.kernel.org Tue Jul 28 01:45:50 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:19 -0400
+Subject: fs/resctrl: Move RMID initialization to first mount
+To: stable@vger.kernel.org
+Cc: Tony Luck <tony.luck@intel.com>, "Borislav Petkov (AMD)" <bp@alien8.de>, Reinette Chatre <reinette.chatre@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727233421.2423844-5-sashal@kernel.org>
+
+From: Tony Luck <tony.luck@intel.com>
+
+[ Upstream commit d0891647fbc6e931f27517364cbc4ee1811d76db ]
+
+L3 monitor features are enumerated during resctrl initialization and
+rmid_ptrs[] that tracks all RMIDs and depends on the number of supported
+RMIDs is allocated during this time.
+
+Telemetry monitor features are enumerated during first resctrl mount and
+may support a different number of RMIDs compared to L3 monitor features.
+
+Delay allocation and initialization of rmid_ptrs[] until first mount.
+Since the number of RMIDs cannot change on later mounts, keep the same set of
+rmid_ptrs[] until resctrl_exit(). This is required because the limbo handler
+keeps running after resctrl is unmounted and needs to access rmid_ptrs[]
+as it keeps tracking busy RMIDs after unmount.
+
+Rename routines to match what they now do:
+dom_data_init() -> setup_rmid_lru_list()
+dom_data_exit() -> free_rmid_lru_list()
+
+Signed-off-by: Tony Luck <tony.luck@intel.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Reviewed-by: Reinette Chatre <reinette.chatre@intel.com>
+Link: https://lore.kernel.org/20251217172121.12030-1-tony.luck@intel.com
+Stable-dep-of: 52fce648607e ("fs/resctrl: Fix use-after-free during unmount")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/resctrl/internal.h |    4 +++
+ fs/resctrl/monitor.c  |   54 +++++++++++++++++++++++---------------------------
+ fs/resctrl/rdtgroup.c |    5 ++++
+ 3 files changed, 34 insertions(+), 29 deletions(-)
+
+--- a/fs/resctrl/internal.h
++++ b/fs/resctrl/internal.h
+@@ -351,6 +351,10 @@ int closids_supported(void);
+ void closid_free(int closid);
++int setup_rmid_lru_list(void);
++
++void free_rmid_lru_list(void);
++
+ int alloc_rmid(u32 closid);
+ void free_rmid(u32 closid, u32 rmid);
+--- a/fs/resctrl/monitor.c
++++ b/fs/resctrl/monitor.c
+@@ -862,20 +862,29 @@ void mbm_setup_overflow_handler(struct r
+               schedule_delayed_work_on(cpu, &dom->mbm_over, delay);
+ }
+-static int dom_data_init(struct rdt_resource *r)
++int setup_rmid_lru_list(void)
+ {
+-      u32 idx_limit = resctrl_arch_system_num_rmid_idx();
+       struct rmid_entry *entry = NULL;
+-      int err = 0, i;
++      u32 idx_limit;
+       u32 idx;
++      int i;
+-      mutex_lock(&rdtgroup_mutex);
++      if (!resctrl_arch_mon_capable())
++              return 0;
++      /*
++       * Called on every mount, but the number of RMIDs cannot change
++       * after the first mount, so keep using the same set of rmid_ptrs[]
++       * until resctrl_exit(). Note that the limbo handler continues to
++       * access rmid_ptrs[] after resctrl is unmounted.
++       */
++      if (rmid_ptrs)
++              return 0;
++
++      idx_limit = resctrl_arch_system_num_rmid_idx();
+       rmid_ptrs = kcalloc(idx_limit, sizeof(struct rmid_entry), GFP_KERNEL);
+-      if (!rmid_ptrs) {
+-              err = -ENOMEM;
+-              goto out_unlock;
+-      }
++      if (!rmid_ptrs)
++              return -ENOMEM;
+       for (i = 0; i < idx_limit; i++) {
+               entry = &rmid_ptrs[i];
+@@ -888,30 +897,24 @@ static int dom_data_init(struct rdt_reso
+       /*
+        * RESCTRL_RESERVED_CLOSID and RESCTRL_RESERVED_RMID are special and
+        * are always allocated. These are used for the rdtgroup_default
+-       * control group, which will be setup later in resctrl_init().
++       * control group, which was setup earlier in rdtgroup_setup_default().
+        */
+       idx = resctrl_arch_rmid_idx_encode(RESCTRL_RESERVED_CLOSID,
+                                          RESCTRL_RESERVED_RMID);
+       entry = __rmid_entry(idx);
+       list_del(&entry->list);
+-out_unlock:
+-      mutex_unlock(&rdtgroup_mutex);
+-
+-      return err;
++      return 0;
+ }
+-static void dom_data_exit(struct rdt_resource *r)
++void free_rmid_lru_list(void)
+ {
+-      mutex_lock(&rdtgroup_mutex);
+-
+-      if (!r->mon_capable)
+-              goto out_unlock;
++      if (!resctrl_arch_mon_capable())
++              return;
++      mutex_lock(&rdtgroup_mutex);
+       kfree(rmid_ptrs);
+       rmid_ptrs = NULL;
+-
+-out_unlock:
+       mutex_unlock(&rdtgroup_mutex);
+ }
+@@ -1773,7 +1776,8 @@ static void closid_num_dirty_rmid_free(v
+  * resctrl_l3_mon_resource_init() - Initialise global monitoring structures.
+  *
+  * Allocate and initialise global monitor resources that do not belong to a
+- * specific domain. i.e. the rmid_ptrs[] used for the limbo and free lists.
++ * specific domain. i.e. the closid_num_dirty_rmid[] used to find the CLOSID
++ * with the cleanest set of RMIDs.
+  * Called once during boot after the struct rdt_resource's have been configured
+  * but before the filesystem is mounted.
+  * Resctrl's cpuhp callbacks may be called before this point to bring a domain
+@@ -1793,12 +1797,6 @@ int resctrl_l3_mon_resource_init(void)
+       if (ret)
+               return ret;
+-      ret = dom_data_init(r);
+-      if (ret) {
+-              closid_num_dirty_rmid_free();
+-              return ret;
+-      }
+-
+       if (resctrl_arch_is_evt_configurable(QOS_L3_MBM_TOTAL_EVENT_ID)) {
+               mon_event_all[QOS_L3_MBM_TOTAL_EVENT_ID].configurable = true;
+               resctrl_file_fflags_init("mbm_total_bytes_config",
+@@ -1845,6 +1843,4 @@ void resctrl_l3_mon_resource_exit(void)
+               return;
+       closid_num_dirty_rmid_free();
+-
+-      dom_data_exit(r);
+ }
+--- a/fs/resctrl/rdtgroup.c
++++ b/fs/resctrl/rdtgroup.c
+@@ -2732,6 +2732,10 @@ static int rdt_get_tree(struct fs_contex
+               goto out;
+       }
++      ret = setup_rmid_lru_list();
++      if (ret)
++              goto out;
++
+       ret = rdtgroup_setup_root(ctx);
+       if (ret)
+               goto out;
+@@ -4518,4 +4522,5 @@ void resctrl_exit(void)
+        */
+       resctrl_l3_mon_resource_exit();
++      free_rmid_lru_list();
+ }
diff --git a/queue-6.18/fs-resctrl-split-l3-dependent-parts-out-of-__mon_event_count.patch b/queue-6.18/fs-resctrl-split-l3-dependent-parts-out-of-__mon_event_count.patch
new file mode 100644 (file)
index 0000000..6f53e8f
--- /dev/null
@@ -0,0 +1,56 @@
+From stable+bounces-289733-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:28 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:15 -0400
+Subject: fs/resctrl: Split L3 dependent parts out of __mon_event_count()
+To: stable@vger.kernel.org
+Cc: Tony Luck <tony.luck@intel.com>, Reinette Chatre <reinette.chatre@intel.com>, "Borislav Petkov (AMD)" <bp@alien8.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727233421.2423844-1-sashal@kernel.org>
+
+From: Tony Luck <tony.luck@intel.com>
+
+[ Upstream commit ad5c2ff75e0c53d2588dfc10eb87458e759b6bbe ]
+
+Carve out the L3 resource specific event reading code into a separate helper
+to support reading event data from a new monitoring resource.
+
+Suggested-by: Reinette Chatre <reinette.chatre@intel.com>
+Signed-off-by: Tony Luck <tony.luck@intel.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Reviewed-by: Reinette Chatre <reinette.chatre@intel.com>
+Link: https://lore.kernel.org/20251217172121.12030-1-tony.luck@intel.com
+Stable-dep-of: 52fce648607e ("fs/resctrl: Fix use-after-free during unmount")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/resctrl/monitor.c |   13 ++++++++++++-
+ 1 file changed, 12 insertions(+), 1 deletion(-)
+
+--- a/fs/resctrl/monitor.c
++++ b/fs/resctrl/monitor.c
+@@ -418,7 +418,7 @@ static void mbm_cntr_free(struct rdt_mon
+       memset(&d->cntr_cfg[cntr_id], 0, sizeof(*d->cntr_cfg));
+ }
+-static int __mon_event_count(struct rdtgroup *rdtgrp, struct rmid_read *rr)
++static int __l3_mon_event_count(struct rdtgroup *rdtgrp, struct rmid_read *rr)
+ {
+       int cpu = smp_processor_id();
+       u32 closid = rdtgrp->closid;
+@@ -499,6 +499,17 @@ static int __mon_event_count(struct rdtg
+       return ret;
+ }
++static int __mon_event_count(struct rdtgroup *rdtgrp, struct rmid_read *rr)
++{
++      switch (rr->r->rid) {
++      case RDT_RESOURCE_L3:
++              return __l3_mon_event_count(rdtgrp, rr);
++      default:
++              rr->err = -EINVAL;
++              return -EINVAL;
++      }
++}
++
+ /*
+  * mbm_bw_count() - Update bw count from values previously read by
+  *                __mon_event_count().
diff --git a/queue-6.18/fuse-uring-fix-race-between-registration-and-connection-abortion.patch b/queue-6.18/fuse-uring-fix-race-between-registration-and-connection-abortion.patch
new file mode 100644 (file)
index 0000000..0e5ac8a
--- /dev/null
@@ -0,0 +1,100 @@
+From stable+bounces-287817-greg=kroah.com@vger.kernel.org Wed Jul 22 13:41:16 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 07:33:50 -0400
+Subject: fuse-uring: fix race between registration and connection abortion
+To: stable@vger.kernel.org
+Cc: Joanne Koong <joannelkoong@gmail.com>, Bernd Schubert <bernd@bsbernd.com>, Miklos Szeredi <mszeredi@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722113350.1151043-1-sashal@kernel.org>
+
+From: Joanne Koong <joannelkoong@gmail.com>
+
+[ Upstream commit 952b5d36f6a298f57c52a59e72076c69386a8aaf ]
+
+This fixes this race:
+- thread a: io_uring_enter -> register sqe ->
+  fuse_uring_create_ring_ent -> allocate ent but doesn't grab queue_ref
+  yet
+- thread b: fuse_conn_destroy() -> fuse_chan_abort() ->
+  fuse_uring_abort() is a no-op due to queue ref being 0
+- thread a: grabs the queue_ref, queue_ref is now 1, rest of
+  fuse_uring_do_register() logic executes
+- thread b: fuse_chan_abort() returns, fuse_chan_wait_aborted() now runs
+  and calls
+  "wait_event(ring->stop_waitq, atomic_read(&ring->queue_refs) == 0);"
+The abort/unmount thread will hang indefinitely in unkillable state as
+nothing will decrement queue_refs or wake stop_waitq, and the ring,
+queue, and ent are leaked.
+
+Fix this by checking fch->connected under fch->lock after the created
+ent has grabbed a ref count on the queue. This ensures that in the
+scenario above, it is guaranteed that we either release the queue ref
+and wake up stop_waitq (in case fuse_chan_wait_aborted() is already
+waiting) in fuse_uring_do_register() when we detect !fch->connected, or
+if the connection is aborted after the check, it is guaranteed that the
+async teardown worker will be running in the background cleaning up ents
+and decrementing the ent's ref on the queue, which will unblock the
+eventual queue and ring teardown.
+
+Fixes: 24fe962c86f5 ("fuse: {io-uring} Handle SQEs - register commands")
+Cc: stable@vger.kernel.org
+Reviewed-by: Bernd Schubert <bernd@bsbernd.com>
+Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+[ changed fch->lock/fch->connected references to fc->lock/fc->connected since struct fuse_chan does not exist in this tree ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev_uring.c |   22 ++++++++++++++++------
+ 1 file changed, 16 insertions(+), 6 deletions(-)
+
+--- a/fs/fuse/dev_uring.c
++++ b/fs/fuse/dev_uring.c
+@@ -998,15 +998,26 @@ static bool is_ring_ready(struct fuse_ri
+ /*
+  * fuse_uring_req_fetch command handling
+  */
+-static void fuse_uring_do_register(struct fuse_ring_ent *ent,
+-                                 struct io_uring_cmd *cmd,
+-                                 unsigned int issue_flags)
++static int fuse_uring_do_register(struct fuse_ring_ent *ent,
++                                struct io_uring_cmd *cmd,
++                                unsigned int issue_flags)
+ {
+       struct fuse_ring_queue *queue = ent->queue;
+       struct fuse_ring *ring = queue->ring;
+       struct fuse_conn *fc = ring->fc;
+       struct fuse_iqueue *fiq = &fc->iq;
++      spin_lock(&fc->lock);
++      /* abort teardown path is running or has run */
++      if (!fc->connected) {
++              spin_unlock(&fc->lock);
++              if (atomic_dec_and_test(&ring->queue_refs))
++                      wake_up_all(&ring->stop_waitq);
++              kfree(ent);
++              return -ECONNABORTED;
++      }
++      spin_unlock(&fc->lock);
++
+       fuse_uring_prepare_cancel(cmd, issue_flags, ent);
+       spin_lock(&queue->lock);
+@@ -1023,6 +1034,7 @@ static void fuse_uring_do_register(struc
+                       wake_up_all(&fc->blocked_waitq);
+               }
+       }
++      return 0;
+ }
+ /*
+@@ -1138,9 +1150,7 @@ static int fuse_uring_register(struct io
+       if (IS_ERR(ent))
+               return PTR_ERR(ent);
+-      fuse_uring_do_register(ent, cmd, issue_flags);
+-
+-      return 0;
++      return fuse_uring_do_register(ent, cmd, issue_flags);
+ }
+ /*
diff --git a/queue-6.18/gpu-buddy-bail-out-of-try_harder-when-alignment-cannot-be-honoured.patch b/queue-6.18/gpu-buddy-bail-out-of-try_harder-when-alignment-cannot-be-honoured.patch
new file mode 100644 (file)
index 0000000..812084d
--- /dev/null
@@ -0,0 +1,140 @@
+From stable+bounces-288353-greg=kroah.com@vger.kernel.org Thu Jul 23 16:29:15 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:19:30 -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: <20260723141931.3218899-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.18/gpu-move-drm-buddy-allocator-one-level-up-part-two.patch b/queue-6.18/gpu-move-drm-buddy-allocator-one-level-up-part-two.patch
new file mode 100644 (file)
index 0000000..cda0733
--- /dev/null
@@ -0,0 +1,6199 @@
+From stable+bounces-288354-greg=kroah.com@vger.kernel.org Thu Jul 23 16:29:16 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:19:29 -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: <20260723141931.3218899-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 
+ MAINTAINERS                                          |   13 
+ drivers/gpu/Kconfig                                  |   13 
+ drivers/gpu/Makefile                                 |    3 
+ drivers/gpu/buddy.c                                  | 1322 +++++++++++++++++++
+ drivers/gpu/drm/Kconfig                              |    5 
+ drivers/gpu/drm/Kconfig.debug                        |    1 
+ drivers/gpu/drm/Makefile                             |    1 
+ drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c              |    2 
+ drivers/gpu/drm/amd/amdgpu/amdgpu_res_cursor.h       |   12 
+ drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c         |   79 -
+ drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h         |   20 
+ drivers/gpu/drm/drm_buddy.c                          | 1278 ------------------
+ drivers/gpu/drm/i915/gem/i915_gem_ttm.c              |    3 
+ drivers/gpu/drm/i915/i915_scatterlist.c              |   10 
+ drivers/gpu/drm/i915/i915_ttm_buddy_manager.c        |   59 
+ 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                     |   44 
+ drivers/gpu/drm/lib/drm_random.h                     |   28 
+ drivers/gpu/drm/tests/Makefile                       |    1 
+ drivers/gpu/drm/tests/drm_buddy_test.c               |  788 -----------
+ 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_svm.c                          |   12 
+ drivers/gpu/drm/xe/xe_ttm_vram_mgr.c                 |   71 -
+ drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h           |    4 
+ drivers/gpu/tests/Makefile                           |    4 
+ drivers/gpu/tests/gpu_buddy_test.c                   |  788 +++++++++++
+ drivers/gpu/tests/gpu_random.c                       |   44 
+ drivers/gpu/tests/gpu_random.h                       |   28 
+ drivers/video/Kconfig                                |    1 
+ include/drm/drm_buddy.h                              |  165 --
+ include/linux/gpu_buddy.h                            |  177 ++
+ 38 files changed, 2597 insertions(+), 2477 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} (66%)
+ rename drivers/gpu/{drm/lib/drm_random.c => tests/gpu_random.c} (59%)
+ 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:
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -8656,6 +8656,19 @@ T:      git https://gitlab.freedesktop.org/dr
+ F:    drivers/gpu/drm/ttm/
+ F:    include/drm/ttm/
++GPU BUDDY ALLOCATOR
++M:    Matthew Auld <matthew.auld@intel.com>
++M:    Arun Pravin <arunpravin.paneerselvam@amd.com>
++R:    Christian Koenig <christian.koenig@amd.com>
++L:    dri-devel@lists.freedesktop.org
++S:    Maintained
++T:    git https://gitlab.freedesktop.org/drm/misc/kernel.git
++F:    drivers/gpu/drm_buddy.c
++F:    drivers/gpu/buddy.c
++F:    drivers/gpu/tests/gpu_buddy_test.c
++F:    include/linux/gpu_buddy.h
++F:    include/drm/drm_buddy.h
++
+ DRM AUTOMATED TESTING
+ M:    Helen Koike <helen.fornazier@gmail.com>
+ M:    Vignesh Raman <vignesh.raman@collabora.com>
+--- /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,7 +2,8 @@
+ # 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_NOVA_CORE)               += nova-core/
++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
+@@ -220,6 +220,7 @@ config DRM_GPUSVM
+ config DRM_BUDDY
+       tristate
+       depends on DRM
++      select GPU_BUDDY
+       help
+         A page based buddy allocator
+@@ -416,10 +417,6 @@ config DRM_HYPERV
+ config DRM_PANEL_BACKLIGHT_QUIRKS
+       tristate
+-config DRM_LIB_RANDOM
+-      bool
+-      default n
+-
+ config DRM_PRIVACY_SCREEN
+       bool
+       default n
+--- a/drivers/gpu/drm/Kconfig.debug
++++ b/drivers/gpu/drm/Kconfig.debug
+@@ -69,7 +69,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 DRM_SYSFB_HELPER
+       select PRIME_NUMBERS
+       default KUNIT_ALL_TESTS
+--- a/drivers/gpu/drm/Makefile
++++ b/drivers/gpu/drm/Makefile
+@@ -77,7 +77,6 @@ drm-$(CONFIG_DRM_CLIENT) += \
+       drm_client.o \
+       drm_client_event.o \
+       drm_client_modeset.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_ras.c
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c
+@@ -5416,7 +5416,7 @@ int amdgpu_ras_add_critical_region(struc
+       struct amdgpu_ras *con = amdgpu_ras_get_context(adev);
+       struct amdgpu_vram_mgr_resource *vres;
+       struct ras_critical_region *region;
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       int ret = 0;
+       if (!bo || !bo->tbo.resource)
+--- 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;
+@@ -125,7 +125,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;
+@@ -146,7 +146,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);
+@@ -175,7 +175,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
+@@ -25,6 +25,7 @@
+ #include <linux/dma-mapping.h>
+ #include <drm/ttm/ttm_range_manager.h>
+ #include <drm/drm_drv.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);
+@@ -403,7 +404,7 @@ int amdgpu_vram_mgr_query_address_block_
+                       uint64_t address, struct amdgpu_vram_block_info *info)
+ {
+       struct amdgpu_vram_mgr_resource *vres;
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       u64 start, size;
+       int ret = -ENOENT;
+@@ -450,8 +451,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;
+@@ -493,17 +494,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)
+@@ -516,7 +517,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);
+@@ -536,7 +537,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,
+@@ -545,7 +546,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);
+@@ -566,7 +567,7 @@ static int amdgpu_vram_mgr_new(struct tt
+       list_add_tail(&vres->vres_node, &mgr->allocated_vres_list);
+       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;
+@@ -576,7 +577,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);
+       }
+@@ -614,7 +615,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);
+@@ -637,8 +638,8 @@ 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);
+@@ -649,7 +650,7 @@ static void amdgpu_vram_mgr_del(struct t
+       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);
+@@ -688,7 +689,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++;
+@@ -704,10 +705,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) {
+@@ -792,10 +793,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);
+ }
+@@ -815,7 +816,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) {
+@@ -848,7 +849,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) {
+@@ -877,7 +878,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",
+@@ -930,7 +931,7 @@ int amdgpu_vram_mgr_init(struct amdgpu_d
+       mgr->default_page_size = PAGE_SIZE;
+       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;
+@@ -965,11 +966,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;
+@@ -57,19 +57,19 @@ struct amdgpu_vram_mgr_resource {
+       struct amdgpu_vres_task task;
+ };
+-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 *
+@@ -82,8 +82,8 @@ static inline void amdgpu_vram_mgr_set_c
+ {
+       struct amdgpu_vram_mgr_resource *ares = to_amdgpu_vram_mgr_resource(res);
+-      WARN_ON(ares->flags & DRM_BUDDY_CLEARED);
+-      ares->flags |= DRM_BUDDY_CLEARED;
++      WARN_ON(ares->flags & GPU_BUDDY_CLEARED);
++      ares->flags |= GPU_BUDDY_CLEARED;
+ }
+ int amdgpu_vram_mgr_query_address_block_info(struct amdgpu_vram_mgr *mgr,
+--- a/drivers/gpu/drm/drm_buddy.c
++++ b/drivers/gpu/drm/drm_buddy.c
+@@ -10,1250 +10,9 @@
+ #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);
+-
+-              if (WARN_ON(!drm_buddy_block_is_free(mm->roots[i])))
+-                      kunit_fail_current_test("buddy_fini() root");
+-
+-              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
+@@ -1262,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);
+ }
+@@ -1279,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;
+@@ -1287,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;
+@@ -1296,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++;
+                       }
+               }
+@@ -1314,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
+@@ -5,9 +5,10 @@
+ #include <linux/shmem_fs.h>
++#include <linux/gpu_buddy.h>
++#include <drm/drm_print.h>
+ #include <drm/ttm/ttm_placement.h>
+ #include <drm/ttm/ttm_tt.h>
+-#include <drm/drm_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
+@@ -5,18 +5,19 @@
+ #include <linux/slab.h>
++#include <linux/gpu_buddy.h>
++#include <drm/drm_buddy.h>
++#include <drm/drm_print.h>
+ #include <drm/ttm/ttm_placement.h>
+ #include <drm/ttm/ttm_bo.h>
+-#include <drm/drm_buddy.h>
+-
+ #include "i915_ttm_buddy_manager.h"
+ #include "i915_gem.h"
+ 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;
+@@ -447,8 +447,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);
+@@ -487,8 +487,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",
+@@ -527,14 +527,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,44 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-#include <linux/bitops.h>
+-#include <linux/export.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/prandom.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
+@@ -7,7 +7,6 @@ obj-$(CONFIG_DRM_KUNIT_TEST) += \
+       drm_atomic_test.o \
+       drm_atomic_state_test.o \
+       drm_bridge_test.o \
+-      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,788 +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;
+-      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 from each
+-       * root, free as cleared and then call fini. This will ensure the multi-root
+-       * force merge during fini.
+-       */
+-      mm_size = (SZ_4K << max_order) + (SZ_4K << (max_order - 2));
+-
+-      KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+-      KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, SZ_4K << max_order,
+-                                                          4 * ps, ps, &allocated,
+-                                                          DRM_BUDDY_RANGE_ALLOCATION),
+-                              "buddy_alloc hit an error size=%lu\n", 4 * ps);
+-      drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, SZ_4K << max_order,
+-                                                          2 * ps, ps, &allocated,
+-                                                          DRM_BUDDY_CLEAR_ALLOCATION),
+-                              "buddy_alloc hit an error size=%lu\n", 2 * ps);
+-      drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
+-      KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, SZ_4K << max_order, mm_size,
+-                                                          ps, ps, &allocated,
+-                                                          DRM_BUDDY_RANGE_ALLOCATION),
+-                              "buddy_alloc hit an error size=%lu\n", ps);
+-      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
+@@ -30,7 +30,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;
+@@ -46,14 +46,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);
+@@ -66,7 +66,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);
+@@ -78,10 +78,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);
+@@ -105,7 +105,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);
+@@ -141,7 +141,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
+@@ -58,7 +58,7 @@ struct xe_res_cursor {
+       /** @dma_addr: Current element in a struct drm_pagemap_addr array */
+       const struct drm_pagemap_addr *dma_addr;
+       /** @mm: Buddy allocator for VRAM cursor */
+-      struct drm_buddy *mm;
++      struct gpu_buddy *mm;
+       /**
+        * @dma_start: DMA start address for the current segment.
+        * This may be different to @dma_addr.addr since elements in
+@@ -69,7 +69,7 @@ struct xe_res_cursor {
+       u64 dma_seg_size;
+ };
+-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;
+@@ -104,30 +104,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;
+@@ -259,7 +259,7 @@ static inline void xe_res_first_dma(cons
+  */
+ 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;
+@@ -295,18 +295,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_svm.c
++++ b/drivers/gpu/drm/xe/xe_svm.c
+@@ -689,7 +689,7 @@ static u64 block_offset_to_pfn(struct xe
+       return PHYS_PFN(offset + vr->hpa_base);
+ }
+-static struct drm_buddy *vram_to_buddy(struct xe_vram_region *vram)
++static struct gpu_buddy *vram_to_buddy(struct xe_vram_region *vram)
+ {
+       return &vram->ttm.mm;
+ }
+@@ -700,16 +700,16 @@ static int xe_svm_populate_devmem_pfn(st
+       struct xe_bo *bo = to_xe_bo(devmem_allocation);
+       struct ttm_resource *res = bo->ttm.resource;
+       struct list_head *blocks = &to_xe_ttm_vram_mgr_resource(res)->blocks;
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       int j = 0;
+       list_for_each_entry(block, blocks, link) {
+               struct xe_vram_region *vr = block->private;
+-              struct drm_buddy *buddy = vram_to_buddy(vr);
+-              u64 block_pfn = block_offset_to_pfn(vr, drm_buddy_block_offset(block));
++              struct gpu_buddy *buddy = vram_to_buddy(vr);
++              u64 block_pfn = block_offset_to_pfn(vr, gpu_buddy_block_offset(block));
+               int i;
+-              for (i = 0; i < drm_buddy_block_size(buddy, block) >> PAGE_SHIFT; ++i)
++              for (i = 0; i < gpu_buddy_block_size(buddy, block) >> PAGE_SHIFT; ++i)
+                       pfn[j++] = block_pfn + i;
+       }
+@@ -877,7 +877,7 @@ static int xe_drm_pagemap_populate_mm(st
+       struct dma_fence *pre_migrate_fence = NULL;
+       struct xe_device *xe = vr->xe;
+       struct device *dev = xe->drm.dev;
+-      struct drm_buddy_block *block;
++      struct gpu_buddy_block *block;
+       struct xe_validation_ctx vctx;
+       struct list_head *blocks;
+       struct drm_exec exec;
+--- a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
++++ b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
+@@ -6,6 +6,7 @@
+ #include <drm/drm_managed.h>
+ #include <drm/drm_drv.h>
++#include <drm/drm_buddy.h>
+ #include <drm/ttm/ttm_placement.h>
+ #include <drm/ttm/ttm_range_manager.h>
+@@ -17,16 +18,16 @@
+ #include "xe_ttm_vram_mgr.h"
+ #include "xe_vram_types.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);
+@@ -34,12 +35,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;
+       }
+@@ -53,7 +54,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, min_page_size;
+       unsigned long lpfn;
+       int err;
+@@ -80,10 +81,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;
+@@ -119,27 +120,27 @@ static int xe_ttm_vram_mgr_new(struct tt
+               lpfn = max_t(unsigned long, place->fpfn + (size >> PAGE_SHIFT), lpfn);
+       }
+-      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, size,
+                                    min_page_size, &vres->blocks, vres->flags);
+       if (err)
+               goto error_unlock;
+       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;
+@@ -159,11 +160,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;
+       }
+@@ -185,10 +186,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);
+@@ -201,7 +202,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",
+@@ -224,8 +225,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;
+@@ -235,9 +236,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;
+@@ -254,8 +255,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;
+@@ -265,9 +266,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;
+@@ -297,7 +298,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);
+@@ -328,7 +329,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;
+@@ -376,7 +377,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++;
+@@ -393,10 +394,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>
+ /**
+@@ -18,7 +18,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;
+       /** @visible_size: Proped size of the CPU visible portion */
+       u64 visible_size;
+       /** @visible_avail: CPU visible portion still unallocated */
+--- /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,788 @@
++// 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 gpu_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 GPU 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 gpu_test_buddy_alloc_clear(struct kunit *test)
++{
++      unsigned long n_pages, total, i = 0;
++      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 from each
++       * root, free as cleared and then call fini. This will ensure the multi-root
++       * force merge during fini.
++       */
++      mm_size = (SZ_4K << max_order) + (SZ_4K << (max_order - 2));
++
++      KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps));
++      KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, SZ_4K << max_order,
++                                                          4 * ps, ps, &allocated,
++                                                          GPU_BUDDY_RANGE_ALLOCATION),
++                              "buddy_alloc hit an error size=%lu\n", 4 * ps);
++      gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED);
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, SZ_4K << max_order,
++                                                          2 * ps, ps, &allocated,
++                                                          GPU_BUDDY_CLEAR_ALLOCATION),
++                              "buddy_alloc hit an error size=%lu\n", 2 * ps);
++      gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED);
++      KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, SZ_4K << max_order, mm_size,
++                                                          ps, ps, &allocated,
++                                                          GPU_BUDDY_RANGE_ALLOCATION),
++                              "buddy_alloc hit an error size=%lu\n", ps);
++      gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED);
++      gpu_buddy_fini(&mm);
++}
++
++static void gpu_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 gpu_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 gpu_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 gpu_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 gpu_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 GPU buddy manager, with random_seed=0x%x\n",
++                 random_seed);
++
++      return 0;
++}
++
++static struct kunit_case gpu_buddy_tests[] = {
++      KUNIT_CASE(gpu_test_buddy_alloc_limit),
++      KUNIT_CASE(gpu_test_buddy_alloc_optimistic),
++      KUNIT_CASE(gpu_test_buddy_alloc_pessimistic),
++      KUNIT_CASE(gpu_test_buddy_alloc_pathological),
++      KUNIT_CASE(gpu_test_buddy_alloc_contiguous),
++      KUNIT_CASE(gpu_test_buddy_alloc_clear),
++      KUNIT_CASE(gpu_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,44 @@
++// SPDX-License-Identifier: GPL-2.0
++#include <linux/bitops.h>
++#include <linux/export.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/prandom.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"
+ source "drivers/gpu/nova-core/Kconfig"
+--- a/include/drm/drm_buddy.h
++++ b/include/drm/drm_buddy.h
+@@ -6,166 +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 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,177 @@
++/* 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>
++
++#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.18/i3c-mipi-i3c-hci-fix-handling-of-shared-irqs-during-early-initialization.patch b/queue-6.18/i3c-mipi-i3c-hci-fix-handling-of-shared-irqs-during-early-initialization.patch
new file mode 100644 (file)
index 0000000..47ea11a
--- /dev/null
@@ -0,0 +1,85 @@
+From stable+bounces-290908-greg=kroah.com@vger.kernel.org Wed Jul 29 23:24:09 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 17:23:51 -0400
+Subject: i3c: mipi-i3c-hci: Fix handling of shared IRQs during early initialization
+To: stable@vger.kernel.org
+Cc: Adrian Hunter <adrian.hunter@intel.com>, Frank Li <Frank.Li@nxp.com>, Alexandre Belloni <alexandre.belloni@bootlin.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729212351.1210709-2-sashal@kernel.org>
+
+From: Adrian Hunter <adrian.hunter@intel.com>
+
+[ Upstream commit c6396b835a5e599c4df656112140f065bb544a24 ]
+
+Shared interrupts may fire unexpectedly, including during periods when the
+controller is not yet fully initialized. Commit b9a15012a1452
+("i3c: mipi-i3c-hci: Add optional Runtime PM support") addressed this issue
+for the runtime-suspended state, but the same problem can also occur before
+the bus is enabled for the first time.
+
+Ensure the IRQ handler ignores interrupts until initialization is complete
+by making consistent use of the existing irq_inactive flag.  The flag is
+now set to false immediately before enabling the bus.
+
+To guarantee correct ordering with respect to the IRQ handler, protect
+all transitions of irq_inactive with the same spinlock used inside the
+handler.
+
+Fixes: b8460480f62e1 ("i3c: mipi-i3c-hci: Allow for Multi-Bus Instances")
+Cc: stable@vger.kernel.org
+Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Link: https://patch.msgid.link/20260306072451.11131-14-adrian.hunter@intel.com
+Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/i3c/master/mipi-i3c-hci/core.c |   13 +++++++++++++
+ drivers/i3c/master/mipi-i3c-hci/hci.h  |    1 +
+ 2 files changed, 14 insertions(+)
+
+--- a/drivers/i3c/master/mipi-i3c-hci/core.c
++++ b/drivers/i3c/master/mipi-i3c-hci/core.c
+@@ -147,6 +147,8 @@ static int i3c_hci_bus_init(struct i3c_m
+       if (hci->quirks & HCI_QUIRK_RESP_BUF_THLD)
+               amd_set_resp_buf_thld(hci);
++      WRITE_ONCE(hci->irq_inactive, false);
++
+       /* Enable bus with Hot-Join disabled */
+       reg_set(HC_CONTROL, HC_CONTROL_BUS_ENABLE | HC_CONTROL_HOT_JOIN_CTRL);
+       dev_dbg(&hci->master.dev, "HC_CONTROL = %#x", reg_read(HC_CONTROL));
+@@ -537,6 +539,15 @@ static irqreturn_t i3c_hci_irq_handler(i
+       irqreturn_t result = IRQ_NONE;
+       u32 val;
++      /*
++       * The IRQ can be shared, so the handler may be called when the IRQ is
++       * due to a different device. That could happen before the controller
++       * has been initialized, so exit immediately if IRQs are not expected
++       * for this device.
++       */
++      if (READ_ONCE(hci->irq_inactive))
++              return IRQ_NONE;
++
+       val = reg_read(INTR_STATUS);
+       reg_write(INTR_STATUS, val);
+       dev_dbg(&hci->master.dev, "INTR_STATUS %#x", val);
+@@ -776,6 +787,8 @@ static int i3c_hci_probe(struct platform
+       if (ret)
+               return ret;
++      WRITE_ONCE(hci->irq_inactive, true);
++
+       irq = platform_get_irq(pdev, 0);
+       ret = devm_request_irq(&pdev->dev, irq, i3c_hci_irq_handler,
+                              IRQF_SHARED, NULL, hci);
+--- a/drivers/i3c/master/mipi-i3c-hci/hci.h
++++ b/drivers/i3c/master/mipi-i3c-hci/hci.h
+@@ -46,6 +46,7 @@ struct i3c_hci {
+       void *io_data;
+       const struct hci_cmd_ops *cmd;
+       spinlock_t lock;
++      bool irq_inactive;
+       struct mutex control_mutex;
+       atomic_t next_cmd_tid;
+       u32 caps;
diff --git a/queue-6.18/i3c-mipi-i3c-hci-fix-hot-join-nack.patch b/queue-6.18/i3c-mipi-i3c-hci-fix-hot-join-nack.patch
new file mode 100644 (file)
index 0000000..6e9b236
--- /dev/null
@@ -0,0 +1,42 @@
+From stable+bounces-290907-greg=kroah.com@vger.kernel.org Wed Jul 29 23:24:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 17:23:50 -0400
+Subject: i3c: mipi-i3c-hci: Fix Hot-Join NACK
+To: stable@vger.kernel.org
+Cc: Adrian Hunter <adrian.hunter@intel.com>, Frank Li <Frank.Li@nxp.com>, Alexandre Belloni <alexandre.belloni@bootlin.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729212351.1210709-1-sashal@kernel.org>
+
+From: Adrian Hunter <adrian.hunter@intel.com>
+
+[ Upstream commit fa9586bd77ada1e3861c7bef65f6bb9dcf8d9481 ]
+
+The MIPI I3C HCI host controller driver does not implement Hot-Join
+handling, yet Hot-Join response control defaults to allowing devices to
+Hot-Join the bus.  Configure HC_CONTROL_HOT_JOIN_CTRL to NACK all Hot-Join
+attempts.
+
+Fixes: 9ad9a52cce282 ("i3c/master: introduce the mipi-i3c-hci driver")
+Cc: stable@vger.kernel.org
+Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Link: https://patch.msgid.link/20260306072451.11131-3-adrian.hunter@intel.com
+Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
+Stable-dep-of: c6396b835a5e ("i3c: mipi-i3c-hci: Fix handling of shared IRQs during early initialization")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/i3c/master/mipi-i3c-hci/core.c |    3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/i3c/master/mipi-i3c-hci/core.c
++++ b/drivers/i3c/master/mipi-i3c-hci/core.c
+@@ -147,7 +147,8 @@ static int i3c_hci_bus_init(struct i3c_m
+       if (hci->quirks & HCI_QUIRK_RESP_BUF_THLD)
+               amd_set_resp_buf_thld(hci);
+-      reg_set(HC_CONTROL, HC_CONTROL_BUS_ENABLE);
++      /* Enable bus with Hot-Join disabled */
++      reg_set(HC_CONTROL, HC_CONTROL_BUS_ENABLE | HC_CONTROL_HOT_JOIN_CTRL);
+       dev_dbg(&hci->master.dev, "HC_CONTROL = %#x", reg_read(HC_CONTROL));
+       return 0;
diff --git a/queue-6.18/kho-add-interfaces-to-unpreserve-folios-page-ranges-and-vmalloc.patch b/queue-6.18/kho-add-interfaces-to-unpreserve-folios-page-ranges-and-vmalloc.patch
new file mode 100644 (file)
index 0000000..48f7032
--- /dev/null
@@ -0,0 +1,227 @@
+From stable+bounces-289085-greg=kroah.com@vger.kernel.org Sat Jul 25 12:54:43 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 06:54:32 -0400
+Subject: kho: add interfaces to unpreserve folios, page ranges, and vmalloc
+To: stable@vger.kernel.org
+Cc: Pasha Tatashin <pasha.tatashin@soleen.com>, Pratyush Yadav <pratyush@kernel.org>, "Mike Rapoport (Microsoft)" <rppt@kernel.org>, Alexander Graf <graf@amazon.com>, Changyuan Lyu <changyuanl@google.com>, Christian Brauner <brauner@kernel.org>, Jason Gunthorpe <jgg@nvidia.com>, Jason Gunthorpe <jgg@ziepe.ca>, Jonathan Corbet <corbet@lwn.net>, Masahiro Yamada <masahiroy@kernel.org>, Miguel Ojeda <ojeda@kernel.org>, Randy Dunlap <rdunlap@infradead.org>, Simon Horman <horms@kernel.org>, Tejun Heo <tj@kernel.org>, Zhu Yanjun <yanjun.zhu@linux.dev>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725105433.1853930-1-sashal@kernel.org>
+
+From: Pasha Tatashin <pasha.tatashin@soleen.com>
+
+[ Upstream commit 36f8f7ef7fd2f238922e9d217e86c69838319d8c ]
+
+Allow users of KHO to cancel the previous preservation by adding the
+necessary interfaces to unpreserve folio, pages, and vmallocs.
+
+Link: https://lkml.kernel.org/r/20251101142325.1326536-4-pasha.tatashin@soleen.com
+Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
+Reviewed-by: Pratyush Yadav <pratyush@kernel.org>
+Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
+Cc: Alexander Graf <graf@amazon.com>
+Cc: Changyuan Lyu <changyuanl@google.com>
+Cc: Christian Brauner <brauner@kernel.org>
+Cc: Jason Gunthorpe <jgg@nvidia.com>
+Cc: Jason Gunthorpe <jgg@ziepe.ca>
+Cc: Jonathan Corbet <corbet@lwn.net>
+Cc: Masahiro Yamada <masahiroy@kernel.org>
+Cc: Miguel Ojeda <ojeda@kernel.org>
+Cc: Randy Dunlap <rdunlap@infradead.org>
+Cc: Simon Horman <horms@kernel.org>
+Cc: Tejun Heo <tj@kernel.org>
+Cc: Zhu Yanjun <yanjun.zhu@linux.dev>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Stable-dep-of: 0e39380a7316 ("kho: make sure scratch size is always aligned by CMA_MIN_ALIGNMENT_BYTES")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/kexec_handover.h |   18 +++++++
+ kernel/kexec_handover.c        |  104 +++++++++++++++++++++++++++++++++++------
+ 2 files changed, 109 insertions(+), 13 deletions(-)
+
+--- a/include/linux/kexec_handover.h
++++ b/include/linux/kexec_handover.h
+@@ -52,8 +52,11 @@ bool kho_is_enabled(void);
+ bool is_kho_boot(void);
+ int kho_preserve_folio(struct folio *folio);
++int kho_unpreserve_folio(struct folio *folio);
+ int kho_preserve_pages(struct page *page, unsigned int nr_pages);
++int kho_unpreserve_pages(struct page *page, unsigned int nr_pages);
+ int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation);
++int kho_unpreserve_vmalloc(struct kho_vmalloc *preservation);
+ struct folio *kho_restore_folio(phys_addr_t phys);
+ struct page *kho_restore_pages(phys_addr_t phys, unsigned int nr_pages);
+ void *kho_restore_vmalloc(const struct kho_vmalloc *preservation);
+@@ -83,16 +86,31 @@ static inline int kho_preserve_folio(str
+       return -EOPNOTSUPP;
+ }
++static inline int kho_unpreserve_folio(struct folio *folio)
++{
++      return -EOPNOTSUPP;
++}
++
+ static inline int kho_preserve_pages(struct page *page, unsigned int nr_pages)
+ {
+       return -EOPNOTSUPP;
+ }
++static inline int kho_unpreserve_pages(struct page *page, unsigned int nr_pages)
++{
++      return -EOPNOTSUPP;
++}
++
+ static inline int kho_preserve_vmalloc(void *ptr,
+                                      struct kho_vmalloc *preservation)
+ {
+       return -EOPNOTSUPP;
+ }
++
++static inline int kho_unpreserve_vmalloc(struct kho_vmalloc *preservation)
++{
++      return -EOPNOTSUPP;
++}
+ static inline struct folio *kho_restore_folio(phys_addr_t phys)
+ {
+--- a/kernel/kexec_handover.c
++++ b/kernel/kexec_handover.c
+@@ -153,26 +153,33 @@ static void *xa_load_or_alloc(struct xar
+       return no_free_ptr(elm);
+ }
+-static void __kho_unpreserve(struct kho_mem_track *track, unsigned long pfn,
+-                           unsigned long end_pfn)
++static void __kho_unpreserve_order(struct kho_mem_track *track, unsigned long pfn,
++                                 unsigned int order)
+ {
+       struct kho_mem_phys_bits *bits;
+       struct kho_mem_phys *physxa;
++      const unsigned long pfn_high = pfn >> order;
+-      while (pfn < end_pfn) {
+-              const unsigned int order =
+-                      min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
+-              const unsigned long pfn_high = pfn >> order;
++      physxa = xa_load(&track->orders, order);
++      if (WARN_ON_ONCE(!physxa))
++              return;
+-              physxa = xa_load(&track->orders, order);
+-              if (WARN_ON_ONCE(!physxa))
+-                      return;
++      bits = xa_load(&physxa->phys_bits, pfn_high / PRESERVE_BITS);
++      if (WARN_ON_ONCE(!bits))
++              return;
++
++      clear_bit(pfn_high % PRESERVE_BITS, bits->preserve);
++}
++
++static void __kho_unpreserve(struct kho_mem_track *track, unsigned long pfn,
++                           unsigned long end_pfn)
++{
++      unsigned int order;
+-              bits = xa_load(&physxa->phys_bits, pfn_high / PRESERVE_BITS);
+-              if (WARN_ON_ONCE(!bits))
+-                      return;
++      while (pfn < end_pfn) {
++              order = min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
+-              clear_bit(pfn_high % PRESERVE_BITS, bits->preserve);
++              __kho_unpreserve_order(track, pfn, order);
+               pfn += 1 << order;
+       }
+@@ -740,6 +747,30 @@ int kho_preserve_folio(struct folio *fol
+ EXPORT_SYMBOL_GPL(kho_preserve_folio);
+ /**
++ * kho_unpreserve_folio - unpreserve a folio.
++ * @folio: folio to unpreserve.
++ *
++ * Instructs KHO to unpreserve a folio that was preserved by
++ * kho_preserve_folio() before. The provided @folio (pfn and order)
++ * must exactly match a previously preserved folio.
++ *
++ * Return: 0 on success, error code on failure
++ */
++int kho_unpreserve_folio(struct folio *folio)
++{
++      const unsigned long pfn = folio_pfn(folio);
++      const unsigned int order = folio_order(folio);
++      struct kho_mem_track *track = &kho_out.track;
++
++      if (kho_out.finalized)
++              return -EBUSY;
++
++      __kho_unpreserve_order(track, pfn, order);
++      return 0;
++}
++EXPORT_SYMBOL_GPL(kho_unpreserve_folio);
++
++/**
+  * kho_preserve_pages - preserve contiguous pages across kexec
+  * @page: first page in the list.
+  * @nr_pages: number of pages.
+@@ -783,6 +814,33 @@ int kho_preserve_pages(struct page *page
+ }
+ EXPORT_SYMBOL_GPL(kho_preserve_pages);
++/**
++ * kho_unpreserve_pages - unpreserve contiguous pages.
++ * @page: first page in the list.
++ * @nr_pages: number of pages.
++ *
++ * Instructs KHO to unpreserve @nr_pages contiguous pages starting from @page.
++ * This must be called with the same @page and @nr_pages as the corresponding
++ * kho_preserve_pages() call. Unpreserving arbitrary sub-ranges of larger
++ * preserved blocks is not supported.
++ *
++ * Return: 0 on success, error code on failure
++ */
++int kho_unpreserve_pages(struct page *page, unsigned int nr_pages)
++{
++      struct kho_mem_track *track = &kho_out.track;
++      const unsigned long start_pfn = page_to_pfn(page);
++      const unsigned long end_pfn = start_pfn + nr_pages;
++
++      if (kho_out.finalized)
++              return -EBUSY;
++
++      __kho_unpreserve(track, start_pfn, end_pfn);
++
++      return 0;
++}
++EXPORT_SYMBOL_GPL(kho_unpreserve_pages);
++
+ struct kho_vmalloc_hdr {
+       DECLARE_KHOSER_PTR(next, struct kho_vmalloc_chunk *);
+ };
+@@ -945,6 +1003,26 @@ err_free:
+ EXPORT_SYMBOL_GPL(kho_preserve_vmalloc);
+ /**
++ * kho_unpreserve_vmalloc - unpreserve memory allocated with vmalloc()
++ * @preservation: preservation metadata returned by kho_preserve_vmalloc()
++ *
++ * Instructs KHO to unpreserve the area in vmalloc address space that was
++ * previously preserved with kho_preserve_vmalloc().
++ *
++ * Return: 0 on success, error code on failure
++ */
++int kho_unpreserve_vmalloc(struct kho_vmalloc *preservation)
++{
++      if (kho_out.finalized)
++              return -EBUSY;
++
++      kho_vmalloc_free_chunks(preservation);
++
++      return 0;
++}
++EXPORT_SYMBOL_GPL(kho_unpreserve_vmalloc);
++
++/**
+  * kho_restore_vmalloc - recreates and populates an area in vmalloc address
+  * space from the preserved memory.
+  * @preservation: preservation metadata.
diff --git a/queue-6.18/kho-make-sure-scratch-size-is-always-aligned-by-cma_min_alignment_bytes.patch b/queue-6.18/kho-make-sure-scratch-size-is-always-aligned-by-cma_min_alignment_bytes.patch
new file mode 100644 (file)
index 0000000..8673de8
--- /dev/null
@@ -0,0 +1,76 @@
+From stable+bounces-289086-greg=kroah.com@vger.kernel.org Sat Jul 25 12:54:48 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 06:54:33 -0400
+Subject: kho: make sure scratch size is always aligned by CMA_MIN_ALIGNMENT_BYTES
+To: stable@vger.kernel.org
+Cc: "Pratyush Yadav (Google)" <pratyush@kernel.org>, stable@kernel.org, Pasha Tatashin <pasha.tatashin@soleen.com>, "Mike Rapoport (Microsoft)" <rppt@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725105433.1853930-2-sashal@kernel.org>
+
+From: "Pratyush Yadav (Google)" <pratyush@kernel.org>
+
+[ Upstream commit 0e39380a7316122e1b00012b3f3cd3e318b3e7d3 ]
+
+When using scratch_scale, the scratch sizes are rounded up to
+CMA_MIN_ALIGNMENT_BYTES since they will be released as MIGRATE_CMA. This
+is not done when using fixed scratch sizes via command line. This can
+result in user specifying a size which is not aligned, and thus kernel
+releasing a pageblock that is only partially scratch.
+
+Do the rounding up for both cases in scratch_size_update().
+
+Fixes: 3dc92c311498 ("kexec: add Kexec HandOver (KHO) generation helpers")
+Cc: stable@kernel.org
+Signed-off-by: Pratyush Yadav (Google) <pratyush@kernel.org>
+Link: https://patch.msgid.link/20260519160554.2713361-1-pratyush@kernel.org
+Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
+Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/kexec_handover.c |   32 +++++++++++++++++++++-----------
+ 1 file changed, 21 insertions(+), 11 deletions(-)
+
+--- a/kernel/kexec_handover.c
++++ b/kernel/kexec_handover.c
+@@ -569,20 +569,30 @@ early_param("kho_scratch", kho_parse_scr
+ static void __init scratch_size_update(void)
+ {
+-      phys_addr_t size;
++      /*
++       * If fixed sizes are not provided via command line, calculate them
++       * now.
++       */
++      if (scratch_scale) {
++              phys_addr_t size;
+-      if (!scratch_scale)
+-              return;
++              size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT,
++                                                 NUMA_NO_NODE);
++              size = size * scratch_scale / 100;
++              scratch_size_lowmem = size;
+-      size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT,
+-                                         NUMA_NO_NODE);
+-      size = size * scratch_scale / 100;
+-      scratch_size_lowmem = round_up(size, CMA_MIN_ALIGNMENT_BYTES);
++              size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
++                                                 NUMA_NO_NODE);
++              size = size * scratch_scale / 100 - scratch_size_lowmem;
++              scratch_size_global = size;
++      }
+-      size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
+-                                         NUMA_NO_NODE);
+-      size = size * scratch_scale / 100 - scratch_size_lowmem;
+-      scratch_size_global = round_up(size, CMA_MIN_ALIGNMENT_BYTES);
++      /*
++       * Scratch areas are released as MIGRATE_CMA. Round them up to the right
++       * size.
++       */
++      scratch_size_lowmem = round_up(scratch_size_lowmem, CMA_MIN_ALIGNMENT_BYTES);
++      scratch_size_global = round_up(scratch_size_global, CMA_MIN_ALIGNMENT_BYTES);
+ }
+ static phys_addr_t __init scratch_size_node(int nid)
diff --git a/queue-6.18/landlock-account-all-audit-data-allocations-to-user-space.patch b/queue-6.18/landlock-account-all-audit-data-allocations-to-user-space.patch
new file mode 100644 (file)
index 0000000..6e3fd33
--- /dev/null
@@ -0,0 +1,80 @@
+From stable+bounces-282936-greg=kroah.com@vger.kernel.org Tue Jul 21 21:57:23 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 15:36:01 -0400
+Subject: landlock: Account all audit data allocations to user space
+To: stable@vger.kernel.org
+Cc: "Mickaël Salaün" <mic@digikod.net>, "Günther Noack" <gnoack@google.com>, "Paul Moore" <paul@paul-moore.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260721193601.410261-2-sashal@kernel.org>
+
+From: Mickaël Salaün <mic@digikod.net>
+
+[ Upstream commit b232bd12789fa57405b5092f28788be97aae9999 ]
+
+Mark the kzalloc_flex() of struct landlock_details with
+GFP_KERNEL_ACCOUNT so the allocation is charged to the calling task,
+like the other Landlock per-domain allocations which have used
+GFP_KERNEL_ACCOUNT forever.
+
+Every property of landlock_details is caller-attributable: allocated by
+landlock_restrict_self(2), owned by the caller's landlock_hierarchy,
+contents are the caller's pid, uid, comm, and exe_path, lifetime bounded
+by the caller's domain.  While the caller may not know nor control the
+size of this allocation (i.e. exe_path), this data should still be
+accounted for it.
+
+The deciding factor is whether userspace can trigger the allocation, not
+whether the size of the data is known nor controlled by the caller.
+This aligns with the kmemcg accounting policy established by commit
+5d097056c9a0 ("kmemcg: account certain kmem allocations to memcg").
+
+No new failure modes: the hierarchy and ruleset are allocated before
+details and are already accounted, so landlock_restrict_self(2) already
+returns -ENOMEM under memcg pressure.  This change widens that existing
+failure window slightly; it does not introduce a new error code.
+
+Cc: Günther Noack <gnoack@google.com>
+Cc: Paul Moore <paul@paul-moore.com>
+Cc: stable@vger.kernel.org
+Fixes: 1d636984e088 ("landlock: Add AUDIT_LANDLOCK_DOMAIN and log domain status")
+Link: https://patch.msgid.link/20260513180309.165840-1-mic@digikod.net
+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/domain.c |    9 +++++----
+ security/landlock/domain.h |    5 +----
+ 2 files changed, 6 insertions(+), 8 deletions(-)
+
+--- a/security/landlock/domain.c
++++ b/security/landlock/domain.c
+@@ -90,11 +90,12 @@ static struct landlock_details *get_curr
+               return ERR_CAST(buffer);
+       /*
+-       * Create the new details according to the path's length.  Do not
+-       * allocate with GFP_KERNEL_ACCOUNT because it is independent from the
+-       * caller.
++       * Create the new details according to the path's length.  Account to
++       * the calling task's memcg, like the other Landlock per-domain
++       * allocations, even if it may not control the related size.
+        */
+-      details = kzalloc_flex(*details, exe_path, path_size);
++      details =
++              kzalloc_flex(*details, exe_path, path_size, GFP_KERNEL_ACCOUNT);
+       if (!details)
+               return ERR_PTR(-ENOMEM);
+--- a/security/landlock/domain.h
++++ b/security/landlock/domain.h
+@@ -33,10 +33,7 @@ enum landlock_log_status {
+  * Rarely accessed, mainly when logging the first domain's denial.
+  *
+  * The contained pointers are initialized at the domain creation time and never
+- * changed again.  Contrary to most other Landlock object types, this one is
+- * not allocated with GFP_KERNEL_ACCOUNT because its size may not be under the
+- * caller's control (e.g. unknown exe_path) and the data is not explicitly
+- * requested nor used by tasks.
++ * changed again.
+  */
+ struct landlock_details {
+       /**
diff --git a/queue-6.18/landlock-fix-formatting.patch b/queue-6.18/landlock-fix-formatting.patch
new file mode 100644 (file)
index 0000000..98eb6d1
--- /dev/null
@@ -0,0 +1,63 @@
+From stable+bounces-282935-greg=kroah.com@vger.kernel.org Tue Jul 21 21:45:34 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 15:36:00 -0400
+Subject: landlock: Fix formatting
+To: stable@vger.kernel.org
+Cc: "Mickaël Salaün" <mic@digikod.net>, "Günther Noack" <gnoack@google.com>, "Kees Cook" <kees@kernel.org>, "Günther Noack" <gnoack3000@gmail.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260721193601.410261-1-sashal@kernel.org>
+
+From: Mickaël Salaün <mic@digikod.net>
+
+[ Upstream commit 405ca72dc589dd746e5ee5378bb9d9ee7f844010 ]
+
+Auto-format with clang-format -i security/landlock/*.[ch]
+
+Cc: Günther Noack <gnoack@google.com>
+Cc: Kees Cook <kees@kernel.org>
+Fixes: 69050f8d6d07 ("treewide: Replace kmalloc with kmalloc_obj for non-scalar types")
+Reviewed-by: Günther Noack <gnoack3000@gmail.com>
+Link: https://lore.kernel.org/r/20260303173632.88040-1-mic@digikod.net
+Signed-off-by: Mickaël Salaün <mic@digikod.net>
+Stable-dep-of: b232bd12789f ("landlock: Account all audit data allocations to user space")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ security/landlock/domain.c  |    3 +--
+ security/landlock/ruleset.c |    7 +++----
+ 2 files changed, 4 insertions(+), 6 deletions(-)
+
+--- a/security/landlock/domain.c
++++ b/security/landlock/domain.c
+@@ -94,8 +94,7 @@ static struct landlock_details *get_curr
+        * allocate with GFP_KERNEL_ACCOUNT because it is independent from the
+        * caller.
+        */
+-      details =
+-              kzalloc(struct_size(details, exe_path, path_size), GFP_KERNEL);
++      details = kzalloc_flex(*details, exe_path, path_size);
+       if (!details)
+               return ERR_PTR(-ENOMEM);
+--- a/security/landlock/ruleset.c
++++ b/security/landlock/ruleset.c
+@@ -33,9 +33,8 @@ static struct landlock_ruleset *create_r
+ {
+       struct landlock_ruleset *new_ruleset;
+-      new_ruleset =
+-              kzalloc(struct_size(new_ruleset, access_masks, num_layers),
+-                      GFP_KERNEL_ACCOUNT);
++      new_ruleset = kzalloc_flex(*new_ruleset, access_masks, num_layers,
++                                 GFP_KERNEL_ACCOUNT);
+       if (!new_ruleset)
+               return ERR_PTR(-ENOMEM);
+       refcount_set(&new_ruleset->usage, 1);
+@@ -553,7 +552,7 @@ landlock_merge_ruleset(struct landlock_r
+               return new_dom;
+       new_dom->hierarchy =
+-              kzalloc(sizeof(*new_dom->hierarchy), GFP_KERNEL_ACCOUNT);
++              kzalloc_obj(*new_dom->hierarchy, GFP_KERNEL_ACCOUNT);
+       if (!new_dom->hierarchy)
+               return ERR_PTR(-ENOMEM);
diff --git a/queue-6.18/mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch b/queue-6.18/mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch
new file mode 100644 (file)
index 0000000..77100e2
--- /dev/null
@@ -0,0 +1,171 @@
+From stable+bounces-288838-greg=kroah.com@vger.kernel.org Fri Jul 24 12:59:27 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 06:49:37 -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: <20260724104937.3975387-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
+@@ -670,7 +670,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,
+@@ -679,13 +684,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);
+ }
+@@ -742,13 +751,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));
+ }
+@@ -761,6 +775,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++) {
+               type = bootmem_type(page);
+@@ -854,14 +871,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;
+@@ -906,7 +919,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.18/mmc-vub300-fix-use-after-free-on-probe-failure.patch b/queue-6.18/mmc-vub300-fix-use-after-free-on-probe-failure.patch
new file mode 100644 (file)
index 0000000..824a10b
--- /dev/null
@@ -0,0 +1,89 @@
+From stable+bounces-289714-greg=kroah.com@vger.kernel.org Tue Jul 28 00:24:26 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 18:20:45 -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>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727222045.2118748-2-sashal@kernel.org>
+
+From: Guangshuo Li <lgs201920130244@gmail.com>
+
+[ Upstream commit a3b5f242997a3be7404112fd48784881560aea57 ]
+
+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>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+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
+@@ -2344,12 +2344,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.18/mmc-vub300-rename-probe-error-labels.patch b/queue-6.18/mmc-vub300-rename-probe-error-labels.patch
new file mode 100644 (file)
index 0000000..3de44bb
--- /dev/null
@@ -0,0 +1,81 @@
+From stable+bounces-289713-greg=kroah.com@vger.kernel.org Tue Jul 28 00:24:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 18:20:44 -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>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727222045.2118748-1-sashal@kernel.org>
+
+From: Johan Hovold <johan@kernel.org>
+
+[ Upstream commit 5b8b35d6f4fa758dd5e8ae18526ea1c73f6787e0 ]
+
+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>
+Stable-dep-of: a3b5f242997a ("mmc: vub300: fix use-after-free on probe failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/mmc/host/vub300.c |   18 ++++++++++--------
+ 1 file changed, 10 insertions(+), 8 deletions(-)
+
+--- a/drivers/mmc/host/vub300.c
++++ b/drivers/mmc/host/vub300.c
+@@ -2115,19 +2115,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(*vub300), &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;
+@@ -2344,10 +2344,11 @@ 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:
++
++err_delete_timer:
+       timer_delete_sync(&vub300->inactivity_timer);
+ err_free_host:
+       mmc_free_host(mmc);
+@@ -2355,12 +2356,13 @@ err_free_host:
+        * 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.18/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch b/queue-6.18/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch
new file mode 100644 (file)
index 0000000..11339fe
--- /dev/null
@@ -0,0 +1,58 @@
+From stable+bounces-289087-greg=kroah.com@vger.kernel.org Sat Jul 25 12:54:49 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 06:54:40 -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: <20260725105440.1854110-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.18/net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch b/queue-6.18/net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch
new file mode 100644 (file)
index 0000000..964ef32
--- /dev/null
@@ -0,0 +1,91 @@
+From stable+bounces-290230-greg=kroah.com@vger.kernel.org Wed Jul 29 04:32:43 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 22:30:25 -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: <20260729023025.1114440-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.18/net-mana-optimize-irq-affinity-for-low-vcpu-configs.patch b/queue-6.18/net-mana-optimize-irq-affinity-for-low-vcpu-configs.patch
new file mode 100644 (file)
index 0000000..876a2d6
--- /dev/null
@@ -0,0 +1,253 @@
+From stable+bounces-289857-greg=kroah.com@vger.kernel.org Tue Jul 28 06:07:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 23:57:14 -0400
+Subject: net: mana: Optimize irq affinity for low vcpu configs
+To: stable@vger.kernel.org
+Cc: Shradha Gupta <shradhagupta@linux.microsoft.com>, Erni Sri Satya Vennela <ernis@linux.microsoft.com>, Haiyang Zhang <haiyangz@microsoft.com>, Simon Horman <horms@kernel.org>, Yury Norov <ynorov@nvidia.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728035714.3173126-1-sashal@kernel.org>
+
+From: Shradha Gupta <shradhagupta@linux.microsoft.com>
+
+[ Upstream commit 5316394b1752f6cf3f9901e7fefdec1cd1d97fd3 ]
+
+Before the commit 755391121038 ("net: mana: Allocate MSI-X vectors
+dynamically"), all the MANA IRQs were assigned statically and together
+during early driver load.
+
+After this commit, the IRQ allocation for MANA was done in two phases.
+HWC IRQ allocated earlier and then, queue IRQs dynamically added at a
+later point. By this time, the IRQ weights on vCPUs can become imbalanced
+and if IRQ count is greater than the vCPU count the topology aware IRQ
+distribution logic in MANA can cause multiple MANA IRQs to land on the
+same vCPUs, while other sibling vCPUs have none (case 1).
+
+On SMP enabled, low-vCPU systems, this becomes a bigger problem as the
+softIRQ handling overhead of two IRQs on the same vCPUs becomes much more
+than their overheads if they were spread across sibling vCPUs.
+
+In such cases when many parallel TCP connections are tested, the
+throughput drops significantly.
+
+Fix the affinity assignment logic, in cases where the IRQ count is greater
+than the vCPU count and when IRQs are added dynamically, by utilizing all
+the vCPUs irrespective of their NUMA/core bindings (case 2).
+
+The results of setting the affinity and hint to NULL were also studied,
+and we observed that, with this logic if there are pre-existing IRQs
+allocated on the VM (apart from MANA), during MANA IRQs allocation, it
+leads to clustering of the MANA queue IRQs again (case 3).
+
+=======================================================
+Case 1: without this patch
+=======================================================
+4 vcpu(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
+
+       TYPE            effective vCPU aff
+=======================================================
+IRQ0:  HWC             0
+IRQ1:  mana_q1         0
+IRQ2:  mana_q2         2
+IRQ3:  mana_q3         0
+IRQ4:  mana_q4         3
+
+%soft on each vCPU(mpstat -P ALL 1) on receiver
+vCPU           0       1       2       3
+=======================================================
+pass 1:                38.85   0.03    24.89   24.65
+pass 2:                39.15   0.03    24.57   25.28
+pass 3:                40.36   0.03    23.20   23.17
+
+=======================================================
+Case 2: with this patch
+=======================================================
+4 vcpu(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
+
+        TYPE            effective vCPU aff
+=======================================================
+IRQ0:   HWC             0
+IRQ1:   mana_q1         0
+IRQ2:   mana_q2         1
+IRQ3:   mana_q3         2
+IRQ4:   mana_q4         3
+
+%soft on each vCPU(mpstat -P ALL 1) on receiver
+vCPU            0       1       2       3
+=======================================================
+pass 1:         15.42  15.85   14.99   14.51
+pass 2:         15.53  15.94   15.81   15.93
+pass 3:         16.41  16.35   16.40   16.36
+
+=======================================================
+Case 3: with affinity set to NULL
+=======================================================
+4 vCPU(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
+
+       TYPE            effective vCPU aff
+=======================================================
+IRQ0:  HWC                     0
+IRQ1:  mana_q1                 2
+IRQ2:  mana_q2                 3
+IRQ3:  mana_q3                 2
+IRQ4:  mana_q4                 3
+
+=======================================================
+Throughput Impact(in Gbps, same env)
+=======================================================
+TCP conn       with patch      w/o patch       aff NULL
+20480          15.65           7.73            5.25
+10240          15.63           8.93            5.77
+8192           15.64           9.69            7.16
+6144           15.64           13.16           9.33
+4096           15.69           15.75           13.50
+2048           15.69           15.83           13.61
+1024           15.71           15.28           13.60
+
+Fixes: 755391121038 ("net: mana: Allocate MSI-X vectors dynamically")
+Cc: stable@vger.kernel.org
+Co-developed-by: Erni Sri Satya Vennela <ernis@linux.microsoft.com>
+Signed-off-by: Erni Sri Satya Vennela <ernis@linux.microsoft.com>
+Signed-off-by: Shradha Gupta <shradhagupta@linux.microsoft.com>
+Reviewed-by: Haiyang Zhang <haiyangz@microsoft.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Reviewed-by: Yury Norov <ynorov@nvidia.com>
+Link: https://patch.msgid.link/20260624072138.1632849-1-shradhagupta@linux.microsoft.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+[ Kept 7.1's `int *irqs, irq, err, i;` declaration (minus the deleted `skip_first_cpu`) instead of upstream's `msi` variant, as this tree lacks the `mana_gd_get_gic()` refactor. ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/microsoft/mana/gdma_main.c |   78 +++++++++++++++++++-----
+ 1 file changed, 64 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
++++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
+@@ -125,6 +125,8 @@ static int mana_gd_query_max_resources(s
+       } else {
+               /* If dynamic allocation is enabled we have already allocated
+                * hwc msi
++               * Also, we make sure in this case the following is always true
++               * (num_msix_usable - 1 HWC) <= num_online_cpus()
+                */
+               gc->num_msix_usable = min(resp.max_msix, num_online_cpus() + 1);
+       }
+@@ -1587,8 +1589,8 @@ void mana_gd_free_res_map(struct gdma_re
+  * do the same thing.
+  */
+-static int irq_setup(unsigned int *irqs, unsigned int len, int node,
+-                   bool skip_first_cpu)
++static int mana_irq_setup_numa_aware(unsigned int *irqs, unsigned int len,
++                                   int node, bool skip_first_cpu)
+ {
+       const struct cpumask *next, *prev = cpu_none_mask;
+       cpumask_var_t cpus __free(free_cpumask_var);
+@@ -1624,11 +1626,24 @@ done:
+       return 0;
+ }
++/* must be called with cpus_read_lock() held */
++static void mana_irq_setup_linear(unsigned int *irqs, unsigned int len)
++{
++      int cpu;
++
++      for_each_online_cpu(cpu) {
++              if (len == 0)
++                      break;
++
++              irq_set_affinity_and_hint(*irqs++, cpumask_of(cpu));
++              len--;
++      }
++}
++
+ static int mana_gd_setup_dyn_irqs(struct pci_dev *pdev, int nvec)
+ {
+       struct gdma_context *gc = pci_get_drvdata(pdev);
+       struct gdma_irq_context *gic;
+-      bool skip_first_cpu = false;
+       int *irqs, irq, err, i;
+       irqs = kmalloc_array(nvec, sizeof(int), GFP_KERNEL);
+@@ -1636,10 +1651,12 @@ static int mana_gd_setup_dyn_irqs(struct
+               return -ENOMEM;
+       /*
++       * In this function, num_msix_usable = HWC IRQ + Queue IRQ.
++       * nvec is only Queue IRQ (HWC already setup).
+        * While processing the next pci irq vector, we start with index 1,
+        * as IRQ vector at index 0 is already processed for HWC.
+        * However, the population of irqs array starts with index 0, to be
+-       * further used in irq_setup()
++       * further used in mana_irq_setup_numa_aware()
+        */
+       for (i = 1; i <= nvec; i++) {
+               gic = kzalloc(sizeof(*gic), GFP_KERNEL);
+@@ -1669,18 +1686,51 @@ static int mana_gd_setup_dyn_irqs(struct
+       }
+       /*
+-       * When calling irq_setup() for dynamically added IRQs, if number of
+-       * CPUs is more than or equal to allocated MSI-X, we need to skip the
+-       * first CPU sibling group since they are already affinitized to HWC IRQ
++       * When calling mana_irq_setup_numa_aware() for dynamically added IRQs,
++       * if number of CPUs is more than or equal to allocated MSI-X, we need to
++       * skip the first CPU sibling group since they are already affinitized to
++       * HWC IRQ
+        */
+       cpus_read_lock();
+-      if (gc->num_msix_usable <= num_online_cpus())
+-              skip_first_cpu = true;
++      if (gc->num_msix_usable <= num_online_cpus()) {
++              err = mana_irq_setup_numa_aware(irqs, nvec, gc->numa_node,
++                                              true);
++              if (err) {
++                      cpus_read_unlock();
++                      goto free_irq;
++              }
++      } else {
++              /*
++               * When num_msix_usable are more than num_online_cpus, our
++               * queue IRQs should be equal to num of online vCPUs.
++               * We try to make sure queue IRQs spread across all vCPUs.
++               * In such a case NUMA or CPU core affinity does not matter.
++               * Note: in this case the total mana IRQ should always be
++               * num_online_cpus + 1. The first HWC IRQ is already handled
++               * in HWC setup calls
++               * However, if CPUs went offline since num_msix_usable was
++               * computed, queue IRQs will be more than num_online_cpus().
++               * In such cases remaining extra IRQs will retain their default
++               * affinity.
++               */
++              int first_unassigned = num_online_cpus();
+-      err = irq_setup(irqs, nvec, gc->numa_node, skip_first_cpu);
+-      if (err) {
+-              cpus_read_unlock();
+-              goto free_irq;
++              if (nvec > first_unassigned) {
++                      char buf[32];
++
++                      if (first_unassigned == nvec - 1)
++                              snprintf(buf, sizeof(buf), "%d",
++                                       first_unassigned);
++                      else
++                              snprintf(buf, sizeof(buf), "%d-%d",
++                                       first_unassigned, nvec - 1);
++
++                      dev_dbg(&pdev->dev,
++                              "MANA IRQ indices #%s will retain the default CPU affinity\n",
++                              buf);
++              }
++
++              mana_irq_setup_linear(irqs, nvec);
+       }
+       cpus_read_unlock();
+@@ -1766,7 +1816,7 @@ static int mana_gd_setup_irqs(struct pci
+               nvec -= 1;
+       }
+-      err = irq_setup(irqs, nvec, gc->numa_node, false);
++      err = mana_irq_setup_numa_aware(irqs, nvec, gc->numa_node, false);
+       if (err) {
+               cpus_read_unlock();
+               goto free_irq;
diff --git a/queue-6.18/net-mana-validate-the-packet-length-reported-by-the-nic.patch b/queue-6.18/net-mana-validate-the-packet-length-reported-by-the-nic.patch
new file mode 100644 (file)
index 0000000..845dced
--- /dev/null
@@ -0,0 +1,50 @@
+From stable+bounces-289815-greg=kroah.com@vger.kernel.org Tue Jul 28 04:51:58 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 22:46:45 -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: <20260728024645.3059155-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
+@@ -2160,6 +2160,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.18/netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch b/queue-6.18/netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch
new file mode 100644 (file)
index 0000000..81b87d3
--- /dev/null
@@ -0,0 +1,39 @@
+From stable+bounces-288335-greg=kroah.com@vger.kernel.org Thu Jul 23 16:28:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:18:39 -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: <20260723141840.3216559-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.18/netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch b/queue-6.18/netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch
new file mode 100644 (file)
index 0000000..f3bd402
--- /dev/null
@@ -0,0 +1,56 @@
+From stable+bounces-288336-greg=kroah.com@vger.kernel.org Thu Jul 23 16:20:14 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:18:40 -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: <20260723141840.3216559-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.18/netfilter-nf_tables-remove-register-tracking-infrastructure.patch b/queue-6.18/netfilter-nf_tables-remove-register-tracking-infrastructure.patch
new file mode 100644 (file)
index 0000000..014f134
--- /dev/null
@@ -0,0 +1,1646 @@
+From stable+bounces-288333-greg=kroah.com@vger.kernel.org Thu Jul 23 16:30:53 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:18:34 -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: <20260723141835.3216078-1-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              |  104 -------------------------------
+ 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, 693 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
+@@ -433,8 +422,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;
+@@ -949,7 +936,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
+@@ -983,8 +969,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,
+@@ -1967,20 +1951,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
+@@ -45,9 +45,6 @@ int nft_meta_get_validate(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
+@@ -134,7 +134,6 @@ static const struct nft_expr_ops nft_met
+       .init           = nft_meta_bridge_get_init,
+       .validate       = nft_meta_bridge_get_validate,
+       .dump           = nft_meta_get_dump,
+-      .reduce         = nft_meta_get_reduce,
+ };
+ static void nft_meta_bridge_set_eval(const struct nft_expr *expr,
+@@ -181,24 +180,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)
+ {
+@@ -223,7 +204,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
+@@ -163,7 +163,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 = {
+@@ -173,7 +172,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
+@@ -939,58 +939,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
+  */
+@@ -10173,16 +10121,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;
+@@ -10219,15 +10160,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
+@@ -402,61 +402,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->sreg2 == bitwise->sreg2 &&
+-          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,
+ };
+@@ -559,48 +510,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,
+ };
+@@ -637,22 +552,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
+@@ -177,23 +177,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
+@@ -698,29 +698,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)
+ {
+@@ -755,27 +732,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,
+@@ -784,7 +742,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
+@@ -795,7 +752,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
+@@ -806,7 +762,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
+@@ -876,7 +831,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
+@@ -429,7 +429,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
+@@ -705,40 +705,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 = {
+@@ -747,7 +719,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 = {
+@@ -756,7 +727,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 = {
+@@ -765,7 +735,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 = {
+@@ -774,7 +743,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 = {
+@@ -783,7 +751,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,
+ };
+ #ifdef CONFIG_NFT_EXTHDR_DCCP
+@@ -793,7 +760,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,
+ };
+ #endif
+--- 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
+@@ -224,7 +224,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
+@@ -743,60 +743,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)),
+@@ -804,7 +760,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
+@@ -256,31 +256,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)
+ {
+@@ -584,7 +559,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,
+ };
+@@ -594,7 +568,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,
+ };
+@@ -1022,32 +995,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
+@@ -259,32 +259,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,
+@@ -293,7 +267,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.18/netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch b/queue-6.18/netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch
new file mode 100644 (file)
index 0000000..5c3a729
--- /dev/null
@@ -0,0 +1,100 @@
+From stable+bounces-288332-greg=kroah.com@vger.kernel.org Thu Jul 23 16:30:50 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:18:35 -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: <20260723141835.3216078-2-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.18/nfsd-pass-nfsd_file-to-nfsd_iter_read.patch b/queue-6.18/nfsd-pass-nfsd_file-to-nfsd_iter_read.patch
new file mode 100644 (file)
index 0000000..2a2cae1
--- /dev/null
@@ -0,0 +1,116 @@
+From stable+bounces-288492-greg=kroah.com@vger.kernel.org Thu Jul 23 20:27:08 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:19:06 -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: <20260723181911.3438372-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
+@@ -4478,7 +4478,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;
+@@ -4489,7 +4489,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;
+@@ -4536,7 +4536,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, eof_offset);
+               return nfserr;
+@@ -5432,7 +5432,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
+@@ -1078,7 +1078,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
+@@ -1091,9 +1091,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;
+       struct kiocb kiocb;
+@@ -1346,7 +1347,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.18/octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch b/queue-6.18/octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch
new file mode 100644 (file)
index 0000000..bfa6249
--- /dev/null
@@ -0,0 +1,54 @@
+From stable+bounces-289995-greg=kroah.com@vger.kernel.org Tue Jul 28 15:39:10 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 09:30:37 -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: <20260728133037.3965153-2-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>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/marvell/octeontx2/af/rvu_cn10k.c |    9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+--- 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(rvu->pdev, req->hdr.pcifunc) !=
++                   rvu_get_pf(rvu->pdev, req->base_pcifunc)))
++                      return -EPERM;
++
+               /* Calculating the LMT table index equivalent to primary
+                * pcifunc.
+                */
diff --git a/queue-6.18/octeontx2-af-validate-body-pcifunc-in-rvu_mbox_handler_rep_event_notify.patch b/queue-6.18/octeontx2-af-validate-body-pcifunc-in-rvu_mbox_handler_rep_event_notify.patch
new file mode 100644 (file)
index 0000000..87d493d
--- /dev/null
@@ -0,0 +1,91 @@
+From stable+bounces-289996-greg=kroah.com@vger.kernel.org Tue Jul 28 15:39:12 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 09:30:36 -0400
+Subject: octeontx2-af: validate body pcifunc in rvu_mbox_handler_rep_event_notify
+To: stable@vger.kernel.org
+Cc: Michael Bommarito <michael.bommarito@gmail.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728133037.3965153-1-sashal@kernel.org>
+
+From: Michael Bommarito <michael.bommarito@gmail.com>
+
+[ Upstream commit 2156a29aecfffa2eb7c558255690084efbe9f3b0 ]
+
+rvu_mbox_handler_rep_event_notify() in drivers/net/ethernet/marvell/
+octeontx2/af/rvu_rep.c queues a sender-controlled REP_EVENT_NOTIFY
+request body verbatim, and rvu_rep_up_notify() then forwards
+event->pcifunc (the nested body field, distinct from the
+AF-normalised header pcifunc) into rvu_get_pfvf(), rvu_get_pf() and
+the AF->PF mailbox device index without any bounds check.
+
+A VF attached to a PF that has been put into switchdev
+representor mode reaches this path: the VF mailbox handler
+otx2_pfvf_mbox_handler() forwards every message id including
+MBOX_MSG_REP_EVENT_NOTIFY to AF without an allowlist, and the AF
+dispatcher rewrites only msg->pcifunc, leaving struct
+rep_event::pcifunc attacker-controlled.  The sibling
+rvu_mbox_handler_esw_cfg() refuses requests whose header pcifunc
+is not rvu->rep_pcifunc; this handler has no equivalent gate.
+
+An out-of-range body pcifunc selects an &rvu->pf[]/&rvu->hwvf[]
+element past the allocated array and, for RVU_EVENT_MAC_ADDR_CHANGE,
+turns into a six-byte attacker-chosen OOB ether_addr_copy() target
+inside the queued worker; KASAN reports a slab-out-of-bounds write
+in rvu_rep_wq_handler.
+
+Reject malformed requests at the handler entry by gating on
+is_pf_func_valid(), which is already the canonical PF/VF range check
+in this driver; expose it via rvu.h so callers in rvu_rep.c can use
+it instead of open-coding the same range arithmetic.
+
+Fixes: b8fea84a0468 ("octeontx2-pf: Add support to sync link state between representor and VFs")
+Cc: stable@vger.kernel.org
+Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
+Link: https://patch.msgid.link/20260520154157.1439319-1-michael.bommarito@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: 8cdcf3d2caac ("octeontx2-af: cn10k: restrict VF LMTLINE sharing to its own PF")
+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_rep.c |    8 ++++++++
+ 3 files changed, 10 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
+@@ -435,7 +435,7 @@ struct rvu_pfvf *rvu_get_pfvf(struct rvu
+               return &rvu->pf[rvu_get_pf(rvu->pdev, 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
+@@ -900,6 +900,7 @@ u16 rvu_get_rsrc_mapcount(struct rvu_pfv
+ 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_rep.c
++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_rep.c
+@@ -97,6 +97,14 @@ int rvu_mbox_handler_rep_event_notify(st
+ {
+       struct rep_evtq_ent *qentry;
++      /* The mailbox dispatcher normalises only the header pcifunc; the
++       * nested struct rep_event::pcifunc body field is sender-controlled
++       * and is later used by rvu_rep_up_notify() to index rvu->pf[] /
++       * rvu->hwvf[].  Reject out-of-range body selectors before queueing.
++       */
++      if (!is_pf_func_valid(rvu, req->pcifunc))
++              return -EINVAL;
++
+       qentry = kmalloc(sizeof(*qentry), GFP_ATOMIC);
+       if (!qentry)
+               return -ENOMEM;
diff --git a/queue-6.18/ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch b/queue-6.18/ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch
new file mode 100644 (file)
index 0000000..8c948b2
--- /dev/null
@@ -0,0 +1,105 @@
+From stable+bounces-289547-greg=kroah.com@vger.kernel.org Mon Jul 27 15:50:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 09:31:39 -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: <20260727133139.539439-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.18/pinctrl-renesas-rzg2l-use-enotsupp-instead-of-eopnotsupp.patch b/queue-6.18/pinctrl-renesas-rzg2l-use-enotsupp-instead-of-eopnotsupp.patch
new file mode 100644 (file)
index 0000000..d23c0ea
--- /dev/null
@@ -0,0 +1,78 @@
+From stable+bounces-288449-greg=kroah.com@vger.kernel.org Thu Jul 23 19:13:25 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 13:03:25 -0400
+Subject: pinctrl: renesas: rzg2l: Use -ENOTSUPP instead of -EOPNOTSUPP
+To: stable@vger.kernel.org
+Cc: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>, Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>, Geert Uytterhoeven <geert+renesas@glider.be>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723170325.3399838-1-sashal@kernel.org>
+
+From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
+
+[ Upstream commit c1492da3939c89372929e062d731f328f7693f1e ]
+
+The pinctrl and GPIO core code make exceptions for the -ENOTSUPP error
+code.  One such example is gpio_set_config_with_argument_optional(),
+which returns success when gpio_set_config_with_argument() returns
+-ENOTSUPP, but reports failure for all other error codes.
+
+Returning -EOPNOTSUPP from the pinctrl driver on the unsupported pinctrl
+operation may lead to boot failures when pinctrl drivers implements
+struct gpio_chip::set_config, the system uses GPIO hogs, and the
+struct gpio_chip::set_config implementation returns -EOPNOTSUPP for the
+unsupported operations.
+
+Return -ENOTSUPP for the unsupported pinctrl operation.
+
+Fixes: 560c633d378a ("pinctrl: renesas: rzg2l: Drop oen_read and oen_write callbacks")
+Fixes: c4c4637eb57f ("pinctrl: renesas: Add RZ/G2L pin and gpio controller driver")
+Cc: stable@vger.kernel.org
+Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
+Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
+Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
+Tested-by: Geert Uytterhoeven <geert+renesas@glider.be>
+Link: https://patch.msgid.link/20260515124008.2947838-2-claudiu.beznea@kernel.org
+Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pinctrl/renesas/pinctrl-rzg2l.c |    8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/pinctrl/renesas/pinctrl-rzg2l.c
++++ b/drivers/pinctrl/renesas/pinctrl-rzg2l.c
+@@ -1097,7 +1097,7 @@ static int rzg2l_read_oen(struct rzg2l_p
+       int bit;
+       if (!pctrl->data->pin_to_oen_bit)
+-              return -EOPNOTSUPP;
++              return -ENOTSUPP;
+       bit = pctrl->data->pin_to_oen_bit(pctrl, _pin);
+       if (bit < 0)
+@@ -1115,7 +1115,7 @@ static int rzg2l_write_oen(struct rzg2l_
+       int bit;
+       if (!pctrl->data->pin_to_oen_bit)
+-              return -EOPNOTSUPP;
++              return -ENOTSUPP;
+       bit = pctrl->data->pin_to_oen_bit(pctrl, _pin);
+       if (bit < 0)
+@@ -1552,7 +1552,7 @@ static int rzg2l_pinctrl_pinconf_set(str
+                       break;
+               default:
+-                      return -EOPNOTSUPP;
++                      return -ENOTSUPP;
+               }
+       }
+@@ -1634,7 +1634,7 @@ static int rzg2l_pinctrl_pinconf_group_g
+               /* Check config matching between to pin  */
+               if (i && prev_config != *config)
+-                      return -EOPNOTSUPP;
++                      return -ENOTSUPP;
+               prev_config = *config;
+       }
diff --git a/queue-6.18/pmdomain-imx93-blk-ctrl-convert-to-devm_-only.patch b/queue-6.18/pmdomain-imx93-blk-ctrl-convert-to-devm_-only.patch
new file mode 100644 (file)
index 0000000..6936a31
--- /dev/null
@@ -0,0 +1,139 @@
+From stable+bounces-290868-greg=kroah.com@vger.kernel.org Wed Jul 29 20:12:23 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 14:02:07 -0400
+Subject: pmdomain: imx93-blk-ctrl: convert to devm_* only
+To: stable@vger.kernel.org
+Cc: Marco Felsch <m.felsch@pengutronix.de>, Frank Li <Frank.Li@nxp.com>, Ulf Hansson <ulf.hansson@linaro.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729180208.3683189-1-sashal@kernel.org>
+
+From: Marco Felsch <m.felsch@pengutronix.de>
+
+[ Upstream commit 52becc142280eeef828d19f9cd01fd653b588786 ]
+
+Convert the driver to devm_ APIs only by making use of
+devm_add_action_or_reset() and devm_pm_runtime_enable() to simplify the
+probe error path and to drop the .remove() callback. This also ensures
+that the device release order equals the device probe error path order.
+
+Furthermore drop the dev_set_drvdata() usage since the only user was the
+.remove() callback which is removed by this commit.
+
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
+Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
+Stable-dep-of: 99611233f8cd ("pmdomain: imx93-blk-ctrl: Extract PHY as shared domain for DSI/CSI")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pmdomain/imx/imx93-blk-ctrl.c |   66 ++++++++++++++--------------------
+ 1 file changed, 29 insertions(+), 37 deletions(-)
+
+--- a/drivers/pmdomain/imx/imx93-blk-ctrl.c
++++ b/drivers/pmdomain/imx/imx93-blk-ctrl.c
+@@ -188,6 +188,20 @@ static int imx93_blk_ctrl_power_off(stru
+       return 0;
+ }
++static void imx93_release_genpd_provider(void *data)
++{
++      struct device_node *of_node = data;
++
++      of_genpd_del_provider(of_node);
++}
++
++static void imx93_release_pm_genpd(void *data)
++{
++      struct generic_pm_domain *genpd = data;
++
++      pm_genpd_remove(genpd);
++}
++
+ static struct lock_class_key blk_ctrl_genpd_lock_class;
+ static int imx93_blk_ctrl_probe(struct platform_device *pdev)
+@@ -258,10 +272,8 @@ static int imx93_blk_ctrl_probe(struct p
+                       domain->clks[j].id = data->clk_names[j];
+               ret = devm_clk_bulk_get(dev, data->num_clks, domain->clks);
+-              if (ret) {
+-                      dev_err_probe(dev, ret, "failed to get clock\n");
+-                      goto cleanup_pds;
+-              }
++              if (ret)
++                      return dev_err_probe(dev, ret, "failed to get clock\n");
+               domain->genpd.name = data->name;
+               domain->genpd.power_on = imx93_blk_ctrl_power_on;
+@@ -269,11 +281,12 @@ static int imx93_blk_ctrl_probe(struct p
+               domain->bc = bc;
+               ret = pm_genpd_init(&domain->genpd, NULL, true);
+-              if (ret) {
+-                      dev_err_probe(dev, ret, "failed to init power domain\n");
+-                      goto cleanup_pds;
+-              }
++              if (ret)
++                      return dev_err_probe(dev, ret, "failed to init power domain\n");
++              ret = devm_add_action_or_reset(dev, imx93_release_pm_genpd, &domain->genpd);
++              if (ret)
++                      return dev_err_probe(dev, ret, "failed to add pm_genpd release callback\n");
+               /*
+                * We use runtime PM to trigger power on/off of the upstream GPC
+                * domain, as a strict hierarchical parent/child power domain
+@@ -290,39 +303,19 @@ static int imx93_blk_ctrl_probe(struct p
+               bc->onecell_data.domains[i] = &domain->genpd;
+       }
+-      pm_runtime_enable(dev);
++      ret = devm_pm_runtime_enable(dev);
++      if (ret)
++              return dev_err_probe(dev, ret, "failed to enable pm-runtime\n");
+       ret = of_genpd_add_provider_onecell(dev->of_node, &bc->onecell_data);
+-      if (ret) {
+-              dev_err_probe(dev, ret, "failed to add power domain provider\n");
+-              goto cleanup_pds;
+-      }
++      if (ret)
++              return dev_err_probe(dev, ret, "failed to add power domain provider\n");
+-      dev_set_drvdata(dev, bc);
++      ret = devm_add_action_or_reset(dev, imx93_release_genpd_provider, dev->of_node);
++      if (ret)
++              return dev_err_probe(dev, ret, "failed to add genpd_provider release callback\n");
+       return 0;
+-
+-cleanup_pds:
+-      for (i--; i >= 0; i--)
+-              pm_genpd_remove(&bc->domains[i].genpd);
+-
+-      return ret;
+-}
+-
+-static void imx93_blk_ctrl_remove(struct platform_device *pdev)
+-{
+-      struct imx93_blk_ctrl *bc = dev_get_drvdata(&pdev->dev);
+-      int i;
+-
+-      of_genpd_del_provider(pdev->dev.of_node);
+-
+-      pm_runtime_disable(&pdev->dev);
+-
+-      for (i = 0; i < bc->onecell_data.num_domains; i++) {
+-              struct imx93_blk_ctrl_domain *domain = &bc->domains[i];
+-
+-              pm_genpd_remove(&domain->genpd);
+-      }
+ }
+ static const struct imx93_blk_ctrl_domain_data imx93_media_blk_ctl_domain_data[] = {
+@@ -457,7 +450,6 @@ MODULE_DEVICE_TABLE(of, imx93_blk_ctrl_o
+ static struct platform_driver imx93_blk_ctrl_driver = {
+       .probe = imx93_blk_ctrl_probe,
+-      .remove = imx93_blk_ctrl_remove,
+       .driver = {
+               .name = "imx93-blk-ctrl",
+               .of_match_table = imx93_blk_ctrl_of_match,
diff --git a/queue-6.18/pmdomain-imx93-blk-ctrl-extract-phy-as-shared-domain-for-dsi-csi.patch b/queue-6.18/pmdomain-imx93-blk-ctrl-extract-phy-as-shared-domain-for-dsi-csi.patch
new file mode 100644 (file)
index 0000000..67f5cab
--- /dev/null
@@ -0,0 +1,170 @@
+From stable+bounces-290869-greg=kroah.com@vger.kernel.org Wed Jul 29 20:12:25 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 14:02:08 -0400
+Subject: pmdomain: imx93-blk-ctrl: Extract PHY as shared domain for DSI/CSI
+To: stable@vger.kernel.org
+Cc: Guoniu Zhou <guoniu.zhou@oss.nxp.com>, Frank Li <Frank.Li@nxp.com>, Peng Fan <peng.fan@nxp.com>, Ulf Hansson <ulfh@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729180208.3683189-2-sashal@kernel.org>
+
+From: Guoniu Zhou <guoniu.zhou@oss.nxp.com>
+
+[ Upstream commit 99611233f8cda833169fa6487d5dacdf189e5cb0 ]
+
+The MIPI DSI and CSI domains share control bits for clock and reset, which
+can lead to incorrect behavior if one domain disables the shared resource
+while the other is still active.
+
+To fix the issue, introduce a shared MIPI PHY power domain to own the
+common resources and make DSI and CSI its subdomains. This ensures the
+shared bits are properly managed and not disabled while still in use.
+
+Fixes: e9aa77d413c9 ("soc: imx: add i.MX93 media blk ctrl driver")
+Cc: stable@vger.kernel.org
+Signed-off-by: Guoniu Zhou <guoniu.zhou@oss.nxp.com>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Reviewed-by: Peng Fan <peng.fan@nxp.com>
+Signed-off-by: Ulf Hansson <ulfh@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pmdomain/imx/imx93-blk-ctrl.c |   60 ++++++++++++++++++++++++++++++++--
+ 1 file changed, 58 insertions(+), 2 deletions(-)
+
+--- a/drivers/pmdomain/imx/imx93-blk-ctrl.c
++++ b/drivers/pmdomain/imx/imx93-blk-ctrl.c
+@@ -47,6 +47,8 @@
+ #define PRIO(X)                       (X)
++#define BLK_CTRL_NO_PARENT    UINT_MAX
++
+ struct imx93_blk_ctrl_domain;
+ struct imx93_blk_ctrl {
+@@ -67,12 +69,18 @@ struct imx93_blk_ctrl_qos {
+       u32 cfg_prio;
+ };
++struct imx93_blk_ctrl_subdomain_link {
++      struct generic_pm_domain *parent;
++      struct generic_pm_domain *subdomain;
++};
++
+ struct imx93_blk_ctrl_domain_data {
+       const char *name;
+       const char * const *clk_names;
+       int num_clks;
+       u32 rst_mask;
+       u32 clk_mask;
++      u32 parent;
+       int num_qos;
+       struct imx93_blk_ctrl_qos qos[DOMAIN_MAX_QOS];
+ };
+@@ -202,6 +210,13 @@ static void imx93_release_pm_genpd(void
+       pm_genpd_remove(genpd);
+ }
++static void imx93_release_subdomain(void *data)
++{
++      struct imx93_blk_ctrl_subdomain_link *link = data;
++
++      pm_genpd_remove_subdomain(link->parent, link->subdomain);
++}
++
+ static struct lock_class_key blk_ctrl_genpd_lock_class;
+ static int imx93_blk_ctrl_probe(struct platform_device *pdev)
+@@ -303,6 +318,34 @@ static int imx93_blk_ctrl_probe(struct p
+               bc->onecell_data.domains[i] = &domain->genpd;
+       }
++      for (i = 0; i < bc_data->num_domains; i++) {
++              struct imx93_blk_ctrl_domain *domain = &bc->domains[i];
++              const struct imx93_blk_ctrl_domain_data *data = domain->data;
++              struct imx93_blk_ctrl_subdomain_link *link;
++
++              if (bc_data->skip_mask & BIT(i) ||
++                  data->parent == BLK_CTRL_NO_PARENT)
++                      continue;
++
++              link = devm_kzalloc(dev, sizeof(*link), GFP_KERNEL);
++              if (!link)
++                      return -ENOMEM;
++
++              link->parent = &bc->domains[data->parent].genpd;
++              link->subdomain = &domain->genpd;
++
++              ret = pm_genpd_add_subdomain(&bc->domains[data->parent].genpd,
++                                           &domain->genpd);
++              if (ret)
++                      return dev_err_probe(dev, ret, "failed to add subdomain %s\n",
++                                           domain->genpd.name);
++
++              ret = devm_add_action_or_reset(dev, imx93_release_subdomain, link);
++              if (ret)
++                      return dev_err_probe(dev, ret,
++                                           "failed to add subdomain release callback\n");
++      }
++
+       ret = devm_pm_runtime_enable(dev);
+       if (ret)
+               return dev_err_probe(dev, ret, "failed to enable pm-runtime\n");
+@@ -323,8 +366,9 @@ static const struct imx93_blk_ctrl_domai
+               .name = "mediablk-mipi-dsi",
+               .clk_names = (const char *[]){ "dsi" },
+               .num_clks = 1,
+-              .rst_mask = BIT(11) | BIT(12),
+-              .clk_mask = BIT(11) | BIT(12),
++              .rst_mask = BIT(11),
++              .clk_mask = BIT(11),
++              .parent = IMX93_MEDIABLK_PD_MIPI_PHY,
+       },
+       [IMX93_MEDIABLK_PD_MIPI_CSI] = {
+               .name = "mediablk-mipi-csi",
+@@ -332,6 +376,7 @@ static const struct imx93_blk_ctrl_domai
+               .num_clks = 2,
+               .rst_mask = BIT(9) | BIT(10),
+               .clk_mask = BIT(9) | BIT(10),
++              .parent = IMX93_MEDIABLK_PD_MIPI_PHY,
+       },
+       [IMX93_MEDIABLK_PD_PXP] = {
+               .name = "mediablk-pxp",
+@@ -339,6 +384,7 @@ static const struct imx93_blk_ctrl_domai
+               .num_clks = 1,
+               .rst_mask = BIT(7) | BIT(8),
+               .clk_mask = BIT(7) | BIT(8),
++              .parent = BLK_CTRL_NO_PARENT,
+               .num_qos = 2,
+               .qos = {
+                       {
+@@ -360,6 +406,7 @@ static const struct imx93_blk_ctrl_domai
+               .num_clks = 2,
+               .rst_mask = BIT(4) | BIT(5) | BIT(6),
+               .clk_mask = BIT(4) | BIT(5) | BIT(6),
++              .parent = BLK_CTRL_NO_PARENT,
+               .num_qos = 1,
+               .qos = {
+                       {
+@@ -376,6 +423,7 @@ static const struct imx93_blk_ctrl_domai
+               .num_clks = 1,
+               .rst_mask = BIT(2) | BIT(3),
+               .clk_mask = BIT(2) | BIT(3),
++              .parent = BLK_CTRL_NO_PARENT,
+               .num_qos = 4,
+               .qos = {
+                       {
+@@ -401,6 +449,14 @@ static const struct imx93_blk_ctrl_domai
+                       }
+               }
+       },
++      [IMX93_MEDIABLK_PD_MIPI_PHY] = {
++              .name = "mediablk-mipi-phy",
++              .clk_names = NULL,
++              .num_clks = 0,
++              .rst_mask = BIT(12),
++              .clk_mask = BIT(12),
++              .parent = BLK_CTRL_NO_PARENT,
++      },
+ };
+ static const struct regmap_range imx93_media_blk_ctl_yes_ranges[] = {
diff --git a/queue-6.18/remoteproc-xlnx-check-remote-core-state.patch b/queue-6.18/remoteproc-xlnx-check-remote-core-state.patch
new file mode 100644 (file)
index 0000000..ef3d31a
--- /dev/null
@@ -0,0 +1,147 @@
+From stable+bounces-288526-greg=kroah.com@vger.kernel.org Thu Jul 23 22:00:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 15:58:58 -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: <20260723195858.3684427-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
+@@ -461,6 +461,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
+@@ -959,16 +959,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;
+@@ -1221,6 +1211,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];
+@@ -1266,6 +1257,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
+@@ -164,6 +164,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,
+@@ -545,6 +546,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
index 8846b64317da0f7bcd03bdad1347e4992d60c614..2ffc8e1b848c3cb5116e84edebe2d6cd5119c25d 100644 (file)
@@ -607,3 +607,66 @@ ksmbd-bound-dacl-dedup-walk-to-copied-aces.patch
 ksmbd-validate-ace-size-against-sid-sub-authorities.patch
 fscrypt-avoid-dynamic-allocation-in-fscrypt_get_devi.patch
 drm-amd-display-fix-dtb-dto-updates-breaking-live-pi.patch
+landlock-fix-formatting.patch
+landlock-account-all-audit-data-allocations-to-user-space.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
+fuse-uring-fix-race-between-registration-and-connection-abortion.patch
+xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch
+vduse-return-internal-vq-group-struct-as-map-token.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
+arm64-dts-qcom-correct-rbr-opp-entry.patch
+arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.patch
+netfilter-nf_tables-remove-register-tracking-infrastructure.patch
+netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch
+netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch
+netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.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
+pinctrl-renesas-rzg2l-use-enotsupp-instead-of-eopnotsupp.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
+cxl-pci-remove-unnecessary-cxl-endpoint-handling-helper-functions.patch
+cxl-pci-remove-unnecessary-cxl-rch-handling-helper-functions.patch
+cxl-pci-remove-cxl-vh-handling-in-config_pcieaer_cxl-conditional-blocks-from-core-pci.c.patch
+cxl-fix-cxl_headerlog_size-to-match-ras-capability-size.patch
+remoteproc-xlnx-check-remote-core-state.patch
+mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch
+kho-add-interfaces-to-unpreserve-folios-page-ranges-and-vmalloc.patch
+kho-make-sure-scratch-size-is-always-aligned-by-cma_min_alignment_bytes.patch
+mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.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
+dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch
+dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch
+ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch
+accel-amdxdna-reject-command-submission-on-devices-without-a-submit-op.patch
+cred-add-kernel_cred-helper.patch
+dm-avoid-leaking-the-caller-s-thread-keyring-via-the-table-device-file.patch
+mmc-vub300-rename-probe-error-labels.patch
+mmc-vub300-fix-use-after-free-on-probe-failure.patch
+fs-resctrl-split-l3-dependent-parts-out-of-__mon_event_count.patch
+x86-fs-resctrl-rename-struct-rdt_mon_domain-and-rdt_hw_mon_domain.patch
+x86-fs-resctrl-rename-some-l3-specific-functions.patch
+fs-resctrl-move-allocation-free-of-closid_num_dirty_rmid.patch
+fs-resctrl-move-rmid-initialization-to-first-mount.patch
+fs-resctrl-fix-use-after-free-during-unmount.patch
+net-mana-validate-the-packet-length-reported-by-the-nic.patch
+net-mana-optimize-irq-affinity-for-low-vcpu-configs.patch
+octeontx2-af-validate-body-pcifunc-in-rvu_mbox_handler_rep_event_notify.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
+ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch
+net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch
+pmdomain-imx93-blk-ctrl-convert-to-devm_-only.patch
+pmdomain-imx93-blk-ctrl-extract-phy-as-shared-domain-for-dsi-csi.patch
+i3c-mipi-i3c-hci-fix-hot-join-nack.patch
+i3c-mipi-i3c-hci-fix-handling-of-shared-irqs-during-early-initialization.patch
diff --git a/queue-6.18/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch b/queue-6.18/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch
new file mode 100644 (file)
index 0000000..8a17501
--- /dev/null
@@ -0,0 +1,279 @@
+From stable+bounces-288493-greg=kroah.com@vger.kernel.org Thu Jul 23 20:21:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:19:08 -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: <20260723181911.3438372-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
+@@ -140,6 +140,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
+@@ -192,6 +192,205 @@ bvec_overflow:
+ EXPORT_SYMBOL_GPL(xdr_buf_to_bvec);
+ /**
++ * 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.18/sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch b/queue-6.18/sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch
new file mode 100644 (file)
index 0000000..a9b671f
--- /dev/null
@@ -0,0 +1,172 @@
+From stable+bounces-288491-greg=kroah.com@vger.kernel.org Thu Jul 23 20:21:30 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:19:07 -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: <20260723181911.3438372-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           |   55 +++++++++++++++++++++++++++++++++++------
+ 2 files changed, 51 insertions(+), 7 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,14 +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, rqstp->rq_maxpages, 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);
+       }
+@@ -1245,19 +1256,19 @@ static int svc_tcp_sendmsg(struct svc_so
+       int ret;
+       /* 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, rqstp->rq_maxpages,
++      count = xdr_buf_to_bvec(svsk->sk_bvec + 1, rqstp->rq_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);
+@@ -1402,6 +1413,20 @@ void svc_sock_update_bufs(struct svc_ser
+       spin_unlock_bh(&serv->sv_lock);
+ }
++static int svc_sock_sendpages(struct svc_serv *serv, struct socket *sock, int flags)
++{
++      switch (sock->type) {
++      case SOCK_STREAM:
++              /* +1 for TCP record marker */
++              if (flags & SVC_SOCK_TEMPORARY)
++                      return svc_serv_maxpages(serv) + 1;
++              return 0;
++      case SOCK_DGRAM:
++              return SUNRPC_MAX_UDP_SENDPAGES;
++      }
++      return -EINVAL;
++}
++
+ /*
+  * Initialize socket for RPC use and create svc_sock struct
+  */
+@@ -1412,12 +1437,26 @@ static struct svc_sock *svc_setup_socket
+       struct svc_sock *svsk;
+       struct sock     *inet;
+       int             pmap_register = !(flags & SVC_SOCK_ANONYMOUS);
++      int             sendpages;
+       unsigned long   pages;
++      sendpages = svc_sock_sendpages(serv, sock, flags);
++      if (sendpages < 0)
++              return ERR_PTR(sendpages);
++
+       pages = svc_serv_maxpages(serv);
+       svsk = kzalloc(struct_size(svsk, sk_pages, pages), GFP_KERNEL);
+       if (!svsk)
+               return ERR_PTR(-ENOMEM);
++
++      if (sendpages) {
++              svsk->sk_bvec = kcalloc(sendpages, sizeof(*svsk->sk_bvec), GFP_KERNEL);
++              if (!svsk->sk_bvec) {
++                      kfree(svsk);
++                      return ERR_PTR(-ENOMEM);
++              }
++      }
++
+       svsk->sk_maxpages = pages;
+       inet = sock->sk;
+@@ -1429,6 +1468,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);
+               }
+@@ -1646,5 +1686,6 @@ static void svc_sock_free(struct svc_xpr
+               sock_release(sock);
+       page_frag_cache_drain(&svsk->sk_frag_cache);
++      kfree(svsk->sk_bvec);
+       kfree(svsk);
+ }
diff --git a/queue-6.18/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch b/queue-6.18/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch
new file mode 100644 (file)
index 0000000..44a8a22
--- /dev/null
@@ -0,0 +1,168 @@
+From stable+bounces-288495-greg=kroah.com@vger.kernel.org Thu Jul 23 20:27:21 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:19:10 -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: <20260723181911.3438372-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>
+---
+ fs/nfsd/vfs.c              |    8 +++++++-
+ include/linux/sunrpc/xdr.h |    4 ++--
+ net/sunrpc/svcsock.c       |   14 ++++++++++++--
+ net/sunrpc/xdr.c           |   11 ++++++-----
+ 4 files changed, 27 insertions(+), 10 deletions(-)
+
+--- a/fs/nfsd/vfs.c
++++ b/fs/nfsd/vfs.c
+@@ -1203,7 +1203,7 @@ nfsd_vfs_write(struct svc_rqst *rqstp, s
+       unsigned long           exp_op_flags = 0;
+       unsigned int            pflags = current->flags;
+       bool                    restore_flags = false;
+-      unsigned int            nvecs;
++      int                     nvecs;
+       trace_nfsd_write_opened(rqstp, fhp, offset, *cnt);
+@@ -1243,7 +1243,13 @@ nfsd_vfs_write(struct svc_rqst *rqstp, s
+       }
+       nvecs = xdr_buf_to_bvec(rqstp->rq_bvec, rqstp->rq_maxpages, payload);
++      if (nvecs < 0) {
++              host_err = nvecs;
++              goto out_nfserr;
++      }
++
+       iov_iter_bvec(&iter, ITER_SOURCE, rqstp->rq_bvec, nvecs, *cnt);
++
+       since = READ_ONCE(file->f_wb_err);
+       if (verf)
+               nfsd_copy_write_verifier(verf, nn);
+--- a/include/linux/sunrpc/xdr.h
++++ b/include/linux/sunrpc/xdr.h
+@@ -138,8 +138,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;
+@@ -1267,10 +1272,15 @@ static int svc_tcp_sendmsg(struct svc_so
+       count = xdr_buf_to_bvec(svsk->sk_bvec + 1, rqstp->rq_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);
+       return ret;
+ }
+--- a/net/sunrpc/xdr.c
++++ b/net/sunrpc/xdr.c
+@@ -139,13 +139,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;
+@@ -187,7 +188,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;
+ }
+ EXPORT_SYMBOL_GPL(xdr_buf_to_bvec);
diff --git a/queue-6.18/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch b/queue-6.18/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch
new file mode 100644 (file)
index 0000000..5ab39f8
--- /dev/null
@@ -0,0 +1,42 @@
+From stable+bounces-289230-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:47 -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: <20260726120050.4138299-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
+@@ -1012,6 +1012,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 = {
+@@ -1120,7 +1121,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.18/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch b/queue-6.18/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch
new file mode 100644 (file)
index 0000000..b7f10fb
--- /dev/null
@@ -0,0 +1,90 @@
+From stable+bounces-289233-greg=kroah.com@vger.kernel.org Sun Jul 26 14:03:26 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:00:50 -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: <20260726120050.4138299-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
+@@ -909,6 +909,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)
+ {
+@@ -2500,19 +2513,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.18/thunderbolt-remove-service-debugfs-entries-during-unregister.patch b/queue-6.18/thunderbolt-remove-service-debugfs-entries-during-unregister.patch
new file mode 100644 (file)
index 0000000..6deb25e
--- /dev/null
@@ -0,0 +1,76 @@
+From stable+bounces-289231-greg=kroah.com@vger.kernel.org Sun Jul 26 14:00:57 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:00:48 -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: <20260726120050.4138299-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
+@@ -1008,7 +1008,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);
+@@ -1023,6 +1022,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;
+@@ -1034,7 +1041,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;
+ }
+@@ -1127,6 +1134,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;
+               }
+@@ -2059,7 +2067,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.18/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch b/queue-6.18/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch
new file mode 100644 (file)
index 0000000..f5081f7
--- /dev/null
@@ -0,0 +1,337 @@
+From stable+bounces-289232-greg=kroah.com@vger.kernel.org Sun Jul 26 14:00:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:00:49 -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: <20260726120050.4138299-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
+@@ -1786,6 +1786,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
+@@ -850,6 +850,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
+@@ -738,6 +738,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);
+ }
+@@ -1762,6 +1763,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,
+@@ -2112,6 +2115,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
+@@ -3603,6 +3603,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
+@@ -2524,6 +2524,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);
+@@ -3110,6 +3112,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);
+@@ -3129,6 +3149,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);
+       /*
+@@ -3171,28 +3192,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);
+@@ -3212,14 +3211,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)
+@@ -3246,11 +3245,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
+@@ -792,6 +792,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)
+ {
+@@ -1262,6 +1263,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
+@@ -2072,41 +2072,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.18/vduse-avoid-leaking-information-to-userspace.patch b/queue-6.18/vduse-avoid-leaking-information-to-userspace.patch
new file mode 100644 (file)
index 0000000..1dabcb2
--- /dev/null
@@ -0,0 +1,53 @@
+From stable+bounces-287927-greg=kroah.com@vger.kernel.org Wed Jul 22 18:08:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:07:31 -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: <20260722160731.1739521-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
+@@ -930,7 +930,7 @@ static void *vduse_dev_alloc_coherent(un
+       if (!token.group)
+               return NULL;
+-      addr = alloc_pages_exact(size, flag);
++      addr = alloc_pages_exact(size, flag | __GFP_ZERO);
+       if (!addr)
+               return NULL;
diff --git a/queue-6.18/vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch b/queue-6.18/vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch
new file mode 100644 (file)
index 0000000..7fab003
--- /dev/null
@@ -0,0 +1,61 @@
+From stable+bounces-287925-greg=kroah.com@vger.kernel.org Wed Jul 22 18:34:33 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:07:29 -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: <20260722160731.1739521-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
+@@ -521,8 +521,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
+@@ -71,8 +71,7 @@ dma_addr_t vduse_domain_alloc_coherent(s
+                                      size_t size, void *orig);
+ 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
+@@ -960,7 +960,7 @@ static void vduse_dev_free_coherent(unio
+       vdev = token.group->dev;
+       domain = vdev->domain;
+-      vduse_domain_free_coherent(domain, size, vaddr, dma_addr, attrs);
++      vduse_domain_free_coherent(domain, size, dma_addr, attrs);
+ }
+ static bool vduse_dev_need_sync(union virtio_map token, dma_addr_t dma_addr)
diff --git a/queue-6.18/vduse-return-internal-vq-group-struct-as-map-token.patch b/queue-6.18/vduse-return-internal-vq-group-struct-as-map-token.patch
new file mode 100644 (file)
index 0000000..96d1ead
--- /dev/null
@@ -0,0 +1,348 @@
+From stable+bounces-287924-greg=kroah.com@vger.kernel.org Wed Jul 22 18:29:31 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:07:28 -0400
+Subject: vduse: return internal vq group struct as map token
+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: <20260722160731.1739521-1-sashal@kernel.org>
+
+From: Eugenio Pérez <eperezma@redhat.com>
+
+[ Upstream commit 02e3f7ffe2906033da73b7c7ea8180b131d0cdbc ]
+
+Return the internal struct that represents the vq group as virtqueue map
+token, instead of the device.  This allows the map functions to access
+the information per group.
+
+At this moment all the virtqueues share the same vq group, that only
+can point to ASID 0.  This change prepares the infrastructure for actual
+per-group address space handling
+
+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-5-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 |   21 ++----
+ drivers/vdpa/vdpa_user/iova_domain.h |    5 -
+ drivers/vdpa/vdpa_user/vduse_dev.c   |  109 ++++++++++++++++++++++++++++++-----
+ include/linux/virtio.h               |    6 -
+ 4 files changed, 108 insertions(+), 33 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)
++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,
+--- 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);
++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,
+                               void *vaddr, dma_addr_t dma_addr,
+--- a/drivers/vdpa/vdpa_user/vduse_dev.c
++++ b/drivers/vdpa/vdpa_user/vduse_dev.c
+@@ -22,6 +22,7 @@
+ #include <linux/uio.h>
+ #include <linux/vdpa.h>
+ #include <linux/nospec.h>
++#include <linux/virtio.h>
+ #include <linux/vmalloc.h>
+ #include <linux/sched/mm.h>
+ #include <uapi/linux/vduse.h>
+@@ -83,6 +84,10 @@ struct vduse_umem {
+       struct mm_struct *mm;
+ };
++struct vduse_vq_group {
++      struct vduse_dev *dev;
++};
++
+ struct vduse_dev {
+       struct vduse_vdpa *vdev;
+       struct device *dev;
+@@ -115,6 +120,7 @@ struct vduse_dev {
+       u32 vq_num;
+       u32 vq_align;
+       struct vduse_umem *umem;
++      struct vduse_vq_group *groups;
+       struct mutex mem_lock;
+       unsigned int bounce_size;
+       struct mutex domain_lock;
+@@ -615,6 +621,16 @@ static int vduse_vdpa_set_vq_state(struc
+       return 0;
+ }
++static union virtio_map vduse_get_vq_map(struct vdpa_device *vdpa, u16 idx)
++{
++      struct vduse_dev *dev = vdpa_to_vduse(vdpa);
++      union virtio_map ret = {
++              .group = &dev->groups[0],
++      };
++
++      return ret;
++}
++
+ static int vduse_vdpa_get_vq_state(struct vdpa_device *vdpa, u16 idx,
+                               struct vdpa_vq_state *state)
+ {
+@@ -834,6 +850,7 @@ static const struct vdpa_config_ops vdus
+       .get_vq_affinity        = vduse_vdpa_get_vq_affinity,
+       .reset                  = vduse_vdpa_reset,
+       .set_map                = vduse_vdpa_set_map,
++      .get_vq_map             = vduse_get_vq_map,
+       .free                   = vduse_vdpa_free,
+ };
+@@ -841,7 +858,14 @@ static void vduse_dev_sync_single_for_de
+                                            dma_addr_t dma_addr, size_t size,
+                                            enum dma_data_direction dir)
+ {
+-      struct vduse_iova_domain *domain = token.iova_domain;
++      struct vduse_dev *vdev;
++      struct vduse_iova_domain *domain;
++
++      if (!token.group)
++              return;
++
++      vdev = token.group->dev;
++      domain = vdev->domain;
+       vduse_domain_sync_single_for_device(domain, dma_addr, size, dir);
+ }
+@@ -850,7 +874,14 @@ static void vduse_dev_sync_single_for_cp
+                                            dma_addr_t dma_addr, size_t size,
+                                            enum dma_data_direction dir)
+ {
+-      struct vduse_iova_domain *domain = token.iova_domain;
++      struct vduse_dev *vdev;
++      struct vduse_iova_domain *domain;
++
++      if (!token.group)
++              return;
++
++      vdev = token.group->dev;
++      domain = vdev->domain;
+       vduse_domain_sync_single_for_cpu(domain, dma_addr, size, dir);
+ }
+@@ -860,7 +891,14 @@ static dma_addr_t vduse_dev_map_page(uni
+                                    enum dma_data_direction dir,
+                                    unsigned long attrs)
+ {
+-      struct vduse_iova_domain *domain = token.iova_domain;
++      struct vduse_dev *vdev;
++      struct vduse_iova_domain *domain;
++
++      if (!token.group)
++              return DMA_MAPPING_ERROR;
++
++      vdev = token.group->dev;
++      domain = vdev->domain;
+       return vduse_domain_map_page(domain, page, offset, size, dir, attrs);
+ }
+@@ -869,7 +907,14 @@ static void vduse_dev_unmap_page(union v
+                                size_t size, enum dma_data_direction dir,
+                                unsigned long attrs)
+ {
+-      struct vduse_iova_domain *domain = token.iova_domain;
++      struct vduse_dev *vdev;
++      struct vduse_iova_domain *domain;
++
++      if (!token.group)
++              return;
++
++      vdev = token.group->dev;
++      domain = vdev->domain;
+       return vduse_domain_unmap_page(domain, dma_addr, size, dir, attrs);
+ }
+@@ -877,33 +922,57 @@ static void vduse_dev_unmap_page(union v
+ static void *vduse_dev_alloc_coherent(union virtio_map token, size_t size,
+                                     dma_addr_t *dma_addr, gfp_t flag)
+ {
+-      struct vduse_iova_domain *domain = token.iova_domain;
+-      unsigned long iova;
++      struct vduse_dev *vdev;
++      struct vduse_iova_domain *domain;
+       void *addr;
+       *dma_addr = DMA_MAPPING_ERROR;
+-      addr = vduse_domain_alloc_coherent(domain, size,
+-                                         (dma_addr_t *)&iova, flag);
++      if (!token.group)
++              return NULL;
++
++      addr = alloc_pages_exact(size, flag);
+       if (!addr)
+               return NULL;
+-      *dma_addr = (dma_addr_t)iova;
++      vdev = token.group->dev;
++      domain = vdev->domain;
++      *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(union virtio_map token, size_t size,
+                                   void *vaddr, dma_addr_t dma_addr,
+                                   unsigned long attrs)
+ {
+-      struct vduse_iova_domain *domain = token.iova_domain;
++      struct vduse_dev *vdev;
++      struct vduse_iova_domain *domain;
++
++      if (!token.group)
++              return;
++
++      vdev = token.group->dev;
++      domain = vdev->domain;
+       vduse_domain_free_coherent(domain, size, vaddr, dma_addr, attrs);
+ }
+ static bool vduse_dev_need_sync(union virtio_map token, dma_addr_t dma_addr)
+ {
+-      struct vduse_iova_domain *domain = token.iova_domain;
++      struct vduse_dev *vdev;
++      struct vduse_iova_domain *domain;
++
++      if (!token.group)
++              return false;
++
++      vdev = token.group->dev;
++      domain = vdev->domain;
+       return dma_addr < domain->bounce_size;
+ }
+@@ -917,7 +986,14 @@ static int vduse_dev_mapping_error(union
+ static size_t vduse_dev_max_mapping_size(union virtio_map token)
+ {
+-      struct vduse_iova_domain *domain = token.iova_domain;
++      struct vduse_dev *vdev;
++      struct vduse_iova_domain *domain;
++
++      if (!token.group)
++              return 0;
++
++      vdev = token.group->dev;
++      domain = vdev->domain;
+       return domain->bounce_size;
+ }
+@@ -1716,6 +1792,7 @@ static int vduse_destroy_dev(char *name)
+       if (dev->domain)
+               vduse_domain_destroy(dev->domain);
+       kfree(dev->name);
++      kfree(dev->groups);
+       vduse_dev_destroy(dev);
+       module_put(THIS_MODULE);
+@@ -1874,6 +1951,11 @@ static int vduse_create_dev(struct vduse
+       dev->device_features = config->features;
+       dev->device_id = config->device_id;
+       dev->vendor_id = config->vendor_id;
++      dev->groups = kcalloc(1, sizeof(dev->groups[0]), GFP_KERNEL);
++      if (!dev->groups)
++              goto err_vq_groups;
++      dev->groups[0].dev = dev;
++
+       dev->name = kstrdup(config->name, GFP_KERNEL);
+       if (!dev->name)
+               goto err_str;
+@@ -1910,6 +1992,8 @@ err_dev:
+ err_idr:
+       kfree(dev->name);
+ err_str:
++      kfree(dev->groups);
++err_vq_groups:
+       vduse_dev_destroy(dev);
+ err:
+       return ret;
+@@ -2071,7 +2155,6 @@ static int vdpa_dev_add(struct vdpa_mgmt
+               return -ENOMEM;
+       }
+-      dev->vdev->vdpa.vmap.iova_domain = dev->domain;
+       ret = _vdpa_register_device(&dev->vdev->vdpa, dev->vq_num);
+       if (ret) {
+               put_device(&dev->vdev->vdpa.dev);
+--- a/include/linux/virtio.h
++++ b/include/linux/virtio.h
+@@ -41,13 +41,13 @@ struct virtqueue {
+       void *priv;
+ };
+-struct vduse_iova_domain;
++struct vduse_vq_group;
+ union virtio_map {
+       /* Device that performs DMA */
+       struct device *dma_dev;
+-      /* VDUSE specific mapping data */
+-      struct vduse_iova_domain *iova_domain;
++      /* VDUSE specific virtqueue group for doing map */
++      struct vduse_vq_group *group;
+ };
+ int virtqueue_add_outbuf(struct virtqueue *vq,
diff --git a/queue-6.18/vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch b/queue-6.18/vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch
new file mode 100644 (file)
index 0000000..16612f6
--- /dev/null
@@ -0,0 +1,61 @@
+From stable+bounces-287926-greg=kroah.com@vger.kernel.org Wed Jul 22 18:08:40 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:07:30 -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: <20260722160731.1739521-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 |    3 ---
+ drivers/vdpa/vdpa_user/vduse_dev.c   |    1 +
+ 2 files changed, 1 insertion(+), 3 deletions(-)
+
+--- a/drivers/vdpa/vdpa_user/iova_domain.c
++++ b/drivers/vdpa/vdpa_user/iova_domain.c
+@@ -526,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,
+@@ -538,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/vduse_dev.c
++++ b/drivers/vdpa/vdpa_user/vduse_dev.c
+@@ -961,6 +961,7 @@ static void vduse_dev_free_coherent(unio
+       domain = vdev->domain;
+       vduse_domain_free_coherent(domain, size, dma_addr, attrs);
++      free_pages_exact(vaddr, size);
+ }
+ static bool vduse_dev_need_sync(union virtio_map token, dma_addr_t dma_addr)
diff --git a/queue-6.18/x86-fs-resctrl-rename-some-l3-specific-functions.patch b/queue-6.18/x86-fs-resctrl-rename-some-l3-specific-functions.patch
new file mode 100644 (file)
index 0000000..9b4595e
--- /dev/null
@@ -0,0 +1,292 @@
+From stable+bounces-289734-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:32 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:17 -0400
+Subject: x86,fs/resctrl: Rename some L3 specific functions
+To: stable@vger.kernel.org
+Cc: Tony Luck <tony.luck@intel.com>, "Borislav Petkov (AMD)" <bp@alien8.de>, Reinette Chatre <reinette.chatre@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727233421.2423844-3-sashal@kernel.org>
+
+From: Tony Luck <tony.luck@intel.com>
+
+[ Upstream commit 9c214d10c50990c7a61b95887493df9ae713eec5 ]
+
+With the arrival of monitor events tied to new domains associated with a
+different resource it would be clearer if the L3 resource specific functions
+are more accurately named.
+
+Rename three groups of functions:
+
+Functions that allocate/free architecture per-RMID MBM state information:
+arch_domain_mbm_alloc()                -> l3_mon_domain_mbm_alloc()
+mon_domain_free()              -> l3_mon_domain_free()
+
+Functions that allocate/free filesystem per-RMID MBM state information:
+domain_setup_mon_state()       -> domain_setup_l3_mon_state()
+domain_destroy_mon_state()     -> domain_destroy_l3_mon_state()
+
+Initialization/exit:
+rdt_get_mon_l3_config()                -> rdt_get_l3_mon_config()
+resctrl_mon_resource_init()    -> resctrl_l3_mon_resource_init()
+resctrl_mon_resource_exit()    -> resctrl_l3_mon_resource_exit()
+
+Ensure kernel-doc descriptions of these functions' return values are present
+and correctly formatted.
+
+Signed-off-by: Tony Luck <tony.luck@intel.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Reviewed-by: Reinette Chatre <reinette.chatre@intel.com>
+Link: https://lore.kernel.org/20251217172121.12030-1-tony.luck@intel.com
+Stable-dep-of: 52fce648607e ("fs/resctrl: Fix use-after-free during unmount")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/x86/kernel/cpu/resctrl/core.c     |   20 +++++++++++---------
+ arch/x86/kernel/cpu/resctrl/internal.h |    2 +-
+ arch/x86/kernel/cpu/resctrl/monitor.c  |    2 +-
+ fs/resctrl/internal.h                  |    6 +++---
+ fs/resctrl/monitor.c                   |    8 ++++----
+ fs/resctrl/rdtgroup.c                  |   24 ++++++++++++------------
+ 6 files changed, 32 insertions(+), 30 deletions(-)
+
+--- a/arch/x86/kernel/cpu/resctrl/core.c
++++ b/arch/x86/kernel/cpu/resctrl/core.c
+@@ -363,7 +363,7 @@ static void ctrl_domain_free(struct rdt_
+       kfree(hw_dom);
+ }
+-static void mon_domain_free(struct rdt_hw_l3_mon_domain *hw_dom)
++static void l3_mon_domain_free(struct rdt_hw_l3_mon_domain *hw_dom)
+ {
+       int idx;
+@@ -396,11 +396,13 @@ static int domain_setup_ctrlval(struct r
+ }
+ /**
+- * arch_domain_mbm_alloc() - Allocate arch private storage for the MBM counters
++ * l3_mon_domain_mbm_alloc() - Allocate arch private storage for the MBM counters
+  * @num_rmid: The size of the MBM counter array
+  * @hw_dom:   The domain that owns the allocated arrays
++ *
++ * Return:    0 for success, or -ENOMEM.
+  */
+-static int arch_domain_mbm_alloc(u32 num_rmid, struct rdt_hw_l3_mon_domain *hw_dom)
++static int l3_mon_domain_mbm_alloc(u32 num_rmid, struct rdt_hw_l3_mon_domain *hw_dom)
+ {
+       size_t tsize = sizeof(*hw_dom->arch_mbm_states[0]);
+       enum resctrl_event_id eventid;
+@@ -536,7 +538,7 @@ static void domain_add_cpu_mon(int cpu,
+       ci = get_cpu_cacheinfo_level(cpu, RESCTRL_L3_CACHE);
+       if (!ci) {
+               pr_warn_once("Can't find L3 cache for CPU:%d resource %s\n", cpu, r->name);
+-              mon_domain_free(hw_dom);
++              l3_mon_domain_free(hw_dom);
+               return;
+       }
+       d->ci_id = ci->id;
+@@ -548,8 +550,8 @@ static void domain_add_cpu_mon(int cpu,
+       arch_mon_domain_online(r, d);
+-      if (arch_domain_mbm_alloc(r->mon.num_rmid, hw_dom)) {
+-              mon_domain_free(hw_dom);
++      if (l3_mon_domain_mbm_alloc(r->mon.num_rmid, hw_dom)) {
++              l3_mon_domain_free(hw_dom);
+               return;
+       }
+@@ -559,7 +561,7 @@ static void domain_add_cpu_mon(int cpu,
+       if (err) {
+               list_del_rcu(&d->hdr.list);
+               synchronize_rcu();
+-              mon_domain_free(hw_dom);
++              l3_mon_domain_free(hw_dom);
+       }
+ }
+@@ -650,7 +652,7 @@ static void domain_remove_cpu_mon(int cp
+               resctrl_offline_mon_domain(r, d);
+               list_del_rcu(&d->hdr.list);
+               synchronize_rcu();
+-              mon_domain_free(hw_dom);
++              l3_mon_domain_free(hw_dom);
+               return;
+       }
+@@ -897,7 +899,7 @@ static __init bool get_rdt_mon_resources
+       if (!ret)
+               return false;
+-      return !rdt_get_mon_l3_config(r);
++      return !rdt_get_l3_mon_config(r);
+ }
+ static __init void __check_quirks_intel(void)
+--- a/arch/x86/kernel/cpu/resctrl/internal.h
++++ b/arch/x86/kernel/cpu/resctrl/internal.h
+@@ -211,7 +211,7 @@ union l3_qos_abmc_cfg {
+ void rdt_ctrl_update(void *arg);
+-int rdt_get_mon_l3_config(struct rdt_resource *r);
++int rdt_get_l3_mon_config(struct rdt_resource *r);
+ bool rdt_cpu_has(int flag);
+--- a/arch/x86/kernel/cpu/resctrl/monitor.c
++++ b/arch/x86/kernel/cpu/resctrl/monitor.c
+@@ -423,7 +423,7 @@ static __init int snc_get_config(void)
+       return ret;
+ }
+-int __init rdt_get_mon_l3_config(struct rdt_resource *r)
++int __init rdt_get_l3_mon_config(struct rdt_resource *r)
+ {
+       unsigned int mbm_offset = boot_cpu_data.x86_cache_mbm_width_offset;
+       struct rdt_hw_resource *hw_res = resctrl_to_arch_res(r);
+--- a/fs/resctrl/internal.h
++++ b/fs/resctrl/internal.h
+@@ -355,7 +355,9 @@ int alloc_rmid(u32 closid);
+ void free_rmid(u32 closid, u32 rmid);
+-void resctrl_mon_resource_exit(void);
++int resctrl_l3_mon_resource_init(void);
++
++void resctrl_l3_mon_resource_exit(void);
+ void mon_event_count(void *info);
+@@ -365,8 +367,6 @@ void mon_event_read(struct rmid_read *rr
+                   struct rdt_l3_mon_domain *d, struct rdtgroup *rdtgrp,
+                   cpumask_t *cpumask, int evtid, int first);
+-int resctrl_mon_resource_init(void);
+-
+ void mbm_setup_overflow_handler(struct rdt_l3_mon_domain *dom,
+                               unsigned long delay_ms,
+                               int exclude_cpu);
+--- a/fs/resctrl/monitor.c
++++ b/fs/resctrl/monitor.c
+@@ -1758,7 +1758,7 @@ ssize_t mbm_L3_assignments_write(struct
+ }
+ /**
+- * resctrl_mon_resource_init() - Initialise global monitoring structures.
++ * resctrl_l3_mon_resource_init() - Initialise global monitoring structures.
+  *
+  * Allocate and initialise global monitor resources that do not belong to a
+  * specific domain. i.e. the rmid_ptrs[] used for the limbo and free lists.
+@@ -1767,9 +1767,9 @@ ssize_t mbm_L3_assignments_write(struct
+  * Resctrl's cpuhp callbacks may be called before this point to bring a domain
+  * online.
+  *
+- * Returns 0 for success, or -ENOMEM.
++ * Return: 0 for success, or -ENOMEM.
+  */
+-int resctrl_mon_resource_init(void)
++int resctrl_l3_mon_resource_init(void)
+ {
+       struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+       int ret;
+@@ -1819,7 +1819,7 @@ int resctrl_mon_resource_init(void)
+       return 0;
+ }
+-void resctrl_mon_resource_exit(void)
++void resctrl_l3_mon_resource_exit(void)
+ {
+       struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+--- a/fs/resctrl/rdtgroup.c
++++ b/fs/resctrl/rdtgroup.c
+@@ -4170,7 +4170,7 @@ static void rdtgroup_setup_default(void)
+       mutex_unlock(&rdtgroup_mutex);
+ }
+-static void domain_destroy_mon_state(struct rdt_l3_mon_domain *d)
++static void domain_destroy_l3_mon_state(struct rdt_l3_mon_domain *d)
+ {
+       int idx;
+@@ -4218,13 +4218,13 @@ void resctrl_offline_mon_domain(struct r
+               cancel_delayed_work(&d->cqm_limbo);
+       }
+-      domain_destroy_mon_state(d);
++      domain_destroy_l3_mon_state(d);
+       mutex_unlock(&rdtgroup_mutex);
+ }
+ /**
+- * domain_setup_mon_state() -  Initialise domain monitoring structures.
++ * domain_setup_l3_mon_state() -  Initialise domain monitoring structures.
+  * @r:        The resource for the newly online domain.
+  * @d:        The newly online domain.
+  *
+@@ -4232,11 +4232,11 @@ void resctrl_offline_mon_domain(struct r
+  * Called when the first CPU of a domain comes online, regardless of whether
+  * the filesystem is mounted.
+  * During boot this may be called before global allocations have been made by
+- * resctrl_mon_resource_init().
++ * resctrl_l3_mon_resource_init().
+  *
+- * Returns 0 for success, or -ENOMEM.
++ * Return: 0 for success, or -ENOMEM.
+  */
+-static int domain_setup_mon_state(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
++static int domain_setup_l3_mon_state(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+       u32 idx_limit = resctrl_arch_system_num_rmid_idx();
+       size_t tsize = sizeof(*d->mbm_states[0]);
+@@ -4298,7 +4298,7 @@ int resctrl_online_mon_domain(struct rdt
+       mutex_lock(&rdtgroup_mutex);
+-      err = domain_setup_mon_state(r, d);
++      err = domain_setup_l3_mon_state(r, d);
+       if (err)
+               goto out_unlock;
+@@ -4413,13 +4413,13 @@ int resctrl_init(void)
+       thread_throttle_mode_init();
+-      ret = resctrl_mon_resource_init();
++      ret = resctrl_l3_mon_resource_init();
+       if (ret)
+               return ret;
+       ret = sysfs_create_mount_point(fs_kobj, "resctrl");
+       if (ret) {
+-              resctrl_mon_resource_exit();
++              resctrl_l3_mon_resource_exit();
+               return ret;
+       }
+@@ -4454,7 +4454,7 @@ int resctrl_init(void)
+ cleanup_mountpoint:
+       sysfs_remove_mount_point(fs_kobj, "resctrl");
+-      resctrl_mon_resource_exit();
++      resctrl_l3_mon_resource_exit();
+       return ret;
+ }
+@@ -4490,7 +4490,7 @@ static bool resctrl_online_domains_exist
+  * When called by the architecture code, all CPUs and resctrl domains must be
+  * offline. This ensures the limbo and overflow handlers are not scheduled to
+  * run, meaning the data structures they access can be freed by
+- * resctrl_mon_resource_exit().
++ * resctrl_l3_mon_resource_exit().
+  *
+  * After resctrl_exit() returns, the architecture code should return an
+  * error from all resctrl_arch_ functions that can do this.
+@@ -4517,5 +4517,5 @@ void resctrl_exit(void)
+        * it can be used to umount resctrl.
+        */
+-      resctrl_mon_resource_exit();
++      resctrl_l3_mon_resource_exit();
+ }
diff --git a/queue-6.18/x86-fs-resctrl-rename-struct-rdt_mon_domain-and-rdt_hw_mon_domain.patch b/queue-6.18/x86-fs-resctrl-rename-struct-rdt_mon_domain-and-rdt_hw_mon_domain.patch
new file mode 100644 (file)
index 0000000..b1fcdbc
--- /dev/null
@@ -0,0 +1,872 @@
+From stable+bounces-289735-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:31 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:16 -0400
+Subject: x86,fs/resctrl: Rename struct rdt_mon_domain and rdt_hw_mon_domain
+To: stable@vger.kernel.org
+Cc: Tony Luck <tony.luck@intel.com>, "Borislav Petkov (AMD)" <bp@alien8.de>, Reinette Chatre <reinette.chatre@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727233421.2423844-2-sashal@kernel.org>
+
+From: Tony Luck <tony.luck@intel.com>
+
+[ Upstream commit 4bc3ef46ff41d5e7ba557e56e9cd2031527cd7f8 ]
+
+The upcoming telemetry event monitoring is not tied to the L3 resource and
+will have a new domain structure.
+
+Rename the L3 resource specific domain data structures to include "l3_"
+in their names to avoid confusion between the different resource specific
+domain structures:
+rdt_mon_domain         -> rdt_l3_mon_domain
+rdt_hw_mon_domain      -> rdt_hw_l3_mon_domain
+
+No functional change.
+
+Signed-off-by: Tony Luck <tony.luck@intel.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Reviewed-by: Reinette Chatre <reinette.chatre@intel.com>
+Link: https://lore.kernel.org/20251217172121.12030-1-tony.luck@intel.com
+Stable-dep-of: 52fce648607e ("fs/resctrl: Fix use-after-free during unmount")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/x86/kernel/cpu/resctrl/core.c     |   16 ++++----
+ arch/x86/kernel/cpu/resctrl/internal.h |   16 ++++----
+ arch/x86/kernel/cpu/resctrl/monitor.c  |   34 +++++++++---------
+ fs/resctrl/ctrlmondata.c               |    6 +--
+ fs/resctrl/internal.h                  |   12 +++---
+ fs/resctrl/monitor.c                   |   62 ++++++++++++++++-----------------
+ fs/resctrl/rdtgroup.c                  |   32 ++++++++---------
+ include/linux/resctrl.h                |   28 +++++++-------
+ 8 files changed, 103 insertions(+), 103 deletions(-)
+
+--- a/arch/x86/kernel/cpu/resctrl/core.c
++++ b/arch/x86/kernel/cpu/resctrl/core.c
+@@ -363,7 +363,7 @@ static void ctrl_domain_free(struct rdt_
+       kfree(hw_dom);
+ }
+-static void mon_domain_free(struct rdt_hw_mon_domain *hw_dom)
++static void mon_domain_free(struct rdt_hw_l3_mon_domain *hw_dom)
+ {
+       int idx;
+@@ -400,7 +400,7 @@ static int domain_setup_ctrlval(struct r
+  * @num_rmid: The size of the MBM counter array
+  * @hw_dom:   The domain that owns the allocated arrays
+  */
+-static int arch_domain_mbm_alloc(u32 num_rmid, struct rdt_hw_mon_domain *hw_dom)
++static int arch_domain_mbm_alloc(u32 num_rmid, struct rdt_hw_l3_mon_domain *hw_dom)
+ {
+       size_t tsize = sizeof(*hw_dom->arch_mbm_states[0]);
+       enum resctrl_event_id eventid;
+@@ -499,9 +499,9 @@ static void domain_add_cpu_mon(int cpu,
+ {
+       int id = get_domain_id_from_scope(cpu, r->mon_scope);
+       struct list_head *add_pos = NULL;
+-      struct rdt_hw_mon_domain *hw_dom;
++      struct rdt_hw_l3_mon_domain *hw_dom;
+       struct rdt_domain_hdr *hdr;
+-      struct rdt_mon_domain *d;
++      struct rdt_l3_mon_domain *d;
+       struct cacheinfo *ci;
+       int err;
+@@ -517,7 +517,7 @@ static void domain_add_cpu_mon(int cpu,
+       if (hdr) {
+               if (WARN_ON_ONCE(hdr->type != RESCTRL_MON_DOMAIN))
+                       return;
+-              d = container_of(hdr, struct rdt_mon_domain, hdr);
++              d = container_of(hdr, struct rdt_l3_mon_domain, hdr);
+               cpumask_set_cpu(cpu, &d->hdr.cpu_mask);
+               /* Update the mbm_assign_mode state for the CPU if supported */
+@@ -620,9 +620,9 @@ static void domain_remove_cpu_ctrl(int c
+ static void domain_remove_cpu_mon(int cpu, struct rdt_resource *r)
+ {
+       int id = get_domain_id_from_scope(cpu, r->mon_scope);
+-      struct rdt_hw_mon_domain *hw_dom;
++      struct rdt_hw_l3_mon_domain *hw_dom;
+       struct rdt_domain_hdr *hdr;
+-      struct rdt_mon_domain *d;
++      struct rdt_l3_mon_domain *d;
+       lockdep_assert_held(&domain_list_lock);
+@@ -642,7 +642,7 @@ static void domain_remove_cpu_mon(int cp
+       if (WARN_ON_ONCE(hdr->type != RESCTRL_MON_DOMAIN))
+               return;
+-      d = container_of(hdr, struct rdt_mon_domain, hdr);
++      d = container_of(hdr, struct rdt_l3_mon_domain, hdr);
+       hw_dom = resctrl_to_arch_mon_dom(d);
+       cpumask_clear_cpu(cpu, &d->hdr.cpu_mask);
+--- a/arch/x86/kernel/cpu/resctrl/internal.h
++++ b/arch/x86/kernel/cpu/resctrl/internal.h
+@@ -63,17 +63,17 @@ struct rdt_hw_ctrl_domain {
+ };
+ /**
+- * struct rdt_hw_mon_domain - Arch private attributes of a set of CPUs that share
+- *                          a resource for a monitor function
+- * @d_resctrl:        Properties exposed to the resctrl file system
++ * struct rdt_hw_l3_mon_domain - Arch private attributes of a set of CPUs sharing
++ *                             RDT_RESOURCE_L3 monitoring
++ * @d_resctrl:                Properties exposed to the resctrl file system
+  * @arch_mbm_states:  Per-event pointer to the MBM event's saved state.
+  *                    An MBM event's state is an array of struct arch_mbm_state
+  *                    indexed by RMID on x86.
+  *
+  * Members of this structure are accessed via helpers that provide abstraction.
+  */
+-struct rdt_hw_mon_domain {
+-      struct rdt_mon_domain           d_resctrl;
++struct rdt_hw_l3_mon_domain {
++      struct rdt_l3_mon_domain        d_resctrl;
+       struct arch_mbm_state           *arch_mbm_states[QOS_NUM_L3_MBM_EVENTS];
+ };
+@@ -82,9 +82,9 @@ static inline struct rdt_hw_ctrl_domain
+       return container_of(r, struct rdt_hw_ctrl_domain, d_resctrl);
+ }
+-static inline struct rdt_hw_mon_domain *resctrl_to_arch_mon_dom(struct rdt_mon_domain *r)
++static inline struct rdt_hw_l3_mon_domain *resctrl_to_arch_mon_dom(struct rdt_l3_mon_domain *r)
+ {
+-      return container_of(r, struct rdt_hw_mon_domain, d_resctrl);
++      return container_of(r, struct rdt_hw_l3_mon_domain, d_resctrl);
+ }
+ /**
+@@ -138,7 +138,7 @@ static inline struct rdt_hw_resource *re
+ extern struct rdt_hw_resource rdt_resources_all[];
+-void arch_mon_domain_online(struct rdt_resource *r, struct rdt_mon_domain *d);
++void arch_mon_domain_online(struct rdt_resource *r, struct rdt_l3_mon_domain *d);
+ /* CPUID.(EAX=10H, ECX=ResID=1).EAX */
+ union cpuid_0x10_1_eax {
+--- a/arch/x86/kernel/cpu/resctrl/monitor.c
++++ b/arch/x86/kernel/cpu/resctrl/monitor.c
+@@ -109,7 +109,7 @@ static inline u64 get_corrected_mbm_coun
+  *
+  * In RMID sharing mode there are fewer "logical RMID" values available
+  * to accumulate data ("physical RMIDs" are divided evenly between SNC
+- * nodes that share an L3 cache). Linux creates an rdt_mon_domain for
++ * nodes that share an L3 cache). Linux creates an rdt_l3_mon_domain for
+  * each SNC node.
+  *
+  * The value loaded into IA32_PQR_ASSOC is the "logical RMID".
+@@ -157,7 +157,7 @@ static int __rmid_read_phys(u32 prmid, e
+       return 0;
+ }
+-static struct arch_mbm_state *get_arch_mbm_state(struct rdt_hw_mon_domain *hw_dom,
++static struct arch_mbm_state *get_arch_mbm_state(struct rdt_hw_l3_mon_domain *hw_dom,
+                                                u32 rmid,
+                                                enum resctrl_event_id eventid)
+ {
+@@ -171,11 +171,11 @@ static struct arch_mbm_state *get_arch_m
+       return state ? &state[rmid] : NULL;
+ }
+-void resctrl_arch_reset_rmid(struct rdt_resource *r, struct rdt_mon_domain *d,
++void resctrl_arch_reset_rmid(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                            u32 unused, u32 rmid,
+                            enum resctrl_event_id eventid)
+ {
+-      struct rdt_hw_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
++      struct rdt_hw_l3_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
+       int cpu = cpumask_any(&d->hdr.cpu_mask);
+       struct arch_mbm_state *am;
+       u32 prmid;
+@@ -194,9 +194,9 @@ void resctrl_arch_reset_rmid(struct rdt_
+  * Assumes that hardware counters are also reset and thus that there is
+  * no need to record initial non-zero counts.
+  */
+-void resctrl_arch_reset_rmid_all(struct rdt_resource *r, struct rdt_mon_domain *d)
++void resctrl_arch_reset_rmid_all(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+-      struct rdt_hw_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
++      struct rdt_hw_l3_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
+       enum resctrl_event_id eventid;
+       int idx;
+@@ -217,10 +217,10 @@ static u64 mbm_overflow_count(u64 prev_m
+       return chunks >> shift;
+ }
+-static u64 get_corrected_val(struct rdt_resource *r, struct rdt_mon_domain *d,
++static u64 get_corrected_val(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                            u32 rmid, enum resctrl_event_id eventid, u64 msr_val)
+ {
+-      struct rdt_hw_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
++      struct rdt_hw_l3_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
+       struct rdt_hw_resource *hw_res = resctrl_to_arch_res(r);
+       struct arch_mbm_state *am;
+       u64 chunks;
+@@ -238,11 +238,11 @@ static u64 get_corrected_val(struct rdt_
+       return chunks * hw_res->mon_scale;
+ }
+-int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_mon_domain *d,
++int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                          u32 unused, u32 rmid, enum resctrl_event_id eventid,
+                          u64 *val, void *ignored)
+ {
+-      struct rdt_hw_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
++      struct rdt_hw_l3_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
+       struct arch_mbm_state *am;
+       u64 msr_val;
+       u32 prmid;
+@@ -308,11 +308,11 @@ static int __cntr_id_read(u32 cntr_id, u
+       return 0;
+ }
+-void resctrl_arch_reset_cntr(struct rdt_resource *r, struct rdt_mon_domain *d,
++void resctrl_arch_reset_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                            u32 unused, u32 rmid, int cntr_id,
+                            enum resctrl_event_id eventid)
+ {
+-      struct rdt_hw_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
++      struct rdt_hw_l3_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
+       struct arch_mbm_state *am;
+       am = get_arch_mbm_state(hw_dom, rmid, eventid);
+@@ -324,7 +324,7 @@ void resctrl_arch_reset_cntr(struct rdt_
+       }
+ }
+-int resctrl_arch_cntr_read(struct rdt_resource *r, struct rdt_mon_domain *d,
++int resctrl_arch_cntr_read(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                          u32 unused, u32 rmid, int cntr_id,
+                          enum resctrl_event_id eventid, u64 *val)
+ {
+@@ -354,7 +354,7 @@ int resctrl_arch_cntr_read(struct rdt_re
+  * must adjust RMID counter numbers based on SNC node. See
+  * logical_rmid_to_physical_rmid() for code that does this.
+  */
+-void arch_mon_domain_online(struct rdt_resource *r, struct rdt_mon_domain *d)
++void arch_mon_domain_online(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+       if (snc_nodes_per_l3_cache > 1)
+               msr_clear_bit(MSR_RMID_SNC_CONFIG, 0);
+@@ -515,7 +515,7 @@ static void resctrl_abmc_set_one_amd(voi
+  */
+ static void _resctrl_abmc_enable(struct rdt_resource *r, bool enable)
+ {
+-      struct rdt_mon_domain *d;
++      struct rdt_l3_mon_domain *d;
+       lockdep_assert_cpus_held();
+@@ -554,11 +554,11 @@ static void resctrl_abmc_config_one_amd(
+ /*
+  * Send an IPI to the domain to assign the counter to RMID, event pair.
+  */
+-void resctrl_arch_config_cntr(struct rdt_resource *r, struct rdt_mon_domain *d,
++void resctrl_arch_config_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                             enum resctrl_event_id evtid, u32 rmid, u32 closid,
+                             u32 cntr_id, bool assign)
+ {
+-      struct rdt_hw_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
++      struct rdt_hw_l3_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
+       union l3_qos_abmc_cfg abmc_cfg = { 0 };
+       struct arch_mbm_state *am;
+--- a/fs/resctrl/ctrlmondata.c
++++ b/fs/resctrl/ctrlmondata.c
+@@ -547,7 +547,7 @@ struct rdt_domain_hdr *resctrl_find_doma
+ }
+ void mon_event_read(struct rmid_read *rr, struct rdt_resource *r,
+-                  struct rdt_mon_domain *d, struct rdtgroup *rdtgrp,
++                  struct rdt_l3_mon_domain *d, struct rdtgroup *rdtgrp,
+                   cpumask_t *cpumask, int evtid, int first)
+ {
+       int cpu;
+@@ -596,9 +596,9 @@ int rdtgroup_mondata_show(struct seq_fil
+       struct kernfs_open_file *of = m->private;
+       enum resctrl_res_level resid;
+       enum resctrl_event_id evtid;
++      struct rdt_l3_mon_domain *d;
+       struct rdt_domain_hdr *hdr;
+       struct rmid_read rr = {0};
+-      struct rdt_mon_domain *d;
+       struct rdtgroup *rdtgrp;
+       int domid, cpu, ret = 0;
+       struct rdt_resource *r;
+@@ -653,7 +653,7 @@ int rdtgroup_mondata_show(struct seq_fil
+                       ret = -ENOENT;
+                       goto out;
+               }
+-              d = container_of(hdr, struct rdt_mon_domain, hdr);
++              d = container_of(hdr, struct rdt_l3_mon_domain, hdr);
+               mon_event_read(&rr, r, d, rdtgrp, &d->hdr.cpu_mask, evtid, false);
+       }
+--- a/fs/resctrl/internal.h
++++ b/fs/resctrl/internal.h
+@@ -123,7 +123,7 @@ struct mon_data {
+ struct rmid_read {
+       struct rdtgroup         *rgrp;
+       struct rdt_resource     *r;
+-      struct rdt_mon_domain   *d;
++      struct rdt_l3_mon_domain *d;
+       enum resctrl_event_id   evtid;
+       bool                    first;
+       struct cacheinfo        *ci;
+@@ -362,12 +362,12 @@ void mon_event_count(void *info);
+ int rdtgroup_mondata_show(struct seq_file *m, void *arg);
+ void mon_event_read(struct rmid_read *rr, struct rdt_resource *r,
+-                  struct rdt_mon_domain *d, struct rdtgroup *rdtgrp,
++                  struct rdt_l3_mon_domain *d, struct rdtgroup *rdtgrp,
+                   cpumask_t *cpumask, int evtid, int first);
+ int resctrl_mon_resource_init(void);
+-void mbm_setup_overflow_handler(struct rdt_mon_domain *dom,
++void mbm_setup_overflow_handler(struct rdt_l3_mon_domain *dom,
+                               unsigned long delay_ms,
+                               int exclude_cpu);
+@@ -375,14 +375,14 @@ void mbm_handle_overflow(struct work_str
+ bool is_mba_sc(struct rdt_resource *r);
+-void cqm_setup_limbo_handler(struct rdt_mon_domain *dom, unsigned long delay_ms,
++void cqm_setup_limbo_handler(struct rdt_l3_mon_domain *dom, unsigned long delay_ms,
+                            int exclude_cpu);
+ void cqm_handle_limbo(struct work_struct *work);
+-bool has_busy_rmid(struct rdt_mon_domain *d);
++bool has_busy_rmid(struct rdt_l3_mon_domain *d);
+-void __check_limbo(struct rdt_mon_domain *d, bool force_free);
++void __check_limbo(struct rdt_l3_mon_domain *d, bool force_free);
+ void resctrl_file_fflags_init(const char *config, unsigned long fflags);
+--- a/fs/resctrl/monitor.c
++++ b/fs/resctrl/monitor.c
+@@ -130,7 +130,7 @@ static void limbo_release_entry(struct r
+  * decrement the count. If the busy count gets to zero on an RMID, we
+  * free the RMID
+  */
+-void __check_limbo(struct rdt_mon_domain *d, bool force_free)
++void __check_limbo(struct rdt_l3_mon_domain *d, bool force_free)
+ {
+       struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+       u32 idx_limit = resctrl_arch_system_num_rmid_idx();
+@@ -193,7 +193,7 @@ void __check_limbo(struct rdt_mon_domain
+       resctrl_arch_mon_ctx_free(r, QOS_L3_OCCUP_EVENT_ID, arch_mon_ctx);
+ }
+-bool has_busy_rmid(struct rdt_mon_domain *d)
++bool has_busy_rmid(struct rdt_l3_mon_domain *d)
+ {
+       u32 idx_limit = resctrl_arch_system_num_rmid_idx();
+@@ -294,7 +294,7 @@ int alloc_rmid(u32 closid)
+ static void add_rmid_to_limbo(struct rmid_entry *entry)
+ {
+       struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+-      struct rdt_mon_domain *d;
++      struct rdt_l3_mon_domain *d;
+       u32 idx;
+       lockdep_assert_held(&rdtgroup_mutex);
+@@ -347,7 +347,7 @@ void free_rmid(u32 closid, u32 rmid)
+               list_add_tail(&entry->list, &rmid_free_lru);
+ }
+-static struct mbm_state *get_mbm_state(struct rdt_mon_domain *d, u32 closid,
++static struct mbm_state *get_mbm_state(struct rdt_l3_mon_domain *d, u32 closid,
+                                      u32 rmid, enum resctrl_event_id evtid)
+ {
+       u32 idx = resctrl_arch_rmid_idx_encode(closid, rmid);
+@@ -367,7 +367,7 @@ static struct mbm_state *get_mbm_state(s
+  * Return:
+  * Valid counter ID on success, or -ENOENT on failure.
+  */
+-static int mbm_cntr_get(struct rdt_resource *r, struct rdt_mon_domain *d,
++static int mbm_cntr_get(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                       struct rdtgroup *rdtgrp, enum resctrl_event_id evtid)
+ {
+       int cntr_id;
+@@ -394,7 +394,7 @@ static int mbm_cntr_get(struct rdt_resou
+  * Return:
+  * Valid counter ID on success, or -ENOSPC on failure.
+  */
+-static int mbm_cntr_alloc(struct rdt_resource *r, struct rdt_mon_domain *d,
++static int mbm_cntr_alloc(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                         struct rdtgroup *rdtgrp, enum resctrl_event_id evtid)
+ {
+       int cntr_id;
+@@ -413,7 +413,7 @@ static int mbm_cntr_alloc(struct rdt_res
+ /*
+  * mbm_cntr_free() - Clear the counter ID configuration details in the domain @d.
+  */
+-static void mbm_cntr_free(struct rdt_mon_domain *d, int cntr_id)
++static void mbm_cntr_free(struct rdt_l3_mon_domain *d, int cntr_id)
+ {
+       memset(&d->cntr_cfg[cntr_id], 0, sizeof(*d->cntr_cfg));
+ }
+@@ -423,7 +423,7 @@ static int __l3_mon_event_count(struct r
+       int cpu = smp_processor_id();
+       u32 closid = rdtgrp->closid;
+       u32 rmid = rdtgrp->mon.rmid;
+-      struct rdt_mon_domain *d;
++      struct rdt_l3_mon_domain *d;
+       int cntr_id = -ENOENT;
+       struct mbm_state *m;
+       int err, ret;
+@@ -628,7 +628,7 @@ static struct rdt_ctrl_domain *get_ctrl_
+  * throttle MSRs already have low percentage values.  To avoid
+  * unnecessarily restricting such rdtgroups, we also increase the bandwidth.
+  */
+-static void update_mba_bw(struct rdtgroup *rgrp, struct rdt_mon_domain *dom_mbm)
++static void update_mba_bw(struct rdtgroup *rgrp, struct rdt_l3_mon_domain *dom_mbm)
+ {
+       u32 closid, rmid, cur_msr_val, new_msr_val;
+       struct mbm_state *pmbm_data, *cmbm_data;
+@@ -696,7 +696,7 @@ static void update_mba_bw(struct rdtgrou
+       resctrl_arch_update_one(r_mba, dom_mba, closid, CDP_NONE, new_msr_val);
+ }
+-static void mbm_update_one_event(struct rdt_resource *r, struct rdt_mon_domain *d,
++static void mbm_update_one_event(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                                struct rdtgroup *rdtgrp, enum resctrl_event_id evtid)
+ {
+       struct rmid_read rr = {0};
+@@ -728,7 +728,7 @@ static void mbm_update_one_event(struct
+               resctrl_arch_mon_ctx_free(rr.r, rr.evtid, rr.arch_mon_ctx);
+ }
+-static void mbm_update(struct rdt_resource *r, struct rdt_mon_domain *d,
++static void mbm_update(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                      struct rdtgroup *rdtgrp)
+ {
+       /*
+@@ -749,12 +749,12 @@ static void mbm_update(struct rdt_resour
+ void cqm_handle_limbo(struct work_struct *work)
+ {
+       unsigned long delay = msecs_to_jiffies(CQM_LIMBOCHECK_INTERVAL);
+-      struct rdt_mon_domain *d;
++      struct rdt_l3_mon_domain *d;
+       cpus_read_lock();
+       mutex_lock(&rdtgroup_mutex);
+-      d = container_of(work, struct rdt_mon_domain, cqm_limbo.work);
++      d = container_of(work, struct rdt_l3_mon_domain, cqm_limbo.work);
+       __check_limbo(d, false);
+@@ -777,7 +777,7 @@ void cqm_handle_limbo(struct work_struct
+  * @exclude_cpu:   Which CPU the handler should not run on,
+  *               RESCTRL_PICK_ANY_CPU to pick any CPU.
+  */
+-void cqm_setup_limbo_handler(struct rdt_mon_domain *dom, unsigned long delay_ms,
++void cqm_setup_limbo_handler(struct rdt_l3_mon_domain *dom, unsigned long delay_ms,
+                            int exclude_cpu)
+ {
+       unsigned long delay = msecs_to_jiffies(delay_ms);
+@@ -794,7 +794,7 @@ void mbm_handle_overflow(struct work_str
+ {
+       unsigned long delay = msecs_to_jiffies(MBM_OVERFLOW_INTERVAL);
+       struct rdtgroup *prgrp, *crgrp;
+-      struct rdt_mon_domain *d;
++      struct rdt_l3_mon_domain *d;
+       struct list_head *head;
+       struct rdt_resource *r;
+@@ -809,7 +809,7 @@ void mbm_handle_overflow(struct work_str
+               goto out_unlock;
+       r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+-      d = container_of(work, struct rdt_mon_domain, mbm_over.work);
++      d = container_of(work, struct rdt_l3_mon_domain, mbm_over.work);
+       list_for_each_entry(prgrp, &rdt_all_groups, rdtgroup_list) {
+               mbm_update(r, d, prgrp);
+@@ -843,7 +843,7 @@ out_unlock:
+  * @exclude_cpu:   Which CPU the handler should not run on,
+  *               RESCTRL_PICK_ANY_CPU to pick any CPU.
+  */
+-void mbm_setup_overflow_handler(struct rdt_mon_domain *dom, unsigned long delay_ms,
++void mbm_setup_overflow_handler(struct rdt_l3_mon_domain *dom, unsigned long delay_ms,
+                               int exclude_cpu)
+ {
+       unsigned long delay = msecs_to_jiffies(delay_ms);
+@@ -1098,7 +1098,7 @@ out_unlock:
+  * mbm_cntr_free_all() - Clear all the counter ID configuration details in the
+  *                     domain @d. Called when mbm_assign_mode is changed.
+  */
+-static void mbm_cntr_free_all(struct rdt_resource *r, struct rdt_mon_domain *d)
++static void mbm_cntr_free_all(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+       memset(d->cntr_cfg, 0, sizeof(*d->cntr_cfg) * r->mon.num_mbm_cntrs);
+ }
+@@ -1107,7 +1107,7 @@ static void mbm_cntr_free_all(struct rdt
+  * resctrl_reset_rmid_all() - Reset all non-architecture states for all the
+  *                          supported RMIDs.
+  */
+-static void resctrl_reset_rmid_all(struct rdt_resource *r, struct rdt_mon_domain *d)
++static void resctrl_reset_rmid_all(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+       u32 idx_limit = resctrl_arch_system_num_rmid_idx();
+       enum resctrl_event_id evt;
+@@ -1128,7 +1128,7 @@ static void resctrl_reset_rmid_all(struc
+  * Assign the counter if @assign is true else unassign the counter. Reset the
+  * associated non-architectural state.
+  */
+-static void rdtgroup_assign_cntr(struct rdt_resource *r, struct rdt_mon_domain *d,
++static void rdtgroup_assign_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                                enum resctrl_event_id evtid, u32 rmid, u32 closid,
+                                u32 cntr_id, bool assign)
+ {
+@@ -1148,7 +1148,7 @@ static void rdtgroup_assign_cntr(struct
+  * Return:
+  * 0 on success, < 0 on failure.
+  */
+-static int rdtgroup_alloc_assign_cntr(struct rdt_resource *r, struct rdt_mon_domain *d,
++static int rdtgroup_alloc_assign_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                                     struct rdtgroup *rdtgrp, struct mon_evt *mevt)
+ {
+       int cntr_id;
+@@ -1183,7 +1183,7 @@ static int rdtgroup_alloc_assign_cntr(st
+  * Return:
+  * 0 on success, < 0 on failure.
+  */
+-static int rdtgroup_assign_cntr_event(struct rdt_mon_domain *d, struct rdtgroup *rdtgrp,
++static int rdtgroup_assign_cntr_event(struct rdt_l3_mon_domain *d, struct rdtgroup *rdtgrp,
+                                     struct mon_evt *mevt)
+ {
+       struct rdt_resource *r = resctrl_arch_get_resource(mevt->rid);
+@@ -1233,7 +1233,7 @@ void rdtgroup_assign_cntrs(struct rdtgro
+  * rdtgroup_free_unassign_cntr() - Unassign and reset the counter ID configuration
+  * for the event pointed to by @mevt within the domain @d and resctrl group @rdtgrp.
+  */
+-static void rdtgroup_free_unassign_cntr(struct rdt_resource *r, struct rdt_mon_domain *d,
++static void rdtgroup_free_unassign_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                                       struct rdtgroup *rdtgrp, struct mon_evt *mevt)
+ {
+       int cntr_id;
+@@ -1254,7 +1254,7 @@ static void rdtgroup_free_unassign_cntr(
+  * the event structure @mevt from the domain @d and the group @rdtgrp. Unassign
+  * the counters from all the domains if @d is NULL else unassign from @d.
+  */
+-static void rdtgroup_unassign_cntr_event(struct rdt_mon_domain *d, struct rdtgroup *rdtgrp,
++static void rdtgroup_unassign_cntr_event(struct rdt_l3_mon_domain *d, struct rdtgroup *rdtgrp,
+                                        struct mon_evt *mevt)
+ {
+       struct rdt_resource *r = resctrl_arch_get_resource(mevt->rid);
+@@ -1329,7 +1329,7 @@ next_config:
+ static void rdtgroup_update_cntr_event(struct rdt_resource *r, struct rdtgroup *rdtgrp,
+                                      enum resctrl_event_id evtid)
+ {
+-      struct rdt_mon_domain *d;
++      struct rdt_l3_mon_domain *d;
+       int cntr_id;
+       list_for_each_entry(d, &r->mon_domains, hdr.list) {
+@@ -1435,7 +1435,7 @@ ssize_t resctrl_mbm_assign_mode_write(st
+                                     size_t nbytes, loff_t off)
+ {
+       struct rdt_resource *r = rdt_kn_parent_priv(of->kn);
+-      struct rdt_mon_domain *d;
++      struct rdt_l3_mon_domain *d;
+       int ret = 0;
+       bool enable;
+@@ -1508,7 +1508,7 @@ int resctrl_num_mbm_cntrs_show(struct ke
+                              struct seq_file *s, void *v)
+ {
+       struct rdt_resource *r = rdt_kn_parent_priv(of->kn);
+-      struct rdt_mon_domain *dom;
++      struct rdt_l3_mon_domain *dom;
+       bool sep = false;
+       cpus_read_lock();
+@@ -1532,7 +1532,7 @@ int resctrl_available_mbm_cntrs_show(str
+                                    struct seq_file *s, void *v)
+ {
+       struct rdt_resource *r = rdt_kn_parent_priv(of->kn);
+-      struct rdt_mon_domain *dom;
++      struct rdt_l3_mon_domain *dom;
+       bool sep = false;
+       u32 cntrs, i;
+       int ret = 0;
+@@ -1573,7 +1573,7 @@ out_unlock:
+ int mbm_L3_assignments_show(struct kernfs_open_file *of, struct seq_file *s, void *v)
+ {
+       struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+-      struct rdt_mon_domain *d;
++      struct rdt_l3_mon_domain *d;
+       struct rdtgroup *rdtgrp;
+       struct mon_evt *mevt;
+       int ret = 0;
+@@ -1636,7 +1636,7 @@ static struct mon_evt *mbm_get_mon_event
+       return NULL;
+ }
+-static int rdtgroup_modify_assign_state(char *assign, struct rdt_mon_domain *d,
++static int rdtgroup_modify_assign_state(char *assign, struct rdt_l3_mon_domain *d,
+                                       struct rdtgroup *rdtgrp, struct mon_evt *mevt)
+ {
+       int ret = 0;
+@@ -1662,7 +1662,7 @@ static int rdtgroup_modify_assign_state(
+ static int resctrl_parse_mbm_assignment(struct rdt_resource *r, struct rdtgroup *rdtgrp,
+                                       char *event, char *tok)
+ {
+-      struct rdt_mon_domain *d;
++      struct rdt_l3_mon_domain *d;
+       unsigned long dom_id = 0;
+       char *dom_str, *id_str;
+       struct mon_evt *mevt;
+--- a/fs/resctrl/rdtgroup.c
++++ b/fs/resctrl/rdtgroup.c
+@@ -1620,7 +1620,7 @@ static void mondata_config_read(struct r
+ static int mbm_config_show(struct seq_file *s, struct rdt_resource *r, u32 evtid)
+ {
+       struct resctrl_mon_config_info mon_info;
+-      struct rdt_mon_domain *dom;
++      struct rdt_l3_mon_domain *dom;
+       bool sep = false;
+       cpus_read_lock();
+@@ -1668,7 +1668,7 @@ static int mbm_local_bytes_config_show(s
+ }
+ static void mbm_config_write_domain(struct rdt_resource *r,
+-                                  struct rdt_mon_domain *d, u32 evtid, u32 val)
++                                  struct rdt_l3_mon_domain *d, u32 evtid, u32 val)
+ {
+       struct resctrl_mon_config_info mon_info = {0};
+@@ -1710,7 +1710,7 @@ static int mon_config_write(struct rdt_r
+ {
+       char *dom_str = NULL, *id_str;
+       unsigned long dom_id, val;
+-      struct rdt_mon_domain *d;
++      struct rdt_l3_mon_domain *d;
+       /* Walking r->domains, ensure it can't race with cpuhp */
+       lockdep_assert_cpus_held();
+@@ -2718,7 +2718,7 @@ static int rdt_get_tree(struct fs_contex
+ {
+       struct rdt_fs_context *ctx = rdt_fc2context(fc);
+       unsigned long flags = RFTYPE_CTRL_BASE;
+-      struct rdt_mon_domain *dom;
++      struct rdt_l3_mon_domain *dom;
+       struct rdt_resource *r;
+       int ret;
+@@ -3169,7 +3169,7 @@ static void mon_rmdir_one_subdir(struct
+  * when last domain being summed is removed.
+  */
+ static void rmdir_mondata_subdir_allrdtgrp(struct rdt_resource *r,
+-                                         struct rdt_mon_domain *d)
++                                         struct rdt_l3_mon_domain *d)
+ {
+       struct rdtgroup *prgrp, *crgrp;
+       char subname[32];
+@@ -3189,7 +3189,7 @@ static void rmdir_mondata_subdir_allrdtg
+       }
+ }
+-static int mon_add_all_files(struct kernfs_node *kn, struct rdt_mon_domain *d,
++static int mon_add_all_files(struct kernfs_node *kn, struct rdt_l3_mon_domain *d,
+                            struct rdt_resource *r, struct rdtgroup *prgrp,
+                            bool do_sum)
+ {
+@@ -3218,7 +3218,7 @@ static int mon_add_all_files(struct kern
+ }
+ static int mkdir_mondata_subdir(struct kernfs_node *parent_kn,
+-                              struct rdt_mon_domain *d,
++                              struct rdt_l3_mon_domain *d,
+                               struct rdt_resource *r, struct rdtgroup *prgrp)
+ {
+       struct kernfs_node *kn, *ckn;
+@@ -3280,7 +3280,7 @@ out_destroy:
+  * and "monitor" groups with given domain id.
+  */
+ static void mkdir_mondata_subdir_allrdtgrp(struct rdt_resource *r,
+-                                         struct rdt_mon_domain *d)
++                                         struct rdt_l3_mon_domain *d)
+ {
+       struct kernfs_node *parent_kn;
+       struct rdtgroup *prgrp, *crgrp;
+@@ -3302,7 +3302,7 @@ static int mkdir_mondata_subdir_alldom(s
+                                      struct rdt_resource *r,
+                                      struct rdtgroup *prgrp)
+ {
+-      struct rdt_mon_domain *dom;
++      struct rdt_l3_mon_domain *dom;
+       int ret;
+       /* Walking r->domains, ensure it can't race with cpuhp */
+@@ -4170,7 +4170,7 @@ static void rdtgroup_setup_default(void)
+       mutex_unlock(&rdtgroup_mutex);
+ }
+-static void domain_destroy_mon_state(struct rdt_mon_domain *d)
++static void domain_destroy_mon_state(struct rdt_l3_mon_domain *d)
+ {
+       int idx;
+@@ -4192,7 +4192,7 @@ void resctrl_offline_ctrl_domain(struct
+       mutex_unlock(&rdtgroup_mutex);
+ }
+-void resctrl_offline_mon_domain(struct rdt_resource *r, struct rdt_mon_domain *d)
++void resctrl_offline_mon_domain(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+       mutex_lock(&rdtgroup_mutex);
+@@ -4236,7 +4236,7 @@ void resctrl_offline_mon_domain(struct r
+  *
+  * Returns 0 for success, or -ENOMEM.
+  */
+-static int domain_setup_mon_state(struct rdt_resource *r, struct rdt_mon_domain *d)
++static int domain_setup_mon_state(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+       u32 idx_limit = resctrl_arch_system_num_rmid_idx();
+       size_t tsize = sizeof(*d->mbm_states[0]);
+@@ -4292,7 +4292,7 @@ int resctrl_online_ctrl_domain(struct rd
+       return err;
+ }
+-int resctrl_online_mon_domain(struct rdt_resource *r, struct rdt_mon_domain *d)
++int resctrl_online_mon_domain(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+       int err;
+@@ -4344,10 +4344,10 @@ static void clear_childcpus(struct rdtgr
+       }
+ }
+-static struct rdt_mon_domain *get_mon_domain_from_cpu(int cpu,
++static struct rdt_l3_mon_domain *get_mon_domain_from_cpu(int cpu,
+                                                     struct rdt_resource *r)
+ {
+-      struct rdt_mon_domain *d;
++      struct rdt_l3_mon_domain *d;
+       lockdep_assert_cpus_held();
+@@ -4363,7 +4363,7 @@ static struct rdt_mon_domain *get_mon_do
+ void resctrl_offline_cpu(unsigned int cpu)
+ {
+       struct rdt_resource *l3 = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+-      struct rdt_mon_domain *d;
++      struct rdt_l3_mon_domain *d;
+       struct rdtgroup *rdtgrp;
+       mutex_lock(&rdtgroup_mutex);
+--- a/include/linux/resctrl.h
++++ b/include/linux/resctrl.h
+@@ -169,7 +169,7 @@ struct mbm_cntr_cfg {
+ };
+ /**
+- * struct rdt_mon_domain - group of CPUs sharing a resctrl monitor resource
++ * struct rdt_l3_mon_domain - group of CPUs sharing RDT_RESOURCE_L3 monitoring
+  * @hdr:              common header for different domain types
+  * @ci_id:            cache info id for this domain
+  * @rmid_busy_llc:    bitmap of which limbo RMIDs are above threshold
+@@ -183,7 +183,7 @@ struct mbm_cntr_cfg {
+  * @cntr_cfg:         array of assignable counters' configuration (indexed
+  *                    by counter ID)
+  */
+-struct rdt_mon_domain {
++struct rdt_l3_mon_domain {
+       struct rdt_domain_hdr           hdr;
+       unsigned int                    ci_id;
+       unsigned long                   *rmid_busy_llc;
+@@ -355,10 +355,10 @@ struct resctrl_cpu_defaults {
+ };
+ struct resctrl_mon_config_info {
+-      struct rdt_resource     *r;
+-      struct rdt_mon_domain   *d;
+-      u32                     evtid;
+-      u32                     mon_config;
++      struct rdt_resource             *r;
++      struct rdt_l3_mon_domain        *d;
++      u32                             evtid;
++      u32                             mon_config;
+ };
+ /**
+@@ -495,9 +495,9 @@ int resctrl_arch_update_one(struct rdt_r
+ u32 resctrl_arch_get_config(struct rdt_resource *r, struct rdt_ctrl_domain *d,
+                           u32 closid, enum resctrl_conf_type type);
+ int resctrl_online_ctrl_domain(struct rdt_resource *r, struct rdt_ctrl_domain *d);
+-int resctrl_online_mon_domain(struct rdt_resource *r, struct rdt_mon_domain *d);
++int resctrl_online_mon_domain(struct rdt_resource *r, struct rdt_l3_mon_domain *d);
+ void resctrl_offline_ctrl_domain(struct rdt_resource *r, struct rdt_ctrl_domain *d);
+-void resctrl_offline_mon_domain(struct rdt_resource *r, struct rdt_mon_domain *d);
++void resctrl_offline_mon_domain(struct rdt_resource *r, struct rdt_l3_mon_domain *d);
+ void resctrl_online_cpu(unsigned int cpu);
+ void resctrl_offline_cpu(unsigned int cpu);
+@@ -526,7 +526,7 @@ void resctrl_offline_cpu(unsigned int cp
+  * Return:
+  * 0 on success, or -EIO, -EINVAL etc on error.
+  */
+-int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_mon_domain *d,
++int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                          u32 closid, u32 rmid, enum resctrl_event_id eventid,
+                          u64 *val, void *arch_mon_ctx);
+@@ -573,7 +573,7 @@ struct rdt_domain_hdr *resctrl_find_doma
+  *
+  * This can be called from any CPU.
+  */
+-void resctrl_arch_reset_rmid(struct rdt_resource *r, struct rdt_mon_domain *d,
++void resctrl_arch_reset_rmid(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                            u32 closid, u32 rmid,
+                            enum resctrl_event_id eventid);
+@@ -586,7 +586,7 @@ void resctrl_arch_reset_rmid(struct rdt_
+  *
+  * This can be called from any CPU.
+  */
+-void resctrl_arch_reset_rmid_all(struct rdt_resource *r, struct rdt_mon_domain *d);
++void resctrl_arch_reset_rmid_all(struct rdt_resource *r, struct rdt_l3_mon_domain *d);
+ /**
+  * resctrl_arch_reset_all_ctrls() - Reset the control for each CLOSID to its
+@@ -612,7 +612,7 @@ void resctrl_arch_reset_all_ctrls(struct
+  *
+  * This can be called from any CPU.
+  */
+-void resctrl_arch_config_cntr(struct rdt_resource *r, struct rdt_mon_domain *d,
++void resctrl_arch_config_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                             enum resctrl_event_id evtid, u32 rmid, u32 closid,
+                             u32 cntr_id, bool assign);
+@@ -635,7 +635,7 @@ void resctrl_arch_config_cntr(struct rdt
+  * Return:
+  * 0 on success, or -EIO, -EINVAL etc on error.
+  */
+-int resctrl_arch_cntr_read(struct rdt_resource *r, struct rdt_mon_domain *d,
++int resctrl_arch_cntr_read(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                          u32 closid, u32 rmid, int cntr_id,
+                          enum resctrl_event_id eventid, u64 *val);
+@@ -650,7 +650,7 @@ int resctrl_arch_cntr_read(struct rdt_re
+  *
+  * This can be called from any CPU.
+  */
+-void resctrl_arch_reset_cntr(struct rdt_resource *r, struct rdt_mon_domain *d,
++void resctrl_arch_reset_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+                            u32 closid, u32 rmid, int cntr_id,
+                            enum resctrl_event_id eventid);
diff --git a/queue-6.18/xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch b/queue-6.18/xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch
new file mode 100644 (file)
index 0000000..3616f57
--- /dev/null
@@ -0,0 +1,429 @@
+From stable+bounces-287818-greg=kroah.com@vger.kernel.org Wed Jul 22 13:40:52 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 07:33:53 -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: <20260722113353.1151198-1-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 |  203 +++++++++++++++++++++++++++++-----------------
+ fs/xfs/scrub/trace.h      |   28 +++---
+ 2 files changed, 148 insertions(+), 83 deletions(-)
+
+--- a/fs/xfs/scrub/cow_repair.c
++++ b/fs/xfs/scrub/cow_repair.c
+@@ -80,12 +80,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.
+@@ -401,22 +395,21 @@ out_rtg:
+ 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;
+@@ -429,8 +422,8 @@ xrep_cow_alloc(
+       xfs_refcount_alloc_cow_extent(sc->tp, false, args.fsbno, args.len);
+-      repl->fsbno = args.fsbno;
+-      repl->len = args.len;
++      del->br_startblock = args.fsbno;
++      del->br_blockcount = args.len;
+       return 0;
+ }
+@@ -441,10 +434,12 @@ xrep_cow_alloc(
+ STATIC int
+ xrep_cow_alloc_rt(
+       struct xfs_scrub        *sc,
+-      xfs_extlen_t            maxlen,
+-      struct xrep_cow_extent  *repl)
++      struct xfs_bmbt_irec    *del)
+ {
+-      xfs_rtxlen_t            maxrtx = xfs_rtb_to_rtx(sc->mp, maxlen);
++      xfs_fsblock_t           fsbno;
++      xfs_rtxlen_t            maxrtx =
++              min(U32_MAX, xfs_blen_to_rtbxlen(sc->mp, del->br_blockcount));
++      xfs_extlen_t            len;
+       int                     error;
+       error = xfs_trans_reserve_more(sc->tp, 0, maxrtx);
+@@ -452,11 +447,14 @@ xrep_cow_alloc_rt(
+               return error;
+       error = xfs_rtallocate_rtgs(sc->tp, NULLRTBLOCK, 1, maxrtx, 1, false,
+-                      false, &repl->fsbno, &repl->len);
++                      false, &fsbno, &len);
+       if (error)
+               return error;
+-      xfs_refcount_alloc_cow_extent(sc->tp, true, repl->fsbno, repl->len);
++      xfs_refcount_alloc_cow_extent(sc->tp, true, fsbno, len);
++
++      del->br_startblock = fsbno;
++      del->br_blockcount = len;
+       return 0;
+ }
+@@ -470,19 +468,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))
++      if (!xfs_iext_lookup_extent(ip, ifp, badoff, icur, got))
+               goto bad;
++      memcpy(rep, got, sizeof(*rep));
+-      if (got->br_startoff > startoff)
+-              goto bad;
+-
+-      if (got->br_blockcount == 0)
++      if (got->br_startoff > badoff)
+               goto bad;
+       if (isnullstartblock(got->br_startblock))
+@@ -491,6 +489,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);
+@@ -501,46 +517,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;
++      }
+ }
+ /*
+@@ -554,33 +616,30 @@ 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);
+       if (XFS_IS_REALTIME_INODE(sc->ip))
+-              error = xrep_cow_alloc_rt(sc, alloc_len, &repl);
++              error = xrep_cow_alloc_rt(sc, &rep);
+       else
+-              error = xrep_cow_alloc(sc, alloc_len, &repl);
++              error = xrep_cow_alloc(sc, &rep);
+       if (error)
+               return error;
+@@ -588,7 +647,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);
+@@ -597,15 +656,15 @@ xrep_cow_replace_range(
+       /* Note the old CoW staging extents; we'll reap them all later. */
+       if (XFS_IS_REALTIME_INODE(sc->ip))
+-              error = xrtb_bitmap_set(&xc->old_cowfork_rtblocks,
+-                              got.br_startblock, repl.len);
++              error = xrtb_bitmap_set(&xc->old_cowfork_rtblocks, old_fsbno,
++                              rep.br_blockcount);
+       else
+-              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
+@@ -2672,9 +2672,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)
+@@ -2682,28 +2682,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,