From: Greg Kroah-Hartman Date: Thu, 30 Jul 2026 12:25:25 +0000 (+0200) Subject: 6.18-stable patches X-Git-Tag: v6.6.148~37 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=7c9cb80b4df486e75d71b962478686cd87958761;p=thirdparty%2Fkernel%2Fstable-queue.git 6.18-stable patches 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 --- 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 index 0000000000..f4fd3a1752 --- /dev/null +++ b/queue-6.18/accel-amdxdna-reject-command-submission-on-devices-without-a-submit-op.patch @@ -0,0 +1,49 @@ +From stable+bounces-289669-greg=kroah.com@vger.kernel.org Mon Jul 27 21:51:19 2026 +From: Sasha Levin +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 , Lizhi Hou , Sasha Levin +Message-ID: <20260727195059.1774971-1-sashal@kernel.org> + +From: Doruk Tan Ozturk + +[ 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 +Reviewed-by: Lizhi Hou +Signed-off-by: Lizhi Hou +Link: https://patch.msgid.link/20260713173030.87541-3-doruk@0sec.ai +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..2bbb8ad539 --- /dev/null +++ b/queue-6.18/arm64-dts-qcom-correct-rbr-opp-entry.patch @@ -0,0 +1,334 @@ +From stable+bounces-288029-greg=kroah.com@vger.kernel.org Wed Jul 22 20:00:57 2026 +From: Sasha Levin +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 , Bjorn Andersson , Sasha Levin +Message-ID: <20260722180048.2184180-1-sashal@kernel.org> + +From: Dmitry Baryshkov + +[ 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 +Link: https://lore.kernel.org/r/20260304-msm-fix-rbr-v1-1-b9eba986eaef@oss.qualcomm.com +Signed-off-by: Bjorn Andersson +Stable-dep-of: c17e22094667 ("arm64: dts: qcom: hamoa: Fix OPP tables for all DisplayPort controllers") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..6e4d8142bc --- /dev/null +++ b/queue-6.18/arm64-dts-qcom-hamoa-fix-opp-tables-for-all-displayport-controllers.patch @@ -0,0 +1,176 @@ +From stable+bounces-288030-greg=kroah.com@vger.kernel.org Wed Jul 22 20:08:13 2026 +From: Sasha Levin +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 , Dmitry Baryshkov , Konrad Dybcio , Bjorn Andersson , Sasha Levin +Message-ID: <20260722180048.2184180-2-sashal@kernel.org> + +From: Abel Vesa + +[ 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 +Reviewed-by: Konrad Dybcio +Reviewed-by: Dmitry Baryshkov +Signed-off-by: Abel Vesa +Link: https://lore.kernel.org/r/20260323-hamoa-fix-dp3-opp-table-v3-1-a823776bd1b0@oss.qualcomm.com +Signed-off-by: Bjorn Andersson +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..17dc7314f1 --- /dev/null +++ b/queue-6.18/ata-libata-core-reject-an-invalid-concurrent-positioning-ranges-count.patch @@ -0,0 +1,100 @@ +From stable+bounces-290198-greg=kroah.com@vger.kernel.org Wed Jul 29 02:30:58 2026 +From: Sasha Levin +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 , Damien Le Moal , Niklas Cassel , Sasha Levin +Message-ID: <20260729003049.888768-1-sashal@kernel.org> + +From: Bryam Vargas + +[ 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 +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 +Reviewed-by: Niklas Cassel +Signed-off-by: Damien Le Moal +[ adapted `kzalloc_flex()` allocation to `kzalloc(struct_size(...), GFP_KERNEL)` and adjusted context offsets. ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..aa65d44c33 --- /dev/null +++ b/queue-6.18/audit-fix-recursive-locking-deadlock-in-audit_dupe_exe.patch @@ -0,0 +1,364 @@ +From stable+bounces-282946-greg=kroah.com@vger.kernel.org Tue Jul 21 21:45:51 2026 +From: Sasha Levin +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 , stable@kernel.org, Waiman Long , Richard Guy Briggs , Nathan Chancellor , Paul Moore , Sasha Levin +Message-ID: <20260721193613.410665-3-sashal@kernel.org> + +From: Ricardo Robaina + +[ 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: + + 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 + + +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 +Acked-by: Richard Guy Briggs +Signed-off-by: Nathan Chancellor +Signed-off-by: Ricardo Robaina +[PM: move link metadata into the msg, apply fix from NC] +Signed-off-by: Paul Moore +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..3ba149984b --- /dev/null +++ b/queue-6.18/audit-use-unsigned-int-instead-of-unsigned.patch @@ -0,0 +1,202 @@ +From stable+bounces-282945-greg=kroah.com@vger.kernel.org Tue Jul 21 23:14:02 2026 +From: Sasha Levin +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 , Paul Moore , Sasha Levin +Message-ID: <20260721193613.410665-2-sashal@kernel.org> + +From: Ricardo Robaina + +[ 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 +Signed-off-by: Paul Moore +Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + +-unsigned compat_dir_class[] = { ++unsigned int compat_dir_class[] = { + #include + ~0U + }; + +-unsigned compat_read_class[] = { ++unsigned int compat_read_class[] = { + #include + ~0U + }; + +-unsigned compat_write_class[] = { ++unsigned int compat_write_class[] = { + #include + ~0U + }; + +-unsigned compat_chattr_class[] = { ++unsigned int compat_chattr_class[] = { + #include + ~0U + }; + +-unsigned compat_signal_class[] = { ++unsigned int compat_signal_class[] = { + #include + ~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 index 0000000000..53c94754ff --- /dev/null +++ b/queue-6.18/audit-widen-ino-fields-to-u64.patch @@ -0,0 +1,187 @@ +From stable+bounces-282943-greg=kroah.com@vger.kernel.org Tue Jul 21 21:45:45 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 15:36:11 -0400 +Subject: audit: widen ino fields to u64 +To: stable@vger.kernel.org +Cc: Jeff Layton , Paul Moore , Christian Brauner , Sasha Levin +Message-ID: <20260721193613.410665-1-sashal@kernel.org> + +From: Jeff Layton + +[ 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 +Link: https://patch.msgid.link/20260304-iino-u64-v3-2-2257ad83d372@kernel.org +Acked-by: Paul Moore +Signed-off-by: Christian Brauner +Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + +-#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 index 0000000000..bd2b60a880 --- /dev/null +++ b/queue-6.18/bootconfig-fix-null-pointer-arithmetic-in-xbc_snprint_cmdline.patch @@ -0,0 +1,97 @@ +From stable+bounces-290081-greg=kroah.com@vger.kernel.org Tue Jul 28 19:58:29 2026 +From: Sasha Levin +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 , "Masami Hiramatsu (Google)" , Sasha Levin +Message-ID: <20260728162946.99526-2-sashal@kernel.org> + +From: Breno Leitao + +[ 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 +Signed-off-by: Masami Hiramatsu (Google) +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..e68a965a13 --- /dev/null +++ b/queue-6.18/bootconfig-move-xbc_snprint_cmdline-to-lib-bootconfig.c.patch @@ -0,0 +1,165 @@ +From stable+bounces-290080-greg=kroah.com@vger.kernel.org Tue Jul 28 19:24:02 2026 +From: Sasha Levin +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 , "Masami Hiramatsu (Google)" , Sasha Levin +Message-ID: <20260728162946.99526-1-sashal@kernel.org> + +From: Breno Leitao + +[ 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 +Signed-off-by: Masami Hiramatsu (Google) +Stable-dep-of: dec4d8118c17 ("bootconfig: fix NULL-pointer arithmetic in xbc_snprint_cmdline()") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..21d65cd1d7 --- /dev/null +++ b/queue-6.18/cred-add-kernel_cred-helper.patch @@ -0,0 +1,48 @@ +From stable+bounces-289686-greg=kroah.com@vger.kernel.org Mon Jul 27 23:04:43 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 16:59:56 -0400 +Subject: cred: add kernel_cred() helper +To: stable@vger.kernel.org +Cc: Christian Brauner , Jens Axboe , Sasha Levin +Message-ID: <20260727205958.1929027-1-sashal@kernel.org> + +From: Christian Brauner + +[ 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 +Signed-off-by: Christian Brauner +Stable-dep-of: 981ccd97f715 ("dm: avoid leaking the caller's thread keyring via the table device file") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..17762e53f2 --- /dev/null +++ b/queue-6.18/cxl-fix-cxl_headerlog_size-to-match-ras-capability-size.patch @@ -0,0 +1,219 @@ +From stable+bounces-288498-greg=kroah.com@vger.kernel.org Thu Jul 23 20:29:18 2026 +From: Sasha Levin +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 , Alison Schofield , Dave Jiang , Ben Cheatham , Richard Cheng , Sasha Levin +Message-ID: <20260723182806.3493205-4-sashal@kernel.org> + +From: Terry Bowman + +[ 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 +Reviewed-by: Alison Schofield +Reviewed-by: Dave Jiang +Reviewed-by: Ben Cheatham +Reviewed-by: Richard Cheng +Link: https://patch.msgid.link/20260605180610.2249458-1-terry.bowman@amd.com +Signed-off-by: Dave Jiang +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #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 index 0000000000..b897b3033d --- /dev/null +++ b/queue-6.18/cxl-pci-remove-cxl-vh-handling-in-config_pcieaer_cxl-conditional-blocks-from-core-pci.c.patch @@ -0,0 +1,585 @@ +From stable+bounces-288499-greg=kroah.com@vger.kernel.org Thu Jul 23 20:35:30 2026 +From: Sasha Levin +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 , Robert Richter , Joshua Hahn , Jonathan Cameron , Alison Schofield , Terry Bowman , Dan Williams , Sasha Levin +Message-ID: <20260723182806.3493205-3-sashal@kernel.org> + +From: Dave Jiang + +[ 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 +Reviewed-by: Joshua Hahn +Reviewed-by: Jonathan Cameron +Signed-off-by: Dave Jiang +Reviewed-by: Alison Schofield +Co-developed-by: Terry Bowman +Signed-off-by: Terry Bowman +Reviewed-by: Dan Williams +Link: https://patch.msgid.link/20260114182055.46029-8-terry.bowman@amd.com +Signed-off-by: Dan Williams +Signed-off-by: Dave Jiang +Stable-dep-of: c268f949e219 ("cxl: Fix CXL_HEADERLOG_SIZE to match RAS Capability size") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + #include ++#include + #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 index 0000000000..30cbb1c260 --- /dev/null +++ b/queue-6.18/cxl-pci-remove-unnecessary-cxl-endpoint-handling-helper-functions.patch @@ -0,0 +1,118 @@ +From stable+bounces-288496-greg=kroah.com@vger.kernel.org Thu Jul 23 20:35:18 2026 +From: Sasha Levin +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 , Kuppuswamy Sathyanarayanan , Jonathan Cameron , Dave Jiang , Joshua Hahn , Dan Williams , Sasha Levin +Message-ID: <20260723182806.3493205-1-sashal@kernel.org> + +From: Terry Bowman + +[ 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 +Reviewed-by: Kuppuswamy Sathyanarayanan +Reviewed-by: Jonathan Cameron +Reviewed-by: Dave Jiang +Tested-by: Joshua Hahn +Reviewed-by: Dan Williams +Link: https://patch.msgid.link/20260114182055.46029-5-terry.bowman@amd.com +Signed-off-by: Dan Williams +Signed-off-by: Dave Jiang +Stable-dep-of: c268f949e219 ("cxl: Fix CXL_HEADERLOG_SIZE to match RAS Capability size") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..968ecd1edf --- /dev/null +++ b/queue-6.18/cxl-pci-remove-unnecessary-cxl-rch-handling-helper-functions.patch @@ -0,0 +1,65 @@ +From stable+bounces-288497-greg=kroah.com@vger.kernel.org Thu Jul 23 20:30:03 2026 +From: Sasha Levin +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 , Alejandro Lucero , Dave Jiang , Jonathan Cameron , Dan Williams , Sasha Levin +Message-ID: <20260723182806.3493205-2-sashal@kernel.org> + +From: Terry Bowman + +[ 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 +Reviewed-by: Alejandro Lucero +Reviewed-by: Dave Jiang +Reviewed-by: Jonathan Cameron +Reviewed-by: Dan Williams +Link: https://patch.msgid.link/20260114182055.46029-6-terry.bowman@amd.com +Signed-off-by: Dan Williams +Signed-off-by: Dave Jiang +Stable-dep-of: c268f949e219 ("cxl: Fix CXL_HEADERLOG_SIZE to match RAS Capability size") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..6fdbaf2a73 --- /dev/null +++ b/queue-6.18/dm-avoid-leaking-the-caller-s-thread-keyring-via-the-table-device-file.patch @@ -0,0 +1,72 @@ +From stable+bounces-289688-greg=kroah.com@vger.kernel.org Mon Jul 27 23:00:30 2026 +From: Sasha Levin +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 , Mikulas Patocka , Ondrej Kozina , Sasha Levin +Message-ID: <20260727205958.1929027-3-sashal@kernel.org> + +From: Ingo Blechschmidt + +[ 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 +Signed-off-by: Mikulas Patocka +Cc: stable@vger.kernel.org +Tested-by: Ondrej Kozina +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..a8aa89acf2 --- /dev/null +++ b/queue-6.18/dmaengine-dw-edma-fix-confusing-cleanup.h-syntax.patch @@ -0,0 +1,55 @@ +From stable+bounces-289272-greg=kroah.com@vger.kernel.org Sun Jul 26 14:47:53 2026 +From: Sasha Levin +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 , Manivannan Sadhasivam , Vinod Koul , Sasha Levin +Message-ID: <20260726124741.149009-1-sashal@kernel.org> + +From: Krzysztof Kozlowski + +[ 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 +Reviewed-by: Manivannan Sadhasivam +Link: https://patch.msgid.link/20251208020729.4654-2-krzysztof.kozlowski@oss.qualcomm.com +Signed-off-by: Vinod Koul +Stable-dep-of: 11d7cfe0c119 ("dmaengine: dw-edma-pcie: Reject devices without driver data") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..006621684e --- /dev/null +++ b/queue-6.18/dmaengine-dw-edma-pcie-reject-devices-without-driver-data.patch @@ -0,0 +1,43 @@ +From stable+bounces-289273-greg=kroah.com@vger.kernel.org Sun Jul 26 14:47:47 2026 +From: Sasha Levin +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 , Frank Li , Vinod Koul , Sasha Levin +Message-ID: <20260726124741.149009-2-sashal@kernel.org> + +From: Koichiro Den + +[ 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 +Reviewed-by: Frank Li +Link: https://patch.msgid.link/20260521142153.2957432-3-den@valinux.co.jp +Signed-off-by: Vinod Koul +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..021bc35ba6 --- /dev/null +++ b/queue-6.18/fs-resctrl-fix-use-after-free-during-unmount.patch @@ -0,0 +1,134 @@ +From stable+bounces-289738-greg=kroah.com@vger.kernel.org Tue Jul 28 01:36:18 2026 +From: Sasha Levin +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 , Sashiko , Reinette Chatre , "Borislav Petkov (AMD)" , Chen Yu , stable@kernel.org, Sasha Levin +Message-ID: <20260727233421.2423844-6-sashal@kernel.org> + +From: Tony Luck + +[ 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 +Signed-off-by: Tony Luck +Signed-off-by: Reinette Chatre +Signed-off-by: Borislav Petkov (AMD) +Reviewed-by: Chen Yu +Cc: +Link: https://patch.msgid.link/49a2ca3ca688f27e1a646cf90e1dc69287021127.1783377598.git.reinette.chatre@intel.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..c125e2b4e5 --- /dev/null +++ b/queue-6.18/fs-resctrl-move-allocation-free-of-closid_num_dirty_rmid.patch @@ -0,0 +1,162 @@ +From stable+bounces-289736-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:44 2026 +From: Sasha Levin +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 , "Borislav Petkov (AMD)" , Reinette Chatre , Sasha Levin +Message-ID: <20260727233421.2423844-4-sashal@kernel.org> + +From: Tony Luck + +[ 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 +Signed-off-by: Borislav Petkov (AMD) +Reviewed-by: Reinette Chatre +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 +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..9578632952 --- /dev/null +++ b/queue-6.18/fs-resctrl-move-rmid-initialization-to-first-mount.patch @@ -0,0 +1,181 @@ +From stable+bounces-289737-greg=kroah.com@vger.kernel.org Tue Jul 28 01:45:50 2026 +From: Sasha Levin +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 , "Borislav Petkov (AMD)" , Reinette Chatre , Sasha Levin +Message-ID: <20260727233421.2423844-5-sashal@kernel.org> + +From: Tony Luck + +[ 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 +Signed-off-by: Borislav Petkov (AMD) +Reviewed-by: Reinette Chatre +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 +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..6f53e8f029 --- /dev/null +++ b/queue-6.18/fs-resctrl-split-l3-dependent-parts-out-of-__mon_event_count.patch @@ -0,0 +1,56 @@ +From stable+bounces-289733-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:28 2026 +From: Sasha Levin +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 , Reinette Chatre , "Borislav Petkov (AMD)" , Sasha Levin +Message-ID: <20260727233421.2423844-1-sashal@kernel.org> + +From: Tony Luck + +[ 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 +Signed-off-by: Tony Luck +Signed-off-by: Borislav Petkov (AMD) +Reviewed-by: Reinette Chatre +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 +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..0e5ac8a706 --- /dev/null +++ b/queue-6.18/fuse-uring-fix-race-between-registration-and-connection-abortion.patch @@ -0,0 +1,100 @@ +From stable+bounces-287817-greg=kroah.com@vger.kernel.org Wed Jul 22 13:41:16 2026 +From: Sasha Levin +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 , Bernd Schubert , Miklos Szeredi , Sasha Levin +Message-ID: <20260722113350.1151043-1-sashal@kernel.org> + +From: Joanne Koong + +[ 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 +Signed-off-by: Joanne Koong +Signed-off-by: Miklos Szeredi +[ 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 +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..812084db9c --- /dev/null +++ b/queue-6.18/gpu-buddy-bail-out-of-try_harder-when-alignment-cannot-be-honoured.patch @@ -0,0 +1,140 @@ +From stable+bounces-288353-greg=kroah.com@vger.kernel.org Thu Jul 23 16:29:15 2026 +From: Sasha Levin +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" , "Christian König" , "Matthew Auld" , "Timur Kristóf" , "John Olender" , "Sasha Levin" +Message-ID: <20260723141931.3218899-2-sashal@kernel.org> + +From: Arunpravin Paneer Selvam + +[ 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 +Cc: Matthew Auld +Cc: Christian König +Cc: Timur Kristóf +Cc: stable@vger.kernel.org +Reviewed-by: Matthew Auld +Tested-by: John Olender +Signed-off-by: Arunpravin Paneer Selvam +Link: https://patch.msgid.link/20260709131050.1022759-1-Arunpravin.PaneerSelvam@amd.com +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..cda0733438 --- /dev/null +++ b/queue-6.18/gpu-move-drm-buddy-allocator-one-level-up-part-two.patch @@ -0,0 +1,6199 @@ +From stable+bounces-288354-greg=kroah.com@vger.kernel.org Thu Jul 23 16:29:16 2026 +From: Sasha Levin +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 , Dave Airlie , Sasha Levin +Message-ID: <20260723141931.3218899-1-sashal@kernel.org> + +From: Joel Fernandes + +[ 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 +Reviewed-by: Dave Airlie +[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 +Stable-dep-of: 56bc6384314f ("gpu/buddy: bail out of try_harder when alignment cannot be honoured") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 ++M: Arun Pravin ++R: Christian Koenig ++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 + M: Vignesh Raman +--- /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 ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++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 + #include + #include ++#include + + #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 ++#include + + 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 + #include + ++#include + #include +- +-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_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 + ++#include ++#include + #include + #include +-#include + + #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 ++#include + #include + + #include +@@ -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 + ++#include ++#include ++#include + #include + #include + +-#include +- + #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 + #include + +-#include ++#include + + #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 +-#include +-#include +-#include +-#include +-#include +- +-#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 +- +-#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 +- */ +- +-#include +- +-#include +-#include +-#include +- +-#include +- +-#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 + #include + +-#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 + +-#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 ++#include + + 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 + #include ++#include + + #include + #include +@@ -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 ++#include + #include + + /** +@@ -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 ++ */ ++ ++#include ++ ++#include ++#include ++#include ++ ++#include ++ ++#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 ++#include ++#include ++#include ++#include ++#include ++ ++#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 ++ ++#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 +-#include +-#include +-#include +-#include ++#include + +-#include ++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 ++#include ++#include ++#include ++#include ++ ++#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 index 0000000000..47ea11aa31 --- /dev/null +++ b/queue-6.18/i3c-mipi-i3c-hci-fix-handling-of-shared-irqs-during-early-initialization.patch @@ -0,0 +1,85 @@ +From stable+bounces-290908-greg=kroah.com@vger.kernel.org Wed Jul 29 23:24:09 2026 +From: Sasha Levin +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 , Frank Li , Alexandre Belloni , Sasha Levin +Message-ID: <20260729212351.1210709-2-sashal@kernel.org> + +From: Adrian Hunter + +[ 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 +Reviewed-by: Frank Li +Link: https://patch.msgid.link/20260306072451.11131-14-adrian.hunter@intel.com +Signed-off-by: Alexandre Belloni +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..6e9b236ec5 --- /dev/null +++ b/queue-6.18/i3c-mipi-i3c-hci-fix-hot-join-nack.patch @@ -0,0 +1,42 @@ +From stable+bounces-290907-greg=kroah.com@vger.kernel.org Wed Jul 29 23:24:07 2026 +From: Sasha Levin +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 , Frank Li , Alexandre Belloni , Sasha Levin +Message-ID: <20260729212351.1210709-1-sashal@kernel.org> + +From: Adrian Hunter + +[ 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 +Reviewed-by: Frank Li +Link: https://patch.msgid.link/20260306072451.11131-3-adrian.hunter@intel.com +Signed-off-by: Alexandre Belloni +Stable-dep-of: c6396b835a5e ("i3c: mipi-i3c-hci: Fix handling of shared IRQs during early initialization") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..48f7032cd4 --- /dev/null +++ b/queue-6.18/kho-add-interfaces-to-unpreserve-folios-page-ranges-and-vmalloc.patch @@ -0,0 +1,227 @@ +From stable+bounces-289085-greg=kroah.com@vger.kernel.org Sat Jul 25 12:54:43 2026 +From: Sasha Levin +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 , Pratyush Yadav , "Mike Rapoport (Microsoft)" , Alexander Graf , Changyuan Lyu , Christian Brauner , Jason Gunthorpe , Jason Gunthorpe , Jonathan Corbet , Masahiro Yamada , Miguel Ojeda , Randy Dunlap , Simon Horman , Tejun Heo , Zhu Yanjun , Andrew Morton , Sasha Levin +Message-ID: <20260725105433.1853930-1-sashal@kernel.org> + +From: Pasha Tatashin + +[ 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 +Reviewed-by: Pratyush Yadav +Reviewed-by: Mike Rapoport (Microsoft) +Cc: Alexander Graf +Cc: Changyuan Lyu +Cc: Christian Brauner +Cc: Jason Gunthorpe +Cc: Jason Gunthorpe +Cc: Jonathan Corbet +Cc: Masahiro Yamada +Cc: Miguel Ojeda +Cc: Randy Dunlap +Cc: Simon Horman +Cc: Tejun Heo +Cc: Zhu Yanjun +Signed-off-by: Andrew Morton +Stable-dep-of: 0e39380a7316 ("kho: make sure scratch size is always aligned by CMA_MIN_ALIGNMENT_BYTES") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..8673de8891 --- /dev/null +++ b/queue-6.18/kho-make-sure-scratch-size-is-always-aligned-by-cma_min_alignment_bytes.patch @@ -0,0 +1,76 @@ +From stable+bounces-289086-greg=kroah.com@vger.kernel.org Sat Jul 25 12:54:48 2026 +From: Sasha Levin +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)" , stable@kernel.org, Pasha Tatashin , "Mike Rapoport (Microsoft)" , Sasha Levin +Message-ID: <20260725105433.1853930-2-sashal@kernel.org> + +From: "Pratyush Yadav (Google)" + +[ 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) +Link: https://patch.msgid.link/20260519160554.2713361-1-pratyush@kernel.org +Signed-off-by: Pasha Tatashin +Signed-off-by: Mike Rapoport (Microsoft) +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..6e3fd33783 --- /dev/null +++ b/queue-6.18/landlock-account-all-audit-data-allocations-to-user-space.patch @@ -0,0 +1,80 @@ +From stable+bounces-282936-greg=kroah.com@vger.kernel.org Tue Jul 21 21:57:23 2026 +From: Sasha Levin +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" , "Günther Noack" , "Paul Moore" , "Sasha Levin" +Message-ID: <20260721193601.410261-2-sashal@kernel.org> + +From: Mickaël Salaün + +[ 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 +Cc: Paul Moore +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 +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..98eb6d1414 --- /dev/null +++ b/queue-6.18/landlock-fix-formatting.patch @@ -0,0 +1,63 @@ +From stable+bounces-282935-greg=kroah.com@vger.kernel.org Tue Jul 21 21:45:34 2026 +From: Sasha Levin +Date: Tue, 21 Jul 2026 15:36:00 -0400 +Subject: landlock: Fix formatting +To: stable@vger.kernel.org +Cc: "Mickaël Salaün" , "Günther Noack" , "Kees Cook" , "Günther Noack" , "Sasha Levin" +Message-ID: <20260721193601.410261-1-sashal@kernel.org> + +From: Mickaël Salaün + +[ Upstream commit 405ca72dc589dd746e5ee5378bb9d9ee7f844010 ] + +Auto-format with clang-format -i security/landlock/*.[ch] + +Cc: Günther Noack +Cc: Kees Cook +Fixes: 69050f8d6d07 ("treewide: Replace kmalloc with kmalloc_obj for non-scalar types") +Reviewed-by: Günther Noack +Link: https://lore.kernel.org/r/20260303173632.88040-1-mic@digikod.net +Signed-off-by: Mickaël Salaün +Stable-dep-of: b232bd12789f ("landlock: Account all audit data allocations to user space") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..77100e26b2 --- /dev/null +++ b/queue-6.18/mm-sparse-vmemmap-fix-vmemmap-accounting-underflow.patch @@ -0,0 +1,171 @@ +From stable+bounces-288838-greg=kroah.com@vger.kernel.org Fri Jul 24 12:59:27 2026 +From: Sasha Levin +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 , "Mike Rapoport (Microsoft)" , Oscar Salvador , "David Hildenbrand (Arm)" , "Liam R. Howlett" , "Aneesh Kumar K.V" , Joao Martins , Lorenzo Stoakes , Madhavan Srinivasan , Michael Ellerman , Michal Hocko , Nicholas Piggin , Suren Baghdasaryan , Vlastimil Babka , Andrew Morton , Sasha Levin +Message-ID: <20260724104937.3975387-1-sashal@kernel.org> + +From: Muchun Song + +[ 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 +Acked-by: Mike Rapoport (Microsoft) +Acked-by: Oscar Salvador +Acked-by: David Hildenbrand (Arm) +Acked-by: Liam R. Howlett +Cc: "Aneesh Kumar K.V" +Cc: Joao Martins +Cc: Lorenzo Stoakes +Cc: Madhavan Srinivasan +Cc: Michael Ellerman +Cc: Michal Hocko +Cc: Nicholas Piggin +Cc: Suren Baghdasaryan +Cc: Vlastimil Babka +Cc: +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..824a10b8c3 --- /dev/null +++ b/queue-6.18/mmc-vub300-fix-use-after-free-on-probe-failure.patch @@ -0,0 +1,89 @@ +From stable+bounces-289714-greg=kroah.com@vger.kernel.org Tue Jul 28 00:24:26 2026 +From: Sasha Levin +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 , Johan Hovold , Ulf Hansson , Sasha Levin +Message-ID: <20260727222045.2118748-2-sashal@kernel.org> + +From: Guangshuo Li + +[ 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 +Reviewed-by: Johan Hovold +Cc: stable@vger.kernel.org +Signed-off-by: Ulf Hansson +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..3de44bbcc0 --- /dev/null +++ b/queue-6.18/mmc-vub300-rename-probe-error-labels.patch @@ -0,0 +1,81 @@ +From stable+bounces-289713-greg=kroah.com@vger.kernel.org Tue Jul 28 00:24:18 2026 +From: Sasha Levin +Date: Mon, 27 Jul 2026 18:20:44 -0400 +Subject: mmc: vub300: rename probe error labels +To: stable@vger.kernel.org +Cc: Johan Hovold , Ulf Hansson , Sasha Levin +Message-ID: <20260727222045.2118748-1-sashal@kernel.org> + +From: Johan Hovold + +[ Upstream commit 5b8b35d6f4fa758dd5e8ae18526ea1c73f6787e0 ] + +Error labels should be named after what they do. + +Rename the probe error labels. + +Signed-off-by: Johan Hovold +Signed-off-by: Ulf Hansson +Stable-dep-of: a3b5f242997a ("mmc: vub300: fix use-after-free on probe failure") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..11339fe4cc --- /dev/null +++ b/queue-6.18/mtd-maps-vmu-flash-fix-fault-in-unaligned-fixup.patch @@ -0,0 +1,58 @@ +From stable+bounces-289087-greg=kroah.com@vger.kernel.org Sat Jul 25 12:54:49 2026 +From: Sasha Levin +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 , Miquel Raynal , Sasha Levin +Message-ID: <20260725105440.1854110-1-sashal@kernel.org> + +From: Florian Fuchs + +[ 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 +Signed-off-by: Miquel Raynal +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..964ef32bdd --- /dev/null +++ b/queue-6.18/net-ipa-fix-smem-state-handle-leaks-in-smp2p-init.patch @@ -0,0 +1,91 @@ +From stable+bounces-290230-greg=kroah.com@vger.kernel.org Wed Jul 29 04:32:43 2026 +From: Sasha Levin +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 , Larysa Zaremba , Alex Elder , Jakub Kicinski , Sasha Levin +Message-ID: <20260729023025.1114440-1-sashal@kernel.org> + +From: Haoxiang Li + +[ 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 +Reviewed-by: Larysa Zaremba +Reviewed-by: Alex Elder +Link: https://patch.msgid.link/20260624065955.2822765-1-haoxiang_li2024@163.com +Signed-off-by: Jakub Kicinski +[ 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 +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..876a2d65a0 --- /dev/null +++ b/queue-6.18/net-mana-optimize-irq-affinity-for-low-vcpu-configs.patch @@ -0,0 +1,253 @@ +From stable+bounces-289857-greg=kroah.com@vger.kernel.org Tue Jul 28 06:07:13 2026 +From: Sasha Levin +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 , Erni Sri Satya Vennela , Haiyang Zhang , Simon Horman , Yury Norov , Jakub Kicinski , Sasha Levin +Message-ID: <20260728035714.3173126-1-sashal@kernel.org> + +From: Shradha Gupta + +[ 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 +Signed-off-by: Erni Sri Satya Vennela +Signed-off-by: Shradha Gupta +Reviewed-by: Haiyang Zhang +Reviewed-by: Simon Horman +Reviewed-by: Yury Norov +Link: https://patch.msgid.link/20260624072138.1632849-1-shradhagupta@linux.microsoft.com +Signed-off-by: Jakub Kicinski +[ 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 +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..845dced5c7 --- /dev/null +++ b/queue-6.18/net-mana-validate-the-packet-length-reported-by-the-nic.patch @@ -0,0 +1,50 @@ +From stable+bounces-289815-greg=kroah.com@vger.kernel.org Tue Jul 28 04:51:58 2026 +From: Sasha Levin +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 , Haiyang Zhang , Paolo Abeni , Sasha Levin +Message-ID: <20260728024645.3059155-1-sashal@kernel.org> + +From: Dexuan Cui + +[ 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 +Signed-off-by: Dexuan Cui +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 +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..81b87d3a42 --- /dev/null +++ b/queue-6.18/netfilter-nf_conntrack_sip-remove-net-variable-shadowing.patch @@ -0,0 +1,39 @@ +From stable+bounces-288335-greg=kroah.com@vger.kernel.org Thu Jul 23 16:28:01 2026 +From: Sasha Levin +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 , Sasha Levin +Message-ID: <20260723141840.3216559-1-sashal@kernel.org> + +From: Florian Westphal + +[ 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 +Stable-dep-of: e5e24a365a5e ("netfilter: nf_conntrack_sip: validate skb_dst() before accessing it") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..f3bd402640 --- /dev/null +++ b/queue-6.18/netfilter-nf_conntrack_sip-validate-skb_dst-before-accessing-it.patch @@ -0,0 +1,56 @@ +From stable+bounces-288336-greg=kroah.com@vger.kernel.org Thu Jul 23 16:20:14 2026 +From: Sasha Levin +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 , Ren Wei , Florian Westphal , Sasha Levin +Message-ID: <20260723141840.3216559-2-sashal@kernel.org> + +From: Pablo Neira Ayuso + +[ 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 +Signed-off-by: Pablo Neira Ayuso +Signed-off-by: Florian Westphal +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..014f13461f --- /dev/null +++ b/queue-6.18/netfilter-nf_tables-remove-register-tracking-infrastructure.patch @@ -0,0 +1,1646 @@ +From stable+bounces-288333-greg=kroah.com@vger.kernel.org Thu Jul 23 16:30:53 2026 +From: Sasha Levin +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 , Jakub Kicinski , Sasha Levin +Message-ID: <20260723141835.3216078-1-sashal@kernel.org> + +From: Florian Westphal + +[ 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 +Link: https://patch.msgid.link/20260224205048.4718-10-fw@strlen.de +Signed-off-by: Jakub Kicinski +Stable-dep-of: d07955dd34ec ("netfilter: nft_fib: reject fib expression on the netdev egress hook") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 "); +--- 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 index 0000000000..5c3a729d5b --- /dev/null +++ b/queue-6.18/netfilter-nft_fib-reject-fib-expression-on-the-netdev-egress-hook.patch @@ -0,0 +1,100 @@ +From stable+bounces-288332-greg=kroah.com@vger.kernel.org Thu Jul 23 16:30:50 2026 +From: Sasha Levin +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 , Florian Westphal , Sasha Levin +Message-ID: <20260723141835.3216078-2-sashal@kernel.org> + +From: Theodor Arsenij Larionov-Trichkine + +[ 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 +Signed-off-by: Florian Westphal +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..2a2cae15ea --- /dev/null +++ b/queue-6.18/nfsd-pass-nfsd_file-to-nfsd_iter_read.patch @@ -0,0 +1,116 @@ +From stable+bounces-288492-greg=kroah.com@vger.kernel.org Thu Jul 23 20:27:08 2026 +From: Sasha Levin +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 , Jeff Layton , NeilBrown , Christoph Hellwig , Chuck Lever , Sasha Levin +Message-ID: <20260723181911.3438372-1-sashal@kernel.org> + +From: Mike Snitzer + +[ 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 +Reviewed-by: Jeff Layton +Reviewed-by: NeilBrown +Reviewed-by: Christoph Hellwig +Signed-off-by: Chuck Lever +Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..bfa6249e38 --- /dev/null +++ b/queue-6.18/octeontx2-af-cn10k-restrict-vf-lmtline-sharing-to-its-own-pf.patch @@ -0,0 +1,54 @@ +From stable+bounces-289995-greg=kroah.com@vger.kernel.org Tue Jul 28 15:39:10 2026 +From: Sasha Levin +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 , Yuhao Jiang , Jakub Kicinski , Sasha Levin +Message-ID: <20260728133037.3965153-2-sashal@kernel.org> + +From: Junrui Luo + +[ 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 +Cc: stable@vger.kernel.org +Signed-off-by: Junrui Luo +Link: https://patch.msgid.link/SYBPR01MB78811656934E713B77DA6CEDAFE62@SYBPR01MB7881.ausprd01.prod.outlook.com +Signed-off-by: Jakub Kicinski +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..87d493dc5b --- /dev/null +++ b/queue-6.18/octeontx2-af-validate-body-pcifunc-in-rvu_mbox_handler_rep_event_notify.patch @@ -0,0 +1,91 @@ +From stable+bounces-289996-greg=kroah.com@vger.kernel.org Tue Jul 28 15:39:12 2026 +From: Sasha Levin +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 , Jakub Kicinski , Sasha Levin +Message-ID: <20260728133037.3965153-1-sashal@kernel.org> + +From: Michael Bommarito + +[ 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 +Link: https://patch.msgid.link/20260520154157.1439319-1-michael.bommarito@gmail.com +Signed-off-by: Jakub Kicinski +Stable-dep-of: 8cdcf3d2caac ("octeontx2-af: cn10k: restrict VF LMTLINE sharing to its own PF") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..8c948b27fb --- /dev/null +++ b/queue-6.18/ovl-use-linked-upper-dentry-in-copy-up-tmpfile.patch @@ -0,0 +1,105 @@ +From stable+bounces-289547-greg=kroah.com@vger.kernel.org Mon Jul 27 15:50:18 2026 +From: Sasha Levin +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 , Amir Goldstein , Miklos Szeredi , "Christian Brauner (Amutable)" , Sasha Levin +Message-ID: <20260727133139.539439-1-sashal@kernel.org> + +From: Souvik Banerjee + +[ 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 "#" 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 + - 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 +Link: https://patch.msgid.link/20260501232735.2610824-1-souvik@amlalabs.com +Reviewed-by: Amir Goldstein +Reviewed-by: Miklos Szeredi +Signed-off-by: Christian Brauner (Amutable) +[ adapted scoped credential and creation helpers to explicit credential, locking, lookup, and cleanup handling ] +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..d23c0ea298 --- /dev/null +++ b/queue-6.18/pinctrl-renesas-rzg2l-use-enotsupp-instead-of-eopnotsupp.patch @@ -0,0 +1,78 @@ +From stable+bounces-288449-greg=kroah.com@vger.kernel.org Thu Jul 23 19:13:25 2026 +From: Sasha Levin +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 , Bartosz Golaszewski , Geert Uytterhoeven , Sasha Levin +Message-ID: <20260723170325.3399838-1-sashal@kernel.org> + +From: Claudiu Beznea + +[ 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 +Reviewed-by: Bartosz Golaszewski +Reviewed-by: Geert Uytterhoeven +Tested-by: Geert Uytterhoeven +Link: https://patch.msgid.link/20260515124008.2947838-2-claudiu.beznea@kernel.org +Signed-off-by: Geert Uytterhoeven +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..6936a312a8 --- /dev/null +++ b/queue-6.18/pmdomain-imx93-blk-ctrl-convert-to-devm_-only.patch @@ -0,0 +1,139 @@ +From stable+bounces-290868-greg=kroah.com@vger.kernel.org Wed Jul 29 20:12:23 2026 +From: Sasha Levin +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 , Frank Li , Ulf Hansson , Sasha Levin +Message-ID: <20260729180208.3683189-1-sashal@kernel.org> + +From: Marco Felsch + +[ 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 +Signed-off-by: Marco Felsch +Signed-off-by: Ulf Hansson +Stable-dep-of: 99611233f8cd ("pmdomain: imx93-blk-ctrl: Extract PHY as shared domain for DSI/CSI") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..67f5cab4b4 --- /dev/null +++ b/queue-6.18/pmdomain-imx93-blk-ctrl-extract-phy-as-shared-domain-for-dsi-csi.patch @@ -0,0 +1,170 @@ +From stable+bounces-290869-greg=kroah.com@vger.kernel.org Wed Jul 29 20:12:25 2026 +From: Sasha Levin +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 , Frank Li , Peng Fan , Ulf Hansson , Sasha Levin +Message-ID: <20260729180208.3683189-2-sashal@kernel.org> + +From: Guoniu Zhou + +[ 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 +Reviewed-by: Frank Li +Reviewed-by: Peng Fan +Signed-off-by: Ulf Hansson +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..ef3d31a38e --- /dev/null +++ b/queue-6.18/remoteproc-xlnx-check-remote-core-state.patch @@ -0,0 +1,147 @@ +From stable+bounces-288526-greg=kroah.com@vger.kernel.org Thu Jul 23 22:00:44 2026 +From: Sasha Levin +Date: Thu, 23 Jul 2026 15:58:58 -0400 +Subject: remoteproc: xlnx: Check remote core state +To: stable@vger.kernel.org +Cc: Tanmay Shah , Beleswar Padhi , Michal Simek , Mathieu Poirier , Sasha Levin +Message-ID: <20260723195858.3684427-1-sashal@kernel.org> + +From: Tanmay Shah + +[ 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 +Fixes: bca4b02ef92e ("remoteproc: xlnx: Add attach detach support") +Reviewed-by: Beleswar Padhi +Acked-by: Michal Simek +Cc: stable@vger.kernel.org +Link: https://lore.kernel.org/r/20260428221855.313752-1-tanmay.shah@amd.com +Signed-off-by: Mathieu Poirier +[ 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 +Signed-off-by: Greg Kroah-Hartman +--- + 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 diff --git a/queue-6.18/series b/queue-6.18/series index 8846b64317..2ffc8e1b84 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -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 index 0000000000..8a17501365 --- /dev/null +++ b/queue-6.18/sunrpc-add-helpers-to-convert-xdr_buf-byte-ranges-to-scatterlists.patch @@ -0,0 +1,279 @@ +From stable+bounces-288493-greg=kroah.com@vger.kernel.org Thu Jul 23 20:21:07 2026 +From: Sasha Levin +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 , Jeff Layton , Anna Schumaker , Sasha Levin +Message-ID: <20260723181911.3438372-3-sashal@kernel.org> + +From: Chuck Lever + +[ 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 +Acked-by: Anna Schumaker +Signed-off-by: Chuck Lever +Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..a9b671f03c --- /dev/null +++ b/queue-6.18/sunrpc-allocate-a-separate-bvec-array-for-socket-sends.patch @@ -0,0 +1,172 @@ +From stable+bounces-288491-greg=kroah.com@vger.kernel.org Thu Jul 23 20:21:30 2026 +From: Sasha Levin +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 , NeilBrown , Chuck Lever , Sasha Levin +Message-ID: <20260723181911.3438372-2-sashal@kernel.org> + +From: Jeff Layton + +[ 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 +Reviewed-by: NeilBrown +Signed-off-by: Chuck Lever +Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..44a8a22d67 --- /dev/null +++ b/queue-6.18/sunrpc-return-an-error-from-xdr_buf_to_bvec-on-overflow.patch @@ -0,0 +1,168 @@ +From stable+bounces-288495-greg=kroah.com@vger.kernel.org Thu Jul 23 20:27:21 2026 +From: Sasha Levin +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 , Chris Mason , Jeff Layton , Sasha Levin +Message-ID: <20260723181911.3438372-5-sashal@kernel.org> + +From: Chuck Lever + +[ 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 +Fixes: 2eb2b9358181 ("SUNRPC: Convert svc_tcp_sendmsg to use bio_vecs directly") +Cc: stable@vger.kernel.org +Reviewed-by: Jeff Layton +Signed-off-by: Chuck Lever +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..5ab39f8d21 --- /dev/null +++ b/queue-6.18/thunderbolt-keep-xdomain-reference-during-the-lifetime-of-a-service.patch @@ -0,0 +1,42 @@ +From stable+bounces-289230-greg=kroah.com@vger.kernel.org Sun Jul 26 14:00:55 2026 +From: Sasha Levin +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 , Sasha Levin +Message-ID: <20260726120050.4138299-1-sashal@kernel.org> + +From: Mika Westerberg + +[ 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 +Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..b7f10fbc15 --- /dev/null +++ b/queue-6.18/thunderbolt-prevent-xdomain-delayed-work-use-after-free-on-disconnect.patch @@ -0,0 +1,90 @@ +From stable+bounces-289233-greg=kroah.com@vger.kernel.org Sun Jul 26 14:03:26 2026 +From: Sasha Levin +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 , Mika Westerberg , Sasha Levin +Message-ID: <20260726120050.4138299-4-sashal@kernel.org> + +From: Michael Bommarito + +[ 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 +Signed-off-by: Mika Westerberg +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..6deb25e221 --- /dev/null +++ b/queue-6.18/thunderbolt-remove-service-debugfs-entries-during-unregister.patch @@ -0,0 +1,76 @@ +From stable+bounces-289231-greg=kroah.com@vger.kernel.org Sun Jul 26 14:00:57 2026 +From: Sasha Levin +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 , Sasha Levin +Message-ID: <20260726120050.4138299-2-sashal@kernel.org> + +From: Mika Westerberg + +[ 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 +Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..f5081f7ff4 --- /dev/null +++ b/queue-6.18/thunderbolt-remove-xdomain-from-the-bus-without-holding-tb-lock.patch @@ -0,0 +1,337 @@ +From stable+bounces-289232-greg=kroah.com@vger.kernel.org Sun Jul 26 14:00:59 2026 +From: Sasha Levin +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 , Sasha Levin +Message-ID: <20260726120050.4138299-3-sashal@kernel.org> + +From: Mika Westerberg + +[ 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 +Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..1dabcb2f2d --- /dev/null +++ b/queue-6.18/vduse-avoid-leaking-information-to-userspace.patch @@ -0,0 +1,53 @@ +From stable+bounces-287927-greg=kroah.com@vger.kernel.org Wed Jul 22 18:08:44 2026 +From: Sasha Levin +Date: Wed, 22 Jul 2026 12:07:31 -0400 +Subject: VDUSE: avoid leaking information to userspace +To: stable@vger.kernel.org +Cc: "Jason Wang" , "Xie Yongji" , "Eugenio Pérez" , "Michael S. Tsirkin" , "Sasha Levin" +Message-ID: <20260722160731.1739521-4-sashal@kernel.org> + +From: Jason Wang + +[ 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 +Reviewed-by: Xie Yongji +Reviewed-by: Eugenio Pérez +Signed-off-by: Michael S. Tsirkin +Message-ID: <20260130050750.4050-1-jasowang@redhat.com> +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..7fab00389f --- /dev/null +++ b/queue-6.18/vduse-remove-unused-vaddr-parameter-of-vduse_domain_free_coherent.patch @@ -0,0 +1,61 @@ +From stable+bounces-287925-greg=kroah.com@vger.kernel.org Wed Jul 22 18:34:33 2026 +From: Sasha Levin +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" , "Michael S. Tsirkin" , "Sasha Levin" +Message-ID: <20260722160731.1739521-2-sashal@kernel.org> + +From: Eugenio Pérez + +[ Upstream commit 766e1749c0ef6a09651be9b8a8283d508c322b58 ] + +We will modify the function in next patches so let's clean it first. + +Signed-off-by: Eugenio Pérez +Signed-off-by: Michael S. Tsirkin +Message-Id: <20260119143306.1818855-9-eperezma@redhat.com> +Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..96d1eadbbe --- /dev/null +++ b/queue-6.18/vduse-return-internal-vq-group-struct-as-map-token.patch @@ -0,0 +1,348 @@ +From stable+bounces-287924-greg=kroah.com@vger.kernel.org Wed Jul 22 18:29:31 2026 +From: Sasha Levin +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" , "Jason Wang" , "Michael S. Tsirkin" , "Sasha Levin" +Message-ID: <20260722160731.1739521-1-sashal@kernel.org> + +From: Eugenio Pérez + +[ 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 +Signed-off-by: Eugenio Pérez +Signed-off-by: Michael S. Tsirkin +Message-Id: <20260119143306.1818855-5-eperezma@redhat.com> +Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 + #include + #include ++#include + #include + #include + #include +@@ -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 index 0000000000..16612f6a33 --- /dev/null +++ b/queue-6.18/vduse-take-out-allocations-from-vduse_dev_alloc_coherent.patch @@ -0,0 +1,61 @@ +From stable+bounces-287926-greg=kroah.com@vger.kernel.org Wed Jul 22 18:08:40 2026 +From: Sasha Levin +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" , "Jason Wang" , "Michael S. Tsirkin" , "Sasha Levin" +Message-ID: <20260722160731.1739521-3-sashal@kernel.org> + +From: Eugenio Pérez + +[ 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 +Signed-off-by: Eugenio Pérez +Signed-off-by: Michael S. Tsirkin +Message-Id: <20260119143306.1818855-10-eperezma@redhat.com> +Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace") +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..9b4595e31d --- /dev/null +++ b/queue-6.18/x86-fs-resctrl-rename-some-l3-specific-functions.patch @@ -0,0 +1,292 @@ +From stable+bounces-289734-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:32 2026 +From: Sasha Levin +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 , "Borislav Petkov (AMD)" , Reinette Chatre , Sasha Levin +Message-ID: <20260727233421.2423844-3-sashal@kernel.org> + +From: Tony Luck + +[ 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 +Signed-off-by: Borislav Petkov (AMD) +Reviewed-by: Reinette Chatre +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 +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..b1fcdbcc34 --- /dev/null +++ b/queue-6.18/x86-fs-resctrl-rename-struct-rdt_mon_domain-and-rdt_hw_mon_domain.patch @@ -0,0 +1,872 @@ +From stable+bounces-289735-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:31 2026 +From: Sasha Levin +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 , "Borislav Petkov (AMD)" , Reinette Chatre , Sasha Levin +Message-ID: <20260727233421.2423844-2-sashal@kernel.org> + +From: Tony Luck + +[ 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 +Signed-off-by: Borislav Petkov (AMD) +Reviewed-by: Reinette Chatre +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 +Signed-off-by: Greg Kroah-Hartman +--- + 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 index 0000000000..3616f57055 --- /dev/null +++ b/queue-6.18/xfs-don-t-replace-the-wrong-part-of-the-cow-fork.patch @@ -0,0 +1,429 @@ +From stable+bounces-287818-greg=kroah.com@vger.kernel.org Wed Jul 22 13:40:52 2026 +From: Sasha Levin +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" , Christoph Hellwig , Carlos Maiolino , Sasha Levin +Message-ID: <20260722113353.1151198-1-sashal@kernel.org> + +From: "Darrick J. Wong" + +[ 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" +Assisted-by: LOLLM # finding obvious bugs +Reviewed-by: Christoph Hellwig +Signed-off-by: Carlos Maiolino +Signed-off-by: Sasha Levin +Signed-off-by: Greg Kroah-Hartman +--- + 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,