--- /dev/null
+From stable+bounces-289669-greg=kroah.com@vger.kernel.org Mon Jul 27 21:51:19 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 15:50:59 -0400
+Subject: accel/amdxdna: reject command submission on devices without a submit op
+To: stable@vger.kernel.org
+Cc: Doruk Tan Ozturk <doruk@0sec.ai>, Lizhi Hou <lizhi.hou@amd.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727195059.1774971-1-sashal@kernel.org>
+
+From: Doruk Tan Ozturk <doruk@0sec.ai>
+
+[ Upstream commit 38953513d7313992676d4136cd425cdb70c6278e ]
+
+amdxdna_cmd_submit() calls xdna->dev_info->ops->cmd_submit()
+unconditionally, but only aie2_dev_ops defines that callback.
+aie4_vf_ops (the AIE4 SR-IOV virtual function) does not, so a user
+AMDXDNA_EXEC_CMD ioctl on an AIE4 device reaches a NULL function-pointer
+call and oopses the kernel. AIE4 submits work through a mapped user queue
+and doorbell, not this ioctl path.
+
+Reject the submission early with -EOPNOTSUPP when the device provides no
+cmd_submit op, so the shared EXEC ioctl is a clean no-op on such devices.
+
+Fixes: aac243092b70 ("accel/amdxdna: Add command execution")
+Cc: stable@vger.kernel.org
+Found by 0sec automated security-research tooling (https://0sec.ai).
+Assisted-by: 0sec:claude-opus-4-8
+Signed-off-by: Doruk Tan Ozturk <doruk@0sec.ai>
+Reviewed-by: Lizhi Hou <lizhi.hou@amd.com>
+Signed-off-by: Lizhi Hou <lizhi.hou@amd.com>
+Link: https://patch.msgid.link/20260713173030.87541-3-doruk@0sec.ai
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/accel/amdxdna/amdxdna_ctx.c | 4 ++++
+ 1 file changed, 4 insertions(+)
+
+--- a/drivers/accel/amdxdna/amdxdna_ctx.c
++++ b/drivers/accel/amdxdna/amdxdna_ctx.c
+@@ -406,6 +406,10 @@ int amdxdna_cmd_submit(struct amdxdna_cl
+ int ret, idx;
+
+ XDNA_DBG(xdna, "Command BO hdl %d, Arg BO count %d", cmd_bo_hdl, arg_bo_cnt);
++
++ if (!xdna->dev_info->ops->cmd_submit)
++ return -EOPNOTSUPP;
++
+ job = kzalloc(struct_size(job, bos, arg_bo_cnt), GFP_KERNEL);
+ if (!job)
+ return -ENOMEM;
--- /dev/null
+From stable+bounces-288029-greg=kroah.com@vger.kernel.org Wed Jul 22 20:00:57 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 14:00:47 -0400
+Subject: arm64: dts: qcom: correct RBR opp entry
+To: stable@vger.kernel.org
+Cc: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>, Bjorn Andersson <andersson@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722180048.2184180-1-sashal@kernel.org>
+
+From: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+
+[ Upstream commit a5c21b9bd5f531e50141b0484faabb707b92f1e2 ]
+
+DisplayPort Reduced Bit Rate uses link rate of 1.62 Gbps, the main link
+clock should be 162 MHz. Having the incorrect frequency (160 MHz) in the
+OPP table will result in selecting wrong link frequency. Correct the
+entry in the OPP table.
+
+Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+Link: https://lore.kernel.org/r/20260304-msm-fix-rbr-v1-1-b9eba986eaef@oss.qualcomm.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Stable-dep-of: c17e22094667 ("arm64: dts: qcom: hamoa: Fix OPP tables for all DisplayPort controllers")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/arm64/boot/dts/qcom/lemans.dtsi | 8 ++++----
+ arch/arm64/boot/dts/qcom/sc7180.dtsi | 4 ++--
+ arch/arm64/boot/dts/qcom/sc7280.dtsi | 8 ++++----
+ arch/arm64/boot/dts/qcom/sc8180x.dtsi | 12 ++++++------
+ arch/arm64/boot/dts/qcom/sc8280xp.dtsi | 32 ++++++++++++++++----------------
+ arch/arm64/boot/dts/qcom/sm6350.dtsi | 4 ++--
+ arch/arm64/boot/dts/qcom/sm8150.dtsi | 4 ++--
+ arch/arm64/boot/dts/qcom/sm8250.dtsi | 4 ++--
+ arch/arm64/boot/dts/qcom/sm8350.dtsi | 4 ++--
+ arch/arm64/boot/dts/qcom/sm8450.dtsi | 4 ++--
+ arch/arm64/boot/dts/qcom/x1e80100.dtsi | 16 ++++++++--------
+ 11 files changed, 50 insertions(+), 50 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/lemans.dtsi
++++ b/arch/arm64/boot/dts/qcom/lemans.dtsi
+@@ -5146,8 +5146,8 @@
+ dp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -5234,8 +5234,8 @@
+ dp1_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sc7180.dtsi
++++ b/arch/arm64/boot/dts/qcom/sc7180.dtsi
+@@ -3452,8 +3452,8 @@
+ dp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sc7280.dtsi
++++ b/arch/arm64/boot/dts/qcom/sc7280.dtsi
+@@ -5196,8 +5196,8 @@
+ edp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -5295,8 +5295,8 @@
+ dp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sc8180x.dtsi
++++ b/arch/arm64/boot/dts/qcom/sc8180x.dtsi
+@@ -3303,8 +3303,8 @@
+ dp0_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -3385,8 +3385,8 @@
+ dp1_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -3461,8 +3461,8 @@
+ edp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sc8280xp.dtsi
++++ b/arch/arm64/boot/dts/qcom/sc8280xp.dtsi
+@@ -4754,8 +4754,8 @@
+ mdss0_dp0_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -4836,8 +4836,8 @@
+ mdss0_dp1_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -4916,8 +4916,8 @@
+ mdss0_dp2_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -4991,8 +4991,8 @@
+ mdss0_dp3_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -6110,8 +6110,8 @@
+ mdss1_dp0_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -6190,8 +6190,8 @@
+ mdss1_dp1_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -6270,8 +6270,8 @@
+ mdss1_dp2_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -6345,8 +6345,8 @@
+ mdss1_dp3_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sm6350.dtsi
++++ b/arch/arm64/boot/dts/qcom/sm6350.dtsi
+@@ -2306,8 +2306,8 @@
+ dp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sm8150.dtsi
++++ b/arch/arm64/boot/dts/qcom/sm8150.dtsi
+@@ -3939,8 +3939,8 @@
+ dp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sm8250.dtsi
++++ b/arch/arm64/boot/dts/qcom/sm8250.dtsi
+@@ -4824,8 +4824,8 @@
+ dp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sm8350.dtsi
++++ b/arch/arm64/boot/dts/qcom/sm8350.dtsi
+@@ -2925,8 +2925,8 @@
+ dp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/sm8450.dtsi
++++ b/arch/arm64/boot/dts/qcom/sm8450.dtsi
+@@ -3483,8 +3483,8 @@
+ dp_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+--- a/arch/arm64/boot/dts/qcom/x1e80100.dtsi
++++ b/arch/arm64/boot/dts/qcom/x1e80100.dtsi
+@@ -5522,8 +5522,8 @@
+ mdss_dp0_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -5610,8 +5610,8 @@
+ mdss_dp1_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -5697,8 +5697,8 @@
+ mdss_dp2_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+@@ -5779,8 +5779,8 @@
+ mdss_dp3_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-160000000 {
+- opp-hz = /bits/ 64 <160000000>;
++ opp-162000000 {
++ opp-hz = /bits/ 64 <162000000>;
+ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
--- /dev/null
+From stable+bounces-288030-greg=kroah.com@vger.kernel.org Wed Jul 22 20:08:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 14:00:48 -0400
+Subject: arm64: dts: qcom: hamoa: Fix OPP tables for all DisplayPort controllers
+To: stable@vger.kernel.org
+Cc: Abel Vesa <abel.vesa@oss.qualcomm.com>, Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>, Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>, Bjorn Andersson <andersson@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722180048.2184180-2-sashal@kernel.org>
+
+From: Abel Vesa <abel.vesa@oss.qualcomm.com>
+
+[ Upstream commit c17e220946675232d383620ed9cff6685735ec48 ]
+
+According to internal documentation, the corners specific for each rate
+from the DP link clock are:
+ - LOWSVS_D1 -> 19.2 MHz
+ - LOWSVS -> 270 MHz
+ - SVS -> 540 MHz (594 MHz in case of DP3)
+ - SVS_L1 -> 594 MHz
+ - NOM -> 810 MHz
+ - NOM_L1 -> 810 MHz
+ - TURBO -> 810 MHz
+
+So fix all tables for each of the four controllers according to the
+documentation, but since DP0 through DP2 have the same entries in their
+tables, lets drop the DP1 and DP2 and have all of them share the DP0
+table instead. However keep a separate table for the DP3 as it is
+different for the SVS, compared to the rest of the controllers.
+
+The 19.2 MHz @ LOWSVS_D1 isn't needed as it's not an actual working
+frequency and the controller will never select it. So remove it.
+
+Cc: stable@vger.kernel.org # v6.9+
+Fixes: 1940c25eaa63 ("arm64: dts: qcom: x1e80100: Add display nodes")
+Suggested-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+Reviewed-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
+Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
+Signed-off-by: Abel Vesa <abel.vesa@oss.qualcomm.com>
+Link: https://lore.kernel.org/r/20260323-hamoa-fix-dp3-opp-table-v3-1-a823776bd1b0@oss.qualcomm.com
+Signed-off-by: Bjorn Andersson <andersson@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/arm64/boot/dts/qcom/x1e80100.dtsi | 77 +++++----------------------------
+ 1 file changed, 12 insertions(+), 65 deletions(-)
+
+--- a/arch/arm64/boot/dts/qcom/x1e80100.dtsi
++++ b/arch/arm64/boot/dts/qcom/x1e80100.dtsi
+@@ -5522,18 +5522,18 @@
+ mdss_dp0_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-162000000 {
+- opp-hz = /bits/ 64 <162000000>;
+- required-opps = <&rpmhpd_opp_low_svs>;
+- };
+-
+ opp-270000000 {
+ opp-hz = /bits/ 64 <270000000>;
+- required-opps = <&rpmhpd_opp_svs>;
++ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+ opp-540000000 {
+ opp-hz = /bits/ 64 <540000000>;
++ required-opps = <&rpmhpd_opp_svs>;
++ };
++
++ opp-594000000 {
++ opp-hz = /bits/ 64 <594000000>;
+ required-opps = <&rpmhpd_opp_svs_l1>;
+ };
+
+@@ -5574,7 +5574,7 @@
+ <&usb_1_ss1_qmpphy QMP_USB43DP_DP_VCO_DIV_CLK>,
+ <&usb_1_ss1_qmpphy QMP_USB43DP_DP_VCO_DIV_CLK>;
+
+- operating-points-v2 = <&mdss_dp1_opp_table>;
++ operating-points-v2 = <&mdss_dp0_opp_table>;
+
+ power-domains = <&rpmhpd RPMHPD_MMCX>;
+
+@@ -5606,30 +5606,6 @@
+ };
+ };
+ };
+-
+- mdss_dp1_opp_table: opp-table {
+- compatible = "operating-points-v2";
+-
+- opp-162000000 {
+- opp-hz = /bits/ 64 <162000000>;
+- required-opps = <&rpmhpd_opp_low_svs>;
+- };
+-
+- opp-270000000 {
+- opp-hz = /bits/ 64 <270000000>;
+- required-opps = <&rpmhpd_opp_svs>;
+- };
+-
+- opp-540000000 {
+- opp-hz = /bits/ 64 <540000000>;
+- required-opps = <&rpmhpd_opp_svs_l1>;
+- };
+-
+- opp-810000000 {
+- opp-hz = /bits/ 64 <810000000>;
+- required-opps = <&rpmhpd_opp_nom>;
+- };
+- };
+ };
+
+ mdss_dp2: displayport-controller@ae9a000 {
+@@ -5662,7 +5638,7 @@
+ <&usb_1_ss2_qmpphy QMP_USB43DP_DP_VCO_DIV_CLK>,
+ <&usb_1_ss2_qmpphy QMP_USB43DP_DP_VCO_DIV_CLK>;
+
+- operating-points-v2 = <&mdss_dp2_opp_table>;
++ operating-points-v2 = <&mdss_dp0_opp_table>;
+
+ power-domains = <&rpmhpd RPMHPD_MMCX>;
+
+@@ -5693,30 +5669,6 @@
+ };
+ };
+ };
+-
+- mdss_dp2_opp_table: opp-table {
+- compatible = "operating-points-v2";
+-
+- opp-162000000 {
+- opp-hz = /bits/ 64 <162000000>;
+- required-opps = <&rpmhpd_opp_low_svs>;
+- };
+-
+- opp-270000000 {
+- opp-hz = /bits/ 64 <270000000>;
+- required-opps = <&rpmhpd_opp_svs>;
+- };
+-
+- opp-540000000 {
+- opp-hz = /bits/ 64 <540000000>;
+- required-opps = <&rpmhpd_opp_svs_l1>;
+- };
+-
+- opp-810000000 {
+- opp-hz = /bits/ 64 <810000000>;
+- required-opps = <&rpmhpd_opp_nom>;
+- };
+- };
+ };
+
+ mdss_dp3: displayport-controller@aea0000 {
+@@ -5779,19 +5731,14 @@
+ mdss_dp3_opp_table: opp-table {
+ compatible = "operating-points-v2";
+
+- opp-162000000 {
+- opp-hz = /bits/ 64 <162000000>;
+- required-opps = <&rpmhpd_opp_low_svs>;
+- };
+-
+ opp-270000000 {
+ opp-hz = /bits/ 64 <270000000>;
+- required-opps = <&rpmhpd_opp_svs>;
++ required-opps = <&rpmhpd_opp_low_svs>;
+ };
+
+- opp-540000000 {
+- opp-hz = /bits/ 64 <540000000>;
+- required-opps = <&rpmhpd_opp_svs_l1>;
++ opp-594000000 {
++ opp-hz = /bits/ 64 <594000000>;
++ required-opps = <&rpmhpd_opp_svs>;
+ };
+
+ opp-810000000 {
--- /dev/null
+From stable+bounces-290198-greg=kroah.com@vger.kernel.org Wed Jul 29 02:30:58 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 20:30:49 -0400
+Subject: ata: libata-core: Reject an invalid concurrent positioning ranges count
+To: stable@vger.kernel.org
+Cc: Bryam Vargas <hexlabsecurity@proton.me>, Damien Le Moal <dlemoal@kernel.org>, Niklas Cassel <cassel@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729003049.888768-1-sashal@kernel.org>
+
+From: Bryam Vargas <hexlabsecurity@proton.me>
+
+[ Upstream commit 533a0b940f901c15e5cbbd4b5d66e871c209e8ce ]
+
+ata_dev_config_cpr() takes the number of range descriptors from buf[0]
+of the concurrent positioning ranges log (up to 255), which the device
+reports independently of the log size in the GPL directory. The count is
+then walked at a fixed 32-byte stride in two places with no bound: the
+log read here, and the INQUIRY VPD page B9h emitter, which writes one
+descriptor per range into the fixed 2048-byte ata_scsi_rbuf. A device
+reporting a count larger than its own log overflows the read buffer (up
+to 7704 bytes past a 512-byte slab), and a count above 62 overflows the
+response buffer on the emit side.
+
+Bound the count once, on probe, against both the log the device returned
+and the number of descriptors the VPD B9h response buffer can hold
+(ATA_DEV_MAX_CPR, derived from the rbuf size). Reject an out-of-range
+count with a warning; this keeps the emitter in bounds with no separate
+change there.
+
+Suggested-by: Damien Le Moal <dlemoal@kernel.org>
+Fixes: fe22e1c2f705 ("libata: support concurrent positioning ranges log")
+Fixes: c745dfc541e7 ("libata: fix reading concurrent positioning ranges log")
+Cc: stable@vger.kernel.org
+Signed-off-by: Bryam Vargas <hexlabsecurity@proton.me>
+Reviewed-by: Niklas Cassel <cassel@kernel.org>
+Signed-off-by: Damien Le Moal <dlemoal@kernel.org>
+[ adapted `kzalloc_flex()` allocation to `kzalloc(struct_size(...), GFP_KERNEL)` and adjusted context offsets. ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/ata/libata-core.c | 18 ++++++++++++++++++
+ drivers/ata/libata-scsi.c | 2 --
+ drivers/ata/libata.h | 9 +++++++++
+ 3 files changed, 27 insertions(+), 2 deletions(-)
+
+--- a/drivers/ata/libata-core.c
++++ b/drivers/ata/libata-core.c
+@@ -2833,6 +2833,24 @@ static void ata_dev_config_cpr(struct at
+ if (!nr_cpr)
+ goto out;
+
++ /*
++ * The device reports the number of CPR descriptors independently of the
++ * log size, and that count is also used to emit VPD page B9h into the
++ * fixed-size rbuf. Reject a count larger than what that buffer can hold
++ * (ATA_DEV_MAX_CPR) or larger than the log the device actually returned.
++ */
++ if (nr_cpr > ATA_DEV_MAX_CPR) {
++ ata_dev_warn(dev,
++ "Too many concurrent positioning ranges\n");
++ goto out;
++ }
++
++ if (buf_len < 64 + (size_t)nr_cpr * 32) {
++ ata_dev_warn(dev,
++ "Invalid number of concurrent positioning ranges\n");
++ goto out;
++ }
++
+ cpr_log = kzalloc(struct_size(cpr_log, cpr, nr_cpr), GFP_KERNEL);
+ if (!cpr_log)
+ goto out;
+--- a/drivers/ata/libata-scsi.c
++++ b/drivers/ata/libata-scsi.c
+@@ -37,8 +37,6 @@
+ #include "libata.h"
+ #include "libata-transport.h"
+
+-#define ATA_SCSI_RBUF_SIZE 2048
+-
+ static DEFINE_SPINLOCK(ata_scsi_rbuf_lock);
+ static u8 ata_scsi_rbuf[ATA_SCSI_RBUF_SIZE];
+
+--- a/drivers/ata/libata.h
++++ b/drivers/ata/libata.h
+@@ -144,6 +144,15 @@ static inline void ata_acpi_bind_dev(str
+ #endif
+
+ /* libata-scsi.c */
++#define ATA_SCSI_RBUF_SIZE 2048
++
++/*
++ * Maximum number of concurrent positioning ranges (CPR) supported. The ACS
++ * specifications allow up to 255, but we limit this to the number of CPR
++ * descriptors that fit in the rbuf buffer used to emit VPD page B9h.
++ */
++#define ATA_DEV_MAX_CPR min(255, ((ATA_SCSI_RBUF_SIZE - 64) / 32))
++
+ extern struct ata_device *ata_scsi_find_dev(struct ata_port *ap,
+ const struct scsi_device *scsidev);
+ extern int ata_scsi_add_hosts(struct ata_host *host,
--- /dev/null
+From stable+bounces-282946-greg=kroah.com@vger.kernel.org Tue Jul 21 21:45:51 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 15:36:13 -0400
+Subject: audit: fix recursive locking deadlock in audit_dupe_exe()
+To: stable@vger.kernel.org
+Cc: Ricardo Robaina <rrobaina@redhat.com>, stable@kernel.org, Waiman Long <longman@redhat.com>, Richard Guy Briggs <rgb@redhat.com>, Nathan Chancellor <nathan@kernel.org>, Paul Moore <paul@paul-moore.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721193613.410665-3-sashal@kernel.org>
+
+From: Ricardo Robaina <rrobaina@redhat.com>
+
+[ Upstream commit 81905b5acbe77284734438df3fbec1158e6429a3 ]
+
+A deadlock occurs in the audit subsystem when duplicating
+executable-related rules.
+
+When a file is moved (e.g., via do_renameat2()), the VFS layer locks
+the parent directory (I_MUTEX_PARENT), which synchronously triggers an
+fsnotify_move event. If an existing executable audit rule matches the
+file being moved, the audit subsystem catches this event and calls
+audit_dupe_exe() to duplicate the watch and update the rule. Then,
+audit_alloc_mark() would call kern_path_parent() to resolve the path,
+leading to a blind attempt to acquire the exact same I_MUTEX_PARENT lock
+already held by the task, resulting in the following recursive locking
+deadlock:
+
+ ============================================
+ WARNING: possible recursive locking detected
+ 6.12.0-55.27.1.el10_0.x86_64+debug #1 Not tainted
+ --------------------------------------------
+ mv/5099 is trying to acquire lock:
+ ffff888132845358 (&inode->i_sb->s_type->i_mutex_dir_key/1){+.+.}-{3:3},
+ at: __kern_path_locked+0x10a/0x2f0
+
+ but task is already holding lock:
+ ffff888132846b58 (&inode->i_sb->s_type->i_mutex_dir_key/1){+.+.}-{3:3},
+ at: lock_two_directories+0x13f/0x2b0
+
+ other info that might help us debug this:
+ Possible unsafe locking scenario:
+
+ CPU0
+ ----
+ lock(&inode->i_sb->s_type->i_mutex_dir_key/1);
+ lock(&inode->i_sb->s_type->i_mutex_dir_key/1);
+
+ *** DEADLOCK ***
+
+ May be due to missing lock nesting notation
+
+ 6 locks held by mv/5099:
+ #0: ffff888112a9c440 (sb_writers#13)
+ at: do_renameat2+0x34c/0xbc0
+ #1: ffff888112a9c790 (&type->s_vfs_rename_key#3)
+ at: do_renameat2+0x415/0xbc0
+ #2: ffff888132846b58 (&inode->i_sb->s_type->i_mutex_dir_key/1)
+ at: lock_two_directories+0x13f/0x2b0
+ #3: ffff888132845358 (&inode->i_sb->s_type->i_mutex_dir_key/5)
+ at: lock_two_directories+0x175/0x2b0
+ #4: ffffffffb3a1fb10 (&fsnotify_mark_srcu)
+ at: fsnotify+0x454/0x28a0
+ #5: ffffffffaf886230 (audit_filter_mutex)
+ at: audit_update_watch+0x36/0x11e0
+
+ stack backtrace:
+ Call Trace:
+ <TASK>
+ dump_stack_lvl+0x6f/0xb0
+ print_deadlock_bug.cold+0xbd/0xca
+ validate_chain+0x83a/0xf00
+ __lock_acquire+0xcac/0x1d20
+ lock_acquire.part.0+0x11b/0x360
+ down_write_nested+0x9f/0x230
+ __kern_path_locked+0x10a/0x2f0
+ kern_path_locked+0x26/0x40
+ audit_alloc_mark+0xfb/0x4f0
+ audit_dupe_exe+0x6c/0xe0
+ audit_dupe_rule+0x6c2/0xc00
+ audit_update_watch+0x4cc/0x11e0
+ audit_watch_handle_event+0x12c/0x1b0
+ send_to_group+0x5d0/0x8b0
+ fsnotify+0x615/0x28a0
+ fsnotify_move+0x1d8/0x630
+ vfs_rename+0xdcd/0x1df0
+ do_renameat2+0x9d4/0xbc0
+ __x64_sys_renameat+0x192/0x260
+ do_syscall_64+0x92/0x180
+ entry_SYSCALL_64_after_hwframe+0x76/0x7e
+ RIP: 0033:0x7f0491fe8c4e
+ Code: 0f 1f 40 00 48 8b 15 c1 e1 16 00 f7 d8 64 89 02 b8 ff ff ff ff
+ c3 66 0f 1f 44 00 00 f3 0f 1e fa 49 89 ca b8 08 01 00 00 0f 05 <48>
+ 3d 00 f0 ff ff 77 0a c3 66 0f 1f 84 00 00 00 00 00 48 8b 15 89
+ RSP: 002b:00007ffc7210bf38 EFLAGS: 00000246 ORIG_RAX: 0000000000000108
+ RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f0491fe8c4e
+ RDX: 0000000000000003 RSI: 00007ffc7210e6c8 RDI: 00000000ffffff9c
+ RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000001
+ R10: 00005575eb2dae2a R11: 0000000000000246 R12: 00005575eb2dae2a
+ R13: 00007ffc7210e6c8 R14: 0000000000000003 R15: 00000000ffffff9c
+ </TASK>
+
+The aforementioned deadlock can be consistently reproduced by running
+the script below:
+
+ audit-dupe-exe-deadlock.sh
+ --------------------------
+ #!/bin/bash
+ auditctl -D
+ mkdir -p /tmp/foo
+ touch /tmp/file
+ auditctl -a always,exit -F exe=/tmp/file -F path=/tmp/file -S all -k dr
+ mv /tmp/file /tmp/foo/file
+ rm -Rf /tmp/foo
+
+This patch fixes the issue by introducing struct audit_watch_ctx to pass
+the fsnotify event context down to audit_alloc_mark(). By utilizing the
+already-resolved directory inode provided by the event, we bypass the
+kern_path_parent() path resolution entirely, safely avoiding the
+recursive lock. Furthermore, it explicitly allows duplicate fsnotify
+marks (allow_dups = 1) during the rename update, allowing the new rule's
+mark to safely coexist with the old rule's mark until the old rule is
+freed.
+
+P.S.: This issue was identified and reproduced during a comprehensive
+code coverage analysis of the audit subsystem. The full report is
+available at the link below:
+
+https://people.redhat.com/rrobaina/audit-code-coverage-analysis.pdf
+
+P.P.S: With the permission of both Ricardo and Nathan, I've squashed a
+fixup patch from Nathan that addresses a compile time error when
+CONFIG_AUDITSYSCALL=n.
+
+Cc: stable@kernel.org
+Fixes: 34d99af52ad4 ("audit: implement audit by executable")
+Acked-by: Waiman Long <longman@redhat.com>
+Acked-by: Richard Guy Briggs <rgb@redhat.com>
+Signed-off-by: Nathan Chancellor <nathan@kernel.org>
+Signed-off-by: Ricardo Robaina <rrobaina@redhat.com>
+[PM: move link metadata into the msg, apply fix from NC]
+Signed-off-by: Paul Moore <paul@paul-moore.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/audit.h | 17 ++++++++++++-----
+ kernel/audit_fsnotify.c | 32 +++++++++++++++++++++++---------
+ kernel/audit_watch.c | 25 +++++++++++++++++--------
+ kernel/auditfilter.c | 9 +++++----
+ 4 files changed, 57 insertions(+), 26 deletions(-)
+
+--- a/kernel/audit.h
++++ b/kernel/audit.h
+@@ -256,8 +256,13 @@ extern int audit_del_rule(struct audit_e
+ extern void audit_free_rule_rcu(struct rcu_head *head);
+ extern struct list_head audit_filter_list[];
+
+-extern struct audit_entry *audit_dupe_rule(struct audit_krule *old);
++struct audit_watch_ctx {
++ struct inode *dir;
++ struct inode *child;
++};
+
++extern struct audit_entry *audit_dupe_rule(struct audit_krule *old,
++ struct audit_watch_ctx *ctx);
+ extern void audit_log_d_path_exe(struct audit_buffer *ab,
+ struct mm_struct *mm);
+
+@@ -280,13 +285,15 @@ extern char *audit_watch_path(struct aud
+ extern int audit_watch_compare(struct audit_watch *watch, u64 ino, dev_t dev);
+
+ extern struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule,
+- char *pathname, int len);
++ char *pathname, int len,
++ struct audit_watch_ctx *ctx);
+ extern char *audit_mark_path(struct audit_fsnotify_mark *mark);
+ extern void audit_remove_mark(struct audit_fsnotify_mark *audit_mark);
+ extern void audit_remove_mark_rule(struct audit_krule *krule);
+ extern int audit_mark_compare(struct audit_fsnotify_mark *mark, u64 ino,
+ dev_t dev);
+-extern int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old);
++extern int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old,
++ struct audit_watch_ctx *ctx);
+ extern int audit_exe_compare(struct task_struct *tsk,
+ struct audit_fsnotify_mark *mark);
+
+@@ -317,13 +324,13 @@ extern struct list_head *audit_killed_tr
+ #define audit_watch_path(w) ""
+ #define audit_watch_compare(w, i, d) 0
+
+-#define audit_alloc_mark(k, p, l) (ERR_PTR(-EINVAL))
++#define audit_alloc_mark(k, p, l, c) (ERR_PTR(-EINVAL))
+ #define audit_mark_path(m) ""
+ #define audit_remove_mark(m) do { } while (0)
+ #define audit_remove_mark_rule(k) do { } while (0)
+ #define audit_mark_compare(m, i, d) 0
+ #define audit_exe_compare(t, m) (-EINVAL)
+-#define audit_dupe_exe(n, o) (-EINVAL)
++#define audit_dupe_exe(n, o, c) (-EINVAL)
+
+ #define audit_remove_tree_rule(rule) BUG()
+ #define audit_add_tree_rule(rule) -EINVAL
+--- a/kernel/audit_fsnotify.c
++++ b/kernel/audit_fsnotify.c
+@@ -71,19 +71,30 @@ static void audit_update_mark(struct aud
+ audit_mark->ino = inode ? inode->i_ino : AUDIT_INO_UNSET;
+ }
+
+-struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule, char *pathname, int len)
++struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule, char *pathname,
++ int len, struct audit_watch_ctx *ctx)
+ {
+ struct audit_fsnotify_mark *audit_mark;
+ struct path path;
+ struct dentry *dentry;
+- int ret;
++ struct inode *dir, *child;
++ int ret, allow_dups;
+
+ if (pathname[0] != '/' || pathname[len-1] == '/')
+ return ERR_PTR(-EINVAL);
+
+- dentry = kern_path_parent(pathname, &path);
+- if (IS_ERR(dentry))
+- return ERR_CAST(dentry); /* returning an error */
++ if (!ctx) {
++ dentry = kern_path_parent(pathname, &path);
++ if (IS_ERR(dentry))
++ return ERR_CAST(dentry); /* returning an error */
++ dir = d_inode(path.dentry);
++ child = d_inode(dentry);
++ allow_dups = 0;
++ } else {
++ dir = ctx->dir;
++ child = ctx->child;
++ allow_dups = 1;
++ }
+
+ audit_mark = kzalloc(sizeof(*audit_mark), GFP_KERNEL);
+ if (unlikely(!audit_mark)) {
+@@ -94,18 +105,21 @@ struct audit_fsnotify_mark *audit_alloc_
+ fsnotify_init_mark(&audit_mark->mark, audit_fsnotify_group);
+ audit_mark->mark.mask = AUDIT_FS_EVENTS;
+ audit_mark->path = pathname;
+- audit_update_mark(audit_mark, dentry->d_inode);
+ audit_mark->rule = krule;
+
+- ret = fsnotify_add_inode_mark(&audit_mark->mark, path.dentry->d_inode, 0);
++ audit_update_mark(audit_mark, child);
++ ret = fsnotify_add_inode_mark(&audit_mark->mark, dir, allow_dups);
++
+ if (ret < 0) {
+ audit_mark->path = NULL;
+ fsnotify_put_mark(&audit_mark->mark);
+ audit_mark = ERR_PTR(ret);
+ }
+ out:
+- dput(dentry);
+- path_put(&path);
++ if (!ctx) {
++ dput(dentry);
++ path_put(&path);
++ }
+ return audit_mark;
+ }
+
+--- a/kernel/audit_watch.c
++++ b/kernel/audit_watch.c
+@@ -244,7 +244,8 @@ static void audit_watch_log_rule_change(
+ /* Update inode info in audit rules based on filesystem event. */
+ static void audit_update_watch(struct audit_parent *parent,
+ const struct qstr *dname, dev_t dev,
+- u64 ino, unsigned int invalidating)
++ u64 ino, unsigned int invalidating,
++ struct audit_watch_ctx *ctx)
+ {
+ struct audit_watch *owatch, *nwatch, *nextw;
+ struct audit_krule *r, *nextr;
+@@ -280,7 +281,7 @@ static void audit_update_watch(struct au
+ list_del(&oentry->rule.rlist);
+ list_del_rcu(&oentry->list);
+
+- nentry = audit_dupe_rule(&oentry->rule);
++ nentry = audit_dupe_rule(&oentry->rule, ctx);
+ if (IS_ERR(nentry)) {
+ list_del(&oentry->rule.list);
+ audit_panic("error updating watch, removing");
+@@ -479,10 +480,17 @@ static int audit_watch_handle_event(stru
+ if (WARN_ON_ONCE(inode_mark->group != audit_watch_group))
+ return 0;
+
+- if (mask & (FS_CREATE|FS_MOVED_TO) && inode)
+- audit_update_watch(parent, dname, inode->i_sb->s_dev, inode->i_ino, 0);
+- else if (mask & (FS_DELETE|FS_MOVED_FROM))
+- audit_update_watch(parent, dname, AUDIT_DEV_UNSET, AUDIT_INO_UNSET, 1);
++ if (mask & (FS_CREATE|FS_MOVED_TO) && inode) {
++ struct audit_watch_ctx ctx = { .dir = dir, .child = inode };
++
++ audit_update_watch(parent, dname, inode->i_sb->s_dev, inode->i_ino, 0,
++ &ctx);
++ } else if (mask & (FS_DELETE|FS_MOVED_FROM)) {
++ struct audit_watch_ctx ctx = { .dir = dir, .child = NULL };
++
++ audit_update_watch(parent, dname, AUDIT_DEV_UNSET, AUDIT_INO_UNSET, 1,
++ &ctx);
++ }
+ else if (mask & (FS_DELETE_SELF|FS_UNMOUNT|FS_MOVE_SELF))
+ audit_remove_parent_watches(parent);
+
+@@ -505,7 +513,8 @@ static int __init audit_watch_init(void)
+ }
+ device_initcall(audit_watch_init);
+
+-int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old)
++int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old,
++ struct audit_watch_ctx *ctx)
+ {
+ struct audit_fsnotify_mark *audit_mark;
+ char *pathname;
+@@ -514,7 +523,7 @@ int audit_dupe_exe(struct audit_krule *n
+ if (!pathname)
+ return -ENOMEM;
+
+- audit_mark = audit_alloc_mark(new, pathname, strlen(pathname));
++ audit_mark = audit_alloc_mark(new, pathname, strlen(pathname), ctx);
+ if (IS_ERR(audit_mark)) {
+ kfree(pathname);
+ return PTR_ERR(audit_mark);
+--- a/kernel/auditfilter.c
++++ b/kernel/auditfilter.c
+@@ -590,7 +590,7 @@ static struct audit_entry *audit_data_to
+ err = PTR_ERR(str);
+ goto exit_free;
+ }
+- audit_mark = audit_alloc_mark(&entry->rule, str, f_val);
++ audit_mark = audit_alloc_mark(&entry->rule, str, f_val, NULL);
+ if (IS_ERR(audit_mark)) {
+ kfree(str);
+ err = PTR_ERR(audit_mark);
+@@ -818,7 +818,8 @@ static inline int audit_dupe_lsm_field(s
+ * rule with the new rule in the filterlist, then free the old rule.
+ * The rlist element is undefined; list manipulations are handled apart from
+ * the initial copy. */
+-struct audit_entry *audit_dupe_rule(struct audit_krule *old)
++struct audit_entry *audit_dupe_rule(struct audit_krule *old,
++ struct audit_watch_ctx *ctx)
+ {
+ u32 fcount = old->field_count;
+ struct audit_entry *entry;
+@@ -877,7 +878,7 @@ struct audit_entry *audit_dupe_rule(stru
+ new->filterkey = fk;
+ break;
+ case AUDIT_EXE:
+- err = audit_dupe_exe(new, old);
++ err = audit_dupe_exe(new, old, ctx);
+ break;
+ }
+ if (err) {
+@@ -1416,7 +1417,7 @@ static int update_lsm_rule(struct audit_
+ if (!security_audit_rule_known(r))
+ return 0;
+
+- nentry = audit_dupe_rule(r);
++ nentry = audit_dupe_rule(r, NULL);
+ if (entry->rule.exe)
+ audit_remove_mark(entry->rule.exe);
+ if (IS_ERR(nentry)) {
--- /dev/null
+From stable+bounces-282945-greg=kroah.com@vger.kernel.org Tue Jul 21 23:14:02 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 15:36:12 -0400
+Subject: audit: use 'unsigned int' instead of 'unsigned'
+To: stable@vger.kernel.org
+Cc: Ricardo Robaina <rrobaina@redhat.com>, Paul Moore <paul@paul-moore.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721193613.410665-2-sashal@kernel.org>
+
+From: Ricardo Robaina <rrobaina@redhat.com>
+
+[ Upstream commit 8b226771014beab1292081151a99530886ce54b4 ]
+
+Address checkpatch.pl warning below, across the audit subsystem:
+
+ WARNING: Prefer 'unsigned int' to bare use of 'unsigned'
+
+Minor cleanup, no functional changes.
+
+Signed-off-by: Ricardo Robaina <rrobaina@redhat.com>
+Signed-off-by: Paul Moore <paul@paul-moore.com>
+Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/audit.h | 4 ++--
+ include/linux/audit_arch.h | 12 ++++++------
+ kernel/audit.c | 2 +-
+ kernel/audit.h | 2 +-
+ kernel/audit_tree.c | 2 +-
+ kernel/audit_watch.c | 2 +-
+ kernel/auditfilter.c | 8 ++++----
+ kernel/auditsc.c | 2 +-
+ lib/compat_audit.c | 12 ++++++------
+ 9 files changed, 23 insertions(+), 23 deletions(-)
+
+--- a/include/linux/audit.h
++++ b/include/linux/audit.h
+@@ -125,8 +125,8 @@ enum audit_nfcfgop {
+ AUDIT_NFT_OP_INVALID,
+ };
+
+-extern int __init audit_register_class(int class, unsigned *list);
+-extern int audit_classify_syscall(int abi, unsigned syscall);
++extern int __init audit_register_class(int class, unsigned int *list);
++extern int audit_classify_syscall(int abi, unsigned int syscall);
+ extern int audit_classify_arch(int arch);
+
+ /* audit_names->type values */
+--- a/include/linux/audit_arch.h
++++ b/include/linux/audit_arch.h
+@@ -21,13 +21,13 @@ enum auditsc_class_t {
+ AUDITSC_NVALS /* count */
+ };
+
+-extern int audit_classify_compat_syscall(int abi, unsigned syscall);
++extern int audit_classify_compat_syscall(int abi, unsigned int syscall);
+
+ /* only for compat system calls */
+-extern unsigned compat_write_class[];
+-extern unsigned compat_read_class[];
+-extern unsigned compat_dir_class[];
+-extern unsigned compat_chattr_class[];
+-extern unsigned compat_signal_class[];
++extern unsigned int compat_write_class[];
++extern unsigned int compat_read_class[];
++extern unsigned int compat_dir_class[];
++extern unsigned int compat_chattr_class[];
++extern unsigned int compat_signal_class[];
+
+ #endif
+--- a/kernel/audit.c
++++ b/kernel/audit.c
+@@ -2030,7 +2030,7 @@ void audit_log_vformat(struct audit_buff
+ * here and AUDIT_BUFSIZ is at least 1024, then we can
+ * log everything that printk could have logged. */
+ avail = audit_expand(ab,
+- max_t(unsigned, AUDIT_BUFSIZ, 1+len-avail));
++ max_t(unsigned int, AUDIT_BUFSIZ, 1+len-avail));
+ if (!avail)
+ goto out_va_end;
+ len = vsnprintf(skb_tail_pointer(skb), avail, fmt, args2);
+--- a/kernel/audit.h
++++ b/kernel/audit.h
+@@ -233,7 +233,7 @@ static inline int audit_hash_ino(u64 ino
+ /* Indicates that audit should log the full pathname. */
+ #define AUDIT_NAME_FULL -1
+
+-extern int audit_match_class(int class, unsigned syscall);
++extern int audit_match_class(int class, unsigned int syscall);
+ extern int audit_comparator(const u32 left, const u32 op, const u32 right);
+ extern int audit_uid_comparator(kuid_t left, u32 op, kuid_t right);
+ extern int audit_gid_comparator(kgid_t left, u32 op, kgid_t right);
+--- a/kernel/audit_tree.c
++++ b/kernel/audit_tree.c
+@@ -33,7 +33,7 @@ struct audit_chunk {
+ struct audit_node {
+ struct list_head list;
+ struct audit_tree *owner;
+- unsigned index; /* index; upper bit indicates 'will prune' */
++ unsigned int index; /* index; upper bit indicates 'will prune' */
+ } owners[] __counted_by(count);
+ };
+
+--- a/kernel/audit_watch.c
++++ b/kernel/audit_watch.c
+@@ -244,7 +244,7 @@ static void audit_watch_log_rule_change(
+ /* Update inode info in audit rules based on filesystem event. */
+ static void audit_update_watch(struct audit_parent *parent,
+ const struct qstr *dname, dev_t dev,
+- u64 ino, unsigned invalidating)
++ u64 ino, unsigned int invalidating)
+ {
+ struct audit_watch *owatch, *nwatch, *nextw;
+ struct audit_krule *r, *nextr;
+--- a/kernel/auditfilter.c
++++ b/kernel/auditfilter.c
+@@ -165,13 +165,13 @@ static inline int audit_to_inode(struct
+
+ static __u32 *classes[AUDIT_SYSCALL_CLASSES];
+
+-int __init audit_register_class(int class, unsigned *list)
++int __init audit_register_class(int class, unsigned int *list)
+ {
+ __u32 *p = kcalloc(AUDIT_BITMASK_SIZE, sizeof(__u32), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
+ while (*list != ~0U) {
+- unsigned n = *list++;
++ unsigned int n = *list++;
+ if (n >= AUDIT_BITMASK_SIZE * 32 - AUDIT_SYSCALL_CLASSES) {
+ kfree(p);
+ return -EINVAL;
+@@ -186,7 +186,7 @@ int __init audit_register_class(int clas
+ return 0;
+ }
+
+-int audit_match_class(int class, unsigned syscall)
++int audit_match_class(int class, unsigned int syscall)
+ {
+ if (unlikely(syscall >= AUDIT_BITMASK_SIZE * 32))
+ return 0;
+@@ -237,7 +237,7 @@ static int audit_match_signal(struct aud
+ /* Common user-space to kernel rule translation. */
+ static inline struct audit_entry *audit_to_entry_common(struct audit_rule_data *rule)
+ {
+- unsigned listnr;
++ unsigned int listnr;
+ struct audit_entry *entry;
+ int i, err;
+
+--- a/kernel/auditsc.c
++++ b/kernel/auditsc.c
+@@ -150,7 +150,7 @@ static const struct audit_nfcfgop_tab au
+
+ static int audit_match_perm(struct audit_context *ctx, int mask)
+ {
+- unsigned n;
++ unsigned int n;
+
+ if (unlikely(!ctx))
+ return 0;
+--- a/lib/compat_audit.c
++++ b/lib/compat_audit.c
+@@ -4,32 +4,32 @@
+ #include <linux/audit_arch.h>
+ #include <asm/unistd32.h>
+
+-unsigned compat_dir_class[] = {
++unsigned int compat_dir_class[] = {
+ #include <asm-generic/audit_dir_write.h>
+ ~0U
+ };
+
+-unsigned compat_read_class[] = {
++unsigned int compat_read_class[] = {
+ #include <asm-generic/audit_read.h>
+ ~0U
+ };
+
+-unsigned compat_write_class[] = {
++unsigned int compat_write_class[] = {
+ #include <asm-generic/audit_write.h>
+ ~0U
+ };
+
+-unsigned compat_chattr_class[] = {
++unsigned int compat_chattr_class[] = {
+ #include <asm-generic/audit_change_attr.h>
+ ~0U
+ };
+
+-unsigned compat_signal_class[] = {
++unsigned int compat_signal_class[] = {
+ #include <asm-generic/audit_signal.h>
+ ~0U
+ };
+
+-int audit_classify_compat_syscall(int abi, unsigned syscall)
++int audit_classify_compat_syscall(int abi, unsigned int syscall)
+ {
+ switch (syscall) {
+ #ifdef __NR_open
--- /dev/null
+From stable+bounces-282943-greg=kroah.com@vger.kernel.org Tue Jul 21 21:45:45 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 15:36:11 -0400
+Subject: audit: widen ino fields to u64
+To: stable@vger.kernel.org
+Cc: Jeff Layton <jlayton@kernel.org>, Paul Moore <paul@paul-moore.com>, Christian Brauner <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260721193613.410665-1-sashal@kernel.org>
+
+From: Jeff Layton <jlayton@kernel.org>
+
+[ Upstream commit 125dfa218134df7cc112667e92984de9d8cd0bf6 ]
+
+inode->i_ino is being widened from unsigned long to u64. The audit
+subsystem uses unsigned long ino in struct fields, function parameters,
+and local variables that store inode numbers from arbitrary filesystems.
+On 32-bit platforms this truncates inode numbers that exceed 32 bits,
+which will cause incorrect audit log entries and broken watch/mark
+comparisons.
+
+Widen all audit ino fields, parameters, and locals to u64, and update
+the inode format string from %lu to %llu to match.
+
+Signed-off-by: Jeff Layton <jlayton@kernel.org>
+Link: https://patch.msgid.link/20260304-iino-u64-v3-2-2257ad83d372@kernel.org
+Acked-by: Paul Moore <paul@paul-moore.com>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 81905b5acbe7 ("audit: fix recursive locking deadlock in audit_dupe_exe()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/audit.h | 2 +-
+ kernel/audit.h | 13 ++++++-------
+ kernel/audit_fsnotify.c | 4 ++--
+ kernel/audit_watch.c | 12 ++++++------
+ kernel/auditsc.c | 4 ++--
+ 5 files changed, 17 insertions(+), 18 deletions(-)
+
+--- a/include/linux/audit.h
++++ b/include/linux/audit.h
+@@ -16,7 +16,7 @@
+ #include <uapi/linux/netfilter/nf_tables.h>
+ #include <uapi/linux/fanotify.h>
+
+-#define AUDIT_INO_UNSET ((unsigned long)-1)
++#define AUDIT_INO_UNSET ((u64)-1)
+ #define AUDIT_DEV_UNSET ((dev_t)-1)
+
+ struct audit_sig_info {
+--- a/kernel/audit.h
++++ b/kernel/audit.h
+@@ -76,7 +76,7 @@ struct audit_names {
+ int name_len; /* number of chars to log */
+ bool hidden; /* don't log this record */
+
+- unsigned long ino;
++ u64 ino;
+ dev_t dev;
+ umode_t mode;
+ kuid_t uid;
+@@ -225,9 +225,9 @@ extern int auditd_test_task(struct task_
+ #define AUDIT_INODE_BUCKETS 32
+ extern struct list_head audit_inode_hash[AUDIT_INODE_BUCKETS];
+
+-static inline int audit_hash_ino(u32 ino)
++static inline int audit_hash_ino(u64 ino)
+ {
+- return (ino & (AUDIT_INODE_BUCKETS-1));
++ return ((u32)ino & (AUDIT_INODE_BUCKETS-1));
+ }
+
+ /* Indicates that audit should log the full pathname. */
+@@ -277,16 +277,15 @@ extern int audit_to_watch(struct audit_k
+ extern int audit_add_watch(struct audit_krule *krule, struct list_head **list);
+ extern void audit_remove_watch_rule(struct audit_krule *krule);
+ extern char *audit_watch_path(struct audit_watch *watch);
+-extern int audit_watch_compare(struct audit_watch *watch, unsigned long ino,
+- dev_t dev);
++extern int audit_watch_compare(struct audit_watch *watch, u64 ino, dev_t dev);
+
+ extern struct audit_fsnotify_mark *audit_alloc_mark(struct audit_krule *krule,
+ char *pathname, int len);
+ extern char *audit_mark_path(struct audit_fsnotify_mark *mark);
+ extern void audit_remove_mark(struct audit_fsnotify_mark *audit_mark);
+ extern void audit_remove_mark_rule(struct audit_krule *krule);
+-extern int audit_mark_compare(struct audit_fsnotify_mark *mark,
+- unsigned long ino, dev_t dev);
++extern int audit_mark_compare(struct audit_fsnotify_mark *mark, u64 ino,
++ dev_t dev);
+ extern int audit_dupe_exe(struct audit_krule *new, struct audit_krule *old);
+ extern int audit_exe_compare(struct task_struct *tsk,
+ struct audit_fsnotify_mark *mark);
+--- a/kernel/audit_fsnotify.c
++++ b/kernel/audit_fsnotify.c
+@@ -25,7 +25,7 @@
+ */
+ struct audit_fsnotify_mark {
+ dev_t dev; /* associated superblock device */
+- unsigned long ino; /* associated inode number */
++ u64 ino; /* associated inode number */
+ char *path; /* insertion path */
+ struct fsnotify_mark mark; /* fsnotify mark on the inode */
+ struct audit_krule *rule;
+@@ -57,7 +57,7 @@ char *audit_mark_path(struct audit_fsnot
+ return mark->path;
+ }
+
+-int audit_mark_compare(struct audit_fsnotify_mark *mark, unsigned long ino, dev_t dev)
++int audit_mark_compare(struct audit_fsnotify_mark *mark, u64 ino, dev_t dev)
+ {
+ if (mark->ino == AUDIT_INO_UNSET)
+ return 0;
+--- a/kernel/audit_watch.c
++++ b/kernel/audit_watch.c
+@@ -37,7 +37,7 @@ struct audit_watch {
+ refcount_t count; /* reference count */
+ dev_t dev; /* associated superblock device */
+ char *path; /* insertion path */
+- unsigned long ino; /* associated inode number */
++ u64 ino; /* associated inode number */
+ struct audit_parent *parent; /* associated parent */
+ struct list_head wlist; /* entry in parent->watches list */
+ struct list_head rules; /* anchor for krule->rlist */
+@@ -125,7 +125,7 @@ char *audit_watch_path(struct audit_watc
+ return watch->path;
+ }
+
+-int audit_watch_compare(struct audit_watch *watch, unsigned long ino, dev_t dev)
++int audit_watch_compare(struct audit_watch *watch, u64 ino, dev_t dev)
+ {
+ return (watch->ino != AUDIT_INO_UNSET) &&
+ (watch->ino == ino) &&
+@@ -244,7 +244,7 @@ static void audit_watch_log_rule_change(
+ /* Update inode info in audit rules based on filesystem event. */
+ static void audit_update_watch(struct audit_parent *parent,
+ const struct qstr *dname, dev_t dev,
+- unsigned long ino, unsigned invalidating)
++ u64 ino, unsigned invalidating)
+ {
+ struct audit_watch *owatch, *nwatch, *nextw;
+ struct audit_krule *r, *nextr;
+@@ -285,7 +285,7 @@ static void audit_update_watch(struct au
+ list_del(&oentry->rule.list);
+ audit_panic("error updating watch, removing");
+ } else {
+- int h = audit_hash_ino((u32)ino);
++ int h = audit_hash_ino(ino);
+
+ /*
+ * nentry->rule.watch == oentry->rule.watch so
+@@ -439,7 +439,7 @@ int audit_add_watch(struct audit_krule *
+
+ audit_add_to_parent(krule, parent);
+
+- h = audit_hash_ino((u32)watch->ino);
++ h = audit_hash_ino(watch->ino);
+ *list = &audit_inode_hash[h];
+ error:
+ path_put(&parent_path);
+@@ -527,7 +527,7 @@ int audit_dupe_exe(struct audit_krule *n
+ int audit_exe_compare(struct task_struct *tsk, struct audit_fsnotify_mark *mark)
+ {
+ struct file *exe_file;
+- unsigned long ino;
++ u64 ino;
+ dev_t dev;
+
+ /* only do exe filtering if we are recording @current events/records */
+--- a/kernel/auditsc.c
++++ b/kernel/auditsc.c
+@@ -886,7 +886,7 @@ static int audit_filter_inode_name(struc
+ struct audit_names *n,
+ struct audit_context *ctx)
+ {
+- int h = audit_hash_ino((u32)n->ino);
++ int h = audit_hash_ino(n->ino);
+ struct list_head *list = &audit_inode_hash[h];
+
+ return __audit_filter_op(tsk, ctx, list, n, ctx->major);
+@@ -1534,7 +1534,7 @@ static void audit_log_name(struct audit_
+ audit_log_format(ab, " name=(null)");
+
+ if (n->ino != AUDIT_INO_UNSET)
+- audit_log_format(ab, " inode=%lu dev=%02x:%02x mode=%#ho ouid=%u ogid=%u rdev=%02x:%02x",
++ audit_log_format(ab, " inode=%llu dev=%02x:%02x mode=%#ho ouid=%u ogid=%u rdev=%02x:%02x",
+ n->ino,
+ MAJOR(n->dev),
+ MINOR(n->dev),
--- /dev/null
+From stable+bounces-290081-greg=kroah.com@vger.kernel.org Tue Jul 28 19:58:29 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 12:29:46 -0400
+Subject: bootconfig: fix NULL-pointer arithmetic in xbc_snprint_cmdline()
+To: stable@vger.kernel.org
+Cc: Breno Leitao <leitao@debian.org>, "Masami Hiramatsu (Google)" <mhiramat@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728162946.99526-2-sashal@kernel.org>
+
+From: Breno Leitao <leitao@debian.org>
+
+[ Upstream commit dec4d8118c179b3d12bca7e609054c6011c4f2ce ]
+
+xbc_snprint_cmdline() is meant to be called twice: first with
+buf=NULL, size=0 to probe the rendered length, then with a real
+buffer to fill it (the standard snprintf() two-pass pattern). The
+probe call makes the function compute "buf + size" (NULL + 0) and,
+on every iteration, advance "buf += ret" from that NULL base and
+pass the result back into snprintf().
+
+Pointer arithmetic on a NULL pointer is undefined behavior. It is
+harmless in the in-kernel callers today, but the follow-up patches
+run this same code in the userspace tools/bootconfig parser at kernel
+build time, where host UBSan / FORTIFY_SOURCE abort the build.
+
+Track a running written length (size_t) instead of mutating @buf, and
+only form "buf + len" when @buf is non-NULL. snprintf(NULL, 0, ...)
+is itself well defined and returns the would-be length, so the
+two-pass "probe then fill" usage returns identical byte counts.
+
+Link: https://lore.kernel.org/all/20260626-bootconfig_using_tools-v7-1-24ab72139c29@debian.org/
+
+Fixes: 51887d03aca1 ("bootconfig: init: Allow admin to use bootconfig for kernel command line")
+Cc: stable@vger.kernel.org
+Signed-off-by: Breno Leitao <leitao@debian.org>
+Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ lib/bootconfig.c | 23 ++++++++++++++++-------
+ 1 file changed, 16 insertions(+), 7 deletions(-)
+
+--- a/lib/bootconfig.c
++++ b/lib/bootconfig.c
+@@ -424,10 +424,18 @@ static char xbc_namebuf[XBC_KEYLEN_MAX]
+ int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root)
+ {
+ struct xbc_node *knode, *vnode;
+- char *end = buf + size;
+ const char *val, *q;
++ size_t len = 0;
+ int ret;
+
++ /*
++ * Track the running written length rather than advancing @buf, so we
++ * never form "buf + size" or "buf += ret" while @buf is NULL (the
++ * size-probe call passes buf=NULL, size=0). NULL pointer arithmetic
++ * is undefined behavior and trips host UBSan / FORTIFY_SOURCE when
++ * this renderer runs at kernel build time. snprintf(NULL, 0, ...)
++ * itself is well defined and returns the would-be length.
++ */
+ xbc_node_for_each_key_value(root, knode, val) {
+ ret = xbc_node_compose_key_after(root, knode,
+ xbc_namebuf, XBC_KEYLEN_MAX);
+@@ -436,10 +444,11 @@ int __init xbc_snprint_cmdline(char *buf
+
+ vnode = xbc_node_get_child(knode);
+ if (!vnode) {
+- ret = snprintf(buf, rest(buf, end), "%s ", xbc_namebuf);
++ ret = snprintf(buf ? buf + len : NULL, rest(len, size),
++ "%s ", xbc_namebuf);
+ if (ret < 0)
+ return ret;
+- buf += ret;
++ len += ret;
+ continue;
+ }
+ xbc_array_for_each_value(vnode, val) {
+@@ -449,15 +458,15 @@ int __init xbc_snprint_cmdline(char *buf
+ * whitespace.
+ */
+ q = strpbrk(val, " \t\r\n") ? "\"" : "";
+- ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ",
+- xbc_namebuf, q, val, q);
++ ret = snprintf(buf ? buf + len : NULL, rest(len, size),
++ "%s=%s%s%s ", xbc_namebuf, q, val, q);
+ if (ret < 0)
+ return ret;
+- buf += ret;
++ len += ret;
+ }
+ }
+
+- return buf - (end - size);
++ return len;
+ }
+ #undef rest
+
--- /dev/null
+From stable+bounces-290080-greg=kroah.com@vger.kernel.org Tue Jul 28 19:24:02 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 12:29:45 -0400
+Subject: bootconfig: move xbc_snprint_cmdline() to lib/bootconfig.c
+To: stable@vger.kernel.org
+Cc: Breno Leitao <leitao@debian.org>, "Masami Hiramatsu (Google)" <mhiramat@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728162946.99526-1-sashal@kernel.org>
+
+From: Breno Leitao <leitao@debian.org>
+
+[ Upstream commit 5a643e4623238e14b03d75ca0d4eda0645720cee ]
+
+Move xbc_snprint_cmdline() from init/main.c to lib/bootconfig.c so the
+function (and its xbc_namebuf scratch buffer) becomes part of the shared
+parser library. tools/bootconfig already compiles lib/bootconfig.c
+directly, which lets a follow-up patch reuse the same renderer in the
+userspace tool to convert a bootconfig file into a flat cmdline string
+at build time.
+
+No functional change.
+
+Link: https://lore.kernel.org/all/20260508-bootconfig_using_tools-v1-1-1132219aa773@debian.org/
+
+Signed-off-by: Breno Leitao <leitao@debian.org>
+Signed-off-by: Masami Hiramatsu (Google) <mhiramat@kernel.org>
+Stable-dep-of: dec4d8118c17 ("bootconfig: fix NULL-pointer arithmetic in xbc_snprint_cmdline()")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/bootconfig.h | 3 ++
+ init/main.c | 45 ------------------------------------
+ lib/bootconfig.c | 56 +++++++++++++++++++++++++++++++++++++++++++++
+ 3 files changed, 59 insertions(+), 45 deletions(-)
+
+--- a/include/linux/bootconfig.h
++++ b/include/linux/bootconfig.h
+@@ -265,6 +265,9 @@ static inline struct xbc_node * __init x
+ int __init xbc_node_compose_key_after(struct xbc_node *root,
+ struct xbc_node *node, char *buf, size_t size);
+
++/* Render key/value pairs under @root as a flat cmdline string */
++int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root);
++
+ /**
+ * xbc_node_compose_key() - Compose full key string of the XBC node
+ * @node: An XBC node.
+--- a/init/main.c
++++ b/init/main.c
+@@ -320,51 +320,6 @@ static void * __init get_boot_config_fro
+
+ #ifdef CONFIG_BOOT_CONFIG
+
+-static char xbc_namebuf[XBC_KEYLEN_MAX] __initdata;
+-
+-#define rest(dst, end) ((end) > (dst) ? (end) - (dst) : 0)
+-
+-static int __init xbc_snprint_cmdline(char *buf, size_t size,
+- struct xbc_node *root)
+-{
+- struct xbc_node *knode, *vnode;
+- char *end = buf + size;
+- const char *val, *q;
+- int ret;
+-
+- xbc_node_for_each_key_value(root, knode, val) {
+- ret = xbc_node_compose_key_after(root, knode,
+- xbc_namebuf, XBC_KEYLEN_MAX);
+- if (ret < 0)
+- return ret;
+-
+- vnode = xbc_node_get_child(knode);
+- if (!vnode) {
+- ret = snprintf(buf, rest(buf, end), "%s ", xbc_namebuf);
+- if (ret < 0)
+- return ret;
+- buf += ret;
+- continue;
+- }
+- xbc_array_for_each_value(vnode, val) {
+- /*
+- * For prettier and more readable /proc/cmdline, only
+- * quote the value when necessary, i.e. when it contains
+- * whitespace.
+- */
+- q = strpbrk(val, " \t\r\n") ? "\"" : "";
+- ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ",
+- xbc_namebuf, q, val, q);
+- if (ret < 0)
+- return ret;
+- buf += ret;
+- }
+- }
+-
+- return buf - (end - size);
+-}
+-#undef rest
+-
+ /* Make an extra command line under given key word */
+ static char * __init xbc_make_cmdline(const char *key)
+ {
+--- a/lib/bootconfig.c
++++ b/lib/bootconfig.c
+@@ -405,6 +405,62 @@ const char * __init xbc_node_find_next_k
+ return ""; /* No value key */
+ }
+
++static char xbc_namebuf[XBC_KEYLEN_MAX] __initdata;
++
++#define rest(dst, end) ((end) > (dst) ? (end) - (dst) : 0)
++
++/**
++ * xbc_snprint_cmdline() - Render bootconfig keys under @root as a cmdline string
++ * @buf: Destination buffer (may be NULL when @size is 0 to query the length)
++ * @size: Size of @buf in bytes
++ * @root: Subtree root whose key=value pairs should be rendered
++ *
++ * Walk all key/value pairs under @root and emit them as a space-separated
++ * cmdline string into @buf. Values containing whitespace are quoted with
++ * double quotes. Returns the number of bytes that would be written if @buf
++ * were large enough (matching snprintf semantics), or a negative errno on
++ * failure.
++ */
++int __init xbc_snprint_cmdline(char *buf, size_t size, struct xbc_node *root)
++{
++ struct xbc_node *knode, *vnode;
++ char *end = buf + size;
++ const char *val, *q;
++ int ret;
++
++ xbc_node_for_each_key_value(root, knode, val) {
++ ret = xbc_node_compose_key_after(root, knode,
++ xbc_namebuf, XBC_KEYLEN_MAX);
++ if (ret < 0)
++ return ret;
++
++ vnode = xbc_node_get_child(knode);
++ if (!vnode) {
++ ret = snprintf(buf, rest(buf, end), "%s ", xbc_namebuf);
++ if (ret < 0)
++ return ret;
++ buf += ret;
++ continue;
++ }
++ xbc_array_for_each_value(vnode, val) {
++ /*
++ * For prettier and more readable /proc/cmdline, only
++ * quote the value when necessary, i.e. when it contains
++ * whitespace.
++ */
++ q = strpbrk(val, " \t\r\n") ? "\"" : "";
++ ret = snprintf(buf, rest(buf, end), "%s=%s%s%s ",
++ xbc_namebuf, q, val, q);
++ if (ret < 0)
++ return ret;
++ buf += ret;
++ }
++ }
++
++ return buf - (end - size);
++}
++#undef rest
++
+ /* XBC parse and tree build */
+
+ static int __init xbc_init_node(struct xbc_node *node, char *data, uint32_t flag)
--- /dev/null
+From stable+bounces-289686-greg=kroah.com@vger.kernel.org Mon Jul 27 23:04:43 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 16:59:56 -0400
+Subject: cred: add kernel_cred() helper
+To: stable@vger.kernel.org
+Cc: Christian Brauner <brauner@kernel.org>, Jens Axboe <axboe@kernel.dk>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727205958.1929027-1-sashal@kernel.org>
+
+From: Christian Brauner <brauner@kernel.org>
+
+[ Upstream commit 4c7ceeb62d3330b6fb2b549ae833a92c0f481f3e ]
+
+Access kernel creds based off of init_task. This will let us avoid any
+direct access to init_cred.
+
+Link: https://patch.msgid.link/20251103-work-creds-init_cred-v1-2-cb3ec8711a6a@kernel.org
+Reviewed-by: Jens Axboe <axboe@kernel.dk>
+Signed-off-by: Christian Brauner <brauner@kernel.org>
+Stable-dep-of: 981ccd97f715 ("dm: avoid leaking the caller's thread keyring via the table device file")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/cred.h | 7 +++++++
+ 1 file changed, 7 insertions(+)
+
+--- a/include/linux/cred.h
++++ b/include/linux/cred.h
+@@ -20,6 +20,8 @@
+ struct cred;
+ struct inode;
+
++extern struct task_struct init_task;
++
+ /*
+ * COW Supplementary groups list
+ */
+@@ -156,6 +158,11 @@ extern struct cred *prepare_exec_creds(v
+ extern int commit_creds(struct cred *);
+ extern void abort_creds(struct cred *);
+ extern struct cred *prepare_kernel_cred(struct task_struct *);
++static inline const struct cred *kernel_cred(void)
++{
++ /* shut up sparse */
++ return rcu_dereference_raw(init_task.cred);
++}
+ extern int set_security_override(struct cred *, u32);
+ extern int set_security_override_from_ctx(struct cred *, const char *);
+ extern int set_create_files_as(struct cred *, struct inode *);
--- /dev/null
+From stable+bounces-288498-greg=kroah.com@vger.kernel.org Thu Jul 23 20:29:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:28:06 -0400
+Subject: cxl: Fix CXL_HEADERLOG_SIZE to match RAS Capability size
+To: stable@vger.kernel.org
+Cc: Terry Bowman <terry.bowman@amd.com>, Alison Schofield <alison.schofield@intel.com>, Dave Jiang <dave.jiang@intel.com>, Ben Cheatham <benjamin.cheatham@amd.com>, Richard Cheng <icheng@nvidia.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723182806.3493205-4-sashal@kernel.org>
+
+From: Terry Bowman <terry.bowman@amd.com>
+
+[ Upstream commit c268f949e219f9e179558e836f457f6c5fbec416 ]
+
+The CXL r4.0 8.2.4.17.7 RAS Capability Structure has total length 0x58
+bytes (CXL_RAS_CAPABILITY_LENGTH); the Header Log occupies the trailing
+64 bytes at offset 0x18. CXL_HEADERLOG_SIZE was defined as SZ_512,
+eight times the actual on-device size.
+
+header_log_copy() reads CXL_HEADERLOG_SIZE_U32 (128) dwords from the
+RAS capability iomap, overrunning the 88-byte mapping by 448 bytes.
+The cxl_aer_uncorrectable_error trace event memcpy()s CXL_HEADERLOG_SIZE
+(512) bytes from its source. For the CPER caller the source is
+struct cxl_ras_capability_regs::header_log[16] (64 bytes) embedded in a
+stack-local cxl_cper_prot_err_work_data, so the memcpy reads 448 bytes
+of kernel stack into the trace event ring buffer where userspace can
+read it via tracefs.
+
+Set CXL_HEADERLOG_SIZE to 64 and derive CXL_HEADERLOG_SIZE_U32 from it,
+bringing all iomap readers into agreement on 16 dwords. Userspace tools
+such as rasdaemon have grown a dependency on the buggy 512-byte (128 u32)
+header_log layout in the cxl_aer_uncorrectable_error trace event. Add
+CXL_HEADERLOG_TRACE_SIZE_U32 = 128 and use it for the trace event
+__array and its memcpy to preserve that ABI. Both callers now pass a
+zero-filled u32[CXL_HEADERLOG_TRACE_SIZE_U32] staging buffer with only
+the first CXL_HEADERLOG_SIZE_U32 (16) entries populated from hardware;
+the remaining 112 u32s are zero-padded, keeping the 512-byte trace ring
+buffer layout intact.
+
+[ dj: Replaced 64 with SZ_64 per RichardC ]
+
+Fixes: 36f257e3b0ba ("acpi/ghes, cxl/pci: Process CXL CPER Protocol Errors")
+Fixes: 2905cb5236cb ("cxl/pci: Add (hopeful) error handling support")
+Cc: stable@vger.kernel.org
+Reported-by: Sashiko
+Signed-off-by: Terry Bowman <terry.bowman@amd.com>
+Reviewed-by: Alison Schofield <alison.schofield@intel.com>
+Reviewed-by: Dave Jiang <dave.jiang@intel.com>
+Reviewed-by: Ben Cheatham <benjamin.cheatham@amd.com>
+Reviewed-by: Richard Cheng <icheng@nvidia.com>
+Link: https://patch.msgid.link/20260605180610.2249458-1-terry.bowman@amd.com
+Signed-off-by: Dave Jiang <dave.jiang@intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/cxl/core/ras.c | 27 ++++++++++++++++++++-------
+ drivers/cxl/core/trace.h | 24 ++++++++++++++++--------
+ drivers/cxl/cxl.h | 14 ++++++++++++--
+ 3 files changed, 48 insertions(+), 17 deletions(-)
+
+--- a/drivers/cxl/core/ras.c
++++ b/drivers/cxl/core/ras.c
+@@ -8,6 +8,10 @@
+ #include <cxlpci.h>
+ #include "trace.h"
+
++/* Check that UCE header definition is maintained to keep ABI intact */
++static_assert(CXL_HEADERLOG_TRACE_SIZE_U32 == 128,
++ "rasdaemon ABI requires exactly 128 u32s");
++
+ static void cxl_cper_trace_corr_port_prot_err(struct pci_dev *pdev,
+ struct cxl_ras_capability_regs ras_cap)
+ {
+@@ -19,6 +23,7 @@ static void cxl_cper_trace_corr_port_pro
+ static void cxl_cper_trace_uncorr_port_prot_err(struct pci_dev *pdev,
+ struct cxl_ras_capability_regs ras_cap)
+ {
++ u32 hl[CXL_HEADERLOG_TRACE_SIZE_U32] = {};
+ u32 status = ras_cap.uncor_status & ~ras_cap.uncor_mask;
+ u32 fe;
+
+@@ -28,8 +33,8 @@ static void cxl_cper_trace_uncorr_port_p
+ else
+ fe = status;
+
+- trace_cxl_port_aer_uncorrectable_error(&pdev->dev, status, fe,
+- ras_cap.header_log);
++ memcpy(hl, ras_cap.header_log, CXL_HEADERLOG_SIZE);
++ trace_cxl_port_aer_uncorrectable_error(&pdev->dev, status, fe, hl);
+ }
+
+ static void cxl_cper_trace_corr_prot_err(struct cxl_memdev *cxlmd,
+@@ -44,6 +49,7 @@ static void
+ cxl_cper_trace_uncorr_prot_err(struct cxl_memdev *cxlmd,
+ struct cxl_ras_capability_regs ras_cap)
+ {
++ u32 hl[CXL_HEADERLOG_TRACE_SIZE_U32] = {};
+ u32 status = ras_cap.uncor_status & ~ras_cap.uncor_mask;
+ u32 fe;
+
+@@ -53,8 +59,15 @@ cxl_cper_trace_uncorr_prot_err(struct cx
+ else
+ fe = status;
+
+- trace_cxl_aer_uncorrectable_error(cxlmd, status, fe,
+- ras_cap.header_log);
++ /*
++ * ras_cap.header_log[] holds CXL_HEADERLOG_SIZE_U32 (16) hardware
++ * dwords. Copy them into the front of a zero-filled
++ * CXL_HEADERLOG_TRACE_SIZE_U32 (128) u32 staging buffer so the trace
++ * event memcpy sees a full 512-byte source and the userspace ABI
++ * (rasdaemon) is preserved.
++ */
++ memcpy(hl, ras_cap.header_log, CXL_HEADERLOG_SIZE);
++ trace_cxl_aer_uncorrectable_error(cxlmd, status, fe, hl);
+ }
+
+ static int match_memdev_by_parent(struct device *dev, const void *uport)
+@@ -181,12 +194,12 @@ static void header_log_copy(void __iomem
+ {
+ void __iomem *addr;
+ u32 *log_addr;
+- int i, log_u32_size = CXL_HEADERLOG_SIZE / sizeof(u32);
++ int i;
+
+ addr = ras_base + CXL_RAS_HEADER_LOG_OFFSET;
+ log_addr = log;
+
+- for (i = 0; i < log_u32_size; i++) {
++ for (i = 0; i < CXL_HEADERLOG_SIZE_U32; i++) {
+ *log_addr = readl(addr);
+ log_addr++;
+ addr += sizeof(u32);
+@@ -199,7 +212,7 @@ static void header_log_copy(void __iomem
+ */
+ bool cxl_handle_ras(struct cxl_dev_state *cxlds, void __iomem *ras_base)
+ {
+- u32 hl[CXL_HEADERLOG_SIZE_U32];
++ u32 hl[CXL_HEADERLOG_TRACE_SIZE_U32] = {};
+ void __iomem *addr;
+ u32 status;
+ u32 fe;
+--- a/drivers/cxl/core/trace.h
++++ b/drivers/cxl/core/trace.h
+@@ -56,7 +56,7 @@ TRACE_EVENT(cxl_port_aer_uncorrectable_e
+ __string(host, dev_name(dev->parent))
+ __field(u32, status)
+ __field(u32, first_error)
+- __array(u32, header_log, CXL_HEADERLOG_SIZE_U32)
++ __array(u32, header_log, CXL_HEADERLOG_TRACE_SIZE_U32)
+ ),
+ TP_fast_assign(
+ __assign_str(device);
+@@ -64,10 +64,14 @@ TRACE_EVENT(cxl_port_aer_uncorrectable_e
+ __entry->status = status;
+ __entry->first_error = fe;
+ /*
+- * Embed the 512B headerlog data for user app retrieval and
+- * parsing, but no need to print this in the trace buffer.
++ * Embed headerlog data for user app retrieval and parsing,
++ * but no need to print in the trace buffer. Only
++ * CXL_HEADERLOG_SIZE_U32 (16) dwords are hardware data;
++ * the remaining entries preserve the 512-byte ABI layout
++ * rasdaemon depends on and are zero-filled by the caller.
+ */
+- memcpy(__entry->header_log, hl, CXL_HEADERLOG_SIZE);
++ memcpy(__entry->header_log, hl,
++ CXL_HEADERLOG_TRACE_SIZE_U32 * sizeof(u32));
+ ),
+ TP_printk("device=%s host=%s status: '%s' first_error: '%s'",
+ __get_str(device), __get_str(host),
+@@ -85,7 +89,7 @@ TRACE_EVENT(cxl_aer_uncorrectable_error,
+ __field(u64, serial)
+ __field(u32, status)
+ __field(u32, first_error)
+- __array(u32, header_log, CXL_HEADERLOG_SIZE_U32)
++ __array(u32, header_log, CXL_HEADERLOG_TRACE_SIZE_U32)
+ ),
+ TP_fast_assign(
+ __assign_str(memdev);
+@@ -94,10 +98,14 @@ TRACE_EVENT(cxl_aer_uncorrectable_error,
+ __entry->status = status;
+ __entry->first_error = fe;
+ /*
+- * Embed the 512B headerlog data for user app retrieval and
+- * parsing, but no need to print this in the trace buffer.
++ * Embed headerlog data for user app retrieval and parsing,
++ * but no need to print in the trace buffer. Only
++ * CXL_HEADERLOG_SIZE_U32 (16) dwords are hardware data;
++ * the remaining entries preserve the 512-byte ABI layout
++ * rasdaemon depends on and are zero-filled by the caller.
+ */
+- memcpy(__entry->header_log, hl, CXL_HEADERLOG_SIZE);
++ memcpy(__entry->header_log, hl,
++ CXL_HEADERLOG_TRACE_SIZE_U32 * sizeof(u32));
+ ),
+ TP_printk("memdev=%s host=%s serial=%lld: status: '%s' first_error: '%s'",
+ __get_str(memdev), __get_str(host), __entry->serial,
+--- a/drivers/cxl/cxl.h
++++ b/drivers/cxl/cxl.h
+@@ -148,8 +148,18 @@ static inline int ways_to_eiw(unsigned i
+ #define CXL_RAS_CAP_CONTROL_FE_MASK GENMASK(5, 0)
+ #define CXL_RAS_HEADER_LOG_OFFSET 0x18
+ #define CXL_RAS_CAPABILITY_LENGTH 0x58
+-#define CXL_HEADERLOG_SIZE SZ_512
+-#define CXL_HEADERLOG_SIZE_U32 SZ_512 / sizeof(u32)
++#define CXL_HEADERLOG_SIZE SZ_64
++#define CXL_HEADERLOG_SIZE_U32 (CXL_HEADERLOG_SIZE / sizeof(u32))
++
++/*
++ * The RAS UCE trace event header array was originally sized at SZ_512/sizeof(u32)
++ * = 128 u32s due to a bug. Userspace tools (rasdaemon) have grown a dependency
++ * on that 512-byte layout. Keep the trace array at 128 u32s to preserve the
++ * ABI; only CXL_HEADERLOG_SIZE_U32 (16) dwords are valid hardware data, the
++ * remainder are zero-filled.
++ */
++#define CXL_HEADERLOG_TRACE_SIZE SZ_512
++#define CXL_HEADERLOG_TRACE_SIZE_U32 (CXL_HEADERLOG_TRACE_SIZE / sizeof(u32))
+
+ /* CXL 2.0 8.2.8.1 Device Capabilities Array Register */
+ #define CXLDEV_CAP_ARRAY_OFFSET 0x0
--- /dev/null
+From stable+bounces-288499-greg=kroah.com@vger.kernel.org Thu Jul 23 20:35:30 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:28:05 -0400
+Subject: cxl/pci: Remove CXL VH handling in CONFIG_PCIEAER_CXL conditional blocks from core/pci.c
+To: stable@vger.kernel.org
+Cc: Dave Jiang <dave.jiang@intel.com>, Robert Richter <rrichter@amd.com>, Joshua Hahn <joshua.hahnjy@gmail.com>, Jonathan Cameron <jonathan.cameron@huawei.com>, Alison Schofield <alison.schofield@intel.com>, Terry Bowman <terry.bowman@amd.com>, Dan Williams <dan.j.williams@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723182806.3493205-3-sashal@kernel.org>
+
+From: Dave Jiang <dave.jiang@intel.com>
+
+[ Upstream commit 7ff8b1d60881c5f97b5ae426e14d2822917d3b69 ]
+
+Create new config CONFIG_CXL_RAS and put all CXL RAS items behind the
+config. The config will depend on CPER and PCIE AER to build. Move the
+related VH RAS code from core/pci.c to core/ras.c.
+
+Restricted CXL host (RCH) RAS functions will be moved in a future patch.
+
+Cc: Robert Richter <rrichter@amd.com>
+Reviewed-by: Joshua Hahn <joshua.hahnjy@gmail.com>
+Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
+Signed-off-by: Dave Jiang <dave.jiang@intel.com>
+Reviewed-by: Alison Schofield <alison.schofield@intel.com>
+Co-developed-by: Terry Bowman <terry.bowman@amd.com>
+Signed-off-by: Terry Bowman <terry.bowman@amd.com>
+Reviewed-by: Dan Williams <dan.j.williams@intel.com>
+Link: https://patch.msgid.link/20260114182055.46029-8-terry.bowman@amd.com
+Signed-off-by: Dan Williams <dan.j.williams@intel.com>
+Signed-off-by: Dave Jiang <dave.jiang@intel.com>
+Stable-dep-of: c268f949e219 ("cxl: Fix CXL_HEADERLOG_SIZE to match RAS Capability size")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/cxl/Kconfig | 4
+ drivers/cxl/core/Makefile | 2
+ drivers/cxl/core/core.h | 31 +++++++
+ drivers/cxl/core/pci.c | 189 ----------------------------------------------
+ drivers/cxl/core/ras.c | 176 ++++++++++++++++++++++++++++++++++++++++++
+ drivers/cxl/cxl.h | 8 -
+ drivers/cxl/cxlpci.h | 16 +++
+ tools/testing/cxl/Kbuild | 2
+ 8 files changed, 233 insertions(+), 195 deletions(-)
+
+--- a/drivers/cxl/Kconfig
++++ b/drivers/cxl/Kconfig
+@@ -234,4 +234,8 @@ config CXL_MCE
+ def_bool y
+ depends on X86_MCE && MEMORY_FAILURE
+
++config CXL_RAS
++ def_bool y
++ depends on ACPI_APEI_GHES && PCIEAER && CXL_PCI
++
+ endif
+--- a/drivers/cxl/core/Makefile
++++ b/drivers/cxl/core/Makefile
+@@ -14,9 +14,9 @@ cxl_core-y += pci.o
+ cxl_core-y += hdm.o
+ cxl_core-y += pmu.o
+ cxl_core-y += cdat.o
+-cxl_core-y += ras.o
+ cxl_core-$(CONFIG_TRACING) += trace.o
+ cxl_core-$(CONFIG_CXL_REGION) += region.o
+ cxl_core-$(CONFIG_CXL_MCE) += mce.o
+ cxl_core-$(CONFIG_CXL_FEATURES) += features.o
+ cxl_core-$(CONFIG_CXL_EDAC_MEM_FEATURES) += edac.o
++cxl_core-$(CONFIG_CXL_RAS) += ras.o
+--- a/drivers/cxl/core/core.h
++++ b/drivers/cxl/core/core.h
+@@ -144,8 +144,39 @@ int cxl_pci_get_bandwidth(struct pci_dev
+ int cxl_port_get_switch_dport_bandwidth(struct cxl_port *port,
+ struct access_coordinate *c);
+
++#ifdef CONFIG_CXL_RAS
+ int cxl_ras_init(void);
+ void cxl_ras_exit(void);
++bool cxl_handle_ras(struct cxl_dev_state *cxlds, void __iomem *ras_base);
++void cxl_handle_cor_ras(struct cxl_dev_state *cxlds, void __iomem *ras_base);
++#else
++static inline int cxl_ras_init(void)
++{
++ return 0;
++}
++
++static inline void cxl_ras_exit(void)
++{
++}
++
++static inline bool cxl_handle_ras(struct cxl_dev_state *cxlds, void __iomem *ras_base)
++{
++ return false;
++}
++static inline void cxl_handle_cor_ras(struct cxl_dev_state *cxlds, void __iomem *ras_base) { }
++#endif /* CONFIG_CXL_RAS */
++
++/* Restricted CXL Host specific RAS functions */
++#ifdef CONFIG_CXL_RAS
++void cxl_dport_map_rch_aer(struct cxl_dport *dport);
++void cxl_disable_rch_root_ints(struct cxl_dport *dport);
++void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds);
++#else
++static inline void cxl_dport_map_rch_aer(struct cxl_dport *dport) { }
++static inline void cxl_disable_rch_root_ints(struct cxl_dport *dport) { }
++static inline void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds) { }
++#endif /* CONFIG_CXL_RAS */
++
+ int cxl_gpf_port_setup(struct cxl_dport *dport);
+
+ struct cxl_hdm;
+--- a/drivers/cxl/core/pci.c
++++ b/drivers/cxl/core/pci.c
+@@ -711,81 +711,8 @@ err:
+ }
+ EXPORT_SYMBOL_NS_GPL(read_cdat_data, "CXL");
+
+-static void cxl_handle_cor_ras(struct cxl_dev_state *cxlds,
+- void __iomem *ras_base)
+-{
+- void __iomem *addr;
+- u32 status;
+-
+- if (!ras_base)
+- return;
+-
+- addr = ras_base + CXL_RAS_CORRECTABLE_STATUS_OFFSET;
+- status = readl(addr);
+- if (status & CXL_RAS_CORRECTABLE_STATUS_MASK) {
+- writel(status & CXL_RAS_CORRECTABLE_STATUS_MASK, addr);
+- trace_cxl_aer_correctable_error(cxlds->cxlmd, status);
+- }
+-}
+-
+-/* CXL spec rev3.0 8.2.4.16.1 */
+-static void header_log_copy(void __iomem *ras_base, u32 *log)
+-{
+- void __iomem *addr;
+- u32 *log_addr;
+- int i, log_u32_size = CXL_HEADERLOG_SIZE / sizeof(u32);
+-
+- addr = ras_base + CXL_RAS_HEADER_LOG_OFFSET;
+- log_addr = log;
+-
+- for (i = 0; i < log_u32_size; i++) {
+- *log_addr = readl(addr);
+- log_addr++;
+- addr += sizeof(u32);
+- }
+-}
+-
+-/*
+- * Log the state of the RAS status registers and prepare them to log the
+- * next error status. Return 1 if reset needed.
+- */
+-static bool cxl_handle_ras(struct cxl_dev_state *cxlds,
+- void __iomem *ras_base)
+-{
+- u32 hl[CXL_HEADERLOG_SIZE_U32];
+- void __iomem *addr;
+- u32 status;
+- u32 fe;
+-
+- if (!ras_base)
+- return false;
+-
+- addr = ras_base + CXL_RAS_UNCORRECTABLE_STATUS_OFFSET;
+- status = readl(addr);
+- if (!(status & CXL_RAS_UNCORRECTABLE_STATUS_MASK))
+- return false;
+-
+- /* If multiple errors, log header points to first error from ctrl reg */
+- if (hweight32(status) > 1) {
+- void __iomem *rcc_addr =
+- ras_base + CXL_RAS_CAP_CONTROL_OFFSET;
+-
+- fe = BIT(FIELD_GET(CXL_RAS_CAP_CONTROL_FE_MASK,
+- readl(rcc_addr)));
+- } else {
+- fe = status;
+- }
+-
+- header_log_copy(ras_base, hl);
+- trace_cxl_aer_uncorrectable_error(cxlds->cxlmd, status, fe, hl);
+- writel(status & CXL_RAS_UNCORRECTABLE_STATUS_MASK, addr);
+-
+- return true;
+-}
+-
+-#ifdef CONFIG_PCIEAER_CXL
+-
+-static void cxl_dport_map_rch_aer(struct cxl_dport *dport)
++#ifdef CONFIG_CXL_RAS
++void cxl_dport_map_rch_aer(struct cxl_dport *dport)
+ {
+ resource_size_t aer_phys;
+ struct device *host;
+@@ -800,19 +727,7 @@ static void cxl_dport_map_rch_aer(struct
+ }
+ }
+
+-static void cxl_dport_map_ras(struct cxl_dport *dport)
+-{
+- struct cxl_register_map *map = &dport->reg_map;
+- struct device *dev = dport->dport_dev;
+-
+- if (!map->component_map.ras.valid)
+- dev_dbg(dev, "RAS registers not found\n");
+- else if (cxl_map_component_regs(map, &dport->regs.component,
+- BIT(CXL_CM_CAP_CAP_ID_RAS)))
+- dev_dbg(dev, "Failed to map RAS capability.\n");
+-}
+-
+-static void cxl_disable_rch_root_ints(struct cxl_dport *dport)
++void cxl_disable_rch_root_ints(struct cxl_dport *dport)
+ {
+ void __iomem *aer_base = dport->regs.dport_aer;
+ u32 aer_cmd_mask, aer_cmd;
+@@ -836,28 +751,6 @@ static void cxl_disable_rch_root_ints(st
+ writel(aer_cmd, aer_base + PCI_ERR_ROOT_COMMAND);
+ }
+
+-/**
+- * cxl_dport_init_ras_reporting - Setup CXL RAS report on this dport
+- * @dport: the cxl_dport that needs to be initialized
+- * @host: host device for devm operations
+- */
+-void cxl_dport_init_ras_reporting(struct cxl_dport *dport, struct device *host)
+-{
+- dport->reg_map.host = host;
+- cxl_dport_map_ras(dport);
+-
+- if (dport->rch) {
+- struct pci_host_bridge *host_bridge = to_pci_host_bridge(dport->dport_dev);
+-
+- if (!host_bridge->native_aer)
+- return;
+-
+- cxl_dport_map_rch_aer(dport);
+- cxl_disable_rch_root_ints(dport);
+- }
+-}
+-EXPORT_SYMBOL_NS_GPL(cxl_dport_init_ras_reporting, "CXL");
+-
+ /*
+ * Copy the AER capability registers using 32 bit read accesses.
+ * This is necessary because RCRB AER capability is MMIO mapped. Clear the
+@@ -906,7 +799,7 @@ static bool cxl_rch_get_aer_severity(str
+ return false;
+ }
+
+-static void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds)
++void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds)
+ {
+ struct pci_dev *pdev = to_pci_dev(cxlds->dev);
+ struct aer_capability_regs aer_regs;
+@@ -931,82 +824,8 @@ static void cxl_handle_rdport_errors(str
+ else
+ cxl_handle_ras(cxlds, dport->regs.ras);
+ }
+-
+-#else
+-static void cxl_handle_rdport_errors(struct cxl_dev_state *cxlds) { }
+ #endif
+
+-void cxl_cor_error_detected(struct pci_dev *pdev)
+-{
+- struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
+- struct device *dev = &cxlds->cxlmd->dev;
+-
+- scoped_guard(device, dev) {
+- if (!dev->driver) {
+- dev_warn(&pdev->dev,
+- "%s: memdev disabled, abort error handling\n",
+- dev_name(dev));
+- return;
+- }
+-
+- if (cxlds->rcd)
+- cxl_handle_rdport_errors(cxlds);
+-
+- cxl_handle_cor_ras(cxlds, cxlds->regs.ras);
+- }
+-}
+-EXPORT_SYMBOL_NS_GPL(cxl_cor_error_detected, "CXL");
+-
+-pci_ers_result_t cxl_error_detected(struct pci_dev *pdev,
+- pci_channel_state_t state)
+-{
+- struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
+- struct cxl_memdev *cxlmd = cxlds->cxlmd;
+- struct device *dev = &cxlmd->dev;
+- bool ue;
+-
+- scoped_guard(device, dev) {
+- if (!dev->driver) {
+- dev_warn(&pdev->dev,
+- "%s: memdev disabled, abort error handling\n",
+- dev_name(dev));
+- return PCI_ERS_RESULT_DISCONNECT;
+- }
+-
+- if (cxlds->rcd)
+- cxl_handle_rdport_errors(cxlds);
+- /*
+- * A frozen channel indicates an impending reset which is fatal to
+- * CXL.mem operation, and will likely crash the system. On the off
+- * chance the situation is recoverable dump the status of the RAS
+- * capability registers and bounce the active state of the memdev.
+- */
+- ue = cxl_handle_ras(cxlds, cxlds->regs.ras);
+- }
+-
+-
+- switch (state) {
+- case pci_channel_io_normal:
+- if (ue) {
+- device_release_driver(dev);
+- return PCI_ERS_RESULT_NEED_RESET;
+- }
+- return PCI_ERS_RESULT_CAN_RECOVER;
+- case pci_channel_io_frozen:
+- dev_warn(&pdev->dev,
+- "%s: frozen state error detected, disable CXL.mem\n",
+- dev_name(dev));
+- device_release_driver(dev);
+- return PCI_ERS_RESULT_NEED_RESET;
+- case pci_channel_io_perm_failure:
+- dev_warn(&pdev->dev,
+- "failure state error detected, request disconnect\n");
+- return PCI_ERS_RESULT_DISCONNECT;
+- }
+- return PCI_ERS_RESULT_NEED_RESET;
+-}
+-EXPORT_SYMBOL_NS_GPL(cxl_error_detected, "CXL");
+-
+ static int cxl_flit_size(struct pci_dev *pdev)
+ {
+ if (cxl_pci_flit_256(pdev))
+--- a/drivers/cxl/core/ras.c
++++ b/drivers/cxl/core/ras.c
+@@ -5,6 +5,7 @@
+ #include <linux/aer.h>
+ #include <cxl/event.h>
+ #include <cxlmem.h>
++#include <cxlpci.h>
+ #include "trace.h"
+
+ static void cxl_cper_trace_corr_port_prot_err(struct pci_dev *pdev,
+@@ -124,3 +125,178 @@ void cxl_ras_exit(void)
+ cxl_cper_unregister_prot_err_work(&cxl_cper_prot_err_work);
+ cancel_work_sync(&cxl_cper_prot_err_work);
+ }
++
++static void cxl_dport_map_ras(struct cxl_dport *dport)
++{
++ struct cxl_register_map *map = &dport->reg_map;
++ struct device *dev = dport->dport_dev;
++
++ if (!map->component_map.ras.valid)
++ dev_dbg(dev, "RAS registers not found\n");
++ else if (cxl_map_component_regs(map, &dport->regs.component,
++ BIT(CXL_CM_CAP_CAP_ID_RAS)))
++ dev_dbg(dev, "Failed to map RAS capability.\n");
++}
++
++/**
++ * cxl_dport_init_ras_reporting - Setup CXL RAS report on this dport
++ * @dport: the cxl_dport that needs to be initialized
++ * @host: host device for devm operations
++ */
++void cxl_dport_init_ras_reporting(struct cxl_dport *dport, struct device *host)
++{
++ dport->reg_map.host = host;
++ cxl_dport_map_ras(dport);
++
++ if (dport->rch) {
++ struct pci_host_bridge *host_bridge = to_pci_host_bridge(dport->dport_dev);
++
++ if (!host_bridge->native_aer)
++ return;
++
++ cxl_dport_map_rch_aer(dport);
++ cxl_disable_rch_root_ints(dport);
++ }
++}
++EXPORT_SYMBOL_NS_GPL(cxl_dport_init_ras_reporting, "CXL");
++
++void cxl_handle_cor_ras(struct cxl_dev_state *cxlds, void __iomem *ras_base)
++{
++ void __iomem *addr;
++ u32 status;
++
++ if (!ras_base)
++ return;
++
++ addr = ras_base + CXL_RAS_CORRECTABLE_STATUS_OFFSET;
++ status = readl(addr);
++ if (status & CXL_RAS_CORRECTABLE_STATUS_MASK) {
++ writel(status & CXL_RAS_CORRECTABLE_STATUS_MASK, addr);
++ trace_cxl_aer_correctable_error(cxlds->cxlmd, status);
++ }
++}
++
++/* CXL spec rev3.0 8.2.4.16.1 */
++static void header_log_copy(void __iomem *ras_base, u32 *log)
++{
++ void __iomem *addr;
++ u32 *log_addr;
++ int i, log_u32_size = CXL_HEADERLOG_SIZE / sizeof(u32);
++
++ addr = ras_base + CXL_RAS_HEADER_LOG_OFFSET;
++ log_addr = log;
++
++ for (i = 0; i < log_u32_size; i++) {
++ *log_addr = readl(addr);
++ log_addr++;
++ addr += sizeof(u32);
++ }
++}
++
++/*
++ * Log the state of the RAS status registers and prepare them to log the
++ * next error status. Return 1 if reset needed.
++ */
++bool cxl_handle_ras(struct cxl_dev_state *cxlds, void __iomem *ras_base)
++{
++ u32 hl[CXL_HEADERLOG_SIZE_U32];
++ void __iomem *addr;
++ u32 status;
++ u32 fe;
++
++ if (!ras_base)
++ return false;
++
++ addr = ras_base + CXL_RAS_UNCORRECTABLE_STATUS_OFFSET;
++ status = readl(addr);
++ if (!(status & CXL_RAS_UNCORRECTABLE_STATUS_MASK))
++ return false;
++
++ /* If multiple errors, log header points to first error from ctrl reg */
++ if (hweight32(status) > 1) {
++ void __iomem *rcc_addr =
++ ras_base + CXL_RAS_CAP_CONTROL_OFFSET;
++
++ fe = BIT(FIELD_GET(CXL_RAS_CAP_CONTROL_FE_MASK,
++ readl(rcc_addr)));
++ } else {
++ fe = status;
++ }
++
++ header_log_copy(ras_base, hl);
++ trace_cxl_aer_uncorrectable_error(cxlds->cxlmd, status, fe, hl);
++ writel(status & CXL_RAS_UNCORRECTABLE_STATUS_MASK, addr);
++
++ return true;
++}
++
++void cxl_cor_error_detected(struct pci_dev *pdev)
++{
++ struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
++ struct device *dev = &cxlds->cxlmd->dev;
++
++ scoped_guard(device, dev) {
++ if (!dev->driver) {
++ dev_warn(&pdev->dev,
++ "%s: memdev disabled, abort error handling\n",
++ dev_name(dev));
++ return;
++ }
++
++ if (cxlds->rcd)
++ cxl_handle_rdport_errors(cxlds);
++
++ cxl_handle_cor_ras(cxlds, cxlds->regs.ras);
++ }
++}
++EXPORT_SYMBOL_NS_GPL(cxl_cor_error_detected, "CXL");
++
++pci_ers_result_t cxl_error_detected(struct pci_dev *pdev,
++ pci_channel_state_t state)
++{
++ struct cxl_dev_state *cxlds = pci_get_drvdata(pdev);
++ struct cxl_memdev *cxlmd = cxlds->cxlmd;
++ struct device *dev = &cxlmd->dev;
++ bool ue;
++
++ scoped_guard(device, dev) {
++ if (!dev->driver) {
++ dev_warn(&pdev->dev,
++ "%s: memdev disabled, abort error handling\n",
++ dev_name(dev));
++ return PCI_ERS_RESULT_DISCONNECT;
++ }
++
++ if (cxlds->rcd)
++ cxl_handle_rdport_errors(cxlds);
++ /*
++ * A frozen channel indicates an impending reset which is fatal to
++ * CXL.mem operation, and will likely crash the system. On the off
++ * chance the situation is recoverable dump the status of the RAS
++ * capability registers and bounce the active state of the memdev.
++ */
++ ue = cxl_handle_ras(cxlds, cxlds->regs.ras);
++ }
++
++
++ switch (state) {
++ case pci_channel_io_normal:
++ if (ue) {
++ device_release_driver(dev);
++ return PCI_ERS_RESULT_NEED_RESET;
++ }
++ return PCI_ERS_RESULT_CAN_RECOVER;
++ case pci_channel_io_frozen:
++ dev_warn(&pdev->dev,
++ "%s: frozen state error detected, disable CXL.mem\n",
++ dev_name(dev));
++ device_release_driver(dev);
++ return PCI_ERS_RESULT_NEED_RESET;
++ case pci_channel_io_perm_failure:
++ dev_warn(&pdev->dev,
++ "failure state error detected, request disconnect\n");
++ return PCI_ERS_RESULT_DISCONNECT;
++ }
++ return PCI_ERS_RESULT_NEED_RESET;
++}
++EXPORT_SYMBOL_NS_GPL(cxl_error_detected, "CXL");
+--- a/drivers/cxl/cxl.h
++++ b/drivers/cxl/cxl.h
+@@ -781,14 +781,6 @@ struct cxl_dport *devm_cxl_add_rch_dport
+ struct device *dport_dev, int port_id,
+ resource_size_t rcrb);
+
+-#ifdef CONFIG_PCIEAER_CXL
+-void cxl_setup_parent_dport(struct device *host, struct cxl_dport *dport);
+-void cxl_dport_init_ras_reporting(struct cxl_dport *dport, struct device *host);
+-#else
+-static inline void cxl_dport_init_ras_reporting(struct cxl_dport *dport,
+- struct device *host) { }
+-#endif
+-
+ struct cxl_decoder *to_cxl_decoder(struct device *dev);
+ struct cxl_root_decoder *to_cxl_root_decoder(struct device *dev);
+ struct cxl_switch_decoder *to_cxl_switch_decoder(struct device *dev);
+--- a/drivers/cxl/cxlpci.h
++++ b/drivers/cxl/cxlpci.h
+@@ -130,7 +130,23 @@ static inline bool cxl_pci_flit_256(stru
+ int devm_cxl_port_enumerate_dports(struct cxl_port *port);
+ struct cxl_dev_state;
+ void read_cdat_data(struct cxl_port *port);
++
++#ifdef CONFIG_CXL_RAS
+ void cxl_cor_error_detected(struct pci_dev *pdev);
+ pci_ers_result_t cxl_error_detected(struct pci_dev *pdev,
+ pci_channel_state_t state);
++void cxl_dport_init_ras_reporting(struct cxl_dport *dport, struct device *host);
++#else
++static inline void cxl_cor_error_detected(struct pci_dev *pdev) { }
++
++static inline pci_ers_result_t cxl_error_detected(struct pci_dev *pdev,
++ pci_channel_state_t state)
++{
++ return PCI_ERS_RESULT_NONE;
++}
++
++static inline void cxl_dport_init_ras_reporting(struct cxl_dport *dport,
++ struct device *host) { }
++#endif
++
+ #endif /* __CXL_PCI_H__ */
+--- a/tools/testing/cxl/Kbuild
++++ b/tools/testing/cxl/Kbuild
+@@ -58,12 +58,12 @@ cxl_core-y += $(CXL_CORE_SRC)/pci.o
+ cxl_core-y += $(CXL_CORE_SRC)/hdm.o
+ cxl_core-y += $(CXL_CORE_SRC)/pmu.o
+ cxl_core-y += $(CXL_CORE_SRC)/cdat.o
+-cxl_core-y += $(CXL_CORE_SRC)/ras.o
+ cxl_core-$(CONFIG_TRACING) += $(CXL_CORE_SRC)/trace.o
+ cxl_core-$(CONFIG_CXL_REGION) += $(CXL_CORE_SRC)/region.o
+ cxl_core-$(CONFIG_CXL_MCE) += $(CXL_CORE_SRC)/mce.o
+ cxl_core-$(CONFIG_CXL_FEATURES) += $(CXL_CORE_SRC)/features.o
+ cxl_core-$(CONFIG_CXL_EDAC_MEM_FEATURES) += $(CXL_CORE_SRC)/edac.o
++cxl_core-$(CONFIG_CXL_RAS) += $(CXL_CORE_SRC)/ras.o
+ cxl_core-y += config_check.o
+ cxl_core-y += cxl_core_test.o
+ cxl_core-y += cxl_core_exports.o
--- /dev/null
+From stable+bounces-288496-greg=kroah.com@vger.kernel.org Thu Jul 23 20:35:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:28:03 -0400
+Subject: cxl/pci: Remove unnecessary CXL Endpoint handling helper functions
+To: stable@vger.kernel.org
+Cc: Terry Bowman <terry.bowman@amd.com>, Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>, Jonathan Cameron <jonathan.cameron@huawei.com>, Dave Jiang <dave.jiang@intel.com>, Joshua Hahn <joshua.hahnjy@gmail.com>, Dan Williams <dan.j.williams@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723182806.3493205-1-sashal@kernel.org>
+
+From: Terry Bowman <terry.bowman@amd.com>
+
+[ Upstream commit ca3d1a53e62093d17436abd447463da9c0f4e56b ]
+
+The CXL driver's cxl_handle_endpoint_cor_ras()/cxl_handle_endpoint_ras()
+are unnecessary helper functions used only for Endpoints. Remove these
+functions as they are not common for all CXL devices and do not provide
+value for EP handling.
+
+Rename __cxl_handle_ras to cxl_handle_ras() and __cxl_handle_cor_ras()
+to cxl_handle_cor_ras().
+
+Signed-off-by: Terry Bowman <terry.bowman@amd.com>
+Reviewed-by: Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>
+Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
+Reviewed-by: Dave Jiang <dave.jiang@intel.com>
+Tested-by: Joshua Hahn <joshua.hahnjy@gmail.com>
+Reviewed-by: Dan Williams <dan.j.williams@intel.com>
+Link: https://patch.msgid.link/20260114182055.46029-5-terry.bowman@amd.com
+Signed-off-by: Dan Williams <dan.j.williams@intel.com>
+Signed-off-by: Dave Jiang <dave.jiang@intel.com>
+Stable-dep-of: c268f949e219 ("cxl: Fix CXL_HEADERLOG_SIZE to match RAS Capability size")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/cxl/core/pci.c | 26 ++++++++------------------
+ 1 file changed, 8 insertions(+), 18 deletions(-)
+
+--- a/drivers/cxl/core/pci.c
++++ b/drivers/cxl/core/pci.c
+@@ -711,8 +711,8 @@ err:
+ }
+ EXPORT_SYMBOL_NS_GPL(read_cdat_data, "CXL");
+
+-static void __cxl_handle_cor_ras(struct cxl_dev_state *cxlds,
+- void __iomem *ras_base)
++static void cxl_handle_cor_ras(struct cxl_dev_state *cxlds,
++ void __iomem *ras_base)
+ {
+ void __iomem *addr;
+ u32 status;
+@@ -728,11 +728,6 @@ static void __cxl_handle_cor_ras(struct
+ }
+ }
+
+-static void cxl_handle_endpoint_cor_ras(struct cxl_dev_state *cxlds)
+-{
+- return __cxl_handle_cor_ras(cxlds, cxlds->regs.ras);
+-}
+-
+ /* CXL spec rev3.0 8.2.4.16.1 */
+ static void header_log_copy(void __iomem *ras_base, u32 *log)
+ {
+@@ -754,8 +749,8 @@ static void header_log_copy(void __iomem
+ * Log the state of the RAS status registers and prepare them to log the
+ * next error status. Return 1 if reset needed.
+ */
+-static bool __cxl_handle_ras(struct cxl_dev_state *cxlds,
+- void __iomem *ras_base)
++static bool cxl_handle_ras(struct cxl_dev_state *cxlds,
++ void __iomem *ras_base)
+ {
+ u32 hl[CXL_HEADERLOG_SIZE_U32];
+ void __iomem *addr;
+@@ -788,11 +783,6 @@ static bool __cxl_handle_ras(struct cxl_
+ return true;
+ }
+
+-static bool cxl_handle_endpoint_ras(struct cxl_dev_state *cxlds)
+-{
+- return __cxl_handle_ras(cxlds, cxlds->regs.ras);
+-}
+-
+ #ifdef CONFIG_PCIEAER_CXL
+
+ static void cxl_dport_map_rch_aer(struct cxl_dport *dport)
+@@ -871,13 +861,13 @@ EXPORT_SYMBOL_NS_GPL(cxl_dport_init_ras_
+ static void cxl_handle_rdport_cor_ras(struct cxl_dev_state *cxlds,
+ struct cxl_dport *dport)
+ {
+- return __cxl_handle_cor_ras(cxlds, dport->regs.ras);
++ return cxl_handle_cor_ras(cxlds, dport->regs.ras);
+ }
+
+ static bool cxl_handle_rdport_ras(struct cxl_dev_state *cxlds,
+ struct cxl_dport *dport)
+ {
+- return __cxl_handle_ras(cxlds, dport->regs.ras);
++ return cxl_handle_ras(cxlds, dport->regs.ras);
+ }
+
+ /*
+@@ -974,7 +964,7 @@ void cxl_cor_error_detected(struct pci_d
+ if (cxlds->rcd)
+ cxl_handle_rdport_errors(cxlds);
+
+- cxl_handle_endpoint_cor_ras(cxlds);
++ cxl_handle_cor_ras(cxlds, cxlds->regs.ras);
+ }
+ }
+ EXPORT_SYMBOL_NS_GPL(cxl_cor_error_detected, "CXL");
+@@ -1003,7 +993,7 @@ pci_ers_result_t cxl_error_detected(stru
+ * chance the situation is recoverable dump the status of the RAS
+ * capability registers and bounce the active state of the memdev.
+ */
+- ue = cxl_handle_endpoint_ras(cxlds);
++ ue = cxl_handle_ras(cxlds, cxlds->regs.ras);
+ }
+
+
--- /dev/null
+From stable+bounces-288497-greg=kroah.com@vger.kernel.org Thu Jul 23 20:30:03 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:28:04 -0400
+Subject: cxl/pci: Remove unnecessary CXL RCH handling helper functions
+To: stable@vger.kernel.org
+Cc: Terry Bowman <terry.bowman@amd.com>, Alejandro Lucero <alucerop@amd.com>, Dave Jiang <dave.jiang@intel.com>, Jonathan Cameron <jonathan.cameron@huawei.com>, Dan Williams <dan.j.williams@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723182806.3493205-2-sashal@kernel.org>
+
+From: Terry Bowman <terry.bowman@amd.com>
+
+[ Upstream commit eb78ef4d6f0e51243c1ee117f801dbc503e886ab ]
+
+cxl_handle_rdport_cor_ras() and cxl_handle_rdport_ras() are specific
+to Restricted CXL Host (RCH) handling. Improve readability and
+maintainability by replacing these and instead using the common
+cxl_handle_cor_ras() and cxl_handle_ras() functions.
+
+Signed-off-by: Terry Bowman <terry.bowman@amd.com>
+Reviewed-by: Alejandro Lucero <alucerop@amd.com>
+Reviewed-by: Dave Jiang <dave.jiang@intel.com>
+Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
+Reviewed-by: Dan Williams <dan.j.williams@intel.com>
+Link: https://patch.msgid.link/20260114182055.46029-6-terry.bowman@amd.com
+Signed-off-by: Dan Williams <dan.j.williams@intel.com>
+Signed-off-by: Dave Jiang <dave.jiang@intel.com>
+Stable-dep-of: c268f949e219 ("cxl: Fix CXL_HEADERLOG_SIZE to match RAS Capability size")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/cxl/core/pci.c | 16 ++--------------
+ 1 file changed, 2 insertions(+), 14 deletions(-)
+
+--- a/drivers/cxl/core/pci.c
++++ b/drivers/cxl/core/pci.c
+@@ -858,18 +858,6 @@ void cxl_dport_init_ras_reporting(struct
+ }
+ EXPORT_SYMBOL_NS_GPL(cxl_dport_init_ras_reporting, "CXL");
+
+-static void cxl_handle_rdport_cor_ras(struct cxl_dev_state *cxlds,
+- struct cxl_dport *dport)
+-{
+- return cxl_handle_cor_ras(cxlds, dport->regs.ras);
+-}
+-
+-static bool cxl_handle_rdport_ras(struct cxl_dev_state *cxlds,
+- struct cxl_dport *dport)
+-{
+- return cxl_handle_ras(cxlds, dport->regs.ras);
+-}
+-
+ /*
+ * Copy the AER capability registers using 32 bit read accesses.
+ * This is necessary because RCRB AER capability is MMIO mapped. Clear the
+@@ -939,9 +927,9 @@ static void cxl_handle_rdport_errors(str
+ pci_print_aer(pdev, severity, &aer_regs);
+
+ if (severity == AER_CORRECTABLE)
+- cxl_handle_rdport_cor_ras(cxlds, dport);
++ cxl_handle_cor_ras(cxlds, dport->regs.ras);
+ else
+- cxl_handle_rdport_ras(cxlds, dport);
++ cxl_handle_ras(cxlds, dport->regs.ras);
+ }
+
+ #else
--- /dev/null
+From stable+bounces-289688-greg=kroah.com@vger.kernel.org Mon Jul 27 23:00:30 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 16:59:58 -0400
+Subject: dm: avoid leaking the caller's thread keyring via the table device file
+To: stable@vger.kernel.org
+Cc: Ingo Blechschmidt <iblech@speicherleck.de>, Mikulas Patocka <mpatocka@redhat.com>, Ondrej Kozina <okozina@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727205958.1929027-3-sashal@kernel.org>
+
+From: Ingo Blechschmidt <iblech@speicherleck.de>
+
+[ Upstream commit 981ccd97f7153d310dfa92a534525bbaf46752c2 ]
+
+The refactoring in commit a28d893eb327 ("md: port block device access to file")
+accidentally causes the caller's thread keyring to be kept alive long
+beyond the caller's lifetime.
+
+As a result, "cryptsetup luksSuspend" silently fails to wipe the
+LUKS volume key from memory.
+
+In detail: "cryptsetup luksOpen" uses its supposedly ephemeral thread
+keyring to pass the volume key to the kernel. dm-crypt's
+crypt_set_keyring_key() copies the key material into its own
+crypt_config structure and then drops its own reference to the key in
+the keyring with key_put().
+
+With this fix, restoring pre-v6.9 behavior, the copy in the thread
+keyring is then promptly garbage collected, such that exactly one copy
+of the volume key remains. This single copy is correctly wiped from
+memory on "cryptsetup luksSuspend".
+
+Without this fix, the thread keyring and the volume key in it remains.
+This second copy is only freed on "luksClose". "luksSuspend" neither
+knows about this copy nor has any way to remove it, so the key remains
+recoverable from RAM after a suspend that is documented to have wiped it.
+
+This fix should not introduce new security problems, as the code is
+anyway gated by CAP_SYS_ADMIN. The device-mapper core, not the calling
+task, is the legitimate owner of this long-lived file.
+
+Fixes: a28d893eb327 ("md: port block device access to file")
+Closes: https://gitlab.com/cryptsetup/cryptsetup/-/work_items/993
+Link: https://www.speicherleck.de/iblech/cryptsetup-luksSuspend-issue-reproduction/
+Signed-off-by: Ingo Blechschmidt <iblech@speicherleck.de>
+Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
+Cc: stable@vger.kernel.org
+Tested-by: Ondrej Kozina <okozina@redhat.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/md/dm.c | 11 ++++++++++-
+ 1 file changed, 10 insertions(+), 1 deletion(-)
+
+--- a/drivers/md/dm.c
++++ b/drivers/md/dm.c
+@@ -735,7 +735,16 @@ static struct table_device *open_table_d
+ return ERR_PTR(-ENOMEM);
+ refcount_set(&td->count, 1);
+
+- bdev_file = bdev_file_open_by_dev(dev, mode, _dm_claim_ptr, NULL);
++ /*
++ * Open the backing device with kernel rather than caller
++ * credentials. Otherwise the caller's credentials would be
++ * pinned in bdev_file->f_cred until the table device is closed.
++ * That would keep the caller's thread keyring alive long beyond the
++ * lifetime of the caller, breaking userspace expectation (e.g.
++ * cryptsetup(8) leaking the LUKS volume key).
++ */
++ scoped_with_kernel_creds()
++ bdev_file = bdev_file_open_by_dev(dev, mode, _dm_claim_ptr, NULL);
+ if (IS_ERR(bdev_file)) {
+ r = PTR_ERR(bdev_file);
+ goto out_free_td;
--- /dev/null
+From stable+bounces-289272-greg=kroah.com@vger.kernel.org Sun Jul 26 14:47:53 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:47:40 -0400
+Subject: dmaengine: dw-edma: Fix confusing cleanup.h syntax
+To: stable@vger.kernel.org
+Cc: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>, Manivannan Sadhasivam <mani@kernel.org>, Vinod Koul <vkoul@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726124741.149009-1-sashal@kernel.org>
+
+From: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
+
+[ Upstream commit f9ef8dedee34e2d7828d5a6a0643cd969aaa8437 ]
+
+Initializing automatic __free variables to NULL without need (e.g.
+branches with different allocations), followed by actual allocation is
+in contrary to explicit coding rules guiding cleanup.h:
+
+"Given that the "__free(...) = NULL" pattern for variables defined at
+the top of the function poses this potential interdependency problem the
+recommendation is to always define and assign variables in one statement
+and not group variable definitions at the top of the function when
+__free() is used."
+
+Code does not have a bug, but is less readable and uses discouraged
+coding practice, so fix that by moving declaration to the place of
+assignment.
+
+Signed-off-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
+Reviewed-by: Manivannan Sadhasivam <mani@kernel.org>
+Link: https://patch.msgid.link/20251208020729.4654-2-krzysztof.kozlowski@oss.qualcomm.com
+Signed-off-by: Vinod Koul <vkoul@kernel.org>
+Stable-dep-of: 11d7cfe0c119 ("dmaengine: dw-edma-pcie: Reject devices without driver data")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/dma/dw-edma/dw-edma-pcie.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/dma/dw-edma/dw-edma-pcie.c
++++ b/drivers/dma/dw-edma/dw-edma-pcie.c
+@@ -161,13 +161,13 @@ static int dw_edma_pcie_probe(struct pci
+ const struct pci_device_id *pid)
+ {
+ struct dw_edma_pcie_data *pdata = (void *)pid->driver_data;
+- struct dw_edma_pcie_data *vsec_data __free(kfree) = NULL;
+ struct device *dev = &pdev->dev;
+ struct dw_edma_chip *chip;
+ int err, nr_irqs;
+ int i, mask;
+
+- vsec_data = kmalloc(sizeof(*vsec_data), GFP_KERNEL);
++ struct dw_edma_pcie_data *vsec_data __free(kfree) =
++ kmalloc(sizeof(*vsec_data), GFP_KERNEL);
+ if (!vsec_data)
+ return -ENOMEM;
+
--- /dev/null
+From stable+bounces-289273-greg=kroah.com@vger.kernel.org Sun Jul 26 14:47:47 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:47:41 -0400
+Subject: dmaengine: dw-edma-pcie: Reject devices without driver data
+To: stable@vger.kernel.org
+Cc: Koichiro Den <den@valinux.co.jp>, Frank Li <Frank.Li@nxp.com>, Vinod Koul <vkoul@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726124741.149009-2-sashal@kernel.org>
+
+From: Koichiro Den <den@valinux.co.jp>
+
+[ Upstream commit 11d7cfe0c119691b2dafbb699bbca90258c678aa ]
+
+dw_edma_pcie_probe() treats the PCI device ID driver_data as the
+template for the controller layout and copies it unconditionally. A
+device bound dynamically via sysfs can match the driver without that
+data, which leads to a NULL pointer dereference.
+
+Reject such matches before enabling the device.
+
+Fixes: 41aaff2a2ac0 ("dmaengine: Add Synopsys eDMA IP PCIe glue-logic")
+Cc: stable@vger.kernel.org
+Signed-off-by: Koichiro Den <den@valinux.co.jp>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Link: https://patch.msgid.link/20260521142153.2957432-3-den@valinux.co.jp
+Signed-off-by: Vinod Koul <vkoul@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/dma/dw-edma/dw-edma-pcie.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/dma/dw-edma/dw-edma-pcie.c
++++ b/drivers/dma/dw-edma/dw-edma-pcie.c
+@@ -166,6 +166,9 @@ static int dw_edma_pcie_probe(struct pci
+ int err, nr_irqs;
+ int i, mask;
+
++ if (!pdata)
++ return -ENODEV;
++
+ struct dw_edma_pcie_data *vsec_data __free(kfree) =
+ kmalloc(sizeof(*vsec_data), GFP_KERNEL);
+ if (!vsec_data)
--- /dev/null
+From stable+bounces-289738-greg=kroah.com@vger.kernel.org Tue Jul 28 01:36:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:20 -0400
+Subject: fs/resctrl: Fix use-after-free during unmount
+To: stable@vger.kernel.org
+Cc: Tony Luck <tony.luck@intel.com>, Sashiko <sashiko-bot@kernel.org>, Reinette Chatre <reinette.chatre@intel.com>, "Borislav Petkov (AMD)" <bp@alien8.de>, Chen Yu <yu.c.chen@intel.com>, stable@kernel.org, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727233421.2423844-6-sashal@kernel.org>
+
+From: Tony Luck <tony.luck@intel.com>
+
+[ Upstream commit 52fce648607e0d6a76eeb443d78708c49df1c554 ]
+
+During unmount or failure teardown all mon_data structures that contain
+monitoring event file private data are freed after which kernfs nodes are
+removed. However, the RDT_DELETED flag is never set for the statically
+allocated default resource group.
+
+A concurrent reader of an event file associated with the default resource
+group may, after dropping kernfs active protection, block on rdtgroup_mutex
+while unmount proceeds to free the file private data and destroy the kernfs
+node without waiting for the reader.
+
+When the mutex is released, the reader wakes up, observes that RDT_DELETED
+is not set for the default group, and dereferences the already-freed
+file private data.
+
+The scenario can be depicted as follows:
+ CPU0 CPU1
+ /*
+ * Default resource group's
+ * monitoring data accessible via
+ * kernfs file with kernfs_node::priv
+ * pointing to a struct mon_data.
+ * User opens the file for reading.
+ */
+ rdtgroup_mondata_show() /* arch encounters fatal error */
+ rdtgroup_kn_lock_live() resctrl_exit()
+ atomic_inc(&rdtgroup_default.waitcount) cpus_read_lock()
+ kernfs_break_active_protection(kn) mutex_lock(&rdtgroup_mutex)
+ cpus_read_lock() resctrl_fs_teardown()
+ mutex_lock(&rdtgroup_mutex) rmdir_all_sub()
+ mon_put_kn_priv()
+ /* Delete all mon_data structures */
+ rdtgroup_destroy_root()
+ kernfs_destroy_root()
+ rdtgroup_default.kn = NULL
+ mutex_unlock(&rdtgroup_mutex)
+ /*
+ * rdtgroup_default.flags is empty so
+ * rdtgroup_kn_lock_live() returns
+ * &rdtgroup_default
+ */
+ md = of->kn->priv;
+
+ /* md points to freed mon_data */
+
+Set RDT_DELETED for the default group unconditionally since the flag does
+not lead to the freeing of this statically allocated group.
+
+Do not allow a new resctrl mount if there are any waiters on default group
+of previous mount. A new mount will re-initialize the default group that
+would appear to waiters from previous mount as though the default group is
+accessible causing them to access the mon_data structures from the previous
+mount that have been removed.
+
+Fixes: 2a6566038544 ("x86/resctrl: Expand the width of domid by replacing mon_data_bits")
+Closes: https://sashiko.dev/#/patchset/20260508182143.14592-1-tony.luck%40intel.com?part=2 [1]
+Reported-by: Sashiko <sashiko-bot@kernel.org>
+Signed-off-by: Tony Luck <tony.luck@intel.com>
+Signed-off-by: Reinette Chatre <reinette.chatre@intel.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Reviewed-by: Chen Yu <yu.c.chen@intel.com>
+Cc: <stable@kernel.org>
+Link: https://patch.msgid.link/49a2ca3ca688f27e1a646cf90e1dc69287021127.1783377598.git.reinette.chatre@intel.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/resctrl/rdtgroup.c | 18 ++++++++++++++++--
+ 1 file changed, 16 insertions(+), 2 deletions(-)
+
+--- a/fs/resctrl/rdtgroup.c
++++ b/fs/resctrl/rdtgroup.c
+@@ -581,14 +581,20 @@ unlock:
+ *
+ * On resource group creation via a mkdir, an extra kernfs_node reference is
+ * taken to ensure that the rdtgroup structure remains accessible for the
+- * rdtgroup_kn_unlock() calls where it is removed.
++ * rdtgroup_kn_unlock() calls where it is removed. The default group is
++ * statically allocated: it does not have an extra reference but will have
++ * RDT_DELETED set on unmount to support safe access to its associated files
++ * via rdtgroup_kn_lock_live/rdtgroup_kn_unlock().
+ *
+- * Drop the extra reference here, then free the rdtgroup structure.
++ * For all but the default group: drop the extra reference, then free the
++ * rdtgroup structure.
+ *
+ * Return: void
+ */
+ static void rdtgroup_remove(struct rdtgroup *rdtgrp)
+ {
++ if (rdtgrp == &rdtgroup_default)
++ return;
+ kernfs_put(rdtgrp->kn);
+ kfree(rdtgrp);
+ }
+@@ -2732,6 +2738,12 @@ static int rdt_get_tree(struct fs_contex
+ goto out;
+ }
+
++ /* Avoid races from pending operations from a previous mount */
++ if (atomic_read(&rdtgroup_default.waitcount) != 0) {
++ ret = -EBUSY;
++ goto out;
++ }
++
+ ret = setup_rmid_lru_list();
+ if (ret)
+ goto out;
+@@ -3094,6 +3106,7 @@ static void resctrl_fs_teardown(void)
+ mon_put_kn_priv();
+ rdt_pseudo_lock_release();
+ rdtgroup_default.mode = RDT_MODE_SHAREABLE;
++ rdtgroup_default.flags = RDT_DELETED;
+ closid_exit();
+ schemata_list_destroy();
+ rdtgroup_destroy_root();
+@@ -4148,6 +4161,7 @@ static int rdtgroup_setup_root(struct rd
+
+ ctx->kfc.root = rdt_root;
+ rdtgroup_default.kn = kernfs_root_to_node(rdt_root);
++ rdtgroup_default.flags = 0;
+
+ return 0;
+ }
--- /dev/null
+From stable+bounces-289736-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:18 -0400
+Subject: fs/resctrl: Move allocation/free of closid_num_dirty_rmid[]
+To: stable@vger.kernel.org
+Cc: Tony Luck <tony.luck@intel.com>, "Borislav Petkov (AMD)" <bp@alien8.de>, Reinette Chatre <reinette.chatre@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727233421.2423844-4-sashal@kernel.org>
+
+From: Tony Luck <tony.luck@intel.com>
+
+[ Upstream commit ee7f6af79f0916b6c49e15edd4cba020b3e4c4ac ]
+
+closid_num_dirty_rmid[] and rmid_ptrs[] are allocated together during resctrl
+initialization and freed together during resctrl exit.
+
+Telemetry events are enumerated on resctrl mount so only at resctrl mount will
+the number of RMID supported by all monitoring resources and needed as size
+for rmid_ptrs[] be known.
+
+Separate closid_num_dirty_rmid[] and rmid_ptrs[] allocation and free in
+preparation for rmid_ptrs[] to be allocated on resctrl mount.
+
+Keep the rdtgroup_mutex protection around the allocation and free of
+closid_num_dirty_rmid[] as ARM needs this to guarantee memory ordering.
+
+Signed-off-by: Tony Luck <tony.luck@intel.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Reviewed-by: Reinette Chatre <reinette.chatre@intel.com>
+Link: https://lore.kernel.org/20251217172121.12030-1-tony.luck@intel.com
+Stable-dep-of: 52fce648607e ("fs/resctrl: Fix use-after-free during unmount")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/resctrl/monitor.c | 79 ++++++++++++++++++++++++++++++++-------------------
+ 1 file changed, 51 insertions(+), 28 deletions(-)
+
+--- a/fs/resctrl/monitor.c
++++ b/fs/resctrl/monitor.c
+@@ -865,36 +865,14 @@ void mbm_setup_overflow_handler(struct r
+ static int dom_data_init(struct rdt_resource *r)
+ {
+ u32 idx_limit = resctrl_arch_system_num_rmid_idx();
+- u32 num_closid = resctrl_arch_get_num_closid(r);
+ struct rmid_entry *entry = NULL;
+ int err = 0, i;
+ u32 idx;
+
+ mutex_lock(&rdtgroup_mutex);
+- if (IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID)) {
+- u32 *tmp;
+-
+- /*
+- * If the architecture hasn't provided a sanitised value here,
+- * this may result in larger arrays than necessary. Resctrl will
+- * use a smaller system wide value based on the resources in
+- * use.
+- */
+- tmp = kcalloc(num_closid, sizeof(*tmp), GFP_KERNEL);
+- if (!tmp) {
+- err = -ENOMEM;
+- goto out_unlock;
+- }
+-
+- closid_num_dirty_rmid = tmp;
+- }
+
+ rmid_ptrs = kcalloc(idx_limit, sizeof(struct rmid_entry), GFP_KERNEL);
+ if (!rmid_ptrs) {
+- if (IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID)) {
+- kfree(closid_num_dirty_rmid);
+- closid_num_dirty_rmid = NULL;
+- }
+ err = -ENOMEM;
+ goto out_unlock;
+ }
+@@ -930,11 +908,6 @@ static void dom_data_exit(struct rdt_res
+ if (!r->mon_capable)
+ goto out_unlock;
+
+- if (IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID)) {
+- kfree(closid_num_dirty_rmid);
+- closid_num_dirty_rmid = NULL;
+- }
+-
+ kfree(rmid_ptrs);
+ rmid_ptrs = NULL;
+
+@@ -1757,6 +1730,45 @@ ssize_t mbm_L3_assignments_write(struct
+ return ret ?: nbytes;
+ }
+
++static int closid_num_dirty_rmid_alloc(struct rdt_resource *r)
++{
++ if (IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID)) {
++ u32 num_closid = resctrl_arch_get_num_closid(r);
++ u32 *tmp;
++
++ /* For ARM memory ordering access to closid_num_dirty_rmid */
++ mutex_lock(&rdtgroup_mutex);
++
++ /*
++ * If the architecture hasn't provided a sanitised value here,
++ * this may result in larger arrays than necessary. Resctrl will
++ * use a smaller system wide value based on the resources in
++ * use.
++ */
++ tmp = kcalloc(num_closid, sizeof(*tmp), GFP_KERNEL);
++ if (!tmp) {
++ mutex_unlock(&rdtgroup_mutex);
++ return -ENOMEM;
++ }
++
++ closid_num_dirty_rmid = tmp;
++
++ mutex_unlock(&rdtgroup_mutex);
++ }
++
++ return 0;
++}
++
++static void closid_num_dirty_rmid_free(void)
++{
++ if (IS_ENABLED(CONFIG_RESCTRL_RMID_DEPENDS_ON_CLOSID)) {
++ mutex_lock(&rdtgroup_mutex);
++ kfree(closid_num_dirty_rmid);
++ closid_num_dirty_rmid = NULL;
++ mutex_unlock(&rdtgroup_mutex);
++ }
++}
++
+ /**
+ * resctrl_l3_mon_resource_init() - Initialise global monitoring structures.
+ *
+@@ -1777,10 +1789,16 @@ int resctrl_l3_mon_resource_init(void)
+ if (!r->mon_capable)
+ return 0;
+
+- ret = dom_data_init(r);
++ ret = closid_num_dirty_rmid_alloc(r);
+ if (ret)
+ return ret;
+
++ ret = dom_data_init(r);
++ if (ret) {
++ closid_num_dirty_rmid_free();
++ return ret;
++ }
++
+ if (resctrl_arch_is_evt_configurable(QOS_L3_MBM_TOTAL_EVENT_ID)) {
+ mon_event_all[QOS_L3_MBM_TOTAL_EVENT_ID].configurable = true;
+ resctrl_file_fflags_init("mbm_total_bytes_config",
+@@ -1823,5 +1841,10 @@ void resctrl_l3_mon_resource_exit(void)
+ {
+ struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+
++ if (!r->mon_capable)
++ return;
++
++ closid_num_dirty_rmid_free();
++
+ dom_data_exit(r);
+ }
--- /dev/null
+From stable+bounces-289737-greg=kroah.com@vger.kernel.org Tue Jul 28 01:45:50 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:19 -0400
+Subject: fs/resctrl: Move RMID initialization to first mount
+To: stable@vger.kernel.org
+Cc: Tony Luck <tony.luck@intel.com>, "Borislav Petkov (AMD)" <bp@alien8.de>, Reinette Chatre <reinette.chatre@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727233421.2423844-5-sashal@kernel.org>
+
+From: Tony Luck <tony.luck@intel.com>
+
+[ Upstream commit d0891647fbc6e931f27517364cbc4ee1811d76db ]
+
+L3 monitor features are enumerated during resctrl initialization and
+rmid_ptrs[] that tracks all RMIDs and depends on the number of supported
+RMIDs is allocated during this time.
+
+Telemetry monitor features are enumerated during first resctrl mount and
+may support a different number of RMIDs compared to L3 monitor features.
+
+Delay allocation and initialization of rmid_ptrs[] until first mount.
+Since the number of RMIDs cannot change on later mounts, keep the same set of
+rmid_ptrs[] until resctrl_exit(). This is required because the limbo handler
+keeps running after resctrl is unmounted and needs to access rmid_ptrs[]
+as it keeps tracking busy RMIDs after unmount.
+
+Rename routines to match what they now do:
+dom_data_init() -> setup_rmid_lru_list()
+dom_data_exit() -> free_rmid_lru_list()
+
+Signed-off-by: Tony Luck <tony.luck@intel.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Reviewed-by: Reinette Chatre <reinette.chatre@intel.com>
+Link: https://lore.kernel.org/20251217172121.12030-1-tony.luck@intel.com
+Stable-dep-of: 52fce648607e ("fs/resctrl: Fix use-after-free during unmount")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/resctrl/internal.h | 4 +++
+ fs/resctrl/monitor.c | 54 +++++++++++++++++++++++---------------------------
+ fs/resctrl/rdtgroup.c | 5 ++++
+ 3 files changed, 34 insertions(+), 29 deletions(-)
+
+--- a/fs/resctrl/internal.h
++++ b/fs/resctrl/internal.h
+@@ -351,6 +351,10 @@ int closids_supported(void);
+
+ void closid_free(int closid);
+
++int setup_rmid_lru_list(void);
++
++void free_rmid_lru_list(void);
++
+ int alloc_rmid(u32 closid);
+
+ void free_rmid(u32 closid, u32 rmid);
+--- a/fs/resctrl/monitor.c
++++ b/fs/resctrl/monitor.c
+@@ -862,20 +862,29 @@ void mbm_setup_overflow_handler(struct r
+ schedule_delayed_work_on(cpu, &dom->mbm_over, delay);
+ }
+
+-static int dom_data_init(struct rdt_resource *r)
++int setup_rmid_lru_list(void)
+ {
+- u32 idx_limit = resctrl_arch_system_num_rmid_idx();
+ struct rmid_entry *entry = NULL;
+- int err = 0, i;
++ u32 idx_limit;
+ u32 idx;
++ int i;
+
+- mutex_lock(&rdtgroup_mutex);
++ if (!resctrl_arch_mon_capable())
++ return 0;
+
++ /*
++ * Called on every mount, but the number of RMIDs cannot change
++ * after the first mount, so keep using the same set of rmid_ptrs[]
++ * until resctrl_exit(). Note that the limbo handler continues to
++ * access rmid_ptrs[] after resctrl is unmounted.
++ */
++ if (rmid_ptrs)
++ return 0;
++
++ idx_limit = resctrl_arch_system_num_rmid_idx();
+ rmid_ptrs = kcalloc(idx_limit, sizeof(struct rmid_entry), GFP_KERNEL);
+- if (!rmid_ptrs) {
+- err = -ENOMEM;
+- goto out_unlock;
+- }
++ if (!rmid_ptrs)
++ return -ENOMEM;
+
+ for (i = 0; i < idx_limit; i++) {
+ entry = &rmid_ptrs[i];
+@@ -888,30 +897,24 @@ static int dom_data_init(struct rdt_reso
+ /*
+ * RESCTRL_RESERVED_CLOSID and RESCTRL_RESERVED_RMID are special and
+ * are always allocated. These are used for the rdtgroup_default
+- * control group, which will be setup later in resctrl_init().
++ * control group, which was setup earlier in rdtgroup_setup_default().
+ */
+ idx = resctrl_arch_rmid_idx_encode(RESCTRL_RESERVED_CLOSID,
+ RESCTRL_RESERVED_RMID);
+ entry = __rmid_entry(idx);
+ list_del(&entry->list);
+
+-out_unlock:
+- mutex_unlock(&rdtgroup_mutex);
+-
+- return err;
++ return 0;
+ }
+
+-static void dom_data_exit(struct rdt_resource *r)
++void free_rmid_lru_list(void)
+ {
+- mutex_lock(&rdtgroup_mutex);
+-
+- if (!r->mon_capable)
+- goto out_unlock;
++ if (!resctrl_arch_mon_capable())
++ return;
+
++ mutex_lock(&rdtgroup_mutex);
+ kfree(rmid_ptrs);
+ rmid_ptrs = NULL;
+-
+-out_unlock:
+ mutex_unlock(&rdtgroup_mutex);
+ }
+
+@@ -1773,7 +1776,8 @@ static void closid_num_dirty_rmid_free(v
+ * resctrl_l3_mon_resource_init() - Initialise global monitoring structures.
+ *
+ * Allocate and initialise global monitor resources that do not belong to a
+- * specific domain. i.e. the rmid_ptrs[] used for the limbo and free lists.
++ * specific domain. i.e. the closid_num_dirty_rmid[] used to find the CLOSID
++ * with the cleanest set of RMIDs.
+ * Called once during boot after the struct rdt_resource's have been configured
+ * but before the filesystem is mounted.
+ * Resctrl's cpuhp callbacks may be called before this point to bring a domain
+@@ -1793,12 +1797,6 @@ int resctrl_l3_mon_resource_init(void)
+ if (ret)
+ return ret;
+
+- ret = dom_data_init(r);
+- if (ret) {
+- closid_num_dirty_rmid_free();
+- return ret;
+- }
+-
+ if (resctrl_arch_is_evt_configurable(QOS_L3_MBM_TOTAL_EVENT_ID)) {
+ mon_event_all[QOS_L3_MBM_TOTAL_EVENT_ID].configurable = true;
+ resctrl_file_fflags_init("mbm_total_bytes_config",
+@@ -1845,6 +1843,4 @@ void resctrl_l3_mon_resource_exit(void)
+ return;
+
+ closid_num_dirty_rmid_free();
+-
+- dom_data_exit(r);
+ }
+--- a/fs/resctrl/rdtgroup.c
++++ b/fs/resctrl/rdtgroup.c
+@@ -2732,6 +2732,10 @@ static int rdt_get_tree(struct fs_contex
+ goto out;
+ }
+
++ ret = setup_rmid_lru_list();
++ if (ret)
++ goto out;
++
+ ret = rdtgroup_setup_root(ctx);
+ if (ret)
+ goto out;
+@@ -4518,4 +4522,5 @@ void resctrl_exit(void)
+ */
+
+ resctrl_l3_mon_resource_exit();
++ free_rmid_lru_list();
+ }
--- /dev/null
+From stable+bounces-289733-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:28 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:15 -0400
+Subject: fs/resctrl: Split L3 dependent parts out of __mon_event_count()
+To: stable@vger.kernel.org
+Cc: Tony Luck <tony.luck@intel.com>, Reinette Chatre <reinette.chatre@intel.com>, "Borislav Petkov (AMD)" <bp@alien8.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727233421.2423844-1-sashal@kernel.org>
+
+From: Tony Luck <tony.luck@intel.com>
+
+[ Upstream commit ad5c2ff75e0c53d2588dfc10eb87458e759b6bbe ]
+
+Carve out the L3 resource specific event reading code into a separate helper
+to support reading event data from a new monitoring resource.
+
+Suggested-by: Reinette Chatre <reinette.chatre@intel.com>
+Signed-off-by: Tony Luck <tony.luck@intel.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Reviewed-by: Reinette Chatre <reinette.chatre@intel.com>
+Link: https://lore.kernel.org/20251217172121.12030-1-tony.luck@intel.com
+Stable-dep-of: 52fce648607e ("fs/resctrl: Fix use-after-free during unmount")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/resctrl/monitor.c | 13 ++++++++++++-
+ 1 file changed, 12 insertions(+), 1 deletion(-)
+
+--- a/fs/resctrl/monitor.c
++++ b/fs/resctrl/monitor.c
+@@ -418,7 +418,7 @@ static void mbm_cntr_free(struct rdt_mon
+ memset(&d->cntr_cfg[cntr_id], 0, sizeof(*d->cntr_cfg));
+ }
+
+-static int __mon_event_count(struct rdtgroup *rdtgrp, struct rmid_read *rr)
++static int __l3_mon_event_count(struct rdtgroup *rdtgrp, struct rmid_read *rr)
+ {
+ int cpu = smp_processor_id();
+ u32 closid = rdtgrp->closid;
+@@ -499,6 +499,17 @@ static int __mon_event_count(struct rdtg
+ return ret;
+ }
+
++static int __mon_event_count(struct rdtgroup *rdtgrp, struct rmid_read *rr)
++{
++ switch (rr->r->rid) {
++ case RDT_RESOURCE_L3:
++ return __l3_mon_event_count(rdtgrp, rr);
++ default:
++ rr->err = -EINVAL;
++ return -EINVAL;
++ }
++}
++
+ /*
+ * mbm_bw_count() - Update bw count from values previously read by
+ * __mon_event_count().
--- /dev/null
+From stable+bounces-287817-greg=kroah.com@vger.kernel.org Wed Jul 22 13:41:16 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 07:33:50 -0400
+Subject: fuse-uring: fix race between registration and connection abortion
+To: stable@vger.kernel.org
+Cc: Joanne Koong <joannelkoong@gmail.com>, Bernd Schubert <bernd@bsbernd.com>, Miklos Szeredi <mszeredi@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722113350.1151043-1-sashal@kernel.org>
+
+From: Joanne Koong <joannelkoong@gmail.com>
+
+[ Upstream commit 952b5d36f6a298f57c52a59e72076c69386a8aaf ]
+
+This fixes this race:
+- thread a: io_uring_enter -> register sqe ->
+ fuse_uring_create_ring_ent -> allocate ent but doesn't grab queue_ref
+ yet
+- thread b: fuse_conn_destroy() -> fuse_chan_abort() ->
+ fuse_uring_abort() is a no-op due to queue ref being 0
+- thread a: grabs the queue_ref, queue_ref is now 1, rest of
+ fuse_uring_do_register() logic executes
+- thread b: fuse_chan_abort() returns, fuse_chan_wait_aborted() now runs
+ and calls
+ "wait_event(ring->stop_waitq, atomic_read(&ring->queue_refs) == 0);"
+The abort/unmount thread will hang indefinitely in unkillable state as
+nothing will decrement queue_refs or wake stop_waitq, and the ring,
+queue, and ent are leaked.
+
+Fix this by checking fch->connected under fch->lock after the created
+ent has grabbed a ref count on the queue. This ensures that in the
+scenario above, it is guaranteed that we either release the queue ref
+and wake up stop_waitq (in case fuse_chan_wait_aborted() is already
+waiting) in fuse_uring_do_register() when we detect !fch->connected, or
+if the connection is aborted after the check, it is guaranteed that the
+async teardown worker will be running in the background cleaning up ents
+and decrementing the ent's ref on the queue, which will unblock the
+eventual queue and ring teardown.
+
+Fixes: 24fe962c86f5 ("fuse: {io-uring} Handle SQEs - register commands")
+Cc: stable@vger.kernel.org
+Reviewed-by: Bernd Schubert <bernd@bsbernd.com>
+Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
+Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
+[ changed fch->lock/fch->connected references to fc->lock/fc->connected since struct fuse_chan does not exist in this tree ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/fuse/dev_uring.c | 22 ++++++++++++++++------
+ 1 file changed, 16 insertions(+), 6 deletions(-)
+
+--- a/fs/fuse/dev_uring.c
++++ b/fs/fuse/dev_uring.c
+@@ -998,15 +998,26 @@ static bool is_ring_ready(struct fuse_ri
+ /*
+ * fuse_uring_req_fetch command handling
+ */
+-static void fuse_uring_do_register(struct fuse_ring_ent *ent,
+- struct io_uring_cmd *cmd,
+- unsigned int issue_flags)
++static int fuse_uring_do_register(struct fuse_ring_ent *ent,
++ struct io_uring_cmd *cmd,
++ unsigned int issue_flags)
+ {
+ struct fuse_ring_queue *queue = ent->queue;
+ struct fuse_ring *ring = queue->ring;
+ struct fuse_conn *fc = ring->fc;
+ struct fuse_iqueue *fiq = &fc->iq;
+
++ spin_lock(&fc->lock);
++ /* abort teardown path is running or has run */
++ if (!fc->connected) {
++ spin_unlock(&fc->lock);
++ if (atomic_dec_and_test(&ring->queue_refs))
++ wake_up_all(&ring->stop_waitq);
++ kfree(ent);
++ return -ECONNABORTED;
++ }
++ spin_unlock(&fc->lock);
++
+ fuse_uring_prepare_cancel(cmd, issue_flags, ent);
+
+ spin_lock(&queue->lock);
+@@ -1023,6 +1034,7 @@ static void fuse_uring_do_register(struc
+ wake_up_all(&fc->blocked_waitq);
+ }
+ }
++ return 0;
+ }
+
+ /*
+@@ -1138,9 +1150,7 @@ static int fuse_uring_register(struct io
+ if (IS_ERR(ent))
+ return PTR_ERR(ent);
+
+- fuse_uring_do_register(ent, cmd, issue_flags);
+-
+- return 0;
++ return fuse_uring_do_register(ent, cmd, issue_flags);
+ }
+
+ /*
--- /dev/null
+From stable+bounces-288353-greg=kroah.com@vger.kernel.org Thu Jul 23 16:29:15 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:19:30 -0400
+Subject: gpu/buddy: bail out of try_harder when alignment cannot be honoured
+To: stable@vger.kernel.org
+Cc: "Arunpravin Paneer Selvam" <Arunpravin.PaneerSelvam@amd.com>, "Christian König" <christian.koenig@amd.com>, "Matthew Auld" <matthew.auld@intel.com>, "Timur Kristóf" <timur.kristof@gmail.com>, "John Olender" <john.olender@gmail.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260723141931.3218899-2-sashal@kernel.org>
+
+From: Arunpravin Paneer Selvam <Arunpravin.PaneerSelvam@amd.com>
+
+[ Upstream commit 56bc6384314fb9ae98975fb2af8b143097ede3dc ]
+
+The try_harder contiguous fallback could return a range whose start
+offset did not match the caller's min_block_size. When a candidate's
+start is misaligned, realign it: free the misaligned run and reallocate
+exactly @size at the next lower min_block_size boundary. This keeps the
+returned size unchanged with no surplus to trim, and rejects the request
+only when no aligned candidate fits.
+
+v2: align misaligned candidates down to min_block_size instead of
+ bailing out, for both the RHS and LHS paths (Matthew).
+
+Fixes: 0a1844bf0b53 ("drm/buddy: Improve contiguous memory allocation")
+Suggested-by: Christian König <christian.koenig@amd.com>
+Cc: Matthew Auld <matthew.auld@intel.com>
+Cc: Christian König <christian.koenig@amd.com>
+Cc: Timur Kristóf <timur.kristof@gmail.com>
+Cc: stable@vger.kernel.org
+Reviewed-by: Matthew Auld <matthew.auld@intel.com>
+Tested-by: John Olender <john.olender@gmail.com>
+Signed-off-by: Arunpravin Paneer Selvam <Arunpravin.PaneerSelvam@amd.com>
+Link: https://patch.msgid.link/20260709131050.1022759-1-Arunpravin.PaneerSelvam@amd.com
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/gpu/buddy.c | 65 ++++++++++++++++++++++++++++++++++++----------------
+ 1 file changed, 45 insertions(+), 20 deletions(-)
+
+--- a/drivers/gpu/buddy.c
++++ b/drivers/gpu/buddy.c
+@@ -901,22 +901,30 @@ static int __gpu_buddy_alloc_range(struc
+ blocks, total_allocated_on_err);
+ }
+
++static int __alloc_contig_aligned_retry(struct gpu_buddy *mm,
++ u64 unaligned_offset,
++ u64 size,
++ u64 min_block_size,
++ struct list_head *blocks)
++{
++ u64 aligned_offset = round_down(unaligned_offset, min_block_size);
++
++ return __gpu_buddy_alloc_range(mm, aligned_offset, size, NULL, blocks);
++}
++
+ static int __alloc_contig_try_harder(struct gpu_buddy *mm,
+ u64 size,
+ u64 min_block_size,
+ struct list_head *blocks)
+ {
+- u64 rhs_offset, lhs_offset, lhs_size, filled;
++ u64 rhs_offset, lhs_offset, filled;
+ struct gpu_buddy_block *block;
+ unsigned int tree, order;
+- LIST_HEAD(blocks_lhs);
+- unsigned long pages;
+ u64 modify_size;
+ int err;
+
+ modify_size = rounddown_pow_of_two(size);
+- pages = modify_size >> ilog2(mm->chunk_size);
+- order = fls(pages) - 1;
++ order = ilog2(modify_size) - ilog2(mm->chunk_size);
+ if (order == 0)
+ return -ENOSPC;
+
+@@ -932,31 +940,48 @@ static int __alloc_contig_try_harder(str
+ while (iter) {
+ block = rbtree_get_free_block(iter);
+
+- /* Allocate blocks traversing RHS */
+ rhs_offset = gpu_buddy_block_offset(block);
++
++ /* Allocate blocks traversing RHS */
+ err = __gpu_buddy_alloc_range(mm, rhs_offset, size,
+ &filled, blocks);
+- if (!err || err != -ENOSPC)
++ if (err && err != -ENOSPC)
+ return err;
+-
+- lhs_size = max((size - filled), min_block_size);
+- if (!IS_ALIGNED(lhs_size, min_block_size))
+- lhs_size = round_up(lhs_size, min_block_size);
+-
+- /* Allocate blocks traversing LHS */
+- lhs_offset = gpu_buddy_block_offset(block) - lhs_size;
+- err = __gpu_buddy_alloc_range(mm, lhs_offset, lhs_size,
+- NULL, &blocks_lhs);
++ if (!err && IS_ALIGNED(rhs_offset, min_block_size))
++ return 0;
+ if (!err) {
+- list_splice(&blocks_lhs, blocks);
++ /* Allocate the unaligned RHS offset using round_down */
++ gpu_buddy_free_list_internal(mm, blocks);
++ err = __alloc_contig_aligned_retry(mm, rhs_offset,
++ size,
++ min_block_size,
++ blocks);
++ if (!err)
++ return 0;
++ if (err != -ENOSPC) {
++ gpu_buddy_free_list_internal(mm, blocks);
++ return err;
++ }
++ goto next;
++ }
++
++ if (size - filled > rhs_offset)
++ goto next;
++
++ lhs_offset = rhs_offset - (size - filled);
++
++ /* Allocate the unaligned LHS offset using round_down */
++ gpu_buddy_free_list_internal(mm, blocks);
++ err = __alloc_contig_aligned_retry(mm, lhs_offset, size,
++ min_block_size, blocks);
++ if (!err)
+ return 0;
+- } else if (err != -ENOSPC) {
++ if (err != -ENOSPC) {
+ gpu_buddy_free_list_internal(mm, blocks);
+ return err;
+ }
+- /* Free blocks for the next iteration */
++next:
+ gpu_buddy_free_list_internal(mm, blocks);
+-
+ iter = rb_prev(iter);
+ }
+ }
--- /dev/null
+From stable+bounces-288354-greg=kroah.com@vger.kernel.org Thu Jul 23 16:29:16 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:19:29 -0400
+Subject: gpu: Move DRM buddy allocator one level up (part two)
+To: stable@vger.kernel.org
+Cc: Joel Fernandes <joelagnelf@nvidia.com>, Dave Airlie <airlied@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723141931.3218899-1-sashal@kernel.org>
+
+From: Joel Fernandes <joelagnelf@nvidia.com>
+
+[ Upstream commit ba110db8e1bc206c13fd7d985e79b033f53bfdea ]
+
+Move the DRM buddy allocator one level up so that it can be used by GPU
+drivers (example, nova-core) that have usecases other than DRM (such as
+VFIO vGPU support). Modify the API, structures and Kconfigs to use
+"gpu_buddy" terminology. Adapt the drivers and tests to use the new API.
+
+The commit cannot be split due to bisectability, however no functional
+change is intended. Verified by running K-UNIT tests and build tested
+various configurations.
+
+Signed-off-by: Joel Fernandes <joelagnelf@nvidia.com>
+Reviewed-by: Dave Airlie <airlied@redhat.com>
+[airlied: I've split this into two so git can find copies easier.
+I've also just nuked drm_random library, that stuff needs to be done
+elsewhere and only the buddy tests seem to be using it].
+Signed-off-by: Dave Airlie <airlied@redhat.com>
+Stable-dep-of: 56bc6384314f ("gpu/buddy: bail out of try_harder when alignment cannot be honoured")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ Documentation/gpu/drm-mm.rst | 10
+ MAINTAINERS | 13
+ drivers/gpu/Kconfig | 13
+ drivers/gpu/Makefile | 3
+ drivers/gpu/buddy.c | 1322 +++++++++++++++++++
+ drivers/gpu/drm/Kconfig | 5
+ drivers/gpu/drm/Kconfig.debug | 1
+ drivers/gpu/drm/Makefile | 1
+ drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c | 2
+ drivers/gpu/drm/amd/amdgpu/amdgpu_res_cursor.h | 12
+ drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c | 79 -
+ drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h | 20
+ drivers/gpu/drm/drm_buddy.c | 1278 ------------------
+ drivers/gpu/drm/i915/gem/i915_gem_ttm.c | 3
+ drivers/gpu/drm/i915/i915_scatterlist.c | 10
+ drivers/gpu/drm/i915/i915_ttm_buddy_manager.c | 59
+ drivers/gpu/drm/i915/i915_ttm_buddy_manager.h | 4
+ drivers/gpu/drm/i915/selftests/intel_memory_region.c | 20
+ drivers/gpu/drm/lib/drm_random.c | 44
+ drivers/gpu/drm/lib/drm_random.h | 28
+ drivers/gpu/drm/tests/Makefile | 1
+ drivers/gpu/drm/tests/drm_buddy_test.c | 788 -----------
+ drivers/gpu/drm/tests/drm_exec_test.c | 2
+ drivers/gpu/drm/tests/drm_mm_test.c | 2
+ drivers/gpu/drm/ttm/tests/ttm_bo_validate_test.c | 4
+ drivers/gpu/drm/ttm/tests/ttm_mock_manager.c | 18
+ drivers/gpu/drm/ttm/tests/ttm_mock_manager.h | 4
+ drivers/gpu/drm/xe/xe_res_cursor.h | 34
+ drivers/gpu/drm/xe/xe_svm.c | 12
+ drivers/gpu/drm/xe/xe_ttm_vram_mgr.c | 71 -
+ drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h | 4
+ drivers/gpu/tests/Makefile | 4
+ drivers/gpu/tests/gpu_buddy_test.c | 788 +++++++++++
+ drivers/gpu/tests/gpu_random.c | 44
+ drivers/gpu/tests/gpu_random.h | 28
+ drivers/video/Kconfig | 1
+ include/drm/drm_buddy.h | 165 --
+ include/linux/gpu_buddy.h | 177 ++
+ 38 files changed, 2597 insertions(+), 2477 deletions(-)
+ create mode 100644 drivers/gpu/Kconfig
+ create mode 100644 drivers/gpu/buddy.c
+ create mode 100644 drivers/gpu/tests/Makefile
+ rename drivers/gpu/{drm/tests/drm_buddy_test.c => tests/gpu_buddy_test.c} (66%)
+ rename drivers/gpu/{drm/lib/drm_random.c => tests/gpu_random.c} (59%)
+ rename drivers/gpu/{drm/lib/drm_random.h => tests/gpu_random.h} (53%)
+ create mode 100644 include/linux/gpu_buddy.h
+
+--- a/Documentation/gpu/drm-mm.rst
++++ b/Documentation/gpu/drm-mm.rst
+@@ -509,8 +509,14 @@ DRM GPUVM Function References
+ DRM Buddy Allocator
+ ===================
+
+-DRM Buddy Function References
+------------------------------
++Buddy Allocator Function References (GPU buddy)
++-----------------------------------------------
++
++.. kernel-doc:: drivers/gpu/buddy.c
++ :export:
++
++DRM Buddy Specific Logging Function References
++----------------------------------------------
+
+ .. kernel-doc:: drivers/gpu/drm/drm_buddy.c
+ :export:
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -8656,6 +8656,19 @@ T: git https://gitlab.freedesktop.org/dr
+ F: drivers/gpu/drm/ttm/
+ F: include/drm/ttm/
+
++GPU BUDDY ALLOCATOR
++M: Matthew Auld <matthew.auld@intel.com>
++M: Arun Pravin <arunpravin.paneerselvam@amd.com>
++R: Christian Koenig <christian.koenig@amd.com>
++L: dri-devel@lists.freedesktop.org
++S: Maintained
++T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
++F: drivers/gpu/drm_buddy.c
++F: drivers/gpu/buddy.c
++F: drivers/gpu/tests/gpu_buddy_test.c
++F: include/linux/gpu_buddy.h
++F: include/drm/drm_buddy.h
++
+ DRM AUTOMATED TESTING
+ M: Helen Koike <helen.fornazier@gmail.com>
+ M: Vignesh Raman <vignesh.raman@collabora.com>
+--- /dev/null
++++ b/drivers/gpu/Kconfig
+@@ -0,0 +1,13 @@
++# SPDX-License-Identifier: GPL-2.0
++
++config GPU_BUDDY
++ bool
++ help
++ A page based buddy allocator for GPU memory.
++
++config GPU_BUDDY_KUNIT_TEST
++ tristate "KUnit tests for GPU buddy allocator" if !KUNIT_ALL_TESTS
++ depends on GPU_BUDDY && KUNIT
++ default KUNIT_ALL_TESTS
++ help
++ KUnit tests for the GPU buddy allocator.
+--- a/drivers/gpu/Makefile
++++ b/drivers/gpu/Makefile
+@@ -2,7 +2,8 @@
+ # drm/tegra depends on host1x, so if both drivers are built-in care must be
+ # taken to initialize them in the correct order. Link order is the only way
+ # to ensure this currently.
+-obj-y += host1x/ drm/ vga/
++obj-y += host1x/ drm/ vga/ tests/
+ obj-$(CONFIG_IMX_IPUV3_CORE) += ipu-v3/
+ obj-$(CONFIG_TRACE_GPU_MEM) += trace/
+ obj-$(CONFIG_NOVA_CORE) += nova-core/
++obj-$(CONFIG_GPU_BUDDY) += buddy.o
+--- /dev/null
++++ b/drivers/gpu/buddy.c
+@@ -0,0 +1,1322 @@
++// SPDX-License-Identifier: MIT
++/*
++ * Copyright © 2021 Intel Corporation
++ */
++
++#include <kunit/test-bug.h>
++
++#include <linux/export.h>
++#include <linux/kmemleak.h>
++#include <linux/module.h>
++#include <linux/sizes.h>
++
++#include <linux/gpu_buddy.h>
++
++static struct kmem_cache *slab_blocks;
++
++static struct gpu_buddy_block *gpu_block_alloc(struct gpu_buddy *mm,
++ struct gpu_buddy_block *parent,
++ unsigned int order,
++ u64 offset)
++{
++ struct gpu_buddy_block *block;
++
++ BUG_ON(order > GPU_BUDDY_MAX_ORDER);
++
++ block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL);
++ if (!block)
++ return NULL;
++
++ block->header = offset;
++ block->header |= order;
++ block->parent = parent;
++
++ RB_CLEAR_NODE(&block->rb);
++
++ BUG_ON(block->header & GPU_BUDDY_HEADER_UNUSED);
++ return block;
++}
++
++static void gpu_block_free(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ kmem_cache_free(slab_blocks, block);
++}
++
++static enum gpu_buddy_free_tree
++get_block_tree(struct gpu_buddy_block *block)
++{
++ return gpu_buddy_block_is_clear(block) ?
++ GPU_BUDDY_CLEAR_TREE : GPU_BUDDY_DIRTY_TREE;
++}
++
++static struct gpu_buddy_block *
++rbtree_get_free_block(const struct rb_node *node)
++{
++ return node ? rb_entry(node, struct gpu_buddy_block, rb) : NULL;
++}
++
++static struct gpu_buddy_block *
++rbtree_last_free_block(struct rb_root *root)
++{
++ return rbtree_get_free_block(rb_last(root));
++}
++
++static bool rbtree_is_empty(struct rb_root *root)
++{
++ return RB_EMPTY_ROOT(root);
++}
++
++static bool gpu_buddy_block_offset_less(const struct gpu_buddy_block *block,
++ const struct gpu_buddy_block *node)
++{
++ return gpu_buddy_block_offset(block) < gpu_buddy_block_offset(node);
++}
++
++static bool rbtree_block_offset_less(struct rb_node *block,
++ const struct rb_node *node)
++{
++ return gpu_buddy_block_offset_less(rbtree_get_free_block(block),
++ rbtree_get_free_block(node));
++}
++
++static void rbtree_insert(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block,
++ enum gpu_buddy_free_tree tree)
++{
++ rb_add(&block->rb,
++ &mm->free_trees[tree][gpu_buddy_block_order(block)],
++ rbtree_block_offset_less);
++}
++
++static void rbtree_remove(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ unsigned int order = gpu_buddy_block_order(block);
++ enum gpu_buddy_free_tree tree;
++ struct rb_root *root;
++
++ tree = get_block_tree(block);
++ root = &mm->free_trees[tree][order];
++
++ rb_erase(&block->rb, root);
++ RB_CLEAR_NODE(&block->rb);
++}
++
++static void clear_reset(struct gpu_buddy_block *block)
++{
++ block->header &= ~GPU_BUDDY_HEADER_CLEAR;
++}
++
++static void mark_cleared(struct gpu_buddy_block *block)
++{
++ block->header |= GPU_BUDDY_HEADER_CLEAR;
++}
++
++static void mark_allocated(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ block->header &= ~GPU_BUDDY_HEADER_STATE;
++ block->header |= GPU_BUDDY_ALLOCATED;
++
++ rbtree_remove(mm, block);
++}
++
++static void mark_free(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ enum gpu_buddy_free_tree tree;
++
++ block->header &= ~GPU_BUDDY_HEADER_STATE;
++ block->header |= GPU_BUDDY_FREE;
++
++ tree = get_block_tree(block);
++ rbtree_insert(mm, block, tree);
++}
++
++static void mark_split(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ block->header &= ~GPU_BUDDY_HEADER_STATE;
++ block->header |= GPU_BUDDY_SPLIT;
++
++ rbtree_remove(mm, block);
++}
++
++static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2)
++{
++ return s1 <= e2 && e1 >= s2;
++}
++
++static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2)
++{
++ return s1 <= s2 && e1 >= e2;
++}
++
++static struct gpu_buddy_block *
++__get_buddy(struct gpu_buddy_block *block)
++{
++ struct gpu_buddy_block *parent;
++
++ parent = block->parent;
++ if (!parent)
++ return NULL;
++
++ if (parent->left == block)
++ return parent->right;
++
++ return parent->left;
++}
++
++static unsigned int __gpu_buddy_free(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block,
++ bool force_merge)
++{
++ struct gpu_buddy_block *parent;
++ unsigned int order;
++
++ while ((parent = block->parent)) {
++ struct gpu_buddy_block *buddy;
++
++ buddy = __get_buddy(block);
++
++ if (!gpu_buddy_block_is_free(buddy))
++ break;
++
++ if (!force_merge) {
++ /*
++ * Check the block and its buddy clear state and exit
++ * the loop if they both have the dissimilar state.
++ */
++ if (gpu_buddy_block_is_clear(block) !=
++ gpu_buddy_block_is_clear(buddy))
++ break;
++
++ if (gpu_buddy_block_is_clear(block))
++ mark_cleared(parent);
++ }
++
++ rbtree_remove(mm, buddy);
++ if (force_merge && gpu_buddy_block_is_clear(buddy))
++ mm->clear_avail -= gpu_buddy_block_size(mm, buddy);
++
++ gpu_block_free(mm, block);
++ gpu_block_free(mm, buddy);
++
++ block = parent;
++ }
++
++ order = gpu_buddy_block_order(block);
++ mark_free(mm, block);
++
++ return order;
++}
++
++static int __force_merge(struct gpu_buddy *mm,
++ u64 start,
++ u64 end,
++ unsigned int min_order)
++{
++ unsigned int tree, order;
++ int i;
++
++ if (!min_order)
++ return -ENOMEM;
++
++ if (min_order > mm->max_order)
++ return -EINVAL;
++
++ for_each_free_tree(tree) {
++ for (i = min_order - 1; i >= 0; i--) {
++ struct rb_node *iter = rb_last(&mm->free_trees[tree][i]);
++
++ while (iter) {
++ struct gpu_buddy_block *block, *buddy;
++ u64 block_start, block_end;
++
++ block = rbtree_get_free_block(iter);
++ iter = rb_prev(iter);
++
++ if (!block || !block->parent)
++ continue;
++
++ block_start = gpu_buddy_block_offset(block);
++ block_end = block_start + gpu_buddy_block_size(mm, block) - 1;
++
++ if (!contains(start, end, block_start, block_end))
++ continue;
++
++ buddy = __get_buddy(block);
++ if (!gpu_buddy_block_is_free(buddy))
++ continue;
++
++ WARN_ON(gpu_buddy_block_is_clear(block) ==
++ gpu_buddy_block_is_clear(buddy));
++
++ /*
++ * Advance to the next node when the current node is the buddy,
++ * as freeing the block will also remove its buddy from the tree.
++ */
++ if (iter == &buddy->rb)
++ iter = rb_prev(iter);
++
++ rbtree_remove(mm, block);
++ if (gpu_buddy_block_is_clear(block))
++ mm->clear_avail -= gpu_buddy_block_size(mm, block);
++
++ order = __gpu_buddy_free(mm, block, true);
++ if (order >= min_order)
++ return 0;
++ }
++ }
++ }
++
++ return -ENOMEM;
++}
++
++/**
++ * gpu_buddy_init - init memory manager
++ *
++ * @mm: GPU buddy manager to initialize
++ * @size: size in bytes to manage
++ * @chunk_size: minimum page size in bytes for our allocations
++ *
++ * Initializes the memory manager and its resources.
++ *
++ * Returns:
++ * 0 on success, error code on failure.
++ */
++int gpu_buddy_init(struct gpu_buddy *mm, u64 size, u64 chunk_size)
++{
++ unsigned int i, j, root_count = 0;
++ u64 offset = 0;
++
++ if (size < chunk_size)
++ return -EINVAL;
++
++ if (chunk_size < SZ_4K)
++ return -EINVAL;
++
++ if (!is_power_of_2(chunk_size))
++ return -EINVAL;
++
++ size = round_down(size, chunk_size);
++
++ mm->size = size;
++ mm->avail = size;
++ mm->clear_avail = 0;
++ mm->chunk_size = chunk_size;
++ mm->max_order = ilog2(size) - ilog2(chunk_size);
++
++ BUG_ON(mm->max_order > GPU_BUDDY_MAX_ORDER);
++
++ mm->free_trees = kmalloc_array(GPU_BUDDY_MAX_FREE_TREES,
++ sizeof(*mm->free_trees),
++ GFP_KERNEL);
++ if (!mm->free_trees)
++ return -ENOMEM;
++
++ for_each_free_tree(i) {
++ mm->free_trees[i] = kmalloc_array(mm->max_order + 1,
++ sizeof(struct rb_root),
++ GFP_KERNEL);
++ if (!mm->free_trees[i])
++ goto out_free_tree;
++
++ for (j = 0; j <= mm->max_order; ++j)
++ mm->free_trees[i][j] = RB_ROOT;
++ }
++
++ mm->n_roots = hweight64(size);
++
++ mm->roots = kmalloc_array(mm->n_roots,
++ sizeof(struct gpu_buddy_block *),
++ GFP_KERNEL);
++ if (!mm->roots)
++ goto out_free_tree;
++
++ /*
++ * Split into power-of-two blocks, in case we are given a size that is
++ * not itself a power-of-two.
++ */
++ do {
++ struct gpu_buddy_block *root;
++ unsigned int order;
++ u64 root_size;
++
++ order = ilog2(size) - ilog2(chunk_size);
++ root_size = chunk_size << order;
++
++ root = gpu_block_alloc(mm, NULL, order, offset);
++ if (!root)
++ goto out_free_roots;
++
++ mark_free(mm, root);
++
++ BUG_ON(root_count > mm->max_order);
++ BUG_ON(gpu_buddy_block_size(mm, root) < chunk_size);
++
++ mm->roots[root_count] = root;
++
++ offset += root_size;
++ size -= root_size;
++ root_count++;
++ } while (size);
++
++ return 0;
++
++out_free_roots:
++ while (root_count--)
++ gpu_block_free(mm, mm->roots[root_count]);
++ kfree(mm->roots);
++out_free_tree:
++ while (i--)
++ kfree(mm->free_trees[i]);
++ kfree(mm->free_trees);
++ return -ENOMEM;
++}
++EXPORT_SYMBOL(gpu_buddy_init);
++
++/**
++ * gpu_buddy_fini - tear down the memory manager
++ *
++ * @mm: GPU buddy manager to free
++ *
++ * Cleanup memory manager resources and the freetree
++ */
++void gpu_buddy_fini(struct gpu_buddy *mm)
++{
++ u64 root_size, size, start;
++ unsigned int order;
++ int i;
++
++ size = mm->size;
++
++ for (i = 0; i < mm->n_roots; ++i) {
++ order = ilog2(size) - ilog2(mm->chunk_size);
++ start = gpu_buddy_block_offset(mm->roots[i]);
++ __force_merge(mm, start, start + size, order);
++
++ if (WARN_ON(!gpu_buddy_block_is_free(mm->roots[i])))
++ kunit_fail_current_test("buddy_fini() root");
++
++ gpu_block_free(mm, mm->roots[i]);
++
++ root_size = mm->chunk_size << order;
++ size -= root_size;
++ }
++
++ WARN_ON(mm->avail != mm->size);
++
++ for_each_free_tree(i)
++ kfree(mm->free_trees[i]);
++ kfree(mm->free_trees);
++ kfree(mm->roots);
++}
++EXPORT_SYMBOL(gpu_buddy_fini);
++
++static int split_block(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ unsigned int block_order = gpu_buddy_block_order(block) - 1;
++ u64 offset = gpu_buddy_block_offset(block);
++
++ BUG_ON(!gpu_buddy_block_is_free(block));
++ BUG_ON(!gpu_buddy_block_order(block));
++
++ block->left = gpu_block_alloc(mm, block, block_order, offset);
++ if (!block->left)
++ return -ENOMEM;
++
++ block->right = gpu_block_alloc(mm, block, block_order,
++ offset + (mm->chunk_size << block_order));
++ if (!block->right) {
++ gpu_block_free(mm, block->left);
++ return -ENOMEM;
++ }
++
++ mark_split(mm, block);
++
++ if (gpu_buddy_block_is_clear(block)) {
++ mark_cleared(block->left);
++ mark_cleared(block->right);
++ clear_reset(block);
++ }
++
++ mark_free(mm, block->left);
++ mark_free(mm, block->right);
++
++ return 0;
++}
++
++/**
++ * gpu_get_buddy - get buddy address
++ *
++ * @block: GPU buddy block
++ *
++ * Returns the corresponding buddy block for @block, or NULL
++ * if this is a root block and can't be merged further.
++ * Requires some kind of locking to protect against
++ * any concurrent allocate and free operations.
++ */
++struct gpu_buddy_block *
++gpu_get_buddy(struct gpu_buddy_block *block)
++{
++ return __get_buddy(block);
++}
++EXPORT_SYMBOL(gpu_get_buddy);
++
++/**
++ * gpu_buddy_reset_clear - reset blocks clear state
++ *
++ * @mm: GPU buddy manager
++ * @is_clear: blocks clear state
++ *
++ * Reset the clear state based on @is_clear value for each block
++ * in the freetree.
++ */
++void gpu_buddy_reset_clear(struct gpu_buddy *mm, bool is_clear)
++{
++ enum gpu_buddy_free_tree src_tree, dst_tree;
++ u64 root_size, size, start;
++ unsigned int order;
++ int i;
++
++ size = mm->size;
++ for (i = 0; i < mm->n_roots; ++i) {
++ order = ilog2(size) - ilog2(mm->chunk_size);
++ start = gpu_buddy_block_offset(mm->roots[i]);
++ __force_merge(mm, start, start + size, order);
++
++ root_size = mm->chunk_size << order;
++ size -= root_size;
++ }
++
++ src_tree = is_clear ? GPU_BUDDY_DIRTY_TREE : GPU_BUDDY_CLEAR_TREE;
++ dst_tree = is_clear ? GPU_BUDDY_CLEAR_TREE : GPU_BUDDY_DIRTY_TREE;
++
++ for (i = 0; i <= mm->max_order; ++i) {
++ struct rb_root *root = &mm->free_trees[src_tree][i];
++ struct gpu_buddy_block *block, *tmp;
++
++ rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
++ rbtree_remove(mm, block);
++ if (is_clear) {
++ mark_cleared(block);
++ mm->clear_avail += gpu_buddy_block_size(mm, block);
++ } else {
++ clear_reset(block);
++ mm->clear_avail -= gpu_buddy_block_size(mm, block);
++ }
++
++ rbtree_insert(mm, block, dst_tree);
++ }
++ }
++}
++EXPORT_SYMBOL(gpu_buddy_reset_clear);
++
++/**
++ * gpu_buddy_free_block - free a block
++ *
++ * @mm: GPU buddy manager
++ * @block: block to be freed
++ */
++void gpu_buddy_free_block(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ BUG_ON(!gpu_buddy_block_is_allocated(block));
++ mm->avail += gpu_buddy_block_size(mm, block);
++ if (gpu_buddy_block_is_clear(block))
++ mm->clear_avail += gpu_buddy_block_size(mm, block);
++
++ __gpu_buddy_free(mm, block, false);
++}
++EXPORT_SYMBOL(gpu_buddy_free_block);
++
++static void __gpu_buddy_free_list(struct gpu_buddy *mm,
++ struct list_head *objects,
++ bool mark_clear,
++ bool mark_dirty)
++{
++ struct gpu_buddy_block *block, *on;
++
++ WARN_ON(mark_dirty && mark_clear);
++
++ list_for_each_entry_safe(block, on, objects, link) {
++ if (mark_clear)
++ mark_cleared(block);
++ else if (mark_dirty)
++ clear_reset(block);
++ gpu_buddy_free_block(mm, block);
++ cond_resched();
++ }
++ INIT_LIST_HEAD(objects);
++}
++
++static void gpu_buddy_free_list_internal(struct gpu_buddy *mm,
++ struct list_head *objects)
++{
++ /*
++ * Don't touch the clear/dirty bit, since allocation is still internal
++ * at this point. For example we might have just failed part of the
++ * allocation.
++ */
++ __gpu_buddy_free_list(mm, objects, false, false);
++}
++
++/**
++ * gpu_buddy_free_list - free blocks
++ *
++ * @mm: GPU buddy manager
++ * @objects: input list head to free blocks
++ * @flags: optional flags like GPU_BUDDY_CLEARED
++ */
++void gpu_buddy_free_list(struct gpu_buddy *mm,
++ struct list_head *objects,
++ unsigned int flags)
++{
++ bool mark_clear = flags & GPU_BUDDY_CLEARED;
++
++ __gpu_buddy_free_list(mm, objects, mark_clear, !mark_clear);
++}
++EXPORT_SYMBOL(gpu_buddy_free_list);
++
++static bool block_incompatible(struct gpu_buddy_block *block, unsigned int flags)
++{
++ bool needs_clear = flags & GPU_BUDDY_CLEAR_ALLOCATION;
++
++ return needs_clear != gpu_buddy_block_is_clear(block);
++}
++
++static struct gpu_buddy_block *
++__alloc_range_bias(struct gpu_buddy *mm,
++ u64 start, u64 end,
++ unsigned int order,
++ unsigned long flags,
++ bool fallback)
++{
++ u64 req_size = mm->chunk_size << order;
++ struct gpu_buddy_block *block;
++ struct gpu_buddy_block *buddy;
++ LIST_HEAD(dfs);
++ int err;
++ int i;
++
++ end = end - 1;
++
++ for (i = 0; i < mm->n_roots; ++i)
++ list_add_tail(&mm->roots[i]->tmp_link, &dfs);
++
++ do {
++ u64 block_start;
++ u64 block_end;
++
++ block = list_first_entry_or_null(&dfs,
++ struct gpu_buddy_block,
++ tmp_link);
++ if (!block)
++ break;
++
++ list_del(&block->tmp_link);
++
++ if (gpu_buddy_block_order(block) < order)
++ continue;
++
++ block_start = gpu_buddy_block_offset(block);
++ block_end = block_start + gpu_buddy_block_size(mm, block) - 1;
++
++ if (!overlaps(start, end, block_start, block_end))
++ continue;
++
++ if (gpu_buddy_block_is_allocated(block))
++ continue;
++
++ if (block_start < start || block_end > end) {
++ u64 adjusted_start = max(block_start, start);
++ u64 adjusted_end = min(block_end, end);
++
++ if (round_down(adjusted_end + 1, req_size) <=
++ round_up(adjusted_start, req_size))
++ continue;
++ }
++
++ if (!fallback && block_incompatible(block, flags))
++ continue;
++
++ if (contains(start, end, block_start, block_end) &&
++ order == gpu_buddy_block_order(block)) {
++ /*
++ * Find the free block within the range.
++ */
++ if (gpu_buddy_block_is_free(block))
++ return block;
++
++ continue;
++ }
++
++ if (!gpu_buddy_block_is_split(block)) {
++ err = split_block(mm, block);
++ if (unlikely(err))
++ goto err_undo;
++ }
++
++ list_add(&block->right->tmp_link, &dfs);
++ list_add(&block->left->tmp_link, &dfs);
++ } while (1);
++
++ return ERR_PTR(-ENOSPC);
++
++err_undo:
++ /*
++ * We really don't want to leave around a bunch of split blocks, since
++ * bigger is better, so make sure we merge everything back before we
++ * free the allocated blocks.
++ */
++ buddy = __get_buddy(block);
++ if (buddy &&
++ (gpu_buddy_block_is_free(block) &&
++ gpu_buddy_block_is_free(buddy)))
++ __gpu_buddy_free(mm, block, false);
++ return ERR_PTR(err);
++}
++
++static struct gpu_buddy_block *
++__gpu_buddy_alloc_range_bias(struct gpu_buddy *mm,
++ u64 start, u64 end,
++ unsigned int order,
++ unsigned long flags)
++{
++ struct gpu_buddy_block *block;
++ bool fallback = false;
++
++ block = __alloc_range_bias(mm, start, end, order,
++ flags, fallback);
++ if (IS_ERR(block))
++ return __alloc_range_bias(mm, start, end, order,
++ flags, !fallback);
++
++ return block;
++}
++
++static struct gpu_buddy_block *
++get_maxblock(struct gpu_buddy *mm,
++ unsigned int order,
++ enum gpu_buddy_free_tree tree)
++{
++ struct gpu_buddy_block *max_block = NULL, *block = NULL;
++ struct rb_root *root;
++ unsigned int i;
++
++ for (i = order; i <= mm->max_order; ++i) {
++ root = &mm->free_trees[tree][i];
++ block = rbtree_last_free_block(root);
++ if (!block)
++ continue;
++
++ if (!max_block) {
++ max_block = block;
++ continue;
++ }
++
++ if (gpu_buddy_block_offset(block) >
++ gpu_buddy_block_offset(max_block)) {
++ max_block = block;
++ }
++ }
++
++ return max_block;
++}
++
++static struct gpu_buddy_block *
++alloc_from_freetree(struct gpu_buddy *mm,
++ unsigned int order,
++ unsigned long flags)
++{
++ struct gpu_buddy_block *block = NULL;
++ struct rb_root *root;
++ enum gpu_buddy_free_tree tree;
++ unsigned int tmp;
++ int err;
++
++ tree = (flags & GPU_BUDDY_CLEAR_ALLOCATION) ?
++ GPU_BUDDY_CLEAR_TREE : GPU_BUDDY_DIRTY_TREE;
++
++ if (flags & GPU_BUDDY_TOPDOWN_ALLOCATION) {
++ block = get_maxblock(mm, order, tree);
++ if (block)
++ /* Store the obtained block order */
++ tmp = gpu_buddy_block_order(block);
++ } else {
++ for (tmp = order; tmp <= mm->max_order; ++tmp) {
++ /* Get RB tree root for this order and tree */
++ root = &mm->free_trees[tree][tmp];
++ block = rbtree_last_free_block(root);
++ if (block)
++ break;
++ }
++ }
++
++ if (!block) {
++ /* Try allocating from the other tree */
++ tree = (tree == GPU_BUDDY_CLEAR_TREE) ?
++ GPU_BUDDY_DIRTY_TREE : GPU_BUDDY_CLEAR_TREE;
++
++ for (tmp = order; tmp <= mm->max_order; ++tmp) {
++ root = &mm->free_trees[tree][tmp];
++ block = rbtree_last_free_block(root);
++ if (block)
++ break;
++ }
++
++ if (!block)
++ return ERR_PTR(-ENOSPC);
++ }
++
++ BUG_ON(!gpu_buddy_block_is_free(block));
++
++ while (tmp != order) {
++ err = split_block(mm, block);
++ if (unlikely(err))
++ goto err_undo;
++
++ block = block->right;
++ tmp--;
++ }
++ return block;
++
++err_undo:
++ if (tmp != order)
++ __gpu_buddy_free(mm, block, false);
++ return ERR_PTR(err);
++}
++
++static int __alloc_range(struct gpu_buddy *mm,
++ struct list_head *dfs,
++ u64 start, u64 size,
++ struct list_head *blocks,
++ u64 *total_allocated_on_err)
++{
++ struct gpu_buddy_block *block;
++ struct gpu_buddy_block *buddy;
++ u64 total_allocated = 0;
++ LIST_HEAD(allocated);
++ u64 end;
++ int err;
++
++ end = start + size - 1;
++
++ do {
++ u64 block_start;
++ u64 block_end;
++
++ block = list_first_entry_or_null(dfs,
++ struct gpu_buddy_block,
++ tmp_link);
++ if (!block)
++ break;
++
++ list_del(&block->tmp_link);
++
++ block_start = gpu_buddy_block_offset(block);
++ block_end = block_start + gpu_buddy_block_size(mm, block) - 1;
++
++ if (!overlaps(start, end, block_start, block_end))
++ continue;
++
++ if (gpu_buddy_block_is_allocated(block)) {
++ err = -ENOSPC;
++ goto err_free;
++ }
++
++ if (contains(start, end, block_start, block_end)) {
++ if (gpu_buddy_block_is_free(block)) {
++ mark_allocated(mm, block);
++ total_allocated += gpu_buddy_block_size(mm, block);
++ mm->avail -= gpu_buddy_block_size(mm, block);
++ if (gpu_buddy_block_is_clear(block))
++ mm->clear_avail -= gpu_buddy_block_size(mm, block);
++ list_add_tail(&block->link, &allocated);
++ continue;
++ } else if (!mm->clear_avail) {
++ err = -ENOSPC;
++ goto err_free;
++ }
++ }
++
++ if (!gpu_buddy_block_is_split(block)) {
++ err = split_block(mm, block);
++ if (unlikely(err))
++ goto err_undo;
++ }
++
++ list_add(&block->right->tmp_link, dfs);
++ list_add(&block->left->tmp_link, dfs);
++ } while (1);
++
++ if (total_allocated < size) {
++ err = -ENOSPC;
++ goto err_free;
++ }
++
++ list_splice_tail(&allocated, blocks);
++
++ return 0;
++
++err_undo:
++ /*
++ * We really don't want to leave around a bunch of split blocks, since
++ * bigger is better, so make sure we merge everything back before we
++ * free the allocated blocks.
++ */
++ buddy = __get_buddy(block);
++ if (buddy &&
++ (gpu_buddy_block_is_free(block) &&
++ gpu_buddy_block_is_free(buddy)))
++ __gpu_buddy_free(mm, block, false);
++
++err_free:
++ if (err == -ENOSPC && total_allocated_on_err) {
++ list_splice_tail(&allocated, blocks);
++ *total_allocated_on_err = total_allocated;
++ } else {
++ gpu_buddy_free_list_internal(mm, &allocated);
++ }
++
++ return err;
++}
++
++static int __gpu_buddy_alloc_range(struct gpu_buddy *mm,
++ u64 start,
++ u64 size,
++ u64 *total_allocated_on_err,
++ struct list_head *blocks)
++{
++ LIST_HEAD(dfs);
++ int i;
++
++ for (i = 0; i < mm->n_roots; ++i)
++ list_add_tail(&mm->roots[i]->tmp_link, &dfs);
++
++ return __alloc_range(mm, &dfs, start, size,
++ blocks, total_allocated_on_err);
++}
++
++static int __alloc_contig_try_harder(struct gpu_buddy *mm,
++ u64 size,
++ u64 min_block_size,
++ struct list_head *blocks)
++{
++ u64 rhs_offset, lhs_offset, lhs_size, filled;
++ struct gpu_buddy_block *block;
++ unsigned int tree, order;
++ LIST_HEAD(blocks_lhs);
++ unsigned long pages;
++ u64 modify_size;
++ int err;
++
++ modify_size = rounddown_pow_of_two(size);
++ pages = modify_size >> ilog2(mm->chunk_size);
++ order = fls(pages) - 1;
++ if (order == 0)
++ return -ENOSPC;
++
++ for_each_free_tree(tree) {
++ struct rb_root *root;
++ struct rb_node *iter;
++
++ root = &mm->free_trees[tree][order];
++ if (rbtree_is_empty(root))
++ continue;
++
++ iter = rb_last(root);
++ while (iter) {
++ block = rbtree_get_free_block(iter);
++
++ /* Allocate blocks traversing RHS */
++ rhs_offset = gpu_buddy_block_offset(block);
++ err = __gpu_buddy_alloc_range(mm, rhs_offset, size,
++ &filled, blocks);
++ if (!err || err != -ENOSPC)
++ return err;
++
++ lhs_size = max((size - filled), min_block_size);
++ if (!IS_ALIGNED(lhs_size, min_block_size))
++ lhs_size = round_up(lhs_size, min_block_size);
++
++ /* Allocate blocks traversing LHS */
++ lhs_offset = gpu_buddy_block_offset(block) - lhs_size;
++ err = __gpu_buddy_alloc_range(mm, lhs_offset, lhs_size,
++ NULL, &blocks_lhs);
++ if (!err) {
++ list_splice(&blocks_lhs, blocks);
++ return 0;
++ } else if (err != -ENOSPC) {
++ gpu_buddy_free_list_internal(mm, blocks);
++ return err;
++ }
++ /* Free blocks for the next iteration */
++ gpu_buddy_free_list_internal(mm, blocks);
++
++ iter = rb_prev(iter);
++ }
++ }
++
++ return -ENOSPC;
++}
++
++/**
++ * gpu_buddy_block_trim - free unused pages
++ *
++ * @mm: GPU buddy manager
++ * @start: start address to begin the trimming.
++ * @new_size: original size requested
++ * @blocks: Input and output list of allocated blocks.
++ * MUST contain single block as input to be trimmed.
++ * On success will contain the newly allocated blocks
++ * making up the @new_size. Blocks always appear in
++ * ascending order
++ *
++ * For contiguous allocation, we round up the size to the nearest
++ * power of two value, drivers consume *actual* size, so remaining
++ * portions are unused and can be optionally freed with this function
++ *
++ * Returns:
++ * 0 on success, error code on failure.
++ */
++int gpu_buddy_block_trim(struct gpu_buddy *mm,
++ u64 *start,
++ u64 new_size,
++ struct list_head *blocks)
++{
++ struct gpu_buddy_block *parent;
++ struct gpu_buddy_block *block;
++ u64 block_start, block_end;
++ LIST_HEAD(dfs);
++ u64 new_start;
++ int err;
++
++ if (!list_is_singular(blocks))
++ return -EINVAL;
++
++ block = list_first_entry(blocks,
++ struct gpu_buddy_block,
++ link);
++
++ block_start = gpu_buddy_block_offset(block);
++ block_end = block_start + gpu_buddy_block_size(mm, block);
++
++ if (WARN_ON(!gpu_buddy_block_is_allocated(block)))
++ return -EINVAL;
++
++ if (new_size > gpu_buddy_block_size(mm, block))
++ return -EINVAL;
++
++ if (!new_size || !IS_ALIGNED(new_size, mm->chunk_size))
++ return -EINVAL;
++
++ if (new_size == gpu_buddy_block_size(mm, block))
++ return 0;
++
++ new_start = block_start;
++ if (start) {
++ new_start = *start;
++
++ if (new_start < block_start)
++ return -EINVAL;
++
++ if (!IS_ALIGNED(new_start, mm->chunk_size))
++ return -EINVAL;
++
++ if (range_overflows(new_start, new_size, block_end))
++ return -EINVAL;
++ }
++
++ list_del(&block->link);
++ mark_free(mm, block);
++ mm->avail += gpu_buddy_block_size(mm, block);
++ if (gpu_buddy_block_is_clear(block))
++ mm->clear_avail += gpu_buddy_block_size(mm, block);
++
++ /* Prevent recursively freeing this node */
++ parent = block->parent;
++ block->parent = NULL;
++
++ list_add(&block->tmp_link, &dfs);
++ err = __alloc_range(mm, &dfs, new_start, new_size, blocks, NULL);
++ if (err) {
++ mark_allocated(mm, block);
++ mm->avail -= gpu_buddy_block_size(mm, block);
++ if (gpu_buddy_block_is_clear(block))
++ mm->clear_avail -= gpu_buddy_block_size(mm, block);
++ list_add(&block->link, blocks);
++ }
++
++ block->parent = parent;
++ return err;
++}
++EXPORT_SYMBOL(gpu_buddy_block_trim);
++
++static struct gpu_buddy_block *
++__gpu_buddy_alloc_blocks(struct gpu_buddy *mm,
++ u64 start, u64 end,
++ unsigned int order,
++ unsigned long flags)
++{
++ if (flags & GPU_BUDDY_RANGE_ALLOCATION)
++ /* Allocate traversing within the range */
++ return __gpu_buddy_alloc_range_bias(mm, start, end,
++ order, flags);
++ else
++ /* Allocate from freetree */
++ return alloc_from_freetree(mm, order, flags);
++}
++
++/**
++ * gpu_buddy_alloc_blocks - allocate power-of-two blocks
++ *
++ * @mm: GPU buddy manager to allocate from
++ * @start: start of the allowed range for this block
++ * @end: end of the allowed range for this block
++ * @size: size of the allocation in bytes
++ * @min_block_size: alignment of the allocation
++ * @blocks: output list head to add allocated blocks
++ * @flags: GPU_BUDDY_*_ALLOCATION flags
++ *
++ * alloc_range_bias() called on range limitations, which traverses
++ * the tree and returns the desired block.
++ *
++ * alloc_from_freetree() called when *no* range restrictions
++ * are enforced, which picks the block from the freetree.
++ *
++ * Returns:
++ * 0 on success, error code on failure.
++ */
++int gpu_buddy_alloc_blocks(struct gpu_buddy *mm,
++ u64 start, u64 end, u64 size,
++ u64 min_block_size,
++ struct list_head *blocks,
++ unsigned long flags)
++{
++ struct gpu_buddy_block *block = NULL;
++ u64 original_size, original_min_size;
++ unsigned int min_order, order;
++ LIST_HEAD(allocated);
++ unsigned long pages;
++ int err;
++
++ if (size < mm->chunk_size)
++ return -EINVAL;
++
++ if (min_block_size < mm->chunk_size)
++ return -EINVAL;
++
++ if (!is_power_of_2(min_block_size))
++ return -EINVAL;
++
++ if (!IS_ALIGNED(start | end | size, mm->chunk_size))
++ return -EINVAL;
++
++ if (end > mm->size)
++ return -EINVAL;
++
++ if (range_overflows(start, size, mm->size))
++ return -EINVAL;
++
++ /* Actual range allocation */
++ if (start + size == end) {
++ if (!IS_ALIGNED(start | end, min_block_size))
++ return -EINVAL;
++
++ return __gpu_buddy_alloc_range(mm, start, size, NULL, blocks);
++ }
++
++ original_size = size;
++ original_min_size = min_block_size;
++
++ /* Roundup the size to power of 2 */
++ if (flags & GPU_BUDDY_CONTIGUOUS_ALLOCATION) {
++ size = roundup_pow_of_two(size);
++ min_block_size = size;
++ /* Align size value to min_block_size */
++ } else if (!IS_ALIGNED(size, min_block_size)) {
++ size = round_up(size, min_block_size);
++ }
++
++ pages = size >> ilog2(mm->chunk_size);
++ order = fls(pages) - 1;
++ min_order = ilog2(min_block_size) - ilog2(mm->chunk_size);
++
++ if (order > mm->max_order || size > mm->size) {
++ if ((flags & GPU_BUDDY_CONTIGUOUS_ALLOCATION) &&
++ !(flags & GPU_BUDDY_RANGE_ALLOCATION))
++ return __alloc_contig_try_harder(mm, original_size,
++ original_min_size, blocks);
++
++ return -EINVAL;
++ }
++
++ do {
++ order = min(order, (unsigned int)fls(pages) - 1);
++ BUG_ON(order > mm->max_order);
++ BUG_ON(order < min_order);
++
++ do {
++ block = __gpu_buddy_alloc_blocks(mm, start,
++ end,
++ order,
++ flags);
++ if (!IS_ERR(block))
++ break;
++
++ if (order-- == min_order) {
++ /* Try allocation through force merge method */
++ if (mm->clear_avail &&
++ !__force_merge(mm, start, end, min_order)) {
++ block = __gpu_buddy_alloc_blocks(mm, start,
++ end,
++ min_order,
++ flags);
++ if (!IS_ERR(block)) {
++ order = min_order;
++ break;
++ }
++ }
++
++ /*
++ * Try contiguous block allocation through
++ * try harder method.
++ */
++ if (flags & GPU_BUDDY_CONTIGUOUS_ALLOCATION &&
++ !(flags & GPU_BUDDY_RANGE_ALLOCATION))
++ return __alloc_contig_try_harder(mm,
++ original_size,
++ original_min_size,
++ blocks);
++ err = -ENOSPC;
++ goto err_free;
++ }
++ } while (1);
++
++ mark_allocated(mm, block);
++ mm->avail -= gpu_buddy_block_size(mm, block);
++ if (gpu_buddy_block_is_clear(block))
++ mm->clear_avail -= gpu_buddy_block_size(mm, block);
++ kmemleak_update_trace(block);
++ list_add_tail(&block->link, &allocated);
++
++ pages -= BIT(order);
++
++ if (!pages)
++ break;
++ } while (1);
++
++ /* Trim the allocated block to the required size */
++ if (!(flags & GPU_BUDDY_TRIM_DISABLE) &&
++ original_size != size) {
++ struct list_head *trim_list;
++ LIST_HEAD(temp);
++ u64 trim_size;
++
++ trim_list = &allocated;
++ trim_size = original_size;
++
++ if (!list_is_singular(&allocated)) {
++ block = list_last_entry(&allocated, typeof(*block), link);
++ list_move(&block->link, &temp);
++ trim_list = &temp;
++ trim_size = gpu_buddy_block_size(mm, block) -
++ (size - original_size);
++ }
++
++ gpu_buddy_block_trim(mm,
++ NULL,
++ trim_size,
++ trim_list);
++
++ if (!list_empty(&temp))
++ list_splice_tail(trim_list, &allocated);
++ }
++
++ list_splice_tail(&allocated, blocks);
++ return 0;
++
++err_free:
++ gpu_buddy_free_list_internal(mm, &allocated);
++ return err;
++}
++EXPORT_SYMBOL(gpu_buddy_alloc_blocks);
++
++/**
++ * gpu_buddy_block_print - print block information
++ *
++ * @mm: GPU buddy manager
++ * @block: GPU buddy block
++ */
++void gpu_buddy_block_print(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ u64 start = gpu_buddy_block_offset(block);
++ u64 size = gpu_buddy_block_size(mm, block);
++
++ pr_info("%#018llx-%#018llx: %llu\n", start, start + size, size);
++}
++EXPORT_SYMBOL(gpu_buddy_block_print);
++
++/**
++ * gpu_buddy_print - print allocator state
++ *
++ * @mm: GPU buddy manager
++ * @p: GPU printer to use
++ */
++void gpu_buddy_print(struct gpu_buddy *mm)
++{
++ int order;
++
++ pr_info("chunk_size: %lluKiB, total: %lluMiB, free: %lluMiB, clear_free: %lluMiB\n",
++ mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20);
++
++ for (order = mm->max_order; order >= 0; order--) {
++ struct gpu_buddy_block *block, *tmp;
++ struct rb_root *root;
++ u64 count = 0, free;
++ unsigned int tree;
++
++ for_each_free_tree(tree) {
++ root = &mm->free_trees[tree][order];
++
++ rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
++ BUG_ON(!gpu_buddy_block_is_free(block));
++ count++;
++ }
++ }
++
++ free = count * (mm->chunk_size << order);
++ if (free < SZ_1M)
++ pr_info("order-%2d free: %8llu KiB, blocks: %llu\n",
++ order, free >> 10, count);
++ else
++ pr_info("order-%2d free: %8llu MiB, blocks: %llu\n",
++ order, free >> 20, count);
++ }
++}
++EXPORT_SYMBOL(gpu_buddy_print);
++
++static void gpu_buddy_module_exit(void)
++{
++ kmem_cache_destroy(slab_blocks);
++}
++
++static int __init gpu_buddy_module_init(void)
++{
++ slab_blocks = KMEM_CACHE(gpu_buddy_block, 0);
++ if (!slab_blocks)
++ return -ENOMEM;
++
++ return 0;
++}
++
++module_init(gpu_buddy_module_init);
++module_exit(gpu_buddy_module_exit);
++
++MODULE_DESCRIPTION("GPU Buddy Allocator");
++MODULE_LICENSE("Dual MIT/GPL");
+--- a/drivers/gpu/drm/Kconfig
++++ b/drivers/gpu/drm/Kconfig
+@@ -220,6 +220,7 @@ config DRM_GPUSVM
+ config DRM_BUDDY
+ tristate
+ depends on DRM
++ select GPU_BUDDY
+ help
+ A page based buddy allocator
+
+@@ -416,10 +417,6 @@ config DRM_HYPERV
+ config DRM_PANEL_BACKLIGHT_QUIRKS
+ tristate
+
+-config DRM_LIB_RANDOM
+- bool
+- default n
+-
+ config DRM_PRIVACY_SCREEN
+ bool
+ default n
+--- a/drivers/gpu/drm/Kconfig.debug
++++ b/drivers/gpu/drm/Kconfig.debug
+@@ -69,7 +69,6 @@ config DRM_KUNIT_TEST
+ select DRM_EXPORT_FOR_TESTS if m
+ select DRM_GEM_SHMEM_HELPER
+ select DRM_KUNIT_TEST_HELPERS
+- select DRM_LIB_RANDOM
+ select DRM_SYSFB_HELPER
+ select PRIME_NUMBERS
+ default KUNIT_ALL_TESTS
+--- a/drivers/gpu/drm/Makefile
++++ b/drivers/gpu/drm/Makefile
+@@ -77,7 +77,6 @@ drm-$(CONFIG_DRM_CLIENT) += \
+ drm_client.o \
+ drm_client_event.o \
+ drm_client_modeset.o
+-drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
+ drm-$(CONFIG_COMPAT) += drm_ioc32.o
+ drm-$(CONFIG_DRM_PANEL) += drm_panel.o
+ drm-$(CONFIG_OF) += drm_of.o
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ras.c
+@@ -5416,7 +5416,7 @@ int amdgpu_ras_add_critical_region(struc
+ struct amdgpu_ras *con = amdgpu_ras_get_context(adev);
+ struct amdgpu_vram_mgr_resource *vres;
+ struct ras_critical_region *region;
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ int ret = 0;
+
+ if (!bo || !bo->tbo.resource)
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_res_cursor.h
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_res_cursor.h
+@@ -55,7 +55,7 @@ static inline void amdgpu_res_first(stru
+ uint64_t start, uint64_t size,
+ struct amdgpu_res_cursor *cur)
+ {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ struct list_head *head, *next;
+ struct drm_mm_node *node;
+
+@@ -71,7 +71,7 @@ static inline void amdgpu_res_first(stru
+ head = &to_amdgpu_vram_mgr_resource(res)->blocks;
+
+ block = list_first_entry_or_null(head,
+- struct drm_buddy_block,
++ struct gpu_buddy_block,
+ link);
+ if (!block)
+ goto fallback;
+@@ -81,7 +81,7 @@ static inline void amdgpu_res_first(stru
+
+ next = block->link.next;
+ if (next != head)
+- block = list_entry(next, struct drm_buddy_block, link);
++ block = list_entry(next, struct gpu_buddy_block, link);
+ }
+
+ cur->start = amdgpu_vram_mgr_block_start(block) + start;
+@@ -125,7 +125,7 @@ fallback:
+ */
+ static inline void amdgpu_res_next(struct amdgpu_res_cursor *cur, uint64_t size)
+ {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ struct drm_mm_node *node;
+ struct list_head *next;
+
+@@ -146,7 +146,7 @@ static inline void amdgpu_res_next(struc
+ block = cur->node;
+
+ next = block->link.next;
+- block = list_entry(next, struct drm_buddy_block, link);
++ block = list_entry(next, struct gpu_buddy_block, link);
+
+ cur->node = block;
+ cur->start = amdgpu_vram_mgr_block_start(block);
+@@ -175,7 +175,7 @@ static inline void amdgpu_res_next(struc
+ */
+ static inline bool amdgpu_res_cleared(struct amdgpu_res_cursor *cur)
+ {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+
+ switch (cur->mem_type) {
+ case TTM_PL_VRAM:
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.c
+@@ -25,6 +25,7 @@
+ #include <linux/dma-mapping.h>
+ #include <drm/ttm/ttm_range_manager.h>
+ #include <drm/drm_drv.h>
++#include <drm/drm_buddy.h>
+
+ #include "amdgpu.h"
+ #include "amdgpu_vm.h"
+@@ -52,15 +53,15 @@ to_amdgpu_device(struct amdgpu_vram_mgr
+ return container_of(mgr, struct amdgpu_device, mman.vram_mgr);
+ }
+
+-static inline struct drm_buddy_block *
++static inline struct gpu_buddy_block *
+ amdgpu_vram_mgr_first_block(struct list_head *list)
+ {
+- return list_first_entry_or_null(list, struct drm_buddy_block, link);
++ return list_first_entry_or_null(list, struct gpu_buddy_block, link);
+ }
+
+ static inline bool amdgpu_is_vram_mgr_blocks_contiguous(struct list_head *head)
+ {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ u64 start, size;
+
+ block = amdgpu_vram_mgr_first_block(head);
+@@ -71,7 +72,7 @@ static inline bool amdgpu_is_vram_mgr_bl
+ start = amdgpu_vram_mgr_block_start(block);
+ size = amdgpu_vram_mgr_block_size(block);
+
+- block = list_entry(block->link.next, struct drm_buddy_block, link);
++ block = list_entry(block->link.next, struct gpu_buddy_block, link);
+ if (start + size != amdgpu_vram_mgr_block_start(block))
+ return false;
+ }
+@@ -81,7 +82,7 @@ static inline bool amdgpu_is_vram_mgr_bl
+
+ static inline u64 amdgpu_vram_mgr_blocks_size(struct list_head *head)
+ {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ u64 size = 0;
+
+ list_for_each_entry(block, head, link)
+@@ -254,7 +255,7 @@ const struct attribute_group amdgpu_vram
+ * Calculate how many bytes of the DRM BUDDY block are inside visible VRAM
+ */
+ static u64 amdgpu_vram_mgr_vis_size(struct amdgpu_device *adev,
+- struct drm_buddy_block *block)
++ struct gpu_buddy_block *block)
+ {
+ u64 start = amdgpu_vram_mgr_block_start(block);
+ u64 end = start + amdgpu_vram_mgr_block_size(block);
+@@ -279,7 +280,7 @@ u64 amdgpu_vram_mgr_bo_visible_size(stru
+ struct amdgpu_device *adev = amdgpu_ttm_adev(bo->tbo.bdev);
+ struct ttm_resource *res = bo->tbo.resource;
+ struct amdgpu_vram_mgr_resource *vres = to_amdgpu_vram_mgr_resource(res);
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ u64 usage = 0;
+
+ if (amdgpu_gmc_vram_full_visible(&adev->gmc))
+@@ -299,15 +300,15 @@ static void amdgpu_vram_mgr_do_reserve(s
+ {
+ struct amdgpu_vram_mgr *mgr = to_vram_mgr(man);
+ struct amdgpu_device *adev = to_amdgpu_device(mgr);
+- struct drm_buddy *mm = &mgr->mm;
++ struct gpu_buddy *mm = &mgr->mm;
+ struct amdgpu_vram_reservation *rsv, *temp;
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ uint64_t vis_usage;
+
+ list_for_each_entry_safe(rsv, temp, &mgr->reservations_pending, blocks) {
+- if (drm_buddy_alloc_blocks(mm, rsv->start, rsv->start + rsv->size,
++ if (gpu_buddy_alloc_blocks(mm, rsv->start, rsv->start + rsv->size,
+ rsv->size, mm->chunk_size, &rsv->allocated,
+- DRM_BUDDY_RANGE_ALLOCATION))
++ GPU_BUDDY_RANGE_ALLOCATION))
+ continue;
+
+ block = amdgpu_vram_mgr_first_block(&rsv->allocated);
+@@ -403,7 +404,7 @@ int amdgpu_vram_mgr_query_address_block_
+ uint64_t address, struct amdgpu_vram_block_info *info)
+ {
+ struct amdgpu_vram_mgr_resource *vres;
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ u64 start, size;
+ int ret = -ENOENT;
+
+@@ -450,8 +451,8 @@ static int amdgpu_vram_mgr_new(struct tt
+ struct amdgpu_vram_mgr_resource *vres;
+ u64 size, remaining_size, lpfn, fpfn;
+ unsigned int adjust_dcc_size = 0;
+- struct drm_buddy *mm = &mgr->mm;
+- struct drm_buddy_block *block;
++ struct gpu_buddy *mm = &mgr->mm;
++ struct gpu_buddy_block *block;
+ unsigned long pages_per_block;
+ int r;
+
+@@ -493,17 +494,17 @@ static int amdgpu_vram_mgr_new(struct tt
+ INIT_LIST_HEAD(&vres->blocks);
+
+ if (place->flags & TTM_PL_FLAG_TOPDOWN)
+- vres->flags |= DRM_BUDDY_TOPDOWN_ALLOCATION;
++ vres->flags |= GPU_BUDDY_TOPDOWN_ALLOCATION;
+
+ if (bo->flags & AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS)
+- vres->flags |= DRM_BUDDY_CONTIGUOUS_ALLOCATION;
++ vres->flags |= GPU_BUDDY_CONTIGUOUS_ALLOCATION;
+
+ if (bo->flags & AMDGPU_GEM_CREATE_VRAM_CLEARED)
+- vres->flags |= DRM_BUDDY_CLEAR_ALLOCATION;
++ vres->flags |= GPU_BUDDY_CLEAR_ALLOCATION;
+
+ if (fpfn || lpfn != mgr->mm.size)
+ /* Allocate blocks in desired range */
+- vres->flags |= DRM_BUDDY_RANGE_ALLOCATION;
++ vres->flags |= GPU_BUDDY_RANGE_ALLOCATION;
+
+ if (bo->flags & AMDGPU_GEM_CREATE_GFX12_DCC &&
+ adev->gmc.gmc_funcs->get_dcc_alignment)
+@@ -516,7 +517,7 @@ static int amdgpu_vram_mgr_new(struct tt
+ dcc_size = roundup_pow_of_two(vres->base.size + adjust_dcc_size);
+ remaining_size = (u64)dcc_size;
+
+- vres->flags |= DRM_BUDDY_TRIM_DISABLE;
++ vres->flags |= GPU_BUDDY_TRIM_DISABLE;
+ }
+
+ mutex_lock(&mgr->lock);
+@@ -536,7 +537,7 @@ static int amdgpu_vram_mgr_new(struct tt
+
+ BUG_ON(min_block_size < mm->chunk_size);
+
+- r = drm_buddy_alloc_blocks(mm, fpfn,
++ r = gpu_buddy_alloc_blocks(mm, fpfn,
+ lpfn,
+ size,
+ min_block_size,
+@@ -545,7 +546,7 @@ static int amdgpu_vram_mgr_new(struct tt
+
+ if (unlikely(r == -ENOSPC) && pages_per_block == ~0ul &&
+ !(place->flags & TTM_PL_FLAG_CONTIGUOUS)) {
+- vres->flags &= ~DRM_BUDDY_CONTIGUOUS_ALLOCATION;
++ vres->flags &= ~GPU_BUDDY_CONTIGUOUS_ALLOCATION;
+ pages_per_block = max_t(u32, 2UL << (20UL - PAGE_SHIFT),
+ tbo->page_alignment);
+
+@@ -566,7 +567,7 @@ static int amdgpu_vram_mgr_new(struct tt
+ list_add_tail(&vres->vres_node, &mgr->allocated_vres_list);
+
+ if (bo->flags & AMDGPU_GEM_CREATE_VRAM_CONTIGUOUS && adjust_dcc_size) {
+- struct drm_buddy_block *dcc_block;
++ struct gpu_buddy_block *dcc_block;
+ unsigned long dcc_start;
+ u64 trim_start;
+
+@@ -576,7 +577,7 @@ static int amdgpu_vram_mgr_new(struct tt
+ roundup((unsigned long)amdgpu_vram_mgr_block_start(dcc_block),
+ adjust_dcc_size);
+ trim_start = (u64)dcc_start;
+- drm_buddy_block_trim(mm, &trim_start,
++ gpu_buddy_block_trim(mm, &trim_start,
+ (u64)vres->base.size,
+ &vres->blocks);
+ }
+@@ -614,7 +615,7 @@ static int amdgpu_vram_mgr_new(struct tt
+ return 0;
+
+ error_free_blocks:
+- drm_buddy_free_list(mm, &vres->blocks, 0);
++ gpu_buddy_free_list(mm, &vres->blocks, 0);
+ mutex_unlock(&mgr->lock);
+ error_fini:
+ ttm_resource_fini(man, &vres->base);
+@@ -637,8 +638,8 @@ static void amdgpu_vram_mgr_del(struct t
+ struct amdgpu_vram_mgr_resource *vres = to_amdgpu_vram_mgr_resource(res);
+ struct amdgpu_vram_mgr *mgr = to_vram_mgr(man);
+ struct amdgpu_device *adev = to_amdgpu_device(mgr);
+- struct drm_buddy *mm = &mgr->mm;
+- struct drm_buddy_block *block;
++ struct gpu_buddy *mm = &mgr->mm;
++ struct gpu_buddy_block *block;
+ uint64_t vis_usage = 0;
+
+ mutex_lock(&mgr->lock);
+@@ -649,7 +650,7 @@ static void amdgpu_vram_mgr_del(struct t
+ list_for_each_entry(block, &vres->blocks, link)
+ vis_usage += amdgpu_vram_mgr_vis_size(adev, block);
+
+- drm_buddy_free_list(mm, &vres->blocks, vres->flags);
++ gpu_buddy_free_list(mm, &vres->blocks, vres->flags);
+ amdgpu_vram_mgr_do_reserve(man);
+ mutex_unlock(&mgr->lock);
+
+@@ -688,7 +689,7 @@ int amdgpu_vram_mgr_alloc_sgt(struct amd
+ if (!*sgt)
+ return -ENOMEM;
+
+- /* Determine the number of DRM_BUDDY blocks to export */
++ /* Determine the number of GPU_BUDDY blocks to export */
+ amdgpu_res_first(res, offset, length, &cursor);
+ while (cursor.remaining) {
+ num_entries++;
+@@ -704,10 +705,10 @@ int amdgpu_vram_mgr_alloc_sgt(struct amd
+ sg->length = 0;
+
+ /*
+- * Walk down DRM_BUDDY blocks to populate scatterlist nodes
+- * @note: Use iterator api to get first the DRM_BUDDY block
++ * Walk down GPU_BUDDY blocks to populate scatterlist nodes
++ * @note: Use iterator api to get first the GPU_BUDDY block
+ * and the number of bytes from it. Access the following
+- * DRM_BUDDY block(s) if more buffer needs to exported
++ * GPU_BUDDY block(s) if more buffer needs to exported
+ */
+ amdgpu_res_first(res, offset, length, &cursor);
+ for_each_sgtable_sg((*sgt), sg, i) {
+@@ -792,10 +793,10 @@ uint64_t amdgpu_vram_mgr_vis_usage(struc
+ void amdgpu_vram_mgr_clear_reset_blocks(struct amdgpu_device *adev)
+ {
+ struct amdgpu_vram_mgr *mgr = &adev->mman.vram_mgr;
+- struct drm_buddy *mm = &mgr->mm;
++ struct gpu_buddy *mm = &mgr->mm;
+
+ mutex_lock(&mgr->lock);
+- drm_buddy_reset_clear(mm, false);
++ gpu_buddy_reset_clear(mm, false);
+ mutex_unlock(&mgr->lock);
+ }
+
+@@ -815,7 +816,7 @@ static bool amdgpu_vram_mgr_intersects(s
+ size_t size)
+ {
+ struct amdgpu_vram_mgr_resource *mgr = to_amdgpu_vram_mgr_resource(res);
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+
+ /* Check each drm buddy block individually */
+ list_for_each_entry(block, &mgr->blocks, link) {
+@@ -848,7 +849,7 @@ static bool amdgpu_vram_mgr_compatible(s
+ size_t size)
+ {
+ struct amdgpu_vram_mgr_resource *mgr = to_amdgpu_vram_mgr_resource(res);
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+
+ /* Check each drm buddy block individually */
+ list_for_each_entry(block, &mgr->blocks, link) {
+@@ -877,7 +878,7 @@ static void amdgpu_vram_mgr_debug(struct
+ struct drm_printer *printer)
+ {
+ struct amdgpu_vram_mgr *mgr = to_vram_mgr(man);
+- struct drm_buddy *mm = &mgr->mm;
++ struct gpu_buddy *mm = &mgr->mm;
+ struct amdgpu_vram_reservation *rsv;
+
+ drm_printf(printer, " vis usage:%llu\n",
+@@ -930,7 +931,7 @@ int amdgpu_vram_mgr_init(struct amdgpu_d
+ mgr->default_page_size = PAGE_SIZE;
+
+ man->func = &amdgpu_vram_mgr_func;
+- err = drm_buddy_init(&mgr->mm, man->size, PAGE_SIZE);
++ err = gpu_buddy_init(&mgr->mm, man->size, PAGE_SIZE);
+ if (err)
+ return err;
+
+@@ -965,11 +966,11 @@ void amdgpu_vram_mgr_fini(struct amdgpu_
+ kfree(rsv);
+
+ list_for_each_entry_safe(rsv, temp, &mgr->reserved_pages, blocks) {
+- drm_buddy_free_list(&mgr->mm, &rsv->allocated, 0);
++ gpu_buddy_free_list(&mgr->mm, &rsv->allocated, 0);
+ kfree(rsv);
+ }
+ if (!adev->gmc.is_app_apu)
+- drm_buddy_fini(&mgr->mm);
++ gpu_buddy_fini(&mgr->mm);
+ mutex_unlock(&mgr->lock);
+
+ ttm_resource_manager_cleanup(man);
+--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h
++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_vram_mgr.h
+@@ -24,11 +24,11 @@
+ #ifndef __AMDGPU_VRAM_MGR_H__
+ #define __AMDGPU_VRAM_MGR_H__
+
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+
+ struct amdgpu_vram_mgr {
+ struct ttm_resource_manager manager;
+- struct drm_buddy mm;
++ struct gpu_buddy mm;
+ /* protects access to buffer objects */
+ struct mutex lock;
+ struct list_head reservations_pending;
+@@ -57,19 +57,19 @@ struct amdgpu_vram_mgr_resource {
+ struct amdgpu_vres_task task;
+ };
+
+-static inline u64 amdgpu_vram_mgr_block_start(struct drm_buddy_block *block)
++static inline u64 amdgpu_vram_mgr_block_start(struct gpu_buddy_block *block)
+ {
+- return drm_buddy_block_offset(block);
++ return gpu_buddy_block_offset(block);
+ }
+
+-static inline u64 amdgpu_vram_mgr_block_size(struct drm_buddy_block *block)
++static inline u64 amdgpu_vram_mgr_block_size(struct gpu_buddy_block *block)
+ {
+- return (u64)PAGE_SIZE << drm_buddy_block_order(block);
++ return (u64)PAGE_SIZE << gpu_buddy_block_order(block);
+ }
+
+-static inline bool amdgpu_vram_mgr_is_cleared(struct drm_buddy_block *block)
++static inline bool amdgpu_vram_mgr_is_cleared(struct gpu_buddy_block *block)
+ {
+- return drm_buddy_block_is_clear(block);
++ return gpu_buddy_block_is_clear(block);
+ }
+
+ static inline struct amdgpu_vram_mgr_resource *
+@@ -82,8 +82,8 @@ static inline void amdgpu_vram_mgr_set_c
+ {
+ struct amdgpu_vram_mgr_resource *ares = to_amdgpu_vram_mgr_resource(res);
+
+- WARN_ON(ares->flags & DRM_BUDDY_CLEARED);
+- ares->flags |= DRM_BUDDY_CLEARED;
++ WARN_ON(ares->flags & GPU_BUDDY_CLEARED);
++ ares->flags |= GPU_BUDDY_CLEARED;
+ }
+
+ int amdgpu_vram_mgr_query_address_block_info(struct amdgpu_vram_mgr *mgr,
+--- a/drivers/gpu/drm/drm_buddy.c
++++ b/drivers/gpu/drm/drm_buddy.c
+@@ -10,1250 +10,9 @@
+ #include <linux/module.h>
+ #include <linux/sizes.h>
+
++#include <linux/gpu_buddy.h>
+ #include <drm/drm_buddy.h>
+-
+-enum drm_buddy_free_tree {
+- DRM_BUDDY_CLEAR_TREE = 0,
+- DRM_BUDDY_DIRTY_TREE,
+- DRM_BUDDY_MAX_FREE_TREES,
+-};
+-
+-static struct kmem_cache *slab_blocks;
+-
+-#define for_each_free_tree(tree) \
+- for ((tree) = 0; (tree) < DRM_BUDDY_MAX_FREE_TREES; (tree)++)
+-
+-static struct drm_buddy_block *drm_block_alloc(struct drm_buddy *mm,
+- struct drm_buddy_block *parent,
+- unsigned int order,
+- u64 offset)
+-{
+- struct drm_buddy_block *block;
+-
+- BUG_ON(order > DRM_BUDDY_MAX_ORDER);
+-
+- block = kmem_cache_zalloc(slab_blocks, GFP_KERNEL);
+- if (!block)
+- return NULL;
+-
+- block->header = offset;
+- block->header |= order;
+- block->parent = parent;
+-
+- RB_CLEAR_NODE(&block->rb);
+-
+- BUG_ON(block->header & DRM_BUDDY_HEADER_UNUSED);
+- return block;
+-}
+-
+-static void drm_block_free(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- kmem_cache_free(slab_blocks, block);
+-}
+-
+-static enum drm_buddy_free_tree
+-get_block_tree(struct drm_buddy_block *block)
+-{
+- return drm_buddy_block_is_clear(block) ?
+- DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE;
+-}
+-
+-static struct drm_buddy_block *
+-rbtree_get_free_block(const struct rb_node *node)
+-{
+- return node ? rb_entry(node, struct drm_buddy_block, rb) : NULL;
+-}
+-
+-static struct drm_buddy_block *
+-rbtree_last_free_block(struct rb_root *root)
+-{
+- return rbtree_get_free_block(rb_last(root));
+-}
+-
+-static bool rbtree_is_empty(struct rb_root *root)
+-{
+- return RB_EMPTY_ROOT(root);
+-}
+-
+-static bool drm_buddy_block_offset_less(const struct drm_buddy_block *block,
+- const struct drm_buddy_block *node)
+-{
+- return drm_buddy_block_offset(block) < drm_buddy_block_offset(node);
+-}
+-
+-static bool rbtree_block_offset_less(struct rb_node *block,
+- const struct rb_node *node)
+-{
+- return drm_buddy_block_offset_less(rbtree_get_free_block(block),
+- rbtree_get_free_block(node));
+-}
+-
+-static void rbtree_insert(struct drm_buddy *mm,
+- struct drm_buddy_block *block,
+- enum drm_buddy_free_tree tree)
+-{
+- rb_add(&block->rb,
+- &mm->free_trees[tree][drm_buddy_block_order(block)],
+- rbtree_block_offset_less);
+-}
+-
+-static void rbtree_remove(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- unsigned int order = drm_buddy_block_order(block);
+- enum drm_buddy_free_tree tree;
+- struct rb_root *root;
+-
+- tree = get_block_tree(block);
+- root = &mm->free_trees[tree][order];
+-
+- rb_erase(&block->rb, root);
+- RB_CLEAR_NODE(&block->rb);
+-}
+-
+-static void clear_reset(struct drm_buddy_block *block)
+-{
+- block->header &= ~DRM_BUDDY_HEADER_CLEAR;
+-}
+-
+-static void mark_cleared(struct drm_buddy_block *block)
+-{
+- block->header |= DRM_BUDDY_HEADER_CLEAR;
+-}
+-
+-static void mark_allocated(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- block->header &= ~DRM_BUDDY_HEADER_STATE;
+- block->header |= DRM_BUDDY_ALLOCATED;
+-
+- rbtree_remove(mm, block);
+-}
+-
+-static void mark_free(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- enum drm_buddy_free_tree tree;
+-
+- block->header &= ~DRM_BUDDY_HEADER_STATE;
+- block->header |= DRM_BUDDY_FREE;
+-
+- tree = get_block_tree(block);
+- rbtree_insert(mm, block, tree);
+-}
+-
+-static void mark_split(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- block->header &= ~DRM_BUDDY_HEADER_STATE;
+- block->header |= DRM_BUDDY_SPLIT;
+-
+- rbtree_remove(mm, block);
+-}
+-
+-static inline bool overlaps(u64 s1, u64 e1, u64 s2, u64 e2)
+-{
+- return s1 <= e2 && e1 >= s2;
+-}
+-
+-static inline bool contains(u64 s1, u64 e1, u64 s2, u64 e2)
+-{
+- return s1 <= s2 && e1 >= e2;
+-}
+-
+-static struct drm_buddy_block *
+-__get_buddy(struct drm_buddy_block *block)
+-{
+- struct drm_buddy_block *parent;
+-
+- parent = block->parent;
+- if (!parent)
+- return NULL;
+-
+- if (parent->left == block)
+- return parent->right;
+-
+- return parent->left;
+-}
+-
+-static unsigned int __drm_buddy_free(struct drm_buddy *mm,
+- struct drm_buddy_block *block,
+- bool force_merge)
+-{
+- struct drm_buddy_block *parent;
+- unsigned int order;
+-
+- while ((parent = block->parent)) {
+- struct drm_buddy_block *buddy;
+-
+- buddy = __get_buddy(block);
+-
+- if (!drm_buddy_block_is_free(buddy))
+- break;
+-
+- if (!force_merge) {
+- /*
+- * Check the block and its buddy clear state and exit
+- * the loop if they both have the dissimilar state.
+- */
+- if (drm_buddy_block_is_clear(block) !=
+- drm_buddy_block_is_clear(buddy))
+- break;
+-
+- if (drm_buddy_block_is_clear(block))
+- mark_cleared(parent);
+- }
+-
+- rbtree_remove(mm, buddy);
+- if (force_merge && drm_buddy_block_is_clear(buddy))
+- mm->clear_avail -= drm_buddy_block_size(mm, buddy);
+-
+- drm_block_free(mm, block);
+- drm_block_free(mm, buddy);
+-
+- block = parent;
+- }
+-
+- order = drm_buddy_block_order(block);
+- mark_free(mm, block);
+-
+- return order;
+-}
+-
+-static int __force_merge(struct drm_buddy *mm,
+- u64 start,
+- u64 end,
+- unsigned int min_order)
+-{
+- unsigned int tree, order;
+- int i;
+-
+- if (!min_order)
+- return -ENOMEM;
+-
+- if (min_order > mm->max_order)
+- return -EINVAL;
+-
+- for_each_free_tree(tree) {
+- for (i = min_order - 1; i >= 0; i--) {
+- struct rb_node *iter = rb_last(&mm->free_trees[tree][i]);
+-
+- while (iter) {
+- struct drm_buddy_block *block, *buddy;
+- u64 block_start, block_end;
+-
+- block = rbtree_get_free_block(iter);
+- iter = rb_prev(iter);
+-
+- if (!block || !block->parent)
+- continue;
+-
+- block_start = drm_buddy_block_offset(block);
+- block_end = block_start + drm_buddy_block_size(mm, block) - 1;
+-
+- if (!contains(start, end, block_start, block_end))
+- continue;
+-
+- buddy = __get_buddy(block);
+- if (!drm_buddy_block_is_free(buddy))
+- continue;
+-
+- WARN_ON(drm_buddy_block_is_clear(block) ==
+- drm_buddy_block_is_clear(buddy));
+-
+- /*
+- * Advance to the next node when the current node is the buddy,
+- * as freeing the block will also remove its buddy from the tree.
+- */
+- if (iter == &buddy->rb)
+- iter = rb_prev(iter);
+-
+- rbtree_remove(mm, block);
+- if (drm_buddy_block_is_clear(block))
+- mm->clear_avail -= drm_buddy_block_size(mm, block);
+-
+- order = __drm_buddy_free(mm, block, true);
+- if (order >= min_order)
+- return 0;
+- }
+- }
+- }
+-
+- return -ENOMEM;
+-}
+-
+-/**
+- * drm_buddy_init - init memory manager
+- *
+- * @mm: DRM buddy manager to initialize
+- * @size: size in bytes to manage
+- * @chunk_size: minimum page size in bytes for our allocations
+- *
+- * Initializes the memory manager and its resources.
+- *
+- * Returns:
+- * 0 on success, error code on failure.
+- */
+-int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size)
+-{
+- unsigned int i, j, root_count = 0;
+- u64 offset = 0;
+-
+- if (size < chunk_size)
+- return -EINVAL;
+-
+- if (chunk_size < SZ_4K)
+- return -EINVAL;
+-
+- if (!is_power_of_2(chunk_size))
+- return -EINVAL;
+-
+- size = round_down(size, chunk_size);
+-
+- mm->size = size;
+- mm->avail = size;
+- mm->clear_avail = 0;
+- mm->chunk_size = chunk_size;
+- mm->max_order = ilog2(size) - ilog2(chunk_size);
+-
+- BUG_ON(mm->max_order > DRM_BUDDY_MAX_ORDER);
+-
+- mm->free_trees = kmalloc_array(DRM_BUDDY_MAX_FREE_TREES,
+- sizeof(*mm->free_trees),
+- GFP_KERNEL);
+- if (!mm->free_trees)
+- return -ENOMEM;
+-
+- for_each_free_tree(i) {
+- mm->free_trees[i] = kmalloc_array(mm->max_order + 1,
+- sizeof(struct rb_root),
+- GFP_KERNEL);
+- if (!mm->free_trees[i])
+- goto out_free_tree;
+-
+- for (j = 0; j <= mm->max_order; ++j)
+- mm->free_trees[i][j] = RB_ROOT;
+- }
+-
+- mm->n_roots = hweight64(size);
+-
+- mm->roots = kmalloc_array(mm->n_roots,
+- sizeof(struct drm_buddy_block *),
+- GFP_KERNEL);
+- if (!mm->roots)
+- goto out_free_tree;
+-
+- /*
+- * Split into power-of-two blocks, in case we are given a size that is
+- * not itself a power-of-two.
+- */
+- do {
+- struct drm_buddy_block *root;
+- unsigned int order;
+- u64 root_size;
+-
+- order = ilog2(size) - ilog2(chunk_size);
+- root_size = chunk_size << order;
+-
+- root = drm_block_alloc(mm, NULL, order, offset);
+- if (!root)
+- goto out_free_roots;
+-
+- mark_free(mm, root);
+-
+- BUG_ON(root_count > mm->max_order);
+- BUG_ON(drm_buddy_block_size(mm, root) < chunk_size);
+-
+- mm->roots[root_count] = root;
+-
+- offset += root_size;
+- size -= root_size;
+- root_count++;
+- } while (size);
+-
+- return 0;
+-
+-out_free_roots:
+- while (root_count--)
+- drm_block_free(mm, mm->roots[root_count]);
+- kfree(mm->roots);
+-out_free_tree:
+- while (i--)
+- kfree(mm->free_trees[i]);
+- kfree(mm->free_trees);
+- return -ENOMEM;
+-}
+-EXPORT_SYMBOL(drm_buddy_init);
+-
+-/**
+- * drm_buddy_fini - tear down the memory manager
+- *
+- * @mm: DRM buddy manager to free
+- *
+- * Cleanup memory manager resources and the freetree
+- */
+-void drm_buddy_fini(struct drm_buddy *mm)
+-{
+- u64 root_size, size, start;
+- unsigned int order;
+- int i;
+-
+- size = mm->size;
+-
+- for (i = 0; i < mm->n_roots; ++i) {
+- order = ilog2(size) - ilog2(mm->chunk_size);
+- start = drm_buddy_block_offset(mm->roots[i]);
+- __force_merge(mm, start, start + size, order);
+-
+- if (WARN_ON(!drm_buddy_block_is_free(mm->roots[i])))
+- kunit_fail_current_test("buddy_fini() root");
+-
+- drm_block_free(mm, mm->roots[i]);
+-
+- root_size = mm->chunk_size << order;
+- size -= root_size;
+- }
+-
+- WARN_ON(mm->avail != mm->size);
+-
+- for_each_free_tree(i)
+- kfree(mm->free_trees[i]);
+- kfree(mm->free_trees);
+- kfree(mm->roots);
+-}
+-EXPORT_SYMBOL(drm_buddy_fini);
+-
+-static int split_block(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- unsigned int block_order = drm_buddy_block_order(block) - 1;
+- u64 offset = drm_buddy_block_offset(block);
+-
+- BUG_ON(!drm_buddy_block_is_free(block));
+- BUG_ON(!drm_buddy_block_order(block));
+-
+- block->left = drm_block_alloc(mm, block, block_order, offset);
+- if (!block->left)
+- return -ENOMEM;
+-
+- block->right = drm_block_alloc(mm, block, block_order,
+- offset + (mm->chunk_size << block_order));
+- if (!block->right) {
+- drm_block_free(mm, block->left);
+- return -ENOMEM;
+- }
+-
+- mark_split(mm, block);
+-
+- if (drm_buddy_block_is_clear(block)) {
+- mark_cleared(block->left);
+- mark_cleared(block->right);
+- clear_reset(block);
+- }
+-
+- mark_free(mm, block->left);
+- mark_free(mm, block->right);
+-
+- return 0;
+-}
+-
+-/**
+- * drm_get_buddy - get buddy address
+- *
+- * @block: DRM buddy block
+- *
+- * Returns the corresponding buddy block for @block, or NULL
+- * if this is a root block and can't be merged further.
+- * Requires some kind of locking to protect against
+- * any concurrent allocate and free operations.
+- */
+-struct drm_buddy_block *
+-drm_get_buddy(struct drm_buddy_block *block)
+-{
+- return __get_buddy(block);
+-}
+-EXPORT_SYMBOL(drm_get_buddy);
+-
+-/**
+- * drm_buddy_reset_clear - reset blocks clear state
+- *
+- * @mm: DRM buddy manager
+- * @is_clear: blocks clear state
+- *
+- * Reset the clear state based on @is_clear value for each block
+- * in the freetree.
+- */
+-void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear)
+-{
+- enum drm_buddy_free_tree src_tree, dst_tree;
+- u64 root_size, size, start;
+- unsigned int order;
+- int i;
+-
+- size = mm->size;
+- for (i = 0; i < mm->n_roots; ++i) {
+- order = ilog2(size) - ilog2(mm->chunk_size);
+- start = drm_buddy_block_offset(mm->roots[i]);
+- __force_merge(mm, start, start + size, order);
+-
+- root_size = mm->chunk_size << order;
+- size -= root_size;
+- }
+-
+- src_tree = is_clear ? DRM_BUDDY_DIRTY_TREE : DRM_BUDDY_CLEAR_TREE;
+- dst_tree = is_clear ? DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE;
+-
+- for (i = 0; i <= mm->max_order; ++i) {
+- struct rb_root *root = &mm->free_trees[src_tree][i];
+- struct drm_buddy_block *block, *tmp;
+-
+- rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
+- rbtree_remove(mm, block);
+- if (is_clear) {
+- mark_cleared(block);
+- mm->clear_avail += drm_buddy_block_size(mm, block);
+- } else {
+- clear_reset(block);
+- mm->clear_avail -= drm_buddy_block_size(mm, block);
+- }
+-
+- rbtree_insert(mm, block, dst_tree);
+- }
+- }
+-}
+-EXPORT_SYMBOL(drm_buddy_reset_clear);
+-
+-/**
+- * drm_buddy_free_block - free a block
+- *
+- * @mm: DRM buddy manager
+- * @block: block to be freed
+- */
+-void drm_buddy_free_block(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- BUG_ON(!drm_buddy_block_is_allocated(block));
+- mm->avail += drm_buddy_block_size(mm, block);
+- if (drm_buddy_block_is_clear(block))
+- mm->clear_avail += drm_buddy_block_size(mm, block);
+-
+- __drm_buddy_free(mm, block, false);
+-}
+-EXPORT_SYMBOL(drm_buddy_free_block);
+-
+-static void __drm_buddy_free_list(struct drm_buddy *mm,
+- struct list_head *objects,
+- bool mark_clear,
+- bool mark_dirty)
+-{
+- struct drm_buddy_block *block, *on;
+-
+- WARN_ON(mark_dirty && mark_clear);
+-
+- list_for_each_entry_safe(block, on, objects, link) {
+- if (mark_clear)
+- mark_cleared(block);
+- else if (mark_dirty)
+- clear_reset(block);
+- drm_buddy_free_block(mm, block);
+- cond_resched();
+- }
+- INIT_LIST_HEAD(objects);
+-}
+-
+-static void drm_buddy_free_list_internal(struct drm_buddy *mm,
+- struct list_head *objects)
+-{
+- /*
+- * Don't touch the clear/dirty bit, since allocation is still internal
+- * at this point. For example we might have just failed part of the
+- * allocation.
+- */
+- __drm_buddy_free_list(mm, objects, false, false);
+-}
+-
+-/**
+- * drm_buddy_free_list - free blocks
+- *
+- * @mm: DRM buddy manager
+- * @objects: input list head to free blocks
+- * @flags: optional flags like DRM_BUDDY_CLEARED
+- */
+-void drm_buddy_free_list(struct drm_buddy *mm,
+- struct list_head *objects,
+- unsigned int flags)
+-{
+- bool mark_clear = flags & DRM_BUDDY_CLEARED;
+-
+- __drm_buddy_free_list(mm, objects, mark_clear, !mark_clear);
+-}
+-EXPORT_SYMBOL(drm_buddy_free_list);
+-
+-static bool block_incompatible(struct drm_buddy_block *block, unsigned int flags)
+-{
+- bool needs_clear = flags & DRM_BUDDY_CLEAR_ALLOCATION;
+-
+- return needs_clear != drm_buddy_block_is_clear(block);
+-}
+-
+-static struct drm_buddy_block *
+-__alloc_range_bias(struct drm_buddy *mm,
+- u64 start, u64 end,
+- unsigned int order,
+- unsigned long flags,
+- bool fallback)
+-{
+- u64 req_size = mm->chunk_size << order;
+- struct drm_buddy_block *block;
+- struct drm_buddy_block *buddy;
+- LIST_HEAD(dfs);
+- int err;
+- int i;
+-
+- end = end - 1;
+-
+- for (i = 0; i < mm->n_roots; ++i)
+- list_add_tail(&mm->roots[i]->tmp_link, &dfs);
+-
+- do {
+- u64 block_start;
+- u64 block_end;
+-
+- block = list_first_entry_or_null(&dfs,
+- struct drm_buddy_block,
+- tmp_link);
+- if (!block)
+- break;
+-
+- list_del(&block->tmp_link);
+-
+- if (drm_buddy_block_order(block) < order)
+- continue;
+-
+- block_start = drm_buddy_block_offset(block);
+- block_end = block_start + drm_buddy_block_size(mm, block) - 1;
+-
+- if (!overlaps(start, end, block_start, block_end))
+- continue;
+-
+- if (drm_buddy_block_is_allocated(block))
+- continue;
+-
+- if (block_start < start || block_end > end) {
+- u64 adjusted_start = max(block_start, start);
+- u64 adjusted_end = min(block_end, end);
+-
+- if (round_down(adjusted_end + 1, req_size) <=
+- round_up(adjusted_start, req_size))
+- continue;
+- }
+-
+- if (!fallback && block_incompatible(block, flags))
+- continue;
+-
+- if (contains(start, end, block_start, block_end) &&
+- order == drm_buddy_block_order(block)) {
+- /*
+- * Find the free block within the range.
+- */
+- if (drm_buddy_block_is_free(block))
+- return block;
+-
+- continue;
+- }
+-
+- if (!drm_buddy_block_is_split(block)) {
+- err = split_block(mm, block);
+- if (unlikely(err))
+- goto err_undo;
+- }
+-
+- list_add(&block->right->tmp_link, &dfs);
+- list_add(&block->left->tmp_link, &dfs);
+- } while (1);
+-
+- return ERR_PTR(-ENOSPC);
+-
+-err_undo:
+- /*
+- * We really don't want to leave around a bunch of split blocks, since
+- * bigger is better, so make sure we merge everything back before we
+- * free the allocated blocks.
+- */
+- buddy = __get_buddy(block);
+- if (buddy &&
+- (drm_buddy_block_is_free(block) &&
+- drm_buddy_block_is_free(buddy)))
+- __drm_buddy_free(mm, block, false);
+- return ERR_PTR(err);
+-}
+-
+-static struct drm_buddy_block *
+-__drm_buddy_alloc_range_bias(struct drm_buddy *mm,
+- u64 start, u64 end,
+- unsigned int order,
+- unsigned long flags)
+-{
+- struct drm_buddy_block *block;
+- bool fallback = false;
+-
+- block = __alloc_range_bias(mm, start, end, order,
+- flags, fallback);
+- if (IS_ERR(block))
+- return __alloc_range_bias(mm, start, end, order,
+- flags, !fallback);
+-
+- return block;
+-}
+-
+-static struct drm_buddy_block *
+-get_maxblock(struct drm_buddy *mm,
+- unsigned int order,
+- enum drm_buddy_free_tree tree)
+-{
+- struct drm_buddy_block *max_block = NULL, *block = NULL;
+- struct rb_root *root;
+- unsigned int i;
+-
+- for (i = order; i <= mm->max_order; ++i) {
+- root = &mm->free_trees[tree][i];
+- block = rbtree_last_free_block(root);
+- if (!block)
+- continue;
+-
+- if (!max_block) {
+- max_block = block;
+- continue;
+- }
+-
+- if (drm_buddy_block_offset(block) >
+- drm_buddy_block_offset(max_block)) {
+- max_block = block;
+- }
+- }
+-
+- return max_block;
+-}
+-
+-static struct drm_buddy_block *
+-alloc_from_freetree(struct drm_buddy *mm,
+- unsigned int order,
+- unsigned long flags)
+-{
+- struct drm_buddy_block *block = NULL;
+- struct rb_root *root;
+- enum drm_buddy_free_tree tree;
+- unsigned int tmp;
+- int err;
+-
+- tree = (flags & DRM_BUDDY_CLEAR_ALLOCATION) ?
+- DRM_BUDDY_CLEAR_TREE : DRM_BUDDY_DIRTY_TREE;
+-
+- if (flags & DRM_BUDDY_TOPDOWN_ALLOCATION) {
+- block = get_maxblock(mm, order, tree);
+- if (block)
+- /* Store the obtained block order */
+- tmp = drm_buddy_block_order(block);
+- } else {
+- for (tmp = order; tmp <= mm->max_order; ++tmp) {
+- /* Get RB tree root for this order and tree */
+- root = &mm->free_trees[tree][tmp];
+- block = rbtree_last_free_block(root);
+- if (block)
+- break;
+- }
+- }
+-
+- if (!block) {
+- /* Try allocating from the other tree */
+- tree = (tree == DRM_BUDDY_CLEAR_TREE) ?
+- DRM_BUDDY_DIRTY_TREE : DRM_BUDDY_CLEAR_TREE;
+-
+- for (tmp = order; tmp <= mm->max_order; ++tmp) {
+- root = &mm->free_trees[tree][tmp];
+- block = rbtree_last_free_block(root);
+- if (block)
+- break;
+- }
+-
+- if (!block)
+- return ERR_PTR(-ENOSPC);
+- }
+-
+- BUG_ON(!drm_buddy_block_is_free(block));
+-
+- while (tmp != order) {
+- err = split_block(mm, block);
+- if (unlikely(err))
+- goto err_undo;
+-
+- block = block->right;
+- tmp--;
+- }
+- return block;
+-
+-err_undo:
+- if (tmp != order)
+- __drm_buddy_free(mm, block, false);
+- return ERR_PTR(err);
+-}
+-
+-static int __alloc_range(struct drm_buddy *mm,
+- struct list_head *dfs,
+- u64 start, u64 size,
+- struct list_head *blocks,
+- u64 *total_allocated_on_err)
+-{
+- struct drm_buddy_block *block;
+- struct drm_buddy_block *buddy;
+- u64 total_allocated = 0;
+- LIST_HEAD(allocated);
+- u64 end;
+- int err;
+-
+- end = start + size - 1;
+-
+- do {
+- u64 block_start;
+- u64 block_end;
+-
+- block = list_first_entry_or_null(dfs,
+- struct drm_buddy_block,
+- tmp_link);
+- if (!block)
+- break;
+-
+- list_del(&block->tmp_link);
+-
+- block_start = drm_buddy_block_offset(block);
+- block_end = block_start + drm_buddy_block_size(mm, block) - 1;
+-
+- if (!overlaps(start, end, block_start, block_end))
+- continue;
+-
+- if (drm_buddy_block_is_allocated(block)) {
+- err = -ENOSPC;
+- goto err_free;
+- }
+-
+- if (contains(start, end, block_start, block_end)) {
+- if (drm_buddy_block_is_free(block)) {
+- mark_allocated(mm, block);
+- total_allocated += drm_buddy_block_size(mm, block);
+- mm->avail -= drm_buddy_block_size(mm, block);
+- if (drm_buddy_block_is_clear(block))
+- mm->clear_avail -= drm_buddy_block_size(mm, block);
+- list_add_tail(&block->link, &allocated);
+- continue;
+- } else if (!mm->clear_avail) {
+- err = -ENOSPC;
+- goto err_free;
+- }
+- }
+-
+- if (!drm_buddy_block_is_split(block)) {
+- err = split_block(mm, block);
+- if (unlikely(err))
+- goto err_undo;
+- }
+-
+- list_add(&block->right->tmp_link, dfs);
+- list_add(&block->left->tmp_link, dfs);
+- } while (1);
+-
+- if (total_allocated < size) {
+- err = -ENOSPC;
+- goto err_free;
+- }
+-
+- list_splice_tail(&allocated, blocks);
+-
+- return 0;
+-
+-err_undo:
+- /*
+- * We really don't want to leave around a bunch of split blocks, since
+- * bigger is better, so make sure we merge everything back before we
+- * free the allocated blocks.
+- */
+- buddy = __get_buddy(block);
+- if (buddy &&
+- (drm_buddy_block_is_free(block) &&
+- drm_buddy_block_is_free(buddy)))
+- __drm_buddy_free(mm, block, false);
+-
+-err_free:
+- if (err == -ENOSPC && total_allocated_on_err) {
+- list_splice_tail(&allocated, blocks);
+- *total_allocated_on_err = total_allocated;
+- } else {
+- drm_buddy_free_list_internal(mm, &allocated);
+- }
+-
+- return err;
+-}
+-
+-static int __drm_buddy_alloc_range(struct drm_buddy *mm,
+- u64 start,
+- u64 size,
+- u64 *total_allocated_on_err,
+- struct list_head *blocks)
+-{
+- LIST_HEAD(dfs);
+- int i;
+-
+- for (i = 0; i < mm->n_roots; ++i)
+- list_add_tail(&mm->roots[i]->tmp_link, &dfs);
+-
+- return __alloc_range(mm, &dfs, start, size,
+- blocks, total_allocated_on_err);
+-}
+-
+-static int __alloc_contig_try_harder(struct drm_buddy *mm,
+- u64 size,
+- u64 min_block_size,
+- struct list_head *blocks)
+-{
+- u64 rhs_offset, lhs_offset, lhs_size, filled;
+- struct drm_buddy_block *block;
+- unsigned int tree, order;
+- LIST_HEAD(blocks_lhs);
+- unsigned long pages;
+- u64 modify_size;
+- int err;
+-
+- modify_size = rounddown_pow_of_two(size);
+- pages = modify_size >> ilog2(mm->chunk_size);
+- order = fls(pages) - 1;
+- if (order == 0)
+- return -ENOSPC;
+-
+- for_each_free_tree(tree) {
+- struct rb_root *root;
+- struct rb_node *iter;
+-
+- root = &mm->free_trees[tree][order];
+- if (rbtree_is_empty(root))
+- continue;
+-
+- iter = rb_last(root);
+- while (iter) {
+- block = rbtree_get_free_block(iter);
+-
+- /* Allocate blocks traversing RHS */
+- rhs_offset = drm_buddy_block_offset(block);
+- err = __drm_buddy_alloc_range(mm, rhs_offset, size,
+- &filled, blocks);
+- if (!err || err != -ENOSPC)
+- return err;
+-
+- lhs_size = max((size - filled), min_block_size);
+- if (!IS_ALIGNED(lhs_size, min_block_size))
+- lhs_size = round_up(lhs_size, min_block_size);
+-
+- /* Allocate blocks traversing LHS */
+- lhs_offset = drm_buddy_block_offset(block) - lhs_size;
+- err = __drm_buddy_alloc_range(mm, lhs_offset, lhs_size,
+- NULL, &blocks_lhs);
+- if (!err) {
+- list_splice(&blocks_lhs, blocks);
+- return 0;
+- } else if (err != -ENOSPC) {
+- drm_buddy_free_list_internal(mm, blocks);
+- return err;
+- }
+- /* Free blocks for the next iteration */
+- drm_buddy_free_list_internal(mm, blocks);
+-
+- iter = rb_prev(iter);
+- }
+- }
+-
+- return -ENOSPC;
+-}
+-
+-/**
+- * drm_buddy_block_trim - free unused pages
+- *
+- * @mm: DRM buddy manager
+- * @start: start address to begin the trimming.
+- * @new_size: original size requested
+- * @blocks: Input and output list of allocated blocks.
+- * MUST contain single block as input to be trimmed.
+- * On success will contain the newly allocated blocks
+- * making up the @new_size. Blocks always appear in
+- * ascending order
+- *
+- * For contiguous allocation, we round up the size to the nearest
+- * power of two value, drivers consume *actual* size, so remaining
+- * portions are unused and can be optionally freed with this function
+- *
+- * Returns:
+- * 0 on success, error code on failure.
+- */
+-int drm_buddy_block_trim(struct drm_buddy *mm,
+- u64 *start,
+- u64 new_size,
+- struct list_head *blocks)
+-{
+- struct drm_buddy_block *parent;
+- struct drm_buddy_block *block;
+- u64 block_start, block_end;
+- LIST_HEAD(dfs);
+- u64 new_start;
+- int err;
+-
+- if (!list_is_singular(blocks))
+- return -EINVAL;
+-
+- block = list_first_entry(blocks,
+- struct drm_buddy_block,
+- link);
+-
+- block_start = drm_buddy_block_offset(block);
+- block_end = block_start + drm_buddy_block_size(mm, block);
+-
+- if (WARN_ON(!drm_buddy_block_is_allocated(block)))
+- return -EINVAL;
+-
+- if (new_size > drm_buddy_block_size(mm, block))
+- return -EINVAL;
+-
+- if (!new_size || !IS_ALIGNED(new_size, mm->chunk_size))
+- return -EINVAL;
+-
+- if (new_size == drm_buddy_block_size(mm, block))
+- return 0;
+-
+- new_start = block_start;
+- if (start) {
+- new_start = *start;
+-
+- if (new_start < block_start)
+- return -EINVAL;
+-
+- if (!IS_ALIGNED(new_start, mm->chunk_size))
+- return -EINVAL;
+-
+- if (range_overflows(new_start, new_size, block_end))
+- return -EINVAL;
+- }
+-
+- list_del(&block->link);
+- mark_free(mm, block);
+- mm->avail += drm_buddy_block_size(mm, block);
+- if (drm_buddy_block_is_clear(block))
+- mm->clear_avail += drm_buddy_block_size(mm, block);
+-
+- /* Prevent recursively freeing this node */
+- parent = block->parent;
+- block->parent = NULL;
+-
+- list_add(&block->tmp_link, &dfs);
+- err = __alloc_range(mm, &dfs, new_start, new_size, blocks, NULL);
+- if (err) {
+- mark_allocated(mm, block);
+- mm->avail -= drm_buddy_block_size(mm, block);
+- if (drm_buddy_block_is_clear(block))
+- mm->clear_avail -= drm_buddy_block_size(mm, block);
+- list_add(&block->link, blocks);
+- }
+-
+- block->parent = parent;
+- return err;
+-}
+-EXPORT_SYMBOL(drm_buddy_block_trim);
+-
+-static struct drm_buddy_block *
+-__drm_buddy_alloc_blocks(struct drm_buddy *mm,
+- u64 start, u64 end,
+- unsigned int order,
+- unsigned long flags)
+-{
+- if (flags & DRM_BUDDY_RANGE_ALLOCATION)
+- /* Allocate traversing within the range */
+- return __drm_buddy_alloc_range_bias(mm, start, end,
+- order, flags);
+- else
+- /* Allocate from freetree */
+- return alloc_from_freetree(mm, order, flags);
+-}
+-
+-/**
+- * drm_buddy_alloc_blocks - allocate power-of-two blocks
+- *
+- * @mm: DRM buddy manager to allocate from
+- * @start: start of the allowed range for this block
+- * @end: end of the allowed range for this block
+- * @size: size of the allocation in bytes
+- * @min_block_size: alignment of the allocation
+- * @blocks: output list head to add allocated blocks
+- * @flags: DRM_BUDDY_*_ALLOCATION flags
+- *
+- * alloc_range_bias() called on range limitations, which traverses
+- * the tree and returns the desired block.
+- *
+- * alloc_from_freetree() called when *no* range restrictions
+- * are enforced, which picks the block from the freetree.
+- *
+- * Returns:
+- * 0 on success, error code on failure.
+- */
+-int drm_buddy_alloc_blocks(struct drm_buddy *mm,
+- u64 start, u64 end, u64 size,
+- u64 min_block_size,
+- struct list_head *blocks,
+- unsigned long flags)
+-{
+- struct drm_buddy_block *block = NULL;
+- u64 original_size, original_min_size;
+- unsigned int min_order, order;
+- LIST_HEAD(allocated);
+- unsigned long pages;
+- int err;
+-
+- if (size < mm->chunk_size)
+- return -EINVAL;
+-
+- if (min_block_size < mm->chunk_size)
+- return -EINVAL;
+-
+- if (!is_power_of_2(min_block_size))
+- return -EINVAL;
+-
+- if (!IS_ALIGNED(start | end | size, mm->chunk_size))
+- return -EINVAL;
+-
+- if (end > mm->size)
+- return -EINVAL;
+-
+- if (range_overflows(start, size, mm->size))
+- return -EINVAL;
+-
+- /* Actual range allocation */
+- if (start + size == end) {
+- if (!IS_ALIGNED(start | end, min_block_size))
+- return -EINVAL;
+-
+- return __drm_buddy_alloc_range(mm, start, size, NULL, blocks);
+- }
+-
+- original_size = size;
+- original_min_size = min_block_size;
+-
+- /* Roundup the size to power of 2 */
+- if (flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION) {
+- size = roundup_pow_of_two(size);
+- min_block_size = size;
+- /* Align size value to min_block_size */
+- } else if (!IS_ALIGNED(size, min_block_size)) {
+- size = round_up(size, min_block_size);
+- }
+-
+- pages = size >> ilog2(mm->chunk_size);
+- order = fls(pages) - 1;
+- min_order = ilog2(min_block_size) - ilog2(mm->chunk_size);
+-
+- if (order > mm->max_order || size > mm->size) {
+- if ((flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION) &&
+- !(flags & DRM_BUDDY_RANGE_ALLOCATION))
+- return __alloc_contig_try_harder(mm, original_size,
+- original_min_size, blocks);
+-
+- return -EINVAL;
+- }
+-
+- do {
+- order = min(order, (unsigned int)fls(pages) - 1);
+- BUG_ON(order > mm->max_order);
+- BUG_ON(order < min_order);
+-
+- do {
+- block = __drm_buddy_alloc_blocks(mm, start,
+- end,
+- order,
+- flags);
+- if (!IS_ERR(block))
+- break;
+-
+- if (order-- == min_order) {
+- /* Try allocation through force merge method */
+- if (mm->clear_avail &&
+- !__force_merge(mm, start, end, min_order)) {
+- block = __drm_buddy_alloc_blocks(mm, start,
+- end,
+- min_order,
+- flags);
+- if (!IS_ERR(block)) {
+- order = min_order;
+- break;
+- }
+- }
+-
+- /*
+- * Try contiguous block allocation through
+- * try harder method.
+- */
+- if (flags & DRM_BUDDY_CONTIGUOUS_ALLOCATION &&
+- !(flags & DRM_BUDDY_RANGE_ALLOCATION))
+- return __alloc_contig_try_harder(mm,
+- original_size,
+- original_min_size,
+- blocks);
+- err = -ENOSPC;
+- goto err_free;
+- }
+- } while (1);
+-
+- mark_allocated(mm, block);
+- mm->avail -= drm_buddy_block_size(mm, block);
+- if (drm_buddy_block_is_clear(block))
+- mm->clear_avail -= drm_buddy_block_size(mm, block);
+- kmemleak_update_trace(block);
+- list_add_tail(&block->link, &allocated);
+-
+- pages -= BIT(order);
+-
+- if (!pages)
+- break;
+- } while (1);
+-
+- /* Trim the allocated block to the required size */
+- if (!(flags & DRM_BUDDY_TRIM_DISABLE) &&
+- original_size != size) {
+- struct list_head *trim_list;
+- LIST_HEAD(temp);
+- u64 trim_size;
+-
+- trim_list = &allocated;
+- trim_size = original_size;
+-
+- if (!list_is_singular(&allocated)) {
+- block = list_last_entry(&allocated, typeof(*block), link);
+- list_move(&block->link, &temp);
+- trim_list = &temp;
+- trim_size = drm_buddy_block_size(mm, block) -
+- (size - original_size);
+- }
+-
+- drm_buddy_block_trim(mm,
+- NULL,
+- trim_size,
+- trim_list);
+-
+- if (!list_empty(&temp))
+- list_splice_tail(trim_list, &allocated);
+- }
+-
+- list_splice_tail(&allocated, blocks);
+- return 0;
+-
+-err_free:
+- drm_buddy_free_list_internal(mm, &allocated);
+- return err;
+-}
+-EXPORT_SYMBOL(drm_buddy_alloc_blocks);
++#include <drm/drm_print.h>
+
+ /**
+ * drm_buddy_block_print - print block information
+@@ -1262,12 +21,12 @@ EXPORT_SYMBOL(drm_buddy_alloc_blocks);
+ * @block: DRM buddy block
+ * @p: DRM printer to use
+ */
+-void drm_buddy_block_print(struct drm_buddy *mm,
+- struct drm_buddy_block *block,
++void drm_buddy_block_print(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block,
+ struct drm_printer *p)
+ {
+- u64 start = drm_buddy_block_offset(block);
+- u64 size = drm_buddy_block_size(mm, block);
++ u64 start = gpu_buddy_block_offset(block);
++ u64 size = gpu_buddy_block_size(mm, block);
+
+ drm_printf(p, "%#018llx-%#018llx: %llu\n", start, start + size, size);
+ }
+@@ -1279,7 +38,7 @@ EXPORT_SYMBOL(drm_buddy_block_print);
+ * @mm: DRM buddy manager
+ * @p: DRM printer to use
+ */
+-void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p)
++void drm_buddy_print(struct gpu_buddy *mm, struct drm_printer *p)
+ {
+ int order;
+
+@@ -1287,7 +46,7 @@ void drm_buddy_print(struct drm_buddy *m
+ mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20);
+
+ for (order = mm->max_order; order >= 0; order--) {
+- struct drm_buddy_block *block, *tmp;
++ struct gpu_buddy_block *block, *tmp;
+ struct rb_root *root;
+ u64 count = 0, free;
+ unsigned int tree;
+@@ -1296,7 +55,7 @@ void drm_buddy_print(struct drm_buddy *m
+ root = &mm->free_trees[tree][order];
+
+ rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
+- BUG_ON(!drm_buddy_block_is_free(block));
++ BUG_ON(!gpu_buddy_block_is_free(block));
+ count++;
+ }
+ }
+@@ -1314,22 +73,5 @@ void drm_buddy_print(struct drm_buddy *m
+ }
+ EXPORT_SYMBOL(drm_buddy_print);
+
+-static void drm_buddy_module_exit(void)
+-{
+- kmem_cache_destroy(slab_blocks);
+-}
+-
+-static int __init drm_buddy_module_init(void)
+-{
+- slab_blocks = KMEM_CACHE(drm_buddy_block, 0);
+- if (!slab_blocks)
+- return -ENOMEM;
+-
+- return 0;
+-}
+-
+-module_init(drm_buddy_module_init);
+-module_exit(drm_buddy_module_exit);
+-
+-MODULE_DESCRIPTION("DRM Buddy Allocator");
++MODULE_DESCRIPTION("DRM-specific GPU Buddy Allocator Print Helpers");
+ MODULE_LICENSE("Dual MIT/GPL");
+--- a/drivers/gpu/drm/i915/gem/i915_gem_ttm.c
++++ b/drivers/gpu/drm/i915/gem/i915_gem_ttm.c
+@@ -5,9 +5,10 @@
+
+ #include <linux/shmem_fs.h>
+
++#include <linux/gpu_buddy.h>
++#include <drm/drm_print.h>
+ #include <drm/ttm/ttm_placement.h>
+ #include <drm/ttm/ttm_tt.h>
+-#include <drm/drm_buddy.h>
+
+ #include "i915_drv.h"
+ #include "i915_ttm_buddy_manager.h"
+--- a/drivers/gpu/drm/i915/i915_scatterlist.c
++++ b/drivers/gpu/drm/i915/i915_scatterlist.c
+@@ -7,7 +7,7 @@
+ #include "i915_scatterlist.h"
+ #include "i915_ttm_buddy_manager.h"
+
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+ #include <drm/drm_mm.h>
+
+ #include <linux/slab.h>
+@@ -167,9 +167,9 @@ struct i915_refct_sgt *i915_rsgt_from_bu
+ struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res);
+ const u64 size = res->size;
+ const u32 max_segment = round_down(UINT_MAX, page_alignment);
+- struct drm_buddy *mm = bman_res->mm;
++ struct gpu_buddy *mm = bman_res->mm;
+ struct list_head *blocks = &bman_res->blocks;
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ struct i915_refct_sgt *rsgt;
+ struct scatterlist *sg;
+ struct sg_table *st;
+@@ -202,8 +202,8 @@ struct i915_refct_sgt *i915_rsgt_from_bu
+ list_for_each_entry(block, blocks, link) {
+ u64 block_size, offset;
+
+- block_size = min_t(u64, size, drm_buddy_block_size(mm, block));
+- offset = drm_buddy_block_offset(block);
++ block_size = min_t(u64, size, gpu_buddy_block_size(mm, block));
++ offset = gpu_buddy_block_offset(block);
+
+ while (block_size) {
+ u64 len;
+--- a/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c
++++ b/drivers/gpu/drm/i915/i915_ttm_buddy_manager.c
+@@ -5,18 +5,19 @@
+
+ #include <linux/slab.h>
+
++#include <linux/gpu_buddy.h>
++#include <drm/drm_buddy.h>
++#include <drm/drm_print.h>
+ #include <drm/ttm/ttm_placement.h>
+ #include <drm/ttm/ttm_bo.h>
+
+-#include <drm/drm_buddy.h>
+-
+ #include "i915_ttm_buddy_manager.h"
+
+ #include "i915_gem.h"
+
+ struct i915_ttm_buddy_manager {
+ struct ttm_resource_manager manager;
+- struct drm_buddy mm;
++ struct gpu_buddy mm;
+ struct list_head reserved;
+ struct mutex lock;
+ unsigned long visible_size;
+@@ -38,7 +39,7 @@ static int i915_ttm_buddy_man_alloc(stru
+ {
+ struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+ struct i915_ttm_buddy_resource *bman_res;
+- struct drm_buddy *mm = &bman->mm;
++ struct gpu_buddy *mm = &bman->mm;
+ unsigned long n_pages, lpfn;
+ u64 min_page_size;
+ u64 size;
+@@ -57,13 +58,13 @@ static int i915_ttm_buddy_man_alloc(stru
+ bman_res->mm = mm;
+
+ if (place->flags & TTM_PL_FLAG_TOPDOWN)
+- bman_res->flags |= DRM_BUDDY_TOPDOWN_ALLOCATION;
++ bman_res->flags |= GPU_BUDDY_TOPDOWN_ALLOCATION;
+
+ if (place->flags & TTM_PL_FLAG_CONTIGUOUS)
+- bman_res->flags |= DRM_BUDDY_CONTIGUOUS_ALLOCATION;
++ bman_res->flags |= GPU_BUDDY_CONTIGUOUS_ALLOCATION;
+
+ if (place->fpfn || lpfn != man->size)
+- bman_res->flags |= DRM_BUDDY_RANGE_ALLOCATION;
++ bman_res->flags |= GPU_BUDDY_RANGE_ALLOCATION;
+
+ GEM_BUG_ON(!bman_res->base.size);
+ size = bman_res->base.size;
+@@ -89,7 +90,7 @@ static int i915_ttm_buddy_man_alloc(stru
+ goto err_free_res;
+ }
+
+- err = drm_buddy_alloc_blocks(mm, (u64)place->fpfn << PAGE_SHIFT,
++ err = gpu_buddy_alloc_blocks(mm, (u64)place->fpfn << PAGE_SHIFT,
+ (u64)lpfn << PAGE_SHIFT,
+ (u64)n_pages << PAGE_SHIFT,
+ min_page_size,
+@@ -101,15 +102,15 @@ static int i915_ttm_buddy_man_alloc(stru
+ if (lpfn <= bman->visible_size) {
+ bman_res->used_visible_size = PFN_UP(bman_res->base.size);
+ } else {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+
+ list_for_each_entry(block, &bman_res->blocks, link) {
+ unsigned long start =
+- drm_buddy_block_offset(block) >> PAGE_SHIFT;
++ gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+
+ if (start < bman->visible_size) {
+ unsigned long end = start +
+- (drm_buddy_block_size(mm, block) >> PAGE_SHIFT);
++ (gpu_buddy_block_size(mm, block) >> PAGE_SHIFT);
+
+ bman_res->used_visible_size +=
+ min(end, bman->visible_size) - start;
+@@ -126,7 +127,7 @@ static int i915_ttm_buddy_man_alloc(stru
+ return 0;
+
+ err_free_blocks:
+- drm_buddy_free_list(mm, &bman_res->blocks, 0);
++ gpu_buddy_free_list(mm, &bman_res->blocks, 0);
+ mutex_unlock(&bman->lock);
+ err_free_res:
+ ttm_resource_fini(man, &bman_res->base);
+@@ -141,7 +142,7 @@ static void i915_ttm_buddy_man_free(stru
+ struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+
+ mutex_lock(&bman->lock);
+- drm_buddy_free_list(&bman->mm, &bman_res->blocks, 0);
++ gpu_buddy_free_list(&bman->mm, &bman_res->blocks, 0);
+ bman->visible_avail += bman_res->used_visible_size;
+ mutex_unlock(&bman->lock);
+
+@@ -156,8 +157,8 @@ static bool i915_ttm_buddy_man_intersect
+ {
+ struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res);
+ struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+- struct drm_buddy *mm = &bman->mm;
+- struct drm_buddy_block *block;
++ struct gpu_buddy *mm = &bman->mm;
++ struct gpu_buddy_block *block;
+
+ if (!place->fpfn && !place->lpfn)
+ return true;
+@@ -176,9 +177,9 @@ static bool i915_ttm_buddy_man_intersect
+ /* Check each drm buddy block individually */
+ list_for_each_entry(block, &bman_res->blocks, link) {
+ unsigned long fpfn =
+- drm_buddy_block_offset(block) >> PAGE_SHIFT;
++ gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+ unsigned long lpfn = fpfn +
+- (drm_buddy_block_size(mm, block) >> PAGE_SHIFT);
++ (gpu_buddy_block_size(mm, block) >> PAGE_SHIFT);
+
+ if (place->fpfn < lpfn && place->lpfn > fpfn)
+ return true;
+@@ -194,8 +195,8 @@ static bool i915_ttm_buddy_man_compatibl
+ {
+ struct i915_ttm_buddy_resource *bman_res = to_ttm_buddy_resource(res);
+ struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+- struct drm_buddy *mm = &bman->mm;
+- struct drm_buddy_block *block;
++ struct gpu_buddy *mm = &bman->mm;
++ struct gpu_buddy_block *block;
+
+ if (!place->fpfn && !place->lpfn)
+ return true;
+@@ -209,9 +210,9 @@ static bool i915_ttm_buddy_man_compatibl
+ /* Check each drm buddy block individually */
+ list_for_each_entry(block, &bman_res->blocks, link) {
+ unsigned long fpfn =
+- drm_buddy_block_offset(block) >> PAGE_SHIFT;
++ gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+ unsigned long lpfn = fpfn +
+- (drm_buddy_block_size(mm, block) >> PAGE_SHIFT);
++ (gpu_buddy_block_size(mm, block) >> PAGE_SHIFT);
+
+ if (fpfn < place->fpfn || lpfn > place->lpfn)
+ return false;
+@@ -224,7 +225,7 @@ static void i915_ttm_buddy_man_debug(str
+ struct drm_printer *printer)
+ {
+ struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+
+ mutex_lock(&bman->lock);
+ drm_printf(printer, "default_page_size: %lluKiB\n",
+@@ -293,7 +294,7 @@ int i915_ttm_buddy_man_init(struct ttm_d
+ if (!bman)
+ return -ENOMEM;
+
+- err = drm_buddy_init(&bman->mm, size, chunk_size);
++ err = gpu_buddy_init(&bman->mm, size, chunk_size);
+ if (err)
+ goto err_free_bman;
+
+@@ -333,7 +334,7 @@ int i915_ttm_buddy_man_fini(struct ttm_d
+ {
+ struct ttm_resource_manager *man = ttm_manager_type(bdev, type);
+ struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+- struct drm_buddy *mm = &bman->mm;
++ struct gpu_buddy *mm = &bman->mm;
+ int ret;
+
+ ttm_resource_manager_set_used(man, false);
+@@ -345,8 +346,8 @@ int i915_ttm_buddy_man_fini(struct ttm_d
+ ttm_set_driver_manager(bdev, type, NULL);
+
+ mutex_lock(&bman->lock);
+- drm_buddy_free_list(mm, &bman->reserved, 0);
+- drm_buddy_fini(mm);
++ gpu_buddy_free_list(mm, &bman->reserved, 0);
++ gpu_buddy_fini(mm);
+ bman->visible_avail += bman->visible_reserved;
+ WARN_ON_ONCE(bman->visible_avail != bman->visible_size);
+ mutex_unlock(&bman->lock);
+@@ -371,15 +372,15 @@ int i915_ttm_buddy_man_reserve(struct tt
+ u64 start, u64 size)
+ {
+ struct i915_ttm_buddy_manager *bman = to_buddy_manager(man);
+- struct drm_buddy *mm = &bman->mm;
++ struct gpu_buddy *mm = &bman->mm;
+ unsigned long fpfn = start >> PAGE_SHIFT;
+ unsigned long flags = 0;
+ int ret;
+
+- flags |= DRM_BUDDY_RANGE_ALLOCATION;
++ flags |= GPU_BUDDY_RANGE_ALLOCATION;
+
+ mutex_lock(&bman->lock);
+- ret = drm_buddy_alloc_blocks(mm, start,
++ ret = gpu_buddy_alloc_blocks(mm, start,
+ start + size,
+ size, mm->chunk_size,
+ &bman->reserved,
+--- a/drivers/gpu/drm/i915/i915_ttm_buddy_manager.h
++++ b/drivers/gpu/drm/i915/i915_ttm_buddy_manager.h
+@@ -13,7 +13,7 @@
+
+ struct ttm_device;
+ struct ttm_resource_manager;
+-struct drm_buddy;
++struct gpu_buddy;
+
+ /**
+ * struct i915_ttm_buddy_resource
+@@ -33,7 +33,7 @@ struct i915_ttm_buddy_resource {
+ struct list_head blocks;
+ unsigned long flags;
+ unsigned long used_visible_size;
+- struct drm_buddy *mm;
++ struct gpu_buddy *mm;
+ };
+
+ /**
+--- a/drivers/gpu/drm/i915/selftests/intel_memory_region.c
++++ b/drivers/gpu/drm/i915/selftests/intel_memory_region.c
+@@ -6,7 +6,7 @@
+ #include <linux/prime_numbers.h>
+ #include <linux/sort.h>
+
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+
+ #include "../i915_selftest.h"
+
+@@ -371,7 +371,7 @@ static int igt_mock_splintered_region(vo
+ struct drm_i915_private *i915 = mem->i915;
+ struct i915_ttm_buddy_resource *res;
+ struct drm_i915_gem_object *obj;
+- struct drm_buddy *mm;
++ struct gpu_buddy *mm;
+ unsigned int expected_order;
+ LIST_HEAD(objects);
+ u64 size;
+@@ -447,8 +447,8 @@ static int igt_mock_max_segment(void *ar
+ struct drm_i915_private *i915 = mem->i915;
+ struct i915_ttm_buddy_resource *res;
+ struct drm_i915_gem_object *obj;
+- struct drm_buddy_block *block;
+- struct drm_buddy *mm;
++ struct gpu_buddy_block *block;
++ struct gpu_buddy *mm;
+ struct list_head *blocks;
+ struct scatterlist *sg;
+ I915_RND_STATE(prng);
+@@ -487,8 +487,8 @@ static int igt_mock_max_segment(void *ar
+ mm = res->mm;
+ size = 0;
+ list_for_each_entry(block, blocks, link) {
+- if (drm_buddy_block_size(mm, block) > size)
+- size = drm_buddy_block_size(mm, block);
++ if (gpu_buddy_block_size(mm, block) > size)
++ size = gpu_buddy_block_size(mm, block);
+ }
+ if (size < max_segment) {
+ pr_err("%s: Failed to create a huge contiguous block [> %u], largest block %lld\n",
+@@ -527,14 +527,14 @@ static u64 igt_object_mappable_total(str
+ struct intel_memory_region *mr = obj->mm.region;
+ struct i915_ttm_buddy_resource *bman_res =
+ to_ttm_buddy_resource(obj->mm.res);
+- struct drm_buddy *mm = bman_res->mm;
+- struct drm_buddy_block *block;
++ struct gpu_buddy *mm = bman_res->mm;
++ struct gpu_buddy_block *block;
+ u64 total;
+
+ total = 0;
+ list_for_each_entry(block, &bman_res->blocks, link) {
+- u64 start = drm_buddy_block_offset(block);
+- u64 end = start + drm_buddy_block_size(mm, block);
++ u64 start = gpu_buddy_block_offset(block);
++ u64 end = start + gpu_buddy_block_size(mm, block);
+
+ if (start < resource_size(&mr->io))
+ total += min_t(u64, end, resource_size(&mr->io)) - start;
+--- a/drivers/gpu/drm/lib/drm_random.c
++++ /dev/null
+@@ -1,44 +0,0 @@
+-// SPDX-License-Identifier: GPL-2.0
+-#include <linux/bitops.h>
+-#include <linux/export.h>
+-#include <linux/kernel.h>
+-#include <linux/random.h>
+-#include <linux/slab.h>
+-#include <linux/types.h>
+-
+-#include "drm_random.h"
+-
+-u32 drm_prandom_u32_max_state(u32 ep_ro, struct rnd_state *state)
+-{
+- return upper_32_bits((u64)prandom_u32_state(state) * ep_ro);
+-}
+-EXPORT_SYMBOL(drm_prandom_u32_max_state);
+-
+-void drm_random_reorder(unsigned int *order, unsigned int count,
+- struct rnd_state *state)
+-{
+- unsigned int i, j;
+-
+- for (i = 0; i < count; ++i) {
+- BUILD_BUG_ON(sizeof(unsigned int) > sizeof(u32));
+- j = drm_prandom_u32_max_state(count, state);
+- swap(order[i], order[j]);
+- }
+-}
+-EXPORT_SYMBOL(drm_random_reorder);
+-
+-unsigned int *drm_random_order(unsigned int count, struct rnd_state *state)
+-{
+- unsigned int *order, i;
+-
+- order = kmalloc_array(count, sizeof(*order), GFP_KERNEL);
+- if (!order)
+- return order;
+-
+- for (i = 0; i < count; i++)
+- order[i] = i;
+-
+- drm_random_reorder(order, count, state);
+- return order;
+-}
+-EXPORT_SYMBOL(drm_random_order);
+--- a/drivers/gpu/drm/lib/drm_random.h
++++ /dev/null
+@@ -1,28 +0,0 @@
+-/* SPDX-License-Identifier: GPL-2.0 */
+-#ifndef __DRM_RANDOM_H__
+-#define __DRM_RANDOM_H__
+-
+-/* This is a temporary home for a couple of utility functions that should
+- * be transposed to lib/ at the earliest convenience.
+- */
+-
+-#include <linux/prandom.h>
+-
+-#define DRM_RND_STATE_INITIALIZER(seed__) ({ \
+- struct rnd_state state__; \
+- prandom_seed_state(&state__, (seed__)); \
+- state__; \
+-})
+-
+-#define DRM_RND_STATE(name__, seed__) \
+- struct rnd_state name__ = DRM_RND_STATE_INITIALIZER(seed__)
+-
+-unsigned int *drm_random_order(unsigned int count,
+- struct rnd_state *state);
+-void drm_random_reorder(unsigned int *order,
+- unsigned int count,
+- struct rnd_state *state);
+-u32 drm_prandom_u32_max_state(u32 ep_ro,
+- struct rnd_state *state);
+-
+-#endif /* !__DRM_RANDOM_H__ */
+--- a/drivers/gpu/drm/tests/Makefile
++++ b/drivers/gpu/drm/tests/Makefile
+@@ -7,7 +7,6 @@ obj-$(CONFIG_DRM_KUNIT_TEST) += \
+ drm_atomic_test.o \
+ drm_atomic_state_test.o \
+ drm_bridge_test.o \
+- drm_buddy_test.o \
+ drm_cmdline_parser_test.o \
+ drm_connector_test.o \
+ drm_damage_helper_test.o \
+--- a/drivers/gpu/drm/tests/drm_buddy_test.c
++++ /dev/null
+@@ -1,788 +0,0 @@
+-// SPDX-License-Identifier: MIT
+-/*
+- * Copyright © 2019 Intel Corporation
+- * Copyright © 2022 MaÃra Canal <mairacanal@riseup.net>
+- */
+-
+-#include <kunit/test.h>
+-
+-#include <linux/prime_numbers.h>
+-#include <linux/sched/signal.h>
+-#include <linux/sizes.h>
+-
+-#include <drm/drm_buddy.h>
+-
+-#include "../lib/drm_random.h"
+-
+-static unsigned int random_seed;
+-
+-static inline u64 get_size(int order, u64 chunk_size)
+-{
+- return (1 << order) * chunk_size;
+-}
+-
+-static void drm_test_buddy_alloc_range_bias(struct kunit *test)
+-{
+- u32 mm_size, size, ps, bias_size, bias_start, bias_end, bias_rem;
+- DRM_RND_STATE(prng, random_seed);
+- unsigned int i, count, *order;
+- struct drm_buddy_block *block;
+- unsigned long flags;
+- struct drm_buddy mm;
+- LIST_HEAD(allocated);
+-
+- bias_size = SZ_1M;
+- ps = roundup_pow_of_two(prandom_u32_state(&prng) % bias_size);
+- ps = max(SZ_4K, ps);
+- mm_size = (SZ_8M-1) & ~(ps-1); /* Multiple roots */
+-
+- kunit_info(test, "mm_size=%u, ps=%u\n", mm_size, ps);
+-
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, ps),
+- "buddy_init failed\n");
+-
+- count = mm_size / bias_size;
+- order = drm_random_order(count, &prng);
+- KUNIT_EXPECT_TRUE(test, order);
+-
+- /*
+- * Idea is to split the address space into uniform bias ranges, and then
+- * in some random order allocate within each bias, using various
+- * patterns within. This should detect if allocations leak out from a
+- * given bias, for example.
+- */
+-
+- for (i = 0; i < count; i++) {
+- LIST_HEAD(tmp);
+- u32 size;
+-
+- bias_start = order[i] * bias_size;
+- bias_end = bias_start + bias_size;
+- bias_rem = bias_size;
+-
+- /* internal round_up too big */
+- KUNIT_ASSERT_TRUE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, bias_size + ps, bias_size,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, bias_size, bias_size);
+-
+- /* size too big */
+- KUNIT_ASSERT_TRUE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, bias_size + ps, ps,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, bias_size + ps, ps);
+-
+- /* bias range too small for size */
+- KUNIT_ASSERT_TRUE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start + ps,
+- bias_end, bias_size, ps,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start + ps, bias_end, bias_size, ps);
+-
+- /* bias misaligned */
+- KUNIT_ASSERT_TRUE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start + ps,
+- bias_end - ps,
+- bias_size >> 1, bias_size >> 1,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc h didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start + ps, bias_end - ps, bias_size >> 1, bias_size >> 1);
+-
+- /* single big page */
+- KUNIT_ASSERT_FALSE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, bias_size, bias_size,
+- &tmp,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc i failed with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, bias_size, bias_size);
+- drm_buddy_free_list(&mm, &tmp, 0);
+-
+- /* single page with internal round_up */
+- KUNIT_ASSERT_FALSE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, ps, bias_size,
+- &tmp,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, ps, bias_size);
+- drm_buddy_free_list(&mm, &tmp, 0);
+-
+- /* random size within */
+- size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+- if (size)
+- KUNIT_ASSERT_FALSE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, size, ps,
+- &tmp,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, size, ps);
+-
+- bias_rem -= size;
+- /* too big for current avail */
+- KUNIT_ASSERT_TRUE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, bias_rem + ps, ps,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, bias_rem + ps, ps);
+-
+- if (bias_rem) {
+- /* random fill of the remainder */
+- size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+- size = max(size, ps);
+-
+- KUNIT_ASSERT_FALSE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, size, ps,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, size, ps);
+- /*
+- * Intentionally allow some space to be left
+- * unallocated, and ideally not always on the bias
+- * boundaries.
+- */
+- drm_buddy_free_list(&mm, &tmp, 0);
+- } else {
+- list_splice_tail(&tmp, &allocated);
+- }
+- }
+-
+- kfree(order);
+- drm_buddy_free_list(&mm, &allocated, 0);
+- drm_buddy_fini(&mm);
+-
+- /*
+- * Something more free-form. Idea is to pick a random starting bias
+- * range within the address space and then start filling it up. Also
+- * randomly grow the bias range in both directions as we go along. This
+- * should give us bias start/end which is not always uniform like above,
+- * and in some cases will require the allocator to jump over already
+- * allocated nodes in the middle of the address space.
+- */
+-
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, ps),
+- "buddy_init failed\n");
+-
+- bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
+- bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
+- bias_end = max(bias_end, bias_start + ps);
+- bias_rem = bias_end - bias_start;
+-
+- do {
+- u32 size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+-
+- KUNIT_ASSERT_FALSE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, size, ps,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, size, ps);
+- bias_rem -= size;
+-
+- /*
+- * Try to randomly grow the bias range in both directions, or
+- * only one, or perhaps don't grow at all.
+- */
+- do {
+- u32 old_bias_start = bias_start;
+- u32 old_bias_end = bias_end;
+-
+- if (bias_start)
+- bias_start -= round_up(prandom_u32_state(&prng) % bias_start, ps);
+- if (bias_end != mm_size)
+- bias_end += round_up(prandom_u32_state(&prng) % (mm_size - bias_end), ps);
+-
+- bias_rem += old_bias_start - bias_start;
+- bias_rem += bias_end - old_bias_end;
+- } while (!bias_rem && (bias_start || bias_end != mm_size));
+- } while (bias_rem);
+-
+- KUNIT_ASSERT_EQ(test, bias_start, 0);
+- KUNIT_ASSERT_EQ(test, bias_end, mm_size);
+- KUNIT_ASSERT_TRUE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start, bias_end,
+- ps, ps,
+- &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc passed with bias(%x-%x), size=%u\n",
+- bias_start, bias_end, ps);
+-
+- drm_buddy_free_list(&mm, &allocated, 0);
+- drm_buddy_fini(&mm);
+-
+- /*
+- * Allocate cleared blocks in the bias range when the DRM buddy's clear avail is
+- * zero. This will validate the bias range allocation in scenarios like system boot
+- * when no cleared blocks are available and exercise the fallback path too. The resulting
+- * blocks should always be dirty.
+- */
+-
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, ps),
+- "buddy_init failed\n");
+-
+- bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
+- bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
+- bias_end = max(bias_end, bias_start + ps);
+- bias_rem = bias_end - bias_start;
+-
+- flags = DRM_BUDDY_CLEAR_ALLOCATION | DRM_BUDDY_RANGE_ALLOCATION;
+- size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
+-
+- KUNIT_ASSERT_FALSE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, bias_start,
+- bias_end, size, ps,
+- &allocated,
+- flags),
+- "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
+- bias_start, bias_end, size, ps);
+-
+- list_for_each_entry(block, &allocated, link)
+- KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), false);
+-
+- drm_buddy_free_list(&mm, &allocated, 0);
+- drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_clear(struct kunit *test)
+-{
+- unsigned long n_pages, total, i = 0;
+- const unsigned long ps = SZ_4K;
+- struct drm_buddy_block *block;
+- const int max_order = 12;
+- LIST_HEAD(allocated);
+- struct drm_buddy mm;
+- unsigned int order;
+- u32 mm_size, size;
+- LIST_HEAD(dirty);
+- LIST_HEAD(clean);
+-
+- mm_size = SZ_4K << max_order;
+- KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+-
+- KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+-
+- /*
+- * Idea is to allocate and free some random portion of the address space,
+- * returning those pages as non-dirty and randomly alternate between
+- * requesting dirty and non-dirty pages (not going over the limit
+- * we freed as non-dirty), putting that into two separate lists.
+- * Loop over both lists at the end checking that the dirty list
+- * is indeed all dirty pages and vice versa. Free it all again,
+- * keeping the dirty/clear status.
+- */
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 5 * ps, ps, &allocated,
+- DRM_BUDDY_TOPDOWN_ALLOCATION),
+- "buddy_alloc hit an error size=%lu\n", 5 * ps);
+- drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
+-
+- n_pages = 10;
+- do {
+- unsigned long flags;
+- struct list_head *list;
+- int slot = i % 2;
+-
+- if (slot == 0) {
+- list = &dirty;
+- flags = 0;
+- } else {
+- list = &clean;
+- flags = DRM_BUDDY_CLEAR_ALLOCATION;
+- }
+-
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- ps, ps, list,
+- flags),
+- "buddy_alloc hit an error size=%lu\n", ps);
+- } while (++i < n_pages);
+-
+- list_for_each_entry(block, &clean, link)
+- KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), true);
+-
+- list_for_each_entry(block, &dirty, link)
+- KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), false);
+-
+- drm_buddy_free_list(&mm, &clean, DRM_BUDDY_CLEARED);
+-
+- /*
+- * Trying to go over the clear limit for some allocation.
+- * The allocation should never fail with reasonable page-size.
+- */
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 10 * ps, ps, &clean,
+- DRM_BUDDY_CLEAR_ALLOCATION),
+- "buddy_alloc hit an error size=%lu\n", 10 * ps);
+-
+- drm_buddy_free_list(&mm, &clean, DRM_BUDDY_CLEARED);
+- drm_buddy_free_list(&mm, &dirty, 0);
+- drm_buddy_fini(&mm);
+-
+- KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+-
+- /*
+- * Create a new mm. Intentionally fragment the address space by creating
+- * two alternating lists. Free both lists, one as dirty the other as clean.
+- * Try to allocate double the previous size with matching min_page_size. The
+- * allocation should never fail as it calls the force_merge. Also check that
+- * the page is always dirty after force_merge. Free the page as dirty, then
+- * repeat the whole thing, increment the order until we hit the max_order.
+- */
+-
+- i = 0;
+- n_pages = mm_size / ps;
+- do {
+- struct list_head *list;
+- int slot = i % 2;
+-
+- if (slot == 0)
+- list = &dirty;
+- else
+- list = &clean;
+-
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- ps, ps, list, 0),
+- "buddy_alloc hit an error size=%lu\n", ps);
+- } while (++i < n_pages);
+-
+- drm_buddy_free_list(&mm, &clean, DRM_BUDDY_CLEARED);
+- drm_buddy_free_list(&mm, &dirty, 0);
+-
+- order = 1;
+- do {
+- size = SZ_4K << order;
+-
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- size, size, &allocated,
+- DRM_BUDDY_CLEAR_ALLOCATION),
+- "buddy_alloc hit an error size=%u\n", size);
+- total = 0;
+- list_for_each_entry(block, &allocated, link) {
+- if (size != mm_size)
+- KUNIT_EXPECT_EQ(test, drm_buddy_block_is_clear(block), false);
+- total += drm_buddy_block_size(&mm, block);
+- }
+- KUNIT_EXPECT_EQ(test, total, size);
+-
+- drm_buddy_free_list(&mm, &allocated, 0);
+- } while (++order <= max_order);
+-
+- drm_buddy_fini(&mm);
+-
+- /*
+- * Create a new mm with a non power-of-two size. Allocate a random size from each
+- * root, free as cleared and then call fini. This will ensure the multi-root
+- * force merge during fini.
+- */
+- mm_size = (SZ_4K << max_order) + (SZ_4K << (max_order - 2));
+-
+- KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+- KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, SZ_4K << max_order,
+- 4 * ps, ps, &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc hit an error size=%lu\n", 4 * ps);
+- drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, SZ_4K << max_order,
+- 2 * ps, ps, &allocated,
+- DRM_BUDDY_CLEAR_ALLOCATION),
+- "buddy_alloc hit an error size=%lu\n", 2 * ps);
+- drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, SZ_4K << max_order, mm_size,
+- ps, ps, &allocated,
+- DRM_BUDDY_RANGE_ALLOCATION),
+- "buddy_alloc hit an error size=%lu\n", ps);
+- drm_buddy_free_list(&mm, &allocated, DRM_BUDDY_CLEARED);
+- drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_contiguous(struct kunit *test)
+-{
+- const unsigned long ps = SZ_4K, mm_size = 16 * 3 * SZ_4K;
+- unsigned long i, n_pages, total;
+- struct drm_buddy_block *block;
+- struct drm_buddy mm;
+- LIST_HEAD(left);
+- LIST_HEAD(middle);
+- LIST_HEAD(right);
+- LIST_HEAD(allocated);
+-
+- KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, mm_size, ps));
+-
+- /*
+- * Idea is to fragment the address space by alternating block
+- * allocations between three different lists; one for left, middle and
+- * right. We can then free a list to simulate fragmentation. In
+- * particular we want to exercise the DRM_BUDDY_CONTIGUOUS_ALLOCATION,
+- * including the try_harder path.
+- */
+-
+- i = 0;
+- n_pages = mm_size / ps;
+- do {
+- struct list_head *list;
+- int slot = i % 3;
+-
+- if (slot == 0)
+- list = &left;
+- else if (slot == 1)
+- list = &middle;
+- else
+- list = &right;
+- KUNIT_ASSERT_FALSE_MSG(test,
+- drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- ps, ps, list, 0),
+- "buddy_alloc hit an error size=%lu\n",
+- ps);
+- } while (++i < n_pages);
+-
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 3 * ps, ps, &allocated,
+- DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+- "buddy_alloc didn't error size=%lu\n", 3 * ps);
+-
+- drm_buddy_free_list(&mm, &middle, 0);
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 3 * ps, ps, &allocated,
+- DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+- "buddy_alloc didn't error size=%lu\n", 3 * ps);
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 2 * ps, ps, &allocated,
+- DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+- "buddy_alloc didn't error size=%lu\n", 2 * ps);
+-
+- drm_buddy_free_list(&mm, &right, 0);
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 3 * ps, ps, &allocated,
+- DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+- "buddy_alloc didn't error size=%lu\n", 3 * ps);
+- /*
+- * At this point we should have enough contiguous space for 2 blocks,
+- * however they are never buddies (since we freed middle and right) so
+- * will require the try_harder logic to find them.
+- */
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 2 * ps, ps, &allocated,
+- DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+- "buddy_alloc hit an error size=%lu\n", 2 * ps);
+-
+- drm_buddy_free_list(&mm, &left, 0);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, 0, mm_size,
+- 3 * ps, ps, &allocated,
+- DRM_BUDDY_CONTIGUOUS_ALLOCATION),
+- "buddy_alloc hit an error size=%lu\n", 3 * ps);
+-
+- total = 0;
+- list_for_each_entry(block, &allocated, link)
+- total += drm_buddy_block_size(&mm, block);
+-
+- KUNIT_ASSERT_EQ(test, total, ps * 2 + ps * 3);
+-
+- drm_buddy_free_list(&mm, &allocated, 0);
+- drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_pathological(struct kunit *test)
+-{
+- u64 mm_size, size, start = 0;
+- struct drm_buddy_block *block;
+- const int max_order = 3;
+- unsigned long flags = 0;
+- int order, top;
+- struct drm_buddy mm;
+- LIST_HEAD(blocks);
+- LIST_HEAD(holes);
+- LIST_HEAD(tmp);
+-
+- /*
+- * Create a pot-sized mm, then allocate one of each possible
+- * order within. This should leave the mm with exactly one
+- * page left. Free the largest block, then whittle down again.
+- * Eventually we will have a fully 50% fragmented mm.
+- */
+-
+- mm_size = SZ_4K << max_order;
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
+- "buddy_init failed\n");
+-
+- KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+-
+- for (top = max_order; top; top--) {
+- /* Make room by freeing the largest allocated block */
+- block = list_first_entry_or_null(&blocks, typeof(*block), link);
+- if (block) {
+- list_del(&block->link);
+- drm_buddy_free_block(&mm, block);
+- }
+-
+- for (order = top; order--;) {
+- size = get_size(order, mm.chunk_size);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start,
+- mm_size, size, size,
+- &tmp, flags),
+- "buddy_alloc hit -ENOMEM with order=%d, top=%d\n",
+- order, top);
+-
+- block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+- KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+- list_move_tail(&block->link, &blocks);
+- }
+-
+- /* There should be one final page for this sub-allocation */
+- size = get_size(0, mm.chunk_size);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc hit -ENOMEM for hole\n");
+-
+- block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+- KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+- list_move_tail(&block->link, &holes);
+-
+- size = get_size(top, mm.chunk_size);
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc unexpectedly succeeded at top-order %d/%d, it should be full!",
+- top, max_order);
+- }
+-
+- drm_buddy_free_list(&mm, &holes, 0);
+-
+- /* Nothing larger than blocks of chunk_size now available */
+- for (order = 1; order <= max_order; order++) {
+- size = get_size(order, mm.chunk_size);
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc unexpectedly succeeded at order %d, it should be full!",
+- order);
+- }
+-
+- list_splice_tail(&holes, &blocks);
+- drm_buddy_free_list(&mm, &blocks, 0);
+- drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_pessimistic(struct kunit *test)
+-{
+- u64 mm_size, size, start = 0;
+- struct drm_buddy_block *block, *bn;
+- const unsigned int max_order = 16;
+- unsigned long flags = 0;
+- struct drm_buddy mm;
+- unsigned int order;
+- LIST_HEAD(blocks);
+- LIST_HEAD(tmp);
+-
+- /*
+- * Create a pot-sized mm, then allocate one of each possible
+- * order within. This should leave the mm with exactly one
+- * page left.
+- */
+-
+- mm_size = SZ_4K << max_order;
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
+- "buddy_init failed\n");
+-
+- KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+-
+- for (order = 0; order < max_order; order++) {
+- size = get_size(order, mm.chunk_size);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc hit -ENOMEM with order=%d\n",
+- order);
+-
+- block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+- KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+- list_move_tail(&block->link, &blocks);
+- }
+-
+- /* And now the last remaining block available */
+- size = get_size(0, mm.chunk_size);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc hit -ENOMEM on final alloc\n");
+-
+- block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+- KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+- list_move_tail(&block->link, &blocks);
+-
+- /* Should be completely full! */
+- for (order = max_order; order--;) {
+- size = get_size(order, mm.chunk_size);
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc unexpectedly succeeded, it should be full!");
+- }
+-
+- block = list_last_entry(&blocks, typeof(*block), link);
+- list_del(&block->link);
+- drm_buddy_free_block(&mm, block);
+-
+- /* As we free in increasing size, we make available larger blocks */
+- order = 1;
+- list_for_each_entry_safe(block, bn, &blocks, link) {
+- list_del(&block->link);
+- drm_buddy_free_block(&mm, block);
+-
+- size = get_size(order, mm.chunk_size);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc hit -ENOMEM with order=%d\n",
+- order);
+-
+- block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+- KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+- list_del(&block->link);
+- drm_buddy_free_block(&mm, block);
+- order++;
+- }
+-
+- /* To confirm, now the whole mm should be available */
+- size = get_size(max_order, mm.chunk_size);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc (realloc) hit -ENOMEM with order=%d\n",
+- max_order);
+-
+- block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+- KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+- list_del(&block->link);
+- drm_buddy_free_block(&mm, block);
+- drm_buddy_free_list(&mm, &blocks, 0);
+- drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_optimistic(struct kunit *test)
+-{
+- u64 mm_size, size, start = 0;
+- struct drm_buddy_block *block;
+- unsigned long flags = 0;
+- const int max_order = 16;
+- struct drm_buddy mm;
+- LIST_HEAD(blocks);
+- LIST_HEAD(tmp);
+- int order;
+-
+- /*
+- * Create a mm with one block of each order available, and
+- * try to allocate them all.
+- */
+-
+- mm_size = SZ_4K * ((1 << (max_order + 1)) - 1);
+-
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_init(&mm, mm_size, SZ_4K),
+- "buddy_init failed\n");
+-
+- KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
+-
+- for (order = 0; order <= max_order; order++) {
+- size = get_size(order, mm.chunk_size);
+- KUNIT_ASSERT_FALSE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc hit -ENOMEM with order=%d\n",
+- order);
+-
+- block = list_first_entry_or_null(&tmp, struct drm_buddy_block, link);
+- KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
+-
+- list_move_tail(&block->link, &blocks);
+- }
+-
+- /* Should be completely full! */
+- size = get_size(0, mm.chunk_size);
+- KUNIT_ASSERT_TRUE_MSG(test, drm_buddy_alloc_blocks(&mm, start, mm_size,
+- size, size, &tmp, flags),
+- "buddy_alloc unexpectedly succeeded, it should be full!");
+-
+- drm_buddy_free_list(&mm, &blocks, 0);
+- drm_buddy_fini(&mm);
+-}
+-
+-static void drm_test_buddy_alloc_limit(struct kunit *test)
+-{
+- u64 size = U64_MAX, start = 0;
+- struct drm_buddy_block *block;
+- unsigned long flags = 0;
+- LIST_HEAD(allocated);
+- struct drm_buddy mm;
+-
+- KUNIT_EXPECT_FALSE(test, drm_buddy_init(&mm, size, SZ_4K));
+-
+- KUNIT_EXPECT_EQ_MSG(test, mm.max_order, DRM_BUDDY_MAX_ORDER,
+- "mm.max_order(%d) != %d\n", mm.max_order,
+- DRM_BUDDY_MAX_ORDER);
+-
+- size = mm.chunk_size << mm.max_order;
+- KUNIT_EXPECT_FALSE(test, drm_buddy_alloc_blocks(&mm, start, size, size,
+- mm.chunk_size, &allocated, flags));
+-
+- block = list_first_entry_or_null(&allocated, struct drm_buddy_block, link);
+- KUNIT_EXPECT_TRUE(test, block);
+-
+- KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_order(block), mm.max_order,
+- "block order(%d) != %d\n",
+- drm_buddy_block_order(block), mm.max_order);
+-
+- KUNIT_EXPECT_EQ_MSG(test, drm_buddy_block_size(&mm, block),
+- BIT_ULL(mm.max_order) * mm.chunk_size,
+- "block size(%llu) != %llu\n",
+- drm_buddy_block_size(&mm, block),
+- BIT_ULL(mm.max_order) * mm.chunk_size);
+-
+- drm_buddy_free_list(&mm, &allocated, 0);
+- drm_buddy_fini(&mm);
+-}
+-
+-static int drm_buddy_suite_init(struct kunit_suite *suite)
+-{
+- while (!random_seed)
+- random_seed = get_random_u32();
+-
+- kunit_info(suite, "Testing DRM buddy manager, with random_seed=0x%x\n",
+- random_seed);
+-
+- return 0;
+-}
+-
+-static struct kunit_case drm_buddy_tests[] = {
+- KUNIT_CASE(drm_test_buddy_alloc_limit),
+- KUNIT_CASE(drm_test_buddy_alloc_optimistic),
+- KUNIT_CASE(drm_test_buddy_alloc_pessimistic),
+- KUNIT_CASE(drm_test_buddy_alloc_pathological),
+- KUNIT_CASE(drm_test_buddy_alloc_contiguous),
+- KUNIT_CASE(drm_test_buddy_alloc_clear),
+- KUNIT_CASE(drm_test_buddy_alloc_range_bias),
+- {}
+-};
+-
+-static struct kunit_suite drm_buddy_test_suite = {
+- .name = "drm_buddy",
+- .suite_init = drm_buddy_suite_init,
+- .test_cases = drm_buddy_tests,
+-};
+-
+-kunit_test_suite(drm_buddy_test_suite);
+-
+-MODULE_AUTHOR("Intel Corporation");
+-MODULE_DESCRIPTION("Kunit test for drm_buddy functions");
+-MODULE_LICENSE("GPL");
+--- a/drivers/gpu/drm/tests/drm_exec_test.c
++++ b/drivers/gpu/drm/tests/drm_exec_test.c
+@@ -16,8 +16,6 @@
+ #include <drm/drm_gem.h>
+ #include <drm/drm_kunit_helpers.h>
+
+-#include "../lib/drm_random.h"
+-
+ struct drm_exec_priv {
+ struct device *dev;
+ struct drm_device *drm;
+--- a/drivers/gpu/drm/tests/drm_mm_test.c
++++ b/drivers/gpu/drm/tests/drm_mm_test.c
+@@ -15,8 +15,6 @@
+
+ #include <drm/drm_mm.h>
+
+-#include "../lib/drm_random.h"
+-
+ enum {
+ BEST,
+ BOTTOMUP,
+--- a/drivers/gpu/drm/ttm/tests/ttm_bo_validate_test.c
++++ b/drivers/gpu/drm/ttm/tests/ttm_bo_validate_test.c
+@@ -251,7 +251,7 @@ static void ttm_bo_validate_basic(struct
+ NULL, &dummy_ttm_bo_destroy);
+ KUNIT_EXPECT_EQ(test, err, 0);
+
+- snd_place = ttm_place_kunit_init(test, snd_mem, DRM_BUDDY_TOPDOWN_ALLOCATION);
++ snd_place = ttm_place_kunit_init(test, snd_mem, GPU_BUDDY_TOPDOWN_ALLOCATION);
+ snd_placement = ttm_placement_kunit_init(test, snd_place, 1);
+
+ err = ttm_bo_validate(bo, snd_placement, &ctx_val);
+@@ -263,7 +263,7 @@ static void ttm_bo_validate_basic(struct
+ KUNIT_EXPECT_TRUE(test, ttm_tt_is_populated(bo->ttm));
+ KUNIT_EXPECT_EQ(test, bo->resource->mem_type, snd_mem);
+ KUNIT_EXPECT_EQ(test, bo->resource->placement,
+- DRM_BUDDY_TOPDOWN_ALLOCATION);
++ GPU_BUDDY_TOPDOWN_ALLOCATION);
+
+ ttm_bo_put(bo);
+ ttm_mock_manager_fini(priv->ttm_dev, snd_mem);
+--- a/drivers/gpu/drm/ttm/tests/ttm_mock_manager.c
++++ b/drivers/gpu/drm/ttm/tests/ttm_mock_manager.c
+@@ -30,7 +30,7 @@ static int ttm_mock_manager_alloc(struct
+ {
+ struct ttm_mock_manager *manager = to_mock_mgr(man);
+ struct ttm_mock_resource *mock_res;
+- struct drm_buddy *mm = &manager->mm;
++ struct gpu_buddy *mm = &manager->mm;
+ u64 lpfn, fpfn, alloc_size;
+ int err;
+
+@@ -46,14 +46,14 @@ static int ttm_mock_manager_alloc(struct
+ INIT_LIST_HEAD(&mock_res->blocks);
+
+ if (place->flags & TTM_PL_FLAG_TOPDOWN)
+- mock_res->flags |= DRM_BUDDY_TOPDOWN_ALLOCATION;
++ mock_res->flags |= GPU_BUDDY_TOPDOWN_ALLOCATION;
+
+ if (place->flags & TTM_PL_FLAG_CONTIGUOUS)
+- mock_res->flags |= DRM_BUDDY_CONTIGUOUS_ALLOCATION;
++ mock_res->flags |= GPU_BUDDY_CONTIGUOUS_ALLOCATION;
+
+ alloc_size = (uint64_t)mock_res->base.size;
+ mutex_lock(&manager->lock);
+- err = drm_buddy_alloc_blocks(mm, fpfn, lpfn, alloc_size,
++ err = gpu_buddy_alloc_blocks(mm, fpfn, lpfn, alloc_size,
+ manager->default_page_size,
+ &mock_res->blocks,
+ mock_res->flags);
+@@ -66,7 +66,7 @@ static int ttm_mock_manager_alloc(struct
+ return 0;
+
+ error_free_blocks:
+- drm_buddy_free_list(mm, &mock_res->blocks, 0);
++ gpu_buddy_free_list(mm, &mock_res->blocks, 0);
+ ttm_resource_fini(man, &mock_res->base);
+ mutex_unlock(&manager->lock);
+
+@@ -78,10 +78,10 @@ static void ttm_mock_manager_free(struct
+ {
+ struct ttm_mock_manager *manager = to_mock_mgr(man);
+ struct ttm_mock_resource *mock_res = to_mock_mgr_resource(res);
+- struct drm_buddy *mm = &manager->mm;
++ struct gpu_buddy *mm = &manager->mm;
+
+ mutex_lock(&manager->lock);
+- drm_buddy_free_list(mm, &mock_res->blocks, 0);
++ gpu_buddy_free_list(mm, &mock_res->blocks, 0);
+ mutex_unlock(&manager->lock);
+
+ ttm_resource_fini(man, res);
+@@ -105,7 +105,7 @@ int ttm_mock_manager_init(struct ttm_dev
+
+ mutex_init(&manager->lock);
+
+- err = drm_buddy_init(&manager->mm, size, PAGE_SIZE);
++ err = gpu_buddy_init(&manager->mm, size, PAGE_SIZE);
+
+ if (err) {
+ kfree(manager);
+@@ -141,7 +141,7 @@ void ttm_mock_manager_fini(struct ttm_de
+ ttm_resource_manager_set_used(man, false);
+
+ mutex_lock(&mock_man->lock);
+- drm_buddy_fini(&mock_man->mm);
++ gpu_buddy_fini(&mock_man->mm);
+ mutex_unlock(&mock_man->lock);
+
+ ttm_set_driver_manager(bdev, mem_type, NULL);
+--- a/drivers/gpu/drm/ttm/tests/ttm_mock_manager.h
++++ b/drivers/gpu/drm/ttm/tests/ttm_mock_manager.h
+@@ -5,11 +5,11 @@
+ #ifndef TTM_MOCK_MANAGER_H
+ #define TTM_MOCK_MANAGER_H
+
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+
+ struct ttm_mock_manager {
+ struct ttm_resource_manager man;
+- struct drm_buddy mm;
++ struct gpu_buddy mm;
+ u64 default_page_size;
+ /* protects allocations of mock buffer objects */
+ struct mutex lock;
+--- a/drivers/gpu/drm/xe/xe_res_cursor.h
++++ b/drivers/gpu/drm/xe/xe_res_cursor.h
+@@ -58,7 +58,7 @@ struct xe_res_cursor {
+ /** @dma_addr: Current element in a struct drm_pagemap_addr array */
+ const struct drm_pagemap_addr *dma_addr;
+ /** @mm: Buddy allocator for VRAM cursor */
+- struct drm_buddy *mm;
++ struct gpu_buddy *mm;
+ /**
+ * @dma_start: DMA start address for the current segment.
+ * This may be different to @dma_addr.addr since elements in
+@@ -69,7 +69,7 @@ struct xe_res_cursor {
+ u64 dma_seg_size;
+ };
+
+-static struct drm_buddy *xe_res_get_buddy(struct ttm_resource *res)
++static struct gpu_buddy *xe_res_get_buddy(struct ttm_resource *res)
+ {
+ struct ttm_resource_manager *mgr;
+
+@@ -104,30 +104,30 @@ static inline void xe_res_first(struct t
+ case XE_PL_STOLEN:
+ case XE_PL_VRAM0:
+ case XE_PL_VRAM1: {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ struct list_head *head, *next;
+- struct drm_buddy *mm = xe_res_get_buddy(res);
++ struct gpu_buddy *mm = xe_res_get_buddy(res);
+
+ head = &to_xe_ttm_vram_mgr_resource(res)->blocks;
+
+ block = list_first_entry_or_null(head,
+- struct drm_buddy_block,
++ struct gpu_buddy_block,
+ link);
+ if (!block)
+ goto fallback;
+
+- while (start >= drm_buddy_block_size(mm, block)) {
+- start -= drm_buddy_block_size(mm, block);
++ while (start >= gpu_buddy_block_size(mm, block)) {
++ start -= gpu_buddy_block_size(mm, block);
+
+ next = block->link.next;
+ if (next != head)
+- block = list_entry(next, struct drm_buddy_block,
++ block = list_entry(next, struct gpu_buddy_block,
+ link);
+ }
+
+ cur->mm = mm;
+- cur->start = drm_buddy_block_offset(block) + start;
+- cur->size = min(drm_buddy_block_size(mm, block) - start,
++ cur->start = gpu_buddy_block_offset(block) + start;
++ cur->size = min(gpu_buddy_block_size(mm, block) - start,
+ size);
+ cur->remaining = size;
+ cur->node = block;
+@@ -259,7 +259,7 @@ static inline void xe_res_first_dma(cons
+ */
+ static inline void xe_res_next(struct xe_res_cursor *cur, u64 size)
+ {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ struct list_head *next;
+ u64 start;
+
+@@ -295,18 +295,18 @@ static inline void xe_res_next(struct xe
+ block = cur->node;
+
+ next = block->link.next;
+- block = list_entry(next, struct drm_buddy_block, link);
++ block = list_entry(next, struct gpu_buddy_block, link);
+
+
+- while (start >= drm_buddy_block_size(cur->mm, block)) {
+- start -= drm_buddy_block_size(cur->mm, block);
++ while (start >= gpu_buddy_block_size(cur->mm, block)) {
++ start -= gpu_buddy_block_size(cur->mm, block);
+
+ next = block->link.next;
+- block = list_entry(next, struct drm_buddy_block, link);
++ block = list_entry(next, struct gpu_buddy_block, link);
+ }
+
+- cur->start = drm_buddy_block_offset(block) + start;
+- cur->size = min(drm_buddy_block_size(cur->mm, block) - start,
++ cur->start = gpu_buddy_block_offset(block) + start;
++ cur->size = min(gpu_buddy_block_size(cur->mm, block) - start,
+ cur->remaining);
+ cur->node = block;
+ break;
+--- a/drivers/gpu/drm/xe/xe_svm.c
++++ b/drivers/gpu/drm/xe/xe_svm.c
+@@ -689,7 +689,7 @@ static u64 block_offset_to_pfn(struct xe
+ return PHYS_PFN(offset + vr->hpa_base);
+ }
+
+-static struct drm_buddy *vram_to_buddy(struct xe_vram_region *vram)
++static struct gpu_buddy *vram_to_buddy(struct xe_vram_region *vram)
+ {
+ return &vram->ttm.mm;
+ }
+@@ -700,16 +700,16 @@ static int xe_svm_populate_devmem_pfn(st
+ struct xe_bo *bo = to_xe_bo(devmem_allocation);
+ struct ttm_resource *res = bo->ttm.resource;
+ struct list_head *blocks = &to_xe_ttm_vram_mgr_resource(res)->blocks;
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ int j = 0;
+
+ list_for_each_entry(block, blocks, link) {
+ struct xe_vram_region *vr = block->private;
+- struct drm_buddy *buddy = vram_to_buddy(vr);
+- u64 block_pfn = block_offset_to_pfn(vr, drm_buddy_block_offset(block));
++ struct gpu_buddy *buddy = vram_to_buddy(vr);
++ u64 block_pfn = block_offset_to_pfn(vr, gpu_buddy_block_offset(block));
+ int i;
+
+- for (i = 0; i < drm_buddy_block_size(buddy, block) >> PAGE_SHIFT; ++i)
++ for (i = 0; i < gpu_buddy_block_size(buddy, block) >> PAGE_SHIFT; ++i)
+ pfn[j++] = block_pfn + i;
+ }
+
+@@ -877,7 +877,7 @@ static int xe_drm_pagemap_populate_mm(st
+ struct dma_fence *pre_migrate_fence = NULL;
+ struct xe_device *xe = vr->xe;
+ struct device *dev = xe->drm.dev;
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ struct xe_validation_ctx vctx;
+ struct list_head *blocks;
+ struct drm_exec exec;
+--- a/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
++++ b/drivers/gpu/drm/xe/xe_ttm_vram_mgr.c
+@@ -6,6 +6,7 @@
+
+ #include <drm/drm_managed.h>
+ #include <drm/drm_drv.h>
++#include <drm/drm_buddy.h>
+
+ #include <drm/ttm/ttm_placement.h>
+ #include <drm/ttm/ttm_range_manager.h>
+@@ -17,16 +18,16 @@
+ #include "xe_ttm_vram_mgr.h"
+ #include "xe_vram_types.h"
+
+-static inline struct drm_buddy_block *
++static inline struct gpu_buddy_block *
+ xe_ttm_vram_mgr_first_block(struct list_head *list)
+ {
+- return list_first_entry_or_null(list, struct drm_buddy_block, link);
++ return list_first_entry_or_null(list, struct gpu_buddy_block, link);
+ }
+
+-static inline bool xe_is_vram_mgr_blocks_contiguous(struct drm_buddy *mm,
++static inline bool xe_is_vram_mgr_blocks_contiguous(struct gpu_buddy *mm,
+ struct list_head *head)
+ {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+ u64 start, size;
+
+ block = xe_ttm_vram_mgr_first_block(head);
+@@ -34,12 +35,12 @@ static inline bool xe_is_vram_mgr_blocks
+ return false;
+
+ while (head != block->link.next) {
+- start = drm_buddy_block_offset(block);
+- size = drm_buddy_block_size(mm, block);
++ start = gpu_buddy_block_offset(block);
++ size = gpu_buddy_block_size(mm, block);
+
+- block = list_entry(block->link.next, struct drm_buddy_block,
++ block = list_entry(block->link.next, struct gpu_buddy_block,
+ link);
+- if (start + size != drm_buddy_block_offset(block))
++ if (start + size != gpu_buddy_block_offset(block))
+ return false;
+ }
+
+@@ -53,7 +54,7 @@ static int xe_ttm_vram_mgr_new(struct tt
+ {
+ struct xe_ttm_vram_mgr *mgr = to_xe_ttm_vram_mgr(man);
+ struct xe_ttm_vram_mgr_resource *vres;
+- struct drm_buddy *mm = &mgr->mm;
++ struct gpu_buddy *mm = &mgr->mm;
+ u64 size, min_page_size;
+ unsigned long lpfn;
+ int err;
+@@ -80,10 +81,10 @@ static int xe_ttm_vram_mgr_new(struct tt
+ INIT_LIST_HEAD(&vres->blocks);
+
+ if (place->flags & TTM_PL_FLAG_TOPDOWN)
+- vres->flags |= DRM_BUDDY_TOPDOWN_ALLOCATION;
++ vres->flags |= GPU_BUDDY_TOPDOWN_ALLOCATION;
+
+ if (place->fpfn || lpfn != man->size >> PAGE_SHIFT)
+- vres->flags |= DRM_BUDDY_RANGE_ALLOCATION;
++ vres->flags |= GPU_BUDDY_RANGE_ALLOCATION;
+
+ if (WARN_ON(!vres->base.size)) {
+ err = -EINVAL;
+@@ -119,27 +120,27 @@ static int xe_ttm_vram_mgr_new(struct tt
+ lpfn = max_t(unsigned long, place->fpfn + (size >> PAGE_SHIFT), lpfn);
+ }
+
+- err = drm_buddy_alloc_blocks(mm, (u64)place->fpfn << PAGE_SHIFT,
++ err = gpu_buddy_alloc_blocks(mm, (u64)place->fpfn << PAGE_SHIFT,
+ (u64)lpfn << PAGE_SHIFT, size,
+ min_page_size, &vres->blocks, vres->flags);
+ if (err)
+ goto error_unlock;
+
+ if (place->flags & TTM_PL_FLAG_CONTIGUOUS) {
+- if (!drm_buddy_block_trim(mm, NULL, vres->base.size, &vres->blocks))
++ if (!gpu_buddy_block_trim(mm, NULL, vres->base.size, &vres->blocks))
+ size = vres->base.size;
+ }
+
+ if (lpfn <= mgr->visible_size >> PAGE_SHIFT) {
+ vres->used_visible_size = size;
+ } else {
+- struct drm_buddy_block *block;
++ struct gpu_buddy_block *block;
+
+ list_for_each_entry(block, &vres->blocks, link) {
+- u64 start = drm_buddy_block_offset(block);
++ u64 start = gpu_buddy_block_offset(block);
+
+ if (start < mgr->visible_size) {
+- u64 end = start + drm_buddy_block_size(mm, block);
++ u64 end = start + gpu_buddy_block_size(mm, block);
+
+ vres->used_visible_size +=
+ min(end, mgr->visible_size) - start;
+@@ -159,11 +160,11 @@ static int xe_ttm_vram_mgr_new(struct tt
+ * the object.
+ */
+ if (vres->base.placement & TTM_PL_FLAG_CONTIGUOUS) {
+- struct drm_buddy_block *block = list_first_entry(&vres->blocks,
++ struct gpu_buddy_block *block = list_first_entry(&vres->blocks,
+ typeof(*block),
+ link);
+
+- vres->base.start = drm_buddy_block_offset(block) >> PAGE_SHIFT;
++ vres->base.start = gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+ } else {
+ vres->base.start = XE_BO_INVALID_OFFSET;
+ }
+@@ -185,10 +186,10 @@ static void xe_ttm_vram_mgr_del(struct t
+ struct xe_ttm_vram_mgr_resource *vres =
+ to_xe_ttm_vram_mgr_resource(res);
+ struct xe_ttm_vram_mgr *mgr = to_xe_ttm_vram_mgr(man);
+- struct drm_buddy *mm = &mgr->mm;
++ struct gpu_buddy *mm = &mgr->mm;
+
+ mutex_lock(&mgr->lock);
+- drm_buddy_free_list(mm, &vres->blocks, 0);
++ gpu_buddy_free_list(mm, &vres->blocks, 0);
+ mgr->visible_avail += vres->used_visible_size;
+ mutex_unlock(&mgr->lock);
+
+@@ -201,7 +202,7 @@ static void xe_ttm_vram_mgr_debug(struct
+ struct drm_printer *printer)
+ {
+ struct xe_ttm_vram_mgr *mgr = to_xe_ttm_vram_mgr(man);
+- struct drm_buddy *mm = &mgr->mm;
++ struct gpu_buddy *mm = &mgr->mm;
+
+ mutex_lock(&mgr->lock);
+ drm_printf(printer, "default_page_size: %lluKiB\n",
+@@ -224,8 +225,8 @@ static bool xe_ttm_vram_mgr_intersects(s
+ struct xe_ttm_vram_mgr *mgr = to_xe_ttm_vram_mgr(man);
+ struct xe_ttm_vram_mgr_resource *vres =
+ to_xe_ttm_vram_mgr_resource(res);
+- struct drm_buddy *mm = &mgr->mm;
+- struct drm_buddy_block *block;
++ struct gpu_buddy *mm = &mgr->mm;
++ struct gpu_buddy_block *block;
+
+ if (!place->fpfn && !place->lpfn)
+ return true;
+@@ -235,9 +236,9 @@ static bool xe_ttm_vram_mgr_intersects(s
+
+ list_for_each_entry(block, &vres->blocks, link) {
+ unsigned long fpfn =
+- drm_buddy_block_offset(block) >> PAGE_SHIFT;
++ gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+ unsigned long lpfn = fpfn +
+- (drm_buddy_block_size(mm, block) >> PAGE_SHIFT);
++ (gpu_buddy_block_size(mm, block) >> PAGE_SHIFT);
+
+ if (place->fpfn < lpfn && place->lpfn > fpfn)
+ return true;
+@@ -254,8 +255,8 @@ static bool xe_ttm_vram_mgr_compatible(s
+ struct xe_ttm_vram_mgr *mgr = to_xe_ttm_vram_mgr(man);
+ struct xe_ttm_vram_mgr_resource *vres =
+ to_xe_ttm_vram_mgr_resource(res);
+- struct drm_buddy *mm = &mgr->mm;
+- struct drm_buddy_block *block;
++ struct gpu_buddy *mm = &mgr->mm;
++ struct gpu_buddy_block *block;
+
+ if (!place->fpfn && !place->lpfn)
+ return true;
+@@ -265,9 +266,9 @@ static bool xe_ttm_vram_mgr_compatible(s
+
+ list_for_each_entry(block, &vres->blocks, link) {
+ unsigned long fpfn =
+- drm_buddy_block_offset(block) >> PAGE_SHIFT;
++ gpu_buddy_block_offset(block) >> PAGE_SHIFT;
+ unsigned long lpfn = fpfn +
+- (drm_buddy_block_size(mm, block) >> PAGE_SHIFT);
++ (gpu_buddy_block_size(mm, block) >> PAGE_SHIFT);
+
+ if (fpfn < place->fpfn || lpfn > place->lpfn)
+ return false;
+@@ -297,7 +298,7 @@ static void ttm_vram_mgr_fini(struct drm
+
+ WARN_ON_ONCE(mgr->visible_avail != mgr->visible_size);
+
+- drm_buddy_fini(&mgr->mm);
++ gpu_buddy_fini(&mgr->mm);
+
+ ttm_resource_manager_cleanup(&mgr->manager);
+
+@@ -328,7 +329,7 @@ int __xe_ttm_vram_mgr_init(struct xe_dev
+ mgr->visible_avail = io_size;
+
+ ttm_resource_manager_init(man, &xe->ttm, size);
+- err = drm_buddy_init(&mgr->mm, man->size, default_page_size);
++ err = gpu_buddy_init(&mgr->mm, man->size, default_page_size);
+ if (err)
+ return err;
+
+@@ -376,7 +377,7 @@ int xe_ttm_vram_mgr_alloc_sgt(struct xe_
+ if (!*sgt)
+ return -ENOMEM;
+
+- /* Determine the number of DRM_BUDDY blocks to export */
++ /* Determine the number of GPU_BUDDY blocks to export */
+ xe_res_first(res, offset, length, &cursor);
+ while (cursor.remaining) {
+ num_entries++;
+@@ -393,10 +394,10 @@ int xe_ttm_vram_mgr_alloc_sgt(struct xe_
+ sg->length = 0;
+
+ /*
+- * Walk down DRM_BUDDY blocks to populate scatterlist nodes
+- * @note: Use iterator api to get first the DRM_BUDDY block
++ * Walk down GPU_BUDDY blocks to populate scatterlist nodes
++ * @note: Use iterator api to get first the GPU_BUDDY block
+ * and the number of bytes from it. Access the following
+- * DRM_BUDDY block(s) if more buffer needs to exported
++ * GPU_BUDDY block(s) if more buffer needs to exported
+ */
+ xe_res_first(res, offset, length, &cursor);
+ for_each_sgtable_sg((*sgt), sg, i) {
+--- a/drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h
++++ b/drivers/gpu/drm/xe/xe_ttm_vram_mgr_types.h
+@@ -6,7 +6,7 @@
+ #ifndef _XE_TTM_VRAM_MGR_TYPES_H_
+ #define _XE_TTM_VRAM_MGR_TYPES_H_
+
+-#include <drm/drm_buddy.h>
++#include <linux/gpu_buddy.h>
+ #include <drm/ttm/ttm_device.h>
+
+ /**
+@@ -18,7 +18,7 @@ struct xe_ttm_vram_mgr {
+ /** @manager: Base TTM resource manager */
+ struct ttm_resource_manager manager;
+ /** @mm: DRM buddy allocator which manages the VRAM */
+- struct drm_buddy mm;
++ struct gpu_buddy mm;
+ /** @visible_size: Proped size of the CPU visible portion */
+ u64 visible_size;
+ /** @visible_avail: CPU visible portion still unallocated */
+--- /dev/null
++++ b/drivers/gpu/tests/Makefile
+@@ -0,0 +1,4 @@
++# SPDX-License-Identifier: GPL-2.0
++
++gpu_buddy_tests-y = gpu_buddy_test.o gpu_random.o
++obj-$(CONFIG_GPU_BUDDY_KUNIT_TEST) += gpu_buddy_tests.o
+--- /dev/null
++++ b/drivers/gpu/tests/gpu_buddy_test.c
+@@ -0,0 +1,788 @@
++// SPDX-License-Identifier: MIT
++/*
++ * Copyright © 2019 Intel Corporation
++ * Copyright © 2022 MaÃra Canal <mairacanal@riseup.net>
++ */
++
++#include <kunit/test.h>
++
++#include <linux/prime_numbers.h>
++#include <linux/sched/signal.h>
++#include <linux/sizes.h>
++
++#include <linux/gpu_buddy.h>
++
++#include "gpu_random.h"
++
++static unsigned int random_seed;
++
++static inline u64 get_size(int order, u64 chunk_size)
++{
++ return (1 << order) * chunk_size;
++}
++
++static void gpu_test_buddy_alloc_range_bias(struct kunit *test)
++{
++ u32 mm_size, size, ps, bias_size, bias_start, bias_end, bias_rem;
++ GPU_RND_STATE(prng, random_seed);
++ unsigned int i, count, *order;
++ struct gpu_buddy_block *block;
++ unsigned long flags;
++ struct gpu_buddy mm;
++ LIST_HEAD(allocated);
++
++ bias_size = SZ_1M;
++ ps = roundup_pow_of_two(prandom_u32_state(&prng) % bias_size);
++ ps = max(SZ_4K, ps);
++ mm_size = (SZ_8M-1) & ~(ps-1); /* Multiple roots */
++
++ kunit_info(test, "mm_size=%u, ps=%u\n", mm_size, ps);
++
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, ps),
++ "buddy_init failed\n");
++
++ count = mm_size / bias_size;
++ order = gpu_random_order(count, &prng);
++ KUNIT_EXPECT_TRUE(test, order);
++
++ /*
++ * Idea is to split the address space into uniform bias ranges, and then
++ * in some random order allocate within each bias, using various
++ * patterns within. This should detect if allocations leak out from a
++ * given bias, for example.
++ */
++
++ for (i = 0; i < count; i++) {
++ LIST_HEAD(tmp);
++ u32 size;
++
++ bias_start = order[i] * bias_size;
++ bias_end = bias_start + bias_size;
++ bias_rem = bias_size;
++
++ /* internal round_up too big */
++ KUNIT_ASSERT_TRUE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, bias_size + ps, bias_size,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, bias_size, bias_size);
++
++ /* size too big */
++ KUNIT_ASSERT_TRUE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, bias_size + ps, ps,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, bias_size + ps, ps);
++
++ /* bias range too small for size */
++ KUNIT_ASSERT_TRUE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start + ps,
++ bias_end, bias_size, ps,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start + ps, bias_end, bias_size, ps);
++
++ /* bias misaligned */
++ KUNIT_ASSERT_TRUE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start + ps,
++ bias_end - ps,
++ bias_size >> 1, bias_size >> 1,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc h didn't fail with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start + ps, bias_end - ps, bias_size >> 1, bias_size >> 1);
++
++ /* single big page */
++ KUNIT_ASSERT_FALSE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, bias_size, bias_size,
++ &tmp,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc i failed with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, bias_size, bias_size);
++ gpu_buddy_free_list(&mm, &tmp, 0);
++
++ /* single page with internal round_up */
++ KUNIT_ASSERT_FALSE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, ps, bias_size,
++ &tmp,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, ps, bias_size);
++ gpu_buddy_free_list(&mm, &tmp, 0);
++
++ /* random size within */
++ size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
++ if (size)
++ KUNIT_ASSERT_FALSE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, size, ps,
++ &tmp,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, size, ps);
++
++ bias_rem -= size;
++ /* too big for current avail */
++ KUNIT_ASSERT_TRUE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, bias_rem + ps, ps,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc didn't fail with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, bias_rem + ps, ps);
++
++ if (bias_rem) {
++ /* random fill of the remainder */
++ size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
++ size = max(size, ps);
++
++ KUNIT_ASSERT_FALSE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, size, ps,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, size, ps);
++ /*
++ * Intentionally allow some space to be left
++ * unallocated, and ideally not always on the bias
++ * boundaries.
++ */
++ gpu_buddy_free_list(&mm, &tmp, 0);
++ } else {
++ list_splice_tail(&tmp, &allocated);
++ }
++ }
++
++ kfree(order);
++ gpu_buddy_free_list(&mm, &allocated, 0);
++ gpu_buddy_fini(&mm);
++
++ /*
++ * Something more free-form. Idea is to pick a random starting bias
++ * range within the address space and then start filling it up. Also
++ * randomly grow the bias range in both directions as we go along. This
++ * should give us bias start/end which is not always uniform like above,
++ * and in some cases will require the allocator to jump over already
++ * allocated nodes in the middle of the address space.
++ */
++
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, ps),
++ "buddy_init failed\n");
++
++ bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
++ bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
++ bias_end = max(bias_end, bias_start + ps);
++ bias_rem = bias_end - bias_start;
++
++ do {
++ u32 size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
++
++ KUNIT_ASSERT_FALSE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, size, ps,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, size, ps);
++ bias_rem -= size;
++
++ /*
++ * Try to randomly grow the bias range in both directions, or
++ * only one, or perhaps don't grow at all.
++ */
++ do {
++ u32 old_bias_start = bias_start;
++ u32 old_bias_end = bias_end;
++
++ if (bias_start)
++ bias_start -= round_up(prandom_u32_state(&prng) % bias_start, ps);
++ if (bias_end != mm_size)
++ bias_end += round_up(prandom_u32_state(&prng) % (mm_size - bias_end), ps);
++
++ bias_rem += old_bias_start - bias_start;
++ bias_rem += bias_end - old_bias_end;
++ } while (!bias_rem && (bias_start || bias_end != mm_size));
++ } while (bias_rem);
++
++ KUNIT_ASSERT_EQ(test, bias_start, 0);
++ KUNIT_ASSERT_EQ(test, bias_end, mm_size);
++ KUNIT_ASSERT_TRUE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start, bias_end,
++ ps, ps,
++ &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc passed with bias(%x-%x), size=%u\n",
++ bias_start, bias_end, ps);
++
++ gpu_buddy_free_list(&mm, &allocated, 0);
++ gpu_buddy_fini(&mm);
++
++ /*
++ * Allocate cleared blocks in the bias range when the GPU buddy's clear avail is
++ * zero. This will validate the bias range allocation in scenarios like system boot
++ * when no cleared blocks are available and exercise the fallback path too. The resulting
++ * blocks should always be dirty.
++ */
++
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, ps),
++ "buddy_init failed\n");
++
++ bias_start = round_up(prandom_u32_state(&prng) % (mm_size - ps), ps);
++ bias_end = round_up(bias_start + prandom_u32_state(&prng) % (mm_size - bias_start), ps);
++ bias_end = max(bias_end, bias_start + ps);
++ bias_rem = bias_end - bias_start;
++
++ flags = GPU_BUDDY_CLEAR_ALLOCATION | GPU_BUDDY_RANGE_ALLOCATION;
++ size = max(round_up(prandom_u32_state(&prng) % bias_rem, ps), ps);
++
++ KUNIT_ASSERT_FALSE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, bias_start,
++ bias_end, size, ps,
++ &allocated,
++ flags),
++ "buddy_alloc failed with bias(%x-%x), size=%u, ps=%u\n",
++ bias_start, bias_end, size, ps);
++
++ list_for_each_entry(block, &allocated, link)
++ KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), false);
++
++ gpu_buddy_free_list(&mm, &allocated, 0);
++ gpu_buddy_fini(&mm);
++}
++
++static void gpu_test_buddy_alloc_clear(struct kunit *test)
++{
++ unsigned long n_pages, total, i = 0;
++ const unsigned long ps = SZ_4K;
++ struct gpu_buddy_block *block;
++ const int max_order = 12;
++ LIST_HEAD(allocated);
++ struct gpu_buddy mm;
++ unsigned int order;
++ u32 mm_size, size;
++ LIST_HEAD(dirty);
++ LIST_HEAD(clean);
++
++ mm_size = SZ_4K << max_order;
++ KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps));
++
++ KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
++
++ /*
++ * Idea is to allocate and free some random portion of the address space,
++ * returning those pages as non-dirty and randomly alternate between
++ * requesting dirty and non-dirty pages (not going over the limit
++ * we freed as non-dirty), putting that into two separate lists.
++ * Loop over both lists at the end checking that the dirty list
++ * is indeed all dirty pages and vice versa. Free it all again,
++ * keeping the dirty/clear status.
++ */
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 5 * ps, ps, &allocated,
++ GPU_BUDDY_TOPDOWN_ALLOCATION),
++ "buddy_alloc hit an error size=%lu\n", 5 * ps);
++ gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED);
++
++ n_pages = 10;
++ do {
++ unsigned long flags;
++ struct list_head *list;
++ int slot = i % 2;
++
++ if (slot == 0) {
++ list = &dirty;
++ flags = 0;
++ } else {
++ list = &clean;
++ flags = GPU_BUDDY_CLEAR_ALLOCATION;
++ }
++
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ ps, ps, list,
++ flags),
++ "buddy_alloc hit an error size=%lu\n", ps);
++ } while (++i < n_pages);
++
++ list_for_each_entry(block, &clean, link)
++ KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), true);
++
++ list_for_each_entry(block, &dirty, link)
++ KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), false);
++
++ gpu_buddy_free_list(&mm, &clean, GPU_BUDDY_CLEARED);
++
++ /*
++ * Trying to go over the clear limit for some allocation.
++ * The allocation should never fail with reasonable page-size.
++ */
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 10 * ps, ps, &clean,
++ GPU_BUDDY_CLEAR_ALLOCATION),
++ "buddy_alloc hit an error size=%lu\n", 10 * ps);
++
++ gpu_buddy_free_list(&mm, &clean, GPU_BUDDY_CLEARED);
++ gpu_buddy_free_list(&mm, &dirty, 0);
++ gpu_buddy_fini(&mm);
++
++ KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps));
++
++ /*
++ * Create a new mm. Intentionally fragment the address space by creating
++ * two alternating lists. Free both lists, one as dirty the other as clean.
++ * Try to allocate double the previous size with matching min_page_size. The
++ * allocation should never fail as it calls the force_merge. Also check that
++ * the page is always dirty after force_merge. Free the page as dirty, then
++ * repeat the whole thing, increment the order until we hit the max_order.
++ */
++
++ i = 0;
++ n_pages = mm_size / ps;
++ do {
++ struct list_head *list;
++ int slot = i % 2;
++
++ if (slot == 0)
++ list = &dirty;
++ else
++ list = &clean;
++
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ ps, ps, list, 0),
++ "buddy_alloc hit an error size=%lu\n", ps);
++ } while (++i < n_pages);
++
++ gpu_buddy_free_list(&mm, &clean, GPU_BUDDY_CLEARED);
++ gpu_buddy_free_list(&mm, &dirty, 0);
++
++ order = 1;
++ do {
++ size = SZ_4K << order;
++
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ size, size, &allocated,
++ GPU_BUDDY_CLEAR_ALLOCATION),
++ "buddy_alloc hit an error size=%u\n", size);
++ total = 0;
++ list_for_each_entry(block, &allocated, link) {
++ if (size != mm_size)
++ KUNIT_EXPECT_EQ(test, gpu_buddy_block_is_clear(block), false);
++ total += gpu_buddy_block_size(&mm, block);
++ }
++ KUNIT_EXPECT_EQ(test, total, size);
++
++ gpu_buddy_free_list(&mm, &allocated, 0);
++ } while (++order <= max_order);
++
++ gpu_buddy_fini(&mm);
++
++ /*
++ * Create a new mm with a non power-of-two size. Allocate a random size from each
++ * root, free as cleared and then call fini. This will ensure the multi-root
++ * force merge during fini.
++ */
++ mm_size = (SZ_4K << max_order) + (SZ_4K << (max_order - 2));
++
++ KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps));
++ KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, SZ_4K << max_order,
++ 4 * ps, ps, &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc hit an error size=%lu\n", 4 * ps);
++ gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, SZ_4K << max_order,
++ 2 * ps, ps, &allocated,
++ GPU_BUDDY_CLEAR_ALLOCATION),
++ "buddy_alloc hit an error size=%lu\n", 2 * ps);
++ gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, SZ_4K << max_order, mm_size,
++ ps, ps, &allocated,
++ GPU_BUDDY_RANGE_ALLOCATION),
++ "buddy_alloc hit an error size=%lu\n", ps);
++ gpu_buddy_free_list(&mm, &allocated, GPU_BUDDY_CLEARED);
++ gpu_buddy_fini(&mm);
++}
++
++static void gpu_test_buddy_alloc_contiguous(struct kunit *test)
++{
++ const unsigned long ps = SZ_4K, mm_size = 16 * 3 * SZ_4K;
++ unsigned long i, n_pages, total;
++ struct gpu_buddy_block *block;
++ struct gpu_buddy mm;
++ LIST_HEAD(left);
++ LIST_HEAD(middle);
++ LIST_HEAD(right);
++ LIST_HEAD(allocated);
++
++ KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, mm_size, ps));
++
++ /*
++ * Idea is to fragment the address space by alternating block
++ * allocations between three different lists; one for left, middle and
++ * right. We can then free a list to simulate fragmentation. In
++ * particular we want to exercise the GPU_BUDDY_CONTIGUOUS_ALLOCATION,
++ * including the try_harder path.
++ */
++
++ i = 0;
++ n_pages = mm_size / ps;
++ do {
++ struct list_head *list;
++ int slot = i % 3;
++
++ if (slot == 0)
++ list = &left;
++ else if (slot == 1)
++ list = &middle;
++ else
++ list = &right;
++ KUNIT_ASSERT_FALSE_MSG(test,
++ gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ ps, ps, list, 0),
++ "buddy_alloc hit an error size=%lu\n",
++ ps);
++ } while (++i < n_pages);
++
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 3 * ps, ps, &allocated,
++ GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++ "buddy_alloc didn't error size=%lu\n", 3 * ps);
++
++ gpu_buddy_free_list(&mm, &middle, 0);
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 3 * ps, ps, &allocated,
++ GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++ "buddy_alloc didn't error size=%lu\n", 3 * ps);
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 2 * ps, ps, &allocated,
++ GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++ "buddy_alloc didn't error size=%lu\n", 2 * ps);
++
++ gpu_buddy_free_list(&mm, &right, 0);
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 3 * ps, ps, &allocated,
++ GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++ "buddy_alloc didn't error size=%lu\n", 3 * ps);
++ /*
++ * At this point we should have enough contiguous space for 2 blocks,
++ * however they are never buddies (since we freed middle and right) so
++ * will require the try_harder logic to find them.
++ */
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 2 * ps, ps, &allocated,
++ GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++ "buddy_alloc hit an error size=%lu\n", 2 * ps);
++
++ gpu_buddy_free_list(&mm, &left, 0);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, 0, mm_size,
++ 3 * ps, ps, &allocated,
++ GPU_BUDDY_CONTIGUOUS_ALLOCATION),
++ "buddy_alloc hit an error size=%lu\n", 3 * ps);
++
++ total = 0;
++ list_for_each_entry(block, &allocated, link)
++ total += gpu_buddy_block_size(&mm, block);
++
++ KUNIT_ASSERT_EQ(test, total, ps * 2 + ps * 3);
++
++ gpu_buddy_free_list(&mm, &allocated, 0);
++ gpu_buddy_fini(&mm);
++}
++
++static void gpu_test_buddy_alloc_pathological(struct kunit *test)
++{
++ u64 mm_size, size, start = 0;
++ struct gpu_buddy_block *block;
++ const int max_order = 3;
++ unsigned long flags = 0;
++ int order, top;
++ struct gpu_buddy mm;
++ LIST_HEAD(blocks);
++ LIST_HEAD(holes);
++ LIST_HEAD(tmp);
++
++ /*
++ * Create a pot-sized mm, then allocate one of each possible
++ * order within. This should leave the mm with exactly one
++ * page left. Free the largest block, then whittle down again.
++ * Eventually we will have a fully 50% fragmented mm.
++ */
++
++ mm_size = SZ_4K << max_order;
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, SZ_4K),
++ "buddy_init failed\n");
++
++ KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
++
++ for (top = max_order; top; top--) {
++ /* Make room by freeing the largest allocated block */
++ block = list_first_entry_or_null(&blocks, typeof(*block), link);
++ if (block) {
++ list_del(&block->link);
++ gpu_buddy_free_block(&mm, block);
++ }
++
++ for (order = top; order--;) {
++ size = get_size(order, mm.chunk_size);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start,
++ mm_size, size, size,
++ &tmp, flags),
++ "buddy_alloc hit -ENOMEM with order=%d, top=%d\n",
++ order, top);
++
++ block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++ KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++ list_move_tail(&block->link, &blocks);
++ }
++
++ /* There should be one final page for this sub-allocation */
++ size = get_size(0, mm.chunk_size);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc hit -ENOMEM for hole\n");
++
++ block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++ KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++ list_move_tail(&block->link, &holes);
++
++ size = get_size(top, mm.chunk_size);
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc unexpectedly succeeded at top-order %d/%d, it should be full!",
++ top, max_order);
++ }
++
++ gpu_buddy_free_list(&mm, &holes, 0);
++
++ /* Nothing larger than blocks of chunk_size now available */
++ for (order = 1; order <= max_order; order++) {
++ size = get_size(order, mm.chunk_size);
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc unexpectedly succeeded at order %d, it should be full!",
++ order);
++ }
++
++ list_splice_tail(&holes, &blocks);
++ gpu_buddy_free_list(&mm, &blocks, 0);
++ gpu_buddy_fini(&mm);
++}
++
++static void gpu_test_buddy_alloc_pessimistic(struct kunit *test)
++{
++ u64 mm_size, size, start = 0;
++ struct gpu_buddy_block *block, *bn;
++ const unsigned int max_order = 16;
++ unsigned long flags = 0;
++ struct gpu_buddy mm;
++ unsigned int order;
++ LIST_HEAD(blocks);
++ LIST_HEAD(tmp);
++
++ /*
++ * Create a pot-sized mm, then allocate one of each possible
++ * order within. This should leave the mm with exactly one
++ * page left.
++ */
++
++ mm_size = SZ_4K << max_order;
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, SZ_4K),
++ "buddy_init failed\n");
++
++ KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
++
++ for (order = 0; order < max_order; order++) {
++ size = get_size(order, mm.chunk_size);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc hit -ENOMEM with order=%d\n",
++ order);
++
++ block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++ KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++ list_move_tail(&block->link, &blocks);
++ }
++
++ /* And now the last remaining block available */
++ size = get_size(0, mm.chunk_size);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc hit -ENOMEM on final alloc\n");
++
++ block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++ KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++ list_move_tail(&block->link, &blocks);
++
++ /* Should be completely full! */
++ for (order = max_order; order--;) {
++ size = get_size(order, mm.chunk_size);
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc unexpectedly succeeded, it should be full!");
++ }
++
++ block = list_last_entry(&blocks, typeof(*block), link);
++ list_del(&block->link);
++ gpu_buddy_free_block(&mm, block);
++
++ /* As we free in increasing size, we make available larger blocks */
++ order = 1;
++ list_for_each_entry_safe(block, bn, &blocks, link) {
++ list_del(&block->link);
++ gpu_buddy_free_block(&mm, block);
++
++ size = get_size(order, mm.chunk_size);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc hit -ENOMEM with order=%d\n",
++ order);
++
++ block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++ KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++ list_del(&block->link);
++ gpu_buddy_free_block(&mm, block);
++ order++;
++ }
++
++ /* To confirm, now the whole mm should be available */
++ size = get_size(max_order, mm.chunk_size);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc (realloc) hit -ENOMEM with order=%d\n",
++ max_order);
++
++ block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++ KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++ list_del(&block->link);
++ gpu_buddy_free_block(&mm, block);
++ gpu_buddy_free_list(&mm, &blocks, 0);
++ gpu_buddy_fini(&mm);
++}
++
++static void gpu_test_buddy_alloc_optimistic(struct kunit *test)
++{
++ u64 mm_size, size, start = 0;
++ struct gpu_buddy_block *block;
++ unsigned long flags = 0;
++ const int max_order = 16;
++ struct gpu_buddy mm;
++ LIST_HEAD(blocks);
++ LIST_HEAD(tmp);
++ int order;
++
++ /*
++ * Create a mm with one block of each order available, and
++ * try to allocate them all.
++ */
++
++ mm_size = SZ_4K * ((1 << (max_order + 1)) - 1);
++
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_init(&mm, mm_size, SZ_4K),
++ "buddy_init failed\n");
++
++ KUNIT_EXPECT_EQ(test, mm.max_order, max_order);
++
++ for (order = 0; order <= max_order; order++) {
++ size = get_size(order, mm.chunk_size);
++ KUNIT_ASSERT_FALSE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc hit -ENOMEM with order=%d\n",
++ order);
++
++ block = list_first_entry_or_null(&tmp, struct gpu_buddy_block, link);
++ KUNIT_ASSERT_TRUE_MSG(test, block, "alloc_blocks has no blocks\n");
++
++ list_move_tail(&block->link, &blocks);
++ }
++
++ /* Should be completely full! */
++ size = get_size(0, mm.chunk_size);
++ KUNIT_ASSERT_TRUE_MSG(test, gpu_buddy_alloc_blocks(&mm, start, mm_size,
++ size, size, &tmp, flags),
++ "buddy_alloc unexpectedly succeeded, it should be full!");
++
++ gpu_buddy_free_list(&mm, &blocks, 0);
++ gpu_buddy_fini(&mm);
++}
++
++static void gpu_test_buddy_alloc_limit(struct kunit *test)
++{
++ u64 size = U64_MAX, start = 0;
++ struct gpu_buddy_block *block;
++ unsigned long flags = 0;
++ LIST_HEAD(allocated);
++ struct gpu_buddy mm;
++
++ KUNIT_EXPECT_FALSE(test, gpu_buddy_init(&mm, size, SZ_4K));
++
++ KUNIT_EXPECT_EQ_MSG(test, mm.max_order, GPU_BUDDY_MAX_ORDER,
++ "mm.max_order(%d) != %d\n", mm.max_order,
++ GPU_BUDDY_MAX_ORDER);
++
++ size = mm.chunk_size << mm.max_order;
++ KUNIT_EXPECT_FALSE(test, gpu_buddy_alloc_blocks(&mm, start, size, size,
++ mm.chunk_size, &allocated, flags));
++
++ block = list_first_entry_or_null(&allocated, struct gpu_buddy_block, link);
++ KUNIT_EXPECT_TRUE(test, block);
++
++ KUNIT_EXPECT_EQ_MSG(test, gpu_buddy_block_order(block), mm.max_order,
++ "block order(%d) != %d\n",
++ gpu_buddy_block_order(block), mm.max_order);
++
++ KUNIT_EXPECT_EQ_MSG(test, gpu_buddy_block_size(&mm, block),
++ BIT_ULL(mm.max_order) * mm.chunk_size,
++ "block size(%llu) != %llu\n",
++ gpu_buddy_block_size(&mm, block),
++ BIT_ULL(mm.max_order) * mm.chunk_size);
++
++ gpu_buddy_free_list(&mm, &allocated, 0);
++ gpu_buddy_fini(&mm);
++}
++
++static int gpu_buddy_suite_init(struct kunit_suite *suite)
++{
++ while (!random_seed)
++ random_seed = get_random_u32();
++
++ kunit_info(suite, "Testing GPU buddy manager, with random_seed=0x%x\n",
++ random_seed);
++
++ return 0;
++}
++
++static struct kunit_case gpu_buddy_tests[] = {
++ KUNIT_CASE(gpu_test_buddy_alloc_limit),
++ KUNIT_CASE(gpu_test_buddy_alloc_optimistic),
++ KUNIT_CASE(gpu_test_buddy_alloc_pessimistic),
++ KUNIT_CASE(gpu_test_buddy_alloc_pathological),
++ KUNIT_CASE(gpu_test_buddy_alloc_contiguous),
++ KUNIT_CASE(gpu_test_buddy_alloc_clear),
++ KUNIT_CASE(gpu_test_buddy_alloc_range_bias),
++ {}
++};
++
++static struct kunit_suite gpu_buddy_test_suite = {
++ .name = "gpu_buddy",
++ .suite_init = gpu_buddy_suite_init,
++ .test_cases = gpu_buddy_tests,
++};
++
++kunit_test_suite(gpu_buddy_test_suite);
++
++MODULE_AUTHOR("Intel Corporation");
++MODULE_DESCRIPTION("Kunit test for gpu_buddy functions");
++MODULE_LICENSE("GPL");
+--- /dev/null
++++ b/drivers/gpu/tests/gpu_random.c
+@@ -0,0 +1,44 @@
++// SPDX-License-Identifier: GPL-2.0
++#include <linux/bitops.h>
++#include <linux/export.h>
++#include <linux/kernel.h>
++#include <linux/random.h>
++#include <linux/slab.h>
++#include <linux/types.h>
++
++#include "gpu_random.h"
++
++u32 gpu_prandom_u32_max_state(u32 ep_ro, struct rnd_state *state)
++{
++ return upper_32_bits((u64)prandom_u32_state(state) * ep_ro);
++}
++EXPORT_SYMBOL(gpu_prandom_u32_max_state);
++
++void gpu_random_reorder(unsigned int *order, unsigned int count,
++ struct rnd_state *state)
++{
++ unsigned int i, j;
++
++ for (i = 0; i < count; ++i) {
++ BUILD_BUG_ON(sizeof(unsigned int) > sizeof(u32));
++ j = gpu_prandom_u32_max_state(count, state);
++ swap(order[i], order[j]);
++ }
++}
++EXPORT_SYMBOL(gpu_random_reorder);
++
++unsigned int *gpu_random_order(unsigned int count, struct rnd_state *state)
++{
++ unsigned int *order, i;
++
++ order = kmalloc_array(count, sizeof(*order), GFP_KERNEL);
++ if (!order)
++ return order;
++
++ for (i = 0; i < count; i++)
++ order[i] = i;
++
++ gpu_random_reorder(order, count, state);
++ return order;
++}
++EXPORT_SYMBOL(gpu_random_order);
+--- /dev/null
++++ b/drivers/gpu/tests/gpu_random.h
+@@ -0,0 +1,28 @@
++/* SPDX-License-Identifier: GPL-2.0 */
++#ifndef __GPU_RANDOM_H__
++#define __GPU_RANDOM_H__
++
++/* This is a temporary home for a couple of utility functions that should
++ * be transposed to lib/ at the earliest convenience.
++ */
++
++#include <linux/prandom.h>
++
++#define GPU_RND_STATE_INITIALIZER(seed__) ({ \
++ struct rnd_state state__; \
++ prandom_seed_state(&state__, (seed__)); \
++ state__; \
++})
++
++#define GPU_RND_STATE(name__, seed__) \
++ struct rnd_state name__ = GPU_RND_STATE_INITIALIZER(seed__)
++
++unsigned int *gpu_random_order(unsigned int count,
++ struct rnd_state *state);
++void gpu_random_reorder(unsigned int *order,
++ unsigned int count,
++ struct rnd_state *state);
++u32 gpu_prandom_u32_max_state(u32 ep_ro,
++ struct rnd_state *state);
++
++#endif /* !__GPU_RANDOM_H__ */
+--- a/drivers/video/Kconfig
++++ b/drivers/video/Kconfig
+@@ -37,6 +37,7 @@ source "drivers/char/agp/Kconfig"
+
+ source "drivers/gpu/vga/Kconfig"
+
++source "drivers/gpu/Kconfig"
+ source "drivers/gpu/host1x/Kconfig"
+ source "drivers/gpu/ipu-v3/Kconfig"
+ source "drivers/gpu/nova-core/Kconfig"
+--- a/include/drm/drm_buddy.h
++++ b/include/drm/drm_buddy.h
+@@ -6,166 +6,13 @@
+ #ifndef __DRM_BUDDY_H__
+ #define __DRM_BUDDY_H__
+
+-#include <linux/bitops.h>
+-#include <linux/list.h>
+-#include <linux/slab.h>
+-#include <linux/sched.h>
+-#include <linux/rbtree.h>
++#include <linux/gpu_buddy.h>
+
+-#include <drm/drm_print.h>
++struct drm_printer;
+
+-#define DRM_BUDDY_RANGE_ALLOCATION BIT(0)
+-#define DRM_BUDDY_TOPDOWN_ALLOCATION BIT(1)
+-#define DRM_BUDDY_CONTIGUOUS_ALLOCATION BIT(2)
+-#define DRM_BUDDY_CLEAR_ALLOCATION BIT(3)
+-#define DRM_BUDDY_CLEARED BIT(4)
+-#define DRM_BUDDY_TRIM_DISABLE BIT(5)
+-
+-struct drm_buddy_block {
+-#define DRM_BUDDY_HEADER_OFFSET GENMASK_ULL(63, 12)
+-#define DRM_BUDDY_HEADER_STATE GENMASK_ULL(11, 10)
+-#define DRM_BUDDY_ALLOCATED (1 << 10)
+-#define DRM_BUDDY_FREE (2 << 10)
+-#define DRM_BUDDY_SPLIT (3 << 10)
+-#define DRM_BUDDY_HEADER_CLEAR GENMASK_ULL(9, 9)
+-/* Free to be used, if needed in the future */
+-#define DRM_BUDDY_HEADER_UNUSED GENMASK_ULL(8, 6)
+-#define DRM_BUDDY_HEADER_ORDER GENMASK_ULL(5, 0)
+- u64 header;
+-
+- struct drm_buddy_block *left;
+- struct drm_buddy_block *right;
+- struct drm_buddy_block *parent;
+-
+- void *private; /* owned by creator */
+-
+- /*
+- * While the block is allocated by the user through drm_buddy_alloc*,
+- * the user has ownership of the link, for example to maintain within
+- * a list, if so desired. As soon as the block is freed with
+- * drm_buddy_free* ownership is given back to the mm.
+- */
+- union {
+- struct rb_node rb;
+- struct list_head link;
+- };
+-
+- struct list_head tmp_link;
+-};
+-
+-/* Order-zero must be at least SZ_4K */
+-#define DRM_BUDDY_MAX_ORDER (63 - 12)
+-
+-/*
+- * Binary Buddy System.
+- *
+- * Locking should be handled by the user, a simple mutex around
+- * drm_buddy_alloc* and drm_buddy_free* should suffice.
+- */
+-struct drm_buddy {
+- /* Maintain a free list for each order. */
+- struct rb_root **free_trees;
+-
+- /*
+- * Maintain explicit binary tree(s) to track the allocation of the
+- * address space. This gives us a simple way of finding a buddy block
+- * and performing the potentially recursive merge step when freeing a
+- * block. Nodes are either allocated or free, in which case they will
+- * also exist on the respective free list.
+- */
+- struct drm_buddy_block **roots;
+-
+- /*
+- * Anything from here is public, and remains static for the lifetime of
+- * the mm. Everything above is considered do-not-touch.
+- */
+- unsigned int n_roots;
+- unsigned int max_order;
+-
+- /* Must be at least SZ_4K */
+- u64 chunk_size;
+- u64 size;
+- u64 avail;
+- u64 clear_avail;
+-};
+-
+-static inline u64
+-drm_buddy_block_offset(const struct drm_buddy_block *block)
+-{
+- return block->header & DRM_BUDDY_HEADER_OFFSET;
+-}
+-
+-static inline unsigned int
+-drm_buddy_block_order(struct drm_buddy_block *block)
+-{
+- return block->header & DRM_BUDDY_HEADER_ORDER;
+-}
+-
+-static inline unsigned int
+-drm_buddy_block_state(struct drm_buddy_block *block)
+-{
+- return block->header & DRM_BUDDY_HEADER_STATE;
+-}
+-
+-static inline bool
+-drm_buddy_block_is_allocated(struct drm_buddy_block *block)
+-{
+- return drm_buddy_block_state(block) == DRM_BUDDY_ALLOCATED;
+-}
+-
+-static inline bool
+-drm_buddy_block_is_clear(struct drm_buddy_block *block)
+-{
+- return block->header & DRM_BUDDY_HEADER_CLEAR;
+-}
+-
+-static inline bool
+-drm_buddy_block_is_free(struct drm_buddy_block *block)
+-{
+- return drm_buddy_block_state(block) == DRM_BUDDY_FREE;
+-}
+-
+-static inline bool
+-drm_buddy_block_is_split(struct drm_buddy_block *block)
+-{
+- return drm_buddy_block_state(block) == DRM_BUDDY_SPLIT;
+-}
+-
+-static inline u64
+-drm_buddy_block_size(struct drm_buddy *mm,
+- struct drm_buddy_block *block)
+-{
+- return mm->chunk_size << drm_buddy_block_order(block);
+-}
+-
+-int drm_buddy_init(struct drm_buddy *mm, u64 size, u64 chunk_size);
+-
+-void drm_buddy_fini(struct drm_buddy *mm);
+-
+-struct drm_buddy_block *
+-drm_get_buddy(struct drm_buddy_block *block);
+-
+-int drm_buddy_alloc_blocks(struct drm_buddy *mm,
+- u64 start, u64 end, u64 size,
+- u64 min_page_size,
+- struct list_head *blocks,
+- unsigned long flags);
+-
+-int drm_buddy_block_trim(struct drm_buddy *mm,
+- u64 *start,
+- u64 new_size,
+- struct list_head *blocks);
+-
+-void drm_buddy_reset_clear(struct drm_buddy *mm, bool is_clear);
+-
+-void drm_buddy_free_block(struct drm_buddy *mm, struct drm_buddy_block *block);
+-
+-void drm_buddy_free_list(struct drm_buddy *mm,
+- struct list_head *objects,
+- unsigned int flags);
+-
+-void drm_buddy_print(struct drm_buddy *mm, struct drm_printer *p);
+-void drm_buddy_block_print(struct drm_buddy *mm,
+- struct drm_buddy_block *block,
++/* DRM-specific GPU Buddy Allocator print helpers */
++void drm_buddy_print(struct gpu_buddy *mm, struct drm_printer *p);
++void drm_buddy_block_print(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block,
+ struct drm_printer *p);
+ #endif
+--- /dev/null
++++ b/include/linux/gpu_buddy.h
+@@ -0,0 +1,177 @@
++/* SPDX-License-Identifier: MIT */
++/*
++ * Copyright © 2021 Intel Corporation
++ */
++
++#ifndef __GPU_BUDDY_H__
++#define __GPU_BUDDY_H__
++
++#include <linux/bitops.h>
++#include <linux/list.h>
++#include <linux/slab.h>
++#include <linux/sched.h>
++#include <linux/rbtree.h>
++
++#define GPU_BUDDY_RANGE_ALLOCATION BIT(0)
++#define GPU_BUDDY_TOPDOWN_ALLOCATION BIT(1)
++#define GPU_BUDDY_CONTIGUOUS_ALLOCATION BIT(2)
++#define GPU_BUDDY_CLEAR_ALLOCATION BIT(3)
++#define GPU_BUDDY_CLEARED BIT(4)
++#define GPU_BUDDY_TRIM_DISABLE BIT(5)
++
++enum gpu_buddy_free_tree {
++ GPU_BUDDY_CLEAR_TREE = 0,
++ GPU_BUDDY_DIRTY_TREE,
++ GPU_BUDDY_MAX_FREE_TREES,
++};
++
++#define for_each_free_tree(tree) \
++ for ((tree) = 0; (tree) < GPU_BUDDY_MAX_FREE_TREES; (tree)++)
++
++struct gpu_buddy_block {
++#define GPU_BUDDY_HEADER_OFFSET GENMASK_ULL(63, 12)
++#define GPU_BUDDY_HEADER_STATE GENMASK_ULL(11, 10)
++#define GPU_BUDDY_ALLOCATED (1 << 10)
++#define GPU_BUDDY_FREE (2 << 10)
++#define GPU_BUDDY_SPLIT (3 << 10)
++#define GPU_BUDDY_HEADER_CLEAR GENMASK_ULL(9, 9)
++/* Free to be used, if needed in the future */
++#define GPU_BUDDY_HEADER_UNUSED GENMASK_ULL(8, 6)
++#define GPU_BUDDY_HEADER_ORDER GENMASK_ULL(5, 0)
++ u64 header;
++
++ struct gpu_buddy_block *left;
++ struct gpu_buddy_block *right;
++ struct gpu_buddy_block *parent;
++
++ void *private; /* owned by creator */
++
++ /*
++ * While the block is allocated by the user through gpu_buddy_alloc*,
++ * the user has ownership of the link, for example to maintain within
++ * a list, if so desired. As soon as the block is freed with
++ * gpu_buddy_free* ownership is given back to the mm.
++ */
++ union {
++ struct rb_node rb;
++ struct list_head link;
++ };
++
++ struct list_head tmp_link;
++};
++
++/* Order-zero must be at least SZ_4K */
++#define GPU_BUDDY_MAX_ORDER (63 - 12)
++
++/*
++ * Binary Buddy System.
++ *
++ * Locking should be handled by the user, a simple mutex around
++ * gpu_buddy_alloc* and gpu_buddy_free* should suffice.
++ */
++struct gpu_buddy {
++ /* Maintain a free list for each order. */
++ struct rb_root **free_trees;
++
++ /*
++ * Maintain explicit binary tree(s) to track the allocation of the
++ * address space. This gives us a simple way of finding a buddy block
++ * and performing the potentially recursive merge step when freeing a
++ * block. Nodes are either allocated or free, in which case they will
++ * also exist on the respective free list.
++ */
++ struct gpu_buddy_block **roots;
++
++ /*
++ * Anything from here is public, and remains static for the lifetime of
++ * the mm. Everything above is considered do-not-touch.
++ */
++ unsigned int n_roots;
++ unsigned int max_order;
++
++ /* Must be at least SZ_4K */
++ u64 chunk_size;
++ u64 size;
++ u64 avail;
++ u64 clear_avail;
++};
++
++static inline u64
++gpu_buddy_block_offset(const struct gpu_buddy_block *block)
++{
++ return block->header & GPU_BUDDY_HEADER_OFFSET;
++}
++
++static inline unsigned int
++gpu_buddy_block_order(struct gpu_buddy_block *block)
++{
++ return block->header & GPU_BUDDY_HEADER_ORDER;
++}
++
++static inline unsigned int
++gpu_buddy_block_state(struct gpu_buddy_block *block)
++{
++ return block->header & GPU_BUDDY_HEADER_STATE;
++}
++
++static inline bool
++gpu_buddy_block_is_allocated(struct gpu_buddy_block *block)
++{
++ return gpu_buddy_block_state(block) == GPU_BUDDY_ALLOCATED;
++}
++
++static inline bool
++gpu_buddy_block_is_clear(struct gpu_buddy_block *block)
++{
++ return block->header & GPU_BUDDY_HEADER_CLEAR;
++}
++
++static inline bool
++gpu_buddy_block_is_free(struct gpu_buddy_block *block)
++{
++ return gpu_buddy_block_state(block) == GPU_BUDDY_FREE;
++}
++
++static inline bool
++gpu_buddy_block_is_split(struct gpu_buddy_block *block)
++{
++ return gpu_buddy_block_state(block) == GPU_BUDDY_SPLIT;
++}
++
++static inline u64
++gpu_buddy_block_size(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block)
++{
++ return mm->chunk_size << gpu_buddy_block_order(block);
++}
++
++int gpu_buddy_init(struct gpu_buddy *mm, u64 size, u64 chunk_size);
++
++void gpu_buddy_fini(struct gpu_buddy *mm);
++
++struct gpu_buddy_block *
++gpu_get_buddy(struct gpu_buddy_block *block);
++
++int gpu_buddy_alloc_blocks(struct gpu_buddy *mm,
++ u64 start, u64 end, u64 size,
++ u64 min_page_size,
++ struct list_head *blocks,
++ unsigned long flags);
++
++int gpu_buddy_block_trim(struct gpu_buddy *mm,
++ u64 *start,
++ u64 new_size,
++ struct list_head *blocks);
++
++void gpu_buddy_reset_clear(struct gpu_buddy *mm, bool is_clear);
++
++void gpu_buddy_free_block(struct gpu_buddy *mm, struct gpu_buddy_block *block);
++
++void gpu_buddy_free_list(struct gpu_buddy *mm,
++ struct list_head *objects,
++ unsigned int flags);
++
++void gpu_buddy_print(struct gpu_buddy *mm);
++void gpu_buddy_block_print(struct gpu_buddy *mm,
++ struct gpu_buddy_block *block);
++#endif
--- /dev/null
+From stable+bounces-290908-greg=kroah.com@vger.kernel.org Wed Jul 29 23:24:09 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 17:23:51 -0400
+Subject: i3c: mipi-i3c-hci: Fix handling of shared IRQs during early initialization
+To: stable@vger.kernel.org
+Cc: Adrian Hunter <adrian.hunter@intel.com>, Frank Li <Frank.Li@nxp.com>, Alexandre Belloni <alexandre.belloni@bootlin.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729212351.1210709-2-sashal@kernel.org>
+
+From: Adrian Hunter <adrian.hunter@intel.com>
+
+[ Upstream commit c6396b835a5e599c4df656112140f065bb544a24 ]
+
+Shared interrupts may fire unexpectedly, including during periods when the
+controller is not yet fully initialized. Commit b9a15012a1452
+("i3c: mipi-i3c-hci: Add optional Runtime PM support") addressed this issue
+for the runtime-suspended state, but the same problem can also occur before
+the bus is enabled for the first time.
+
+Ensure the IRQ handler ignores interrupts until initialization is complete
+by making consistent use of the existing irq_inactive flag. The flag is
+now set to false immediately before enabling the bus.
+
+To guarantee correct ordering with respect to the IRQ handler, protect
+all transitions of irq_inactive with the same spinlock used inside the
+handler.
+
+Fixes: b8460480f62e1 ("i3c: mipi-i3c-hci: Allow for Multi-Bus Instances")
+Cc: stable@vger.kernel.org
+Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Link: https://patch.msgid.link/20260306072451.11131-14-adrian.hunter@intel.com
+Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/i3c/master/mipi-i3c-hci/core.c | 13 +++++++++++++
+ drivers/i3c/master/mipi-i3c-hci/hci.h | 1 +
+ 2 files changed, 14 insertions(+)
+
+--- a/drivers/i3c/master/mipi-i3c-hci/core.c
++++ b/drivers/i3c/master/mipi-i3c-hci/core.c
+@@ -147,6 +147,8 @@ static int i3c_hci_bus_init(struct i3c_m
+ if (hci->quirks & HCI_QUIRK_RESP_BUF_THLD)
+ amd_set_resp_buf_thld(hci);
+
++ WRITE_ONCE(hci->irq_inactive, false);
++
+ /* Enable bus with Hot-Join disabled */
+ reg_set(HC_CONTROL, HC_CONTROL_BUS_ENABLE | HC_CONTROL_HOT_JOIN_CTRL);
+ dev_dbg(&hci->master.dev, "HC_CONTROL = %#x", reg_read(HC_CONTROL));
+@@ -537,6 +539,15 @@ static irqreturn_t i3c_hci_irq_handler(i
+ irqreturn_t result = IRQ_NONE;
+ u32 val;
+
++ /*
++ * The IRQ can be shared, so the handler may be called when the IRQ is
++ * due to a different device. That could happen before the controller
++ * has been initialized, so exit immediately if IRQs are not expected
++ * for this device.
++ */
++ if (READ_ONCE(hci->irq_inactive))
++ return IRQ_NONE;
++
+ val = reg_read(INTR_STATUS);
+ reg_write(INTR_STATUS, val);
+ dev_dbg(&hci->master.dev, "INTR_STATUS %#x", val);
+@@ -776,6 +787,8 @@ static int i3c_hci_probe(struct platform
+ if (ret)
+ return ret;
+
++ WRITE_ONCE(hci->irq_inactive, true);
++
+ irq = platform_get_irq(pdev, 0);
+ ret = devm_request_irq(&pdev->dev, irq, i3c_hci_irq_handler,
+ IRQF_SHARED, NULL, hci);
+--- a/drivers/i3c/master/mipi-i3c-hci/hci.h
++++ b/drivers/i3c/master/mipi-i3c-hci/hci.h
+@@ -46,6 +46,7 @@ struct i3c_hci {
+ void *io_data;
+ const struct hci_cmd_ops *cmd;
+ spinlock_t lock;
++ bool irq_inactive;
+ struct mutex control_mutex;
+ atomic_t next_cmd_tid;
+ u32 caps;
--- /dev/null
+From stable+bounces-290907-greg=kroah.com@vger.kernel.org Wed Jul 29 23:24:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 17:23:50 -0400
+Subject: i3c: mipi-i3c-hci: Fix Hot-Join NACK
+To: stable@vger.kernel.org
+Cc: Adrian Hunter <adrian.hunter@intel.com>, Frank Li <Frank.Li@nxp.com>, Alexandre Belloni <alexandre.belloni@bootlin.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729212351.1210709-1-sashal@kernel.org>
+
+From: Adrian Hunter <adrian.hunter@intel.com>
+
+[ Upstream commit fa9586bd77ada1e3861c7bef65f6bb9dcf8d9481 ]
+
+The MIPI I3C HCI host controller driver does not implement Hot-Join
+handling, yet Hot-Join response control defaults to allowing devices to
+Hot-Join the bus. Configure HC_CONTROL_HOT_JOIN_CTRL to NACK all Hot-Join
+attempts.
+
+Fixes: 9ad9a52cce282 ("i3c/master: introduce the mipi-i3c-hci driver")
+Cc: stable@vger.kernel.org
+Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Link: https://patch.msgid.link/20260306072451.11131-3-adrian.hunter@intel.com
+Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
+Stable-dep-of: c6396b835a5e ("i3c: mipi-i3c-hci: Fix handling of shared IRQs during early initialization")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/i3c/master/mipi-i3c-hci/core.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/i3c/master/mipi-i3c-hci/core.c
++++ b/drivers/i3c/master/mipi-i3c-hci/core.c
+@@ -147,7 +147,8 @@ static int i3c_hci_bus_init(struct i3c_m
+ if (hci->quirks & HCI_QUIRK_RESP_BUF_THLD)
+ amd_set_resp_buf_thld(hci);
+
+- reg_set(HC_CONTROL, HC_CONTROL_BUS_ENABLE);
++ /* Enable bus with Hot-Join disabled */
++ reg_set(HC_CONTROL, HC_CONTROL_BUS_ENABLE | HC_CONTROL_HOT_JOIN_CTRL);
+ dev_dbg(&hci->master.dev, "HC_CONTROL = %#x", reg_read(HC_CONTROL));
+
+ return 0;
--- /dev/null
+From stable+bounces-289085-greg=kroah.com@vger.kernel.org Sat Jul 25 12:54:43 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 06:54:32 -0400
+Subject: kho: add interfaces to unpreserve folios, page ranges, and vmalloc
+To: stable@vger.kernel.org
+Cc: Pasha Tatashin <pasha.tatashin@soleen.com>, Pratyush Yadav <pratyush@kernel.org>, "Mike Rapoport (Microsoft)" <rppt@kernel.org>, Alexander Graf <graf@amazon.com>, Changyuan Lyu <changyuanl@google.com>, Christian Brauner <brauner@kernel.org>, Jason Gunthorpe <jgg@nvidia.com>, Jason Gunthorpe <jgg@ziepe.ca>, Jonathan Corbet <corbet@lwn.net>, Masahiro Yamada <masahiroy@kernel.org>, Miguel Ojeda <ojeda@kernel.org>, Randy Dunlap <rdunlap@infradead.org>, Simon Horman <horms@kernel.org>, Tejun Heo <tj@kernel.org>, Zhu Yanjun <yanjun.zhu@linux.dev>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725105433.1853930-1-sashal@kernel.org>
+
+From: Pasha Tatashin <pasha.tatashin@soleen.com>
+
+[ Upstream commit 36f8f7ef7fd2f238922e9d217e86c69838319d8c ]
+
+Allow users of KHO to cancel the previous preservation by adding the
+necessary interfaces to unpreserve folio, pages, and vmallocs.
+
+Link: https://lkml.kernel.org/r/20251101142325.1326536-4-pasha.tatashin@soleen.com
+Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
+Reviewed-by: Pratyush Yadav <pratyush@kernel.org>
+Reviewed-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
+Cc: Alexander Graf <graf@amazon.com>
+Cc: Changyuan Lyu <changyuanl@google.com>
+Cc: Christian Brauner <brauner@kernel.org>
+Cc: Jason Gunthorpe <jgg@nvidia.com>
+Cc: Jason Gunthorpe <jgg@ziepe.ca>
+Cc: Jonathan Corbet <corbet@lwn.net>
+Cc: Masahiro Yamada <masahiroy@kernel.org>
+Cc: Miguel Ojeda <ojeda@kernel.org>
+Cc: Randy Dunlap <rdunlap@infradead.org>
+Cc: Simon Horman <horms@kernel.org>
+Cc: Tejun Heo <tj@kernel.org>
+Cc: Zhu Yanjun <yanjun.zhu@linux.dev>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Stable-dep-of: 0e39380a7316 ("kho: make sure scratch size is always aligned by CMA_MIN_ALIGNMENT_BYTES")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/kexec_handover.h | 18 +++++++
+ kernel/kexec_handover.c | 104 +++++++++++++++++++++++++++++++++++------
+ 2 files changed, 109 insertions(+), 13 deletions(-)
+
+--- a/include/linux/kexec_handover.h
++++ b/include/linux/kexec_handover.h
+@@ -52,8 +52,11 @@ bool kho_is_enabled(void);
+ bool is_kho_boot(void);
+
+ int kho_preserve_folio(struct folio *folio);
++int kho_unpreserve_folio(struct folio *folio);
+ int kho_preserve_pages(struct page *page, unsigned int nr_pages);
++int kho_unpreserve_pages(struct page *page, unsigned int nr_pages);
+ int kho_preserve_vmalloc(void *ptr, struct kho_vmalloc *preservation);
++int kho_unpreserve_vmalloc(struct kho_vmalloc *preservation);
+ struct folio *kho_restore_folio(phys_addr_t phys);
+ struct page *kho_restore_pages(phys_addr_t phys, unsigned int nr_pages);
+ void *kho_restore_vmalloc(const struct kho_vmalloc *preservation);
+@@ -83,16 +86,31 @@ static inline int kho_preserve_folio(str
+ return -EOPNOTSUPP;
+ }
+
++static inline int kho_unpreserve_folio(struct folio *folio)
++{
++ return -EOPNOTSUPP;
++}
++
+ static inline int kho_preserve_pages(struct page *page, unsigned int nr_pages)
+ {
+ return -EOPNOTSUPP;
+ }
+
++static inline int kho_unpreserve_pages(struct page *page, unsigned int nr_pages)
++{
++ return -EOPNOTSUPP;
++}
++
+ static inline int kho_preserve_vmalloc(void *ptr,
+ struct kho_vmalloc *preservation)
+ {
+ return -EOPNOTSUPP;
+ }
++
++static inline int kho_unpreserve_vmalloc(struct kho_vmalloc *preservation)
++{
++ return -EOPNOTSUPP;
++}
+
+ static inline struct folio *kho_restore_folio(phys_addr_t phys)
+ {
+--- a/kernel/kexec_handover.c
++++ b/kernel/kexec_handover.c
+@@ -153,26 +153,33 @@ static void *xa_load_or_alloc(struct xar
+ return no_free_ptr(elm);
+ }
+
+-static void __kho_unpreserve(struct kho_mem_track *track, unsigned long pfn,
+- unsigned long end_pfn)
++static void __kho_unpreserve_order(struct kho_mem_track *track, unsigned long pfn,
++ unsigned int order)
+ {
+ struct kho_mem_phys_bits *bits;
+ struct kho_mem_phys *physxa;
++ const unsigned long pfn_high = pfn >> order;
+
+- while (pfn < end_pfn) {
+- const unsigned int order =
+- min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
+- const unsigned long pfn_high = pfn >> order;
++ physxa = xa_load(&track->orders, order);
++ if (WARN_ON_ONCE(!physxa))
++ return;
+
+- physxa = xa_load(&track->orders, order);
+- if (WARN_ON_ONCE(!physxa))
+- return;
++ bits = xa_load(&physxa->phys_bits, pfn_high / PRESERVE_BITS);
++ if (WARN_ON_ONCE(!bits))
++ return;
++
++ clear_bit(pfn_high % PRESERVE_BITS, bits->preserve);
++}
++
++static void __kho_unpreserve(struct kho_mem_track *track, unsigned long pfn,
++ unsigned long end_pfn)
++{
++ unsigned int order;
+
+- bits = xa_load(&physxa->phys_bits, pfn_high / PRESERVE_BITS);
+- if (WARN_ON_ONCE(!bits))
+- return;
++ while (pfn < end_pfn) {
++ order = min(count_trailing_zeros(pfn), ilog2(end_pfn - pfn));
+
+- clear_bit(pfn_high % PRESERVE_BITS, bits->preserve);
++ __kho_unpreserve_order(track, pfn, order);
+
+ pfn += 1 << order;
+ }
+@@ -740,6 +747,30 @@ int kho_preserve_folio(struct folio *fol
+ EXPORT_SYMBOL_GPL(kho_preserve_folio);
+
+ /**
++ * kho_unpreserve_folio - unpreserve a folio.
++ * @folio: folio to unpreserve.
++ *
++ * Instructs KHO to unpreserve a folio that was preserved by
++ * kho_preserve_folio() before. The provided @folio (pfn and order)
++ * must exactly match a previously preserved folio.
++ *
++ * Return: 0 on success, error code on failure
++ */
++int kho_unpreserve_folio(struct folio *folio)
++{
++ const unsigned long pfn = folio_pfn(folio);
++ const unsigned int order = folio_order(folio);
++ struct kho_mem_track *track = &kho_out.track;
++
++ if (kho_out.finalized)
++ return -EBUSY;
++
++ __kho_unpreserve_order(track, pfn, order);
++ return 0;
++}
++EXPORT_SYMBOL_GPL(kho_unpreserve_folio);
++
++/**
+ * kho_preserve_pages - preserve contiguous pages across kexec
+ * @page: first page in the list.
+ * @nr_pages: number of pages.
+@@ -783,6 +814,33 @@ int kho_preserve_pages(struct page *page
+ }
+ EXPORT_SYMBOL_GPL(kho_preserve_pages);
+
++/**
++ * kho_unpreserve_pages - unpreserve contiguous pages.
++ * @page: first page in the list.
++ * @nr_pages: number of pages.
++ *
++ * Instructs KHO to unpreserve @nr_pages contiguous pages starting from @page.
++ * This must be called with the same @page and @nr_pages as the corresponding
++ * kho_preserve_pages() call. Unpreserving arbitrary sub-ranges of larger
++ * preserved blocks is not supported.
++ *
++ * Return: 0 on success, error code on failure
++ */
++int kho_unpreserve_pages(struct page *page, unsigned int nr_pages)
++{
++ struct kho_mem_track *track = &kho_out.track;
++ const unsigned long start_pfn = page_to_pfn(page);
++ const unsigned long end_pfn = start_pfn + nr_pages;
++
++ if (kho_out.finalized)
++ return -EBUSY;
++
++ __kho_unpreserve(track, start_pfn, end_pfn);
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(kho_unpreserve_pages);
++
+ struct kho_vmalloc_hdr {
+ DECLARE_KHOSER_PTR(next, struct kho_vmalloc_chunk *);
+ };
+@@ -945,6 +1003,26 @@ err_free:
+ EXPORT_SYMBOL_GPL(kho_preserve_vmalloc);
+
+ /**
++ * kho_unpreserve_vmalloc - unpreserve memory allocated with vmalloc()
++ * @preservation: preservation metadata returned by kho_preserve_vmalloc()
++ *
++ * Instructs KHO to unpreserve the area in vmalloc address space that was
++ * previously preserved with kho_preserve_vmalloc().
++ *
++ * Return: 0 on success, error code on failure
++ */
++int kho_unpreserve_vmalloc(struct kho_vmalloc *preservation)
++{
++ if (kho_out.finalized)
++ return -EBUSY;
++
++ kho_vmalloc_free_chunks(preservation);
++
++ return 0;
++}
++EXPORT_SYMBOL_GPL(kho_unpreserve_vmalloc);
++
++/**
+ * kho_restore_vmalloc - recreates and populates an area in vmalloc address
+ * space from the preserved memory.
+ * @preservation: preservation metadata.
--- /dev/null
+From stable+bounces-289086-greg=kroah.com@vger.kernel.org Sat Jul 25 12:54:48 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 06:54:33 -0400
+Subject: kho: make sure scratch size is always aligned by CMA_MIN_ALIGNMENT_BYTES
+To: stable@vger.kernel.org
+Cc: "Pratyush Yadav (Google)" <pratyush@kernel.org>, stable@kernel.org, Pasha Tatashin <pasha.tatashin@soleen.com>, "Mike Rapoport (Microsoft)" <rppt@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725105433.1853930-2-sashal@kernel.org>
+
+From: "Pratyush Yadav (Google)" <pratyush@kernel.org>
+
+[ Upstream commit 0e39380a7316122e1b00012b3f3cd3e318b3e7d3 ]
+
+When using scratch_scale, the scratch sizes are rounded up to
+CMA_MIN_ALIGNMENT_BYTES since they will be released as MIGRATE_CMA. This
+is not done when using fixed scratch sizes via command line. This can
+result in user specifying a size which is not aligned, and thus kernel
+releasing a pageblock that is only partially scratch.
+
+Do the rounding up for both cases in scratch_size_update().
+
+Fixes: 3dc92c311498 ("kexec: add Kexec HandOver (KHO) generation helpers")
+Cc: stable@kernel.org
+Signed-off-by: Pratyush Yadav (Google) <pratyush@kernel.org>
+Link: https://patch.msgid.link/20260519160554.2713361-1-pratyush@kernel.org
+Signed-off-by: Pasha Tatashin <pasha.tatashin@soleen.com>
+Signed-off-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ kernel/kexec_handover.c | 32 +++++++++++++++++++++-----------
+ 1 file changed, 21 insertions(+), 11 deletions(-)
+
+--- a/kernel/kexec_handover.c
++++ b/kernel/kexec_handover.c
+@@ -569,20 +569,30 @@ early_param("kho_scratch", kho_parse_scr
+
+ static void __init scratch_size_update(void)
+ {
+- phys_addr_t size;
++ /*
++ * If fixed sizes are not provided via command line, calculate them
++ * now.
++ */
++ if (scratch_scale) {
++ phys_addr_t size;
+
+- if (!scratch_scale)
+- return;
++ size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT,
++ NUMA_NO_NODE);
++ size = size * scratch_scale / 100;
++ scratch_size_lowmem = size;
+
+- size = memblock_reserved_kern_size(ARCH_LOW_ADDRESS_LIMIT,
+- NUMA_NO_NODE);
+- size = size * scratch_scale / 100;
+- scratch_size_lowmem = round_up(size, CMA_MIN_ALIGNMENT_BYTES);
++ size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
++ NUMA_NO_NODE);
++ size = size * scratch_scale / 100 - scratch_size_lowmem;
++ scratch_size_global = size;
++ }
+
+- size = memblock_reserved_kern_size(MEMBLOCK_ALLOC_ANYWHERE,
+- NUMA_NO_NODE);
+- size = size * scratch_scale / 100 - scratch_size_lowmem;
+- scratch_size_global = round_up(size, CMA_MIN_ALIGNMENT_BYTES);
++ /*
++ * Scratch areas are released as MIGRATE_CMA. Round them up to the right
++ * size.
++ */
++ scratch_size_lowmem = round_up(scratch_size_lowmem, CMA_MIN_ALIGNMENT_BYTES);
++ scratch_size_global = round_up(scratch_size_global, CMA_MIN_ALIGNMENT_BYTES);
+ }
+
+ static phys_addr_t __init scratch_size_node(int nid)
--- /dev/null
+From stable+bounces-282936-greg=kroah.com@vger.kernel.org Tue Jul 21 21:57:23 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 15:36:01 -0400
+Subject: landlock: Account all audit data allocations to user space
+To: stable@vger.kernel.org
+Cc: "Mickaël Salaün" <mic@digikod.net>, "Günther Noack" <gnoack@google.com>, "Paul Moore" <paul@paul-moore.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260721193601.410261-2-sashal@kernel.org>
+
+From: Mickaël Salaün <mic@digikod.net>
+
+[ Upstream commit b232bd12789fa57405b5092f28788be97aae9999 ]
+
+Mark the kzalloc_flex() of struct landlock_details with
+GFP_KERNEL_ACCOUNT so the allocation is charged to the calling task,
+like the other Landlock per-domain allocations which have used
+GFP_KERNEL_ACCOUNT forever.
+
+Every property of landlock_details is caller-attributable: allocated by
+landlock_restrict_self(2), owned by the caller's landlock_hierarchy,
+contents are the caller's pid, uid, comm, and exe_path, lifetime bounded
+by the caller's domain. While the caller may not know nor control the
+size of this allocation (i.e. exe_path), this data should still be
+accounted for it.
+
+The deciding factor is whether userspace can trigger the allocation, not
+whether the size of the data is known nor controlled by the caller.
+This aligns with the kmemcg accounting policy established by commit
+5d097056c9a0 ("kmemcg: account certain kmem allocations to memcg").
+
+No new failure modes: the hierarchy and ruleset are allocated before
+details and are already accounted, so landlock_restrict_self(2) already
+returns -ENOMEM under memcg pressure. This change widens that existing
+failure window slightly; it does not introduce a new error code.
+
+Cc: Günther Noack <gnoack@google.com>
+Cc: Paul Moore <paul@paul-moore.com>
+Cc: stable@vger.kernel.org
+Fixes: 1d636984e088 ("landlock: Add AUDIT_LANDLOCK_DOMAIN and log domain status")
+Link: https://patch.msgid.link/20260513180309.165840-1-mic@digikod.net
+Signed-off-by: Mickaël Salaün <mic@digikod.net>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ security/landlock/domain.c | 9 +++++----
+ security/landlock/domain.h | 5 +----
+ 2 files changed, 6 insertions(+), 8 deletions(-)
+
+--- a/security/landlock/domain.c
++++ b/security/landlock/domain.c
+@@ -90,11 +90,12 @@ static struct landlock_details *get_curr
+ return ERR_CAST(buffer);
+
+ /*
+- * Create the new details according to the path's length. Do not
+- * allocate with GFP_KERNEL_ACCOUNT because it is independent from the
+- * caller.
++ * Create the new details according to the path's length. Account to
++ * the calling task's memcg, like the other Landlock per-domain
++ * allocations, even if it may not control the related size.
+ */
+- details = kzalloc_flex(*details, exe_path, path_size);
++ details =
++ kzalloc_flex(*details, exe_path, path_size, GFP_KERNEL_ACCOUNT);
+ if (!details)
+ return ERR_PTR(-ENOMEM);
+
+--- a/security/landlock/domain.h
++++ b/security/landlock/domain.h
+@@ -33,10 +33,7 @@ enum landlock_log_status {
+ * Rarely accessed, mainly when logging the first domain's denial.
+ *
+ * The contained pointers are initialized at the domain creation time and never
+- * changed again. Contrary to most other Landlock object types, this one is
+- * not allocated with GFP_KERNEL_ACCOUNT because its size may not be under the
+- * caller's control (e.g. unknown exe_path) and the data is not explicitly
+- * requested nor used by tasks.
++ * changed again.
+ */
+ struct landlock_details {
+ /**
--- /dev/null
+From stable+bounces-282935-greg=kroah.com@vger.kernel.org Tue Jul 21 21:45:34 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 21 Jul 2026 15:36:00 -0400
+Subject: landlock: Fix formatting
+To: stable@vger.kernel.org
+Cc: "Mickaël Salaün" <mic@digikod.net>, "Günther Noack" <gnoack@google.com>, "Kees Cook" <kees@kernel.org>, "Günther Noack" <gnoack3000@gmail.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260721193601.410261-1-sashal@kernel.org>
+
+From: Mickaël Salaün <mic@digikod.net>
+
+[ Upstream commit 405ca72dc589dd746e5ee5378bb9d9ee7f844010 ]
+
+Auto-format with clang-format -i security/landlock/*.[ch]
+
+Cc: Günther Noack <gnoack@google.com>
+Cc: Kees Cook <kees@kernel.org>
+Fixes: 69050f8d6d07 ("treewide: Replace kmalloc with kmalloc_obj for non-scalar types")
+Reviewed-by: Günther Noack <gnoack3000@gmail.com>
+Link: https://lore.kernel.org/r/20260303173632.88040-1-mic@digikod.net
+Signed-off-by: Mickaël Salaün <mic@digikod.net>
+Stable-dep-of: b232bd12789f ("landlock: Account all audit data allocations to user space")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ security/landlock/domain.c | 3 +--
+ security/landlock/ruleset.c | 7 +++----
+ 2 files changed, 4 insertions(+), 6 deletions(-)
+
+--- a/security/landlock/domain.c
++++ b/security/landlock/domain.c
+@@ -94,8 +94,7 @@ static struct landlock_details *get_curr
+ * allocate with GFP_KERNEL_ACCOUNT because it is independent from the
+ * caller.
+ */
+- details =
+- kzalloc(struct_size(details, exe_path, path_size), GFP_KERNEL);
++ details = kzalloc_flex(*details, exe_path, path_size);
+ if (!details)
+ return ERR_PTR(-ENOMEM);
+
+--- a/security/landlock/ruleset.c
++++ b/security/landlock/ruleset.c
+@@ -33,9 +33,8 @@ static struct landlock_ruleset *create_r
+ {
+ struct landlock_ruleset *new_ruleset;
+
+- new_ruleset =
+- kzalloc(struct_size(new_ruleset, access_masks, num_layers),
+- GFP_KERNEL_ACCOUNT);
++ new_ruleset = kzalloc_flex(*new_ruleset, access_masks, num_layers,
++ GFP_KERNEL_ACCOUNT);
+ if (!new_ruleset)
+ return ERR_PTR(-ENOMEM);
+ refcount_set(&new_ruleset->usage, 1);
+@@ -553,7 +552,7 @@ landlock_merge_ruleset(struct landlock_r
+ return new_dom;
+
+ new_dom->hierarchy =
+- kzalloc(sizeof(*new_dom->hierarchy), GFP_KERNEL_ACCOUNT);
++ kzalloc_obj(*new_dom->hierarchy, GFP_KERNEL_ACCOUNT);
+ if (!new_dom->hierarchy)
+ return ERR_PTR(-ENOMEM);
+
--- /dev/null
+From stable+bounces-288838-greg=kroah.com@vger.kernel.org Fri Jul 24 12:59:27 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Fri, 24 Jul 2026 06:49:37 -0400
+Subject: mm/sparse-vmemmap: fix vmemmap accounting underflow
+To: stable@vger.kernel.org
+Cc: Muchun Song <songmuchun@bytedance.com>, "Mike Rapoport (Microsoft)" <rppt@kernel.org>, Oscar Salvador <osalvador@suse.de>, "David Hildenbrand (Arm)" <david@kernel.org>, "Liam R. Howlett" <liam@infradead.org>, "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>, Joao Martins <joao.m.martins@oracle.com>, Lorenzo Stoakes <ljs@kernel.org>, Madhavan Srinivasan <maddy@linux.ibm.com>, Michael Ellerman <mpe@ellerman.id.au>, Michal Hocko <mhocko@suse.com>, Nicholas Piggin <npiggin@gmail.com>, Suren Baghdasaryan <surenb@google.com>, Vlastimil Babka <vbabka@kernel.org>, Andrew Morton <akpm@linux-foundation.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260724104937.3975387-1-sashal@kernel.org>
+
+From: Muchun Song <songmuchun@bytedance.com>
+
+[ Upstream commit c373f7f98e6ad591c85d40548cf8b6443be69311 ]
+
+Patch series "mm: Fix vmemmap optimization accounting and initialization",
+v8.
+
+The series fixes several bugs in vmemmap optimization, mainly around
+incorrect page accounting and memmap initialization in DAX and memory
+hotplug paths. It also fixes pageblock migratetype initialization and
+struct page initialization for ZONE_DEVICE compound pages.
+
+Patches 1-4 fix vmemmap accounting issues. Patch 1 fixes an accounting
+underflow in the section activation failure path by moving vmemmap page
+accounting into the lower-level allocation and freeing helpers. Patch 2
+fixes incorrect altmap passing in the memory hotplug error path. Patch 3
+passes pgmap through memory deactivation paths so the teardown side can
+determine whether vmemmap optimization was in effect. Patch 4 uses that
+information to account the optimized DAX vmemmap size correctly.
+
+Patches 5-6 fix initialization issues in mm/mm_init. One makes sure all
+pageblocks in ZONE_DEVICE compound pages get their migratetype
+initialized. The other fixes a case where DAX memory hotplug reuses an
+unoptimized early-section memmap while compound_nr_pages() still assumes
+vmemmap optimization, leaving tail struct pages uninitialized.
+
+This patch (of 6):
+
+In section_activate(), if populate_section_memmap() fails, the error
+handling path calls section_deactivate() to roll back the state. This
+causes a vmemmap accounting imbalance.
+
+Since commit c3576889d87b ("mm: fix accounting of memmap pages"), memmap
+pages are accounted for only after populate_section_memmap() succeeds.
+However, the failure path unconditionally calls section_deactivate(),
+which decreases the vmemmap count. Consequently, a failure in
+populate_section_memmap() leads to an accounting underflow, incorrectly
+reducing the system's tracked vmemmap usage.
+
+Fix this more thoroughly by moving all accounting calls into the lower
+level functions that actually perform the vmemmap allocation and freeing:
+
+ - populate_section_memmap() accounts for newly allocated vmemmap pages -
+depopulate_section_memmap() unaccounts when vmemmap is freed
+
+This ensures proper accounting in all code paths, including error handling
+and early section cases.
+
+Link: https://lore.kernel.org/20260428081855.1249045-1-songmuchun@bytedance.com
+Link: https://lore.kernel.org/20260428081855.1249045-2-songmuchun@bytedance.com
+Fixes: c3576889d87b ("mm: fix accounting of memmap pages")
+Signed-off-by: Muchun Song <songmuchun@bytedance.com>
+Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
+Acked-by: Oscar Salvador <osalvador@suse.de>
+Acked-by: David Hildenbrand (Arm) <david@kernel.org>
+Acked-by: Liam R. Howlett <liam@infradead.org>
+Cc: "Aneesh Kumar K.V" <aneesh.kumar@linux.ibm.com>
+Cc: Joao Martins <joao.m.martins@oracle.com>
+Cc: Lorenzo Stoakes <ljs@kernel.org>
+Cc: Madhavan Srinivasan <maddy@linux.ibm.com>
+Cc: Michael Ellerman <mpe@ellerman.id.au>
+Cc: Michal Hocko <mhocko@suse.com>
+Cc: Nicholas Piggin <npiggin@gmail.com>
+Cc: Suren Baghdasaryan <surenb@google.com>
+Cc: Vlastimil Babka <vbabka@kernel.org>
+Cc: <stable@vger.kernel.org>
+Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ mm/sparse.c | 32 ++++++++++++++++++++++----------
+ 1 file changed, 22 insertions(+), 10 deletions(-)
+
+--- a/mm/sparse.c
++++ b/mm/sparse.c
+@@ -670,7 +670,12 @@ static struct page * __meminit populate_
+ unsigned long nr_pages, int nid, struct vmem_altmap *altmap,
+ struct dev_pagemap *pgmap)
+ {
+- return __populate_section_memmap(pfn, nr_pages, nid, altmap, pgmap);
++ struct page *page = __populate_section_memmap(pfn, nr_pages, nid, altmap,
++ pgmap);
++
++ memmap_pages_add(DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE));
++
++ return page;
+ }
+
+ static void depopulate_section_memmap(unsigned long pfn, unsigned long nr_pages,
+@@ -679,13 +684,17 @@ static void depopulate_section_memmap(un
+ unsigned long start = (unsigned long) pfn_to_page(pfn);
+ unsigned long end = start + nr_pages * sizeof(struct page);
+
++ memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)));
+ vmemmap_free(start, end, altmap);
+ }
++
+ static void free_map_bootmem(struct page *memmap)
+ {
+ unsigned long start = (unsigned long)memmap;
+ unsigned long end = (unsigned long)(memmap + PAGES_PER_SECTION);
+
++ memmap_boot_pages_add(-1L * (DIV_ROUND_UP(PAGES_PER_SECTION * sizeof(struct page),
++ PAGE_SIZE)));
+ vmemmap_free(start, end, NULL);
+ }
+
+@@ -742,13 +751,18 @@ static struct page * __meminit populate_
+ unsigned long nr_pages, int nid, struct vmem_altmap *altmap,
+ struct dev_pagemap *pgmap)
+ {
+- return kvmalloc_node(array_size(sizeof(struct page),
+- PAGES_PER_SECTION), GFP_KERNEL, nid);
++ struct page *page = kvmalloc_node(array_size(sizeof(struct page),
++ PAGES_PER_SECTION), GFP_KERNEL, nid);
++
++ memmap_pages_add(DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE));
++
++ return page;
+ }
+
+ static void depopulate_section_memmap(unsigned long pfn, unsigned long nr_pages,
+ struct vmem_altmap *altmap)
+ {
++ memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)));
+ kvfree(pfn_to_page(pfn));
+ }
+
+@@ -761,6 +775,9 @@ static void free_map_bootmem(struct page
+ nr_pages = PAGE_ALIGN(PAGES_PER_SECTION * sizeof(struct page))
+ >> PAGE_SHIFT;
+
++ memmap_boot_pages_add(-1L * (DIV_ROUND_UP(PAGES_PER_SECTION * sizeof(struct page),
++ PAGE_SIZE)));
++
+ for (i = 0; i < nr_pages; i++, page++) {
+ type = bootmem_type(page);
+
+@@ -854,14 +871,10 @@ static void section_deactivate(unsigned
+ * The memmap of early sections is always fully populated. See
+ * section_activate() and pfn_valid() .
+ */
+- if (!section_is_early) {
+- memmap_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE)));
++ if (!section_is_early)
+ depopulate_section_memmap(pfn, nr_pages, altmap);
+- } else if (memmap) {
+- memmap_boot_pages_add(-1L * (DIV_ROUND_UP(nr_pages * sizeof(struct page),
+- PAGE_SIZE)));
++ else if (memmap)
+ free_map_bootmem(memmap);
+- }
+
+ if (empty)
+ ms->section_mem_map = (unsigned long)NULL;
+@@ -906,7 +919,6 @@ static struct page * __meminit section_a
+ section_deactivate(pfn, nr_pages, altmap);
+ return ERR_PTR(-ENOMEM);
+ }
+- memmap_pages_add(DIV_ROUND_UP(nr_pages * sizeof(struct page), PAGE_SIZE));
+
+ return memmap;
+ }
--- /dev/null
+From stable+bounces-289714-greg=kroah.com@vger.kernel.org Tue Jul 28 00:24:26 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 18:20:45 -0400
+Subject: mmc: vub300: fix use-after-free on probe failure
+To: stable@vger.kernel.org
+Cc: Guangshuo Li <lgs201920130244@gmail.com>, Johan Hovold <johan@kernel.org>, Ulf Hansson <ulfh@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727222045.2118748-2-sashal@kernel.org>
+
+From: Guangshuo Li <lgs201920130244@gmail.com>
+
+[ Upstream commit a3b5f242997a3be7404112fd48784881560aea57 ]
+
+The vub300 driver lifetime-manages its controller state using
+vub300->kref, with vub300_delete() freeing the mmc host when the last
+reference is dropped. The probe error path after the inactivity timer has
+been armed still bypasses that lifetime rule, however, and falls through
+to mmc_free_host() directly if mmc_add_host() fails.
+
+The race window is between arming the inactivity timer and reaching the
+probe error unwind after mmc_add_host() fails:
+
+ probe thread timer/workqueue
+ ------------ ---------------
+ kref_init(&vub300->kref) ref = 1
+ kref_get(&vub300->kref) ref = 2, timer ref
+ add_timer(inactivity_timer) fires after one second
+ |
+ | race window
+ |<---------------------------------------------------->
+ |
+ mmc_add_host(mmc)
+ inactivity timer fires
+ vub300_queue_dead_work()
+ kref_get() ref = 3
+ queue_work(deadwork)
+ mmc_add_host() fails
+ timer_delete_sync()
+ mmc_free_host(mmc)
+ frees vub300
+ deadwork runs
+ use-after-free
+
+The inactivity timeout is one second, so this would require
+mmc_add_host() to both fail and take more than one second to do so. This
+is unlikely to happen in practice, but the error path is still wrong.
+
+timer_delete_sync() only waits for the timer callback itself. It does
+not flush deadwork that the callback may already have queued. As a
+result, queued deadwork can still hold a kref while the probe error path
+directly frees the backing mmc host, including the vub300 storage.
+
+Fix this by using the same lifetime mechanism as disconnect. Clear
+vub300->interface so that the timer callback and any queued deadwork
+return early and drop their references, then drop the initial probe
+reference and return without falling through to err_free_host.
+
+Fixes: 0613ad2401f8 ("mmc: vub300: fix return value check of mmc_add_host()")
+Signed-off-by: Guangshuo Li <lgs201920130244@gmail.com>
+Reviewed-by: Johan Hovold <johan@kernel.org>
+Cc: stable@vger.kernel.org
+Signed-off-by: Ulf Hansson <ulfh@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/mmc/host/vub300.c | 10 +++++++---
+ 1 file changed, 7 insertions(+), 3 deletions(-)
+
+--- a/drivers/mmc/host/vub300.c
++++ b/drivers/mmc/host/vub300.c
+@@ -2344,12 +2344,16 @@ static int vub300_probe(struct usb_inter
+ interface_to_InterfaceNumber(interface));
+ retval = mmc_add_host(mmc);
+ if (retval)
+- goto err_delete_timer;
++ goto err_stop_io;
+
+ return 0;
+
+-err_delete_timer:
+- timer_delete_sync(&vub300->inactivity_timer);
++err_stop_io:
++ vub300->interface = NULL;
++ kref_put(&vub300->kref, vub300_delete);
++
++ return retval;
++
+ err_free_host:
+ mmc_free_host(mmc);
+ /*
--- /dev/null
+From stable+bounces-289713-greg=kroah.com@vger.kernel.org Tue Jul 28 00:24:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 18:20:44 -0400
+Subject: mmc: vub300: rename probe error labels
+To: stable@vger.kernel.org
+Cc: Johan Hovold <johan@kernel.org>, Ulf Hansson <ulf.hansson@linaro.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727222045.2118748-1-sashal@kernel.org>
+
+From: Johan Hovold <johan@kernel.org>
+
+[ Upstream commit 5b8b35d6f4fa758dd5e8ae18526ea1c73f6787e0 ]
+
+Error labels should be named after what they do.
+
+Rename the probe error labels.
+
+Signed-off-by: Johan Hovold <johan@kernel.org>
+Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
+Stable-dep-of: a3b5f242997a ("mmc: vub300: fix use-after-free on probe failure")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/mmc/host/vub300.c | 18 ++++++++++--------
+ 1 file changed, 10 insertions(+), 8 deletions(-)
+
+--- a/drivers/mmc/host/vub300.c
++++ b/drivers/mmc/host/vub300.c
+@@ -2115,19 +2115,19 @@ static int vub300_probe(struct usb_inter
+ command_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!command_out_urb) {
+ retval = -ENOMEM;
+- goto error0;
++ goto err_put_udev;
+ }
+ command_res_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!command_res_urb) {
+ retval = -ENOMEM;
+- goto error1;
++ goto err_free_out_urb;
+ }
+ /* this also allocates memory for our VUB300 mmc host device */
+ mmc = mmc_alloc_host(sizeof(*vub300), &udev->dev);
+ if (!mmc) {
+ retval = -ENOMEM;
+ dev_err(&udev->dev, "not enough memory for the mmc_host\n");
+- goto error4;
++ goto err_free_res_urb;
+ }
+ /* MMC core transfer sizes tunable parameters */
+ mmc->caps = 0;
+@@ -2344,10 +2344,11 @@ static int vub300_probe(struct usb_inter
+ interface_to_InterfaceNumber(interface));
+ retval = mmc_add_host(mmc);
+ if (retval)
+- goto error6;
++ goto err_delete_timer;
+
+ return 0;
+-error6:
++
++err_delete_timer:
+ timer_delete_sync(&vub300->inactivity_timer);
+ err_free_host:
+ mmc_free_host(mmc);
+@@ -2355,12 +2356,13 @@ err_free_host:
+ * and hence also frees vub300
+ * which is contained at the end of struct mmc
+ */
+-error4:
++err_free_res_urb:
+ usb_free_urb(command_res_urb);
+-error1:
++err_free_out_urb:
+ usb_free_urb(command_out_urb);
+-error0:
++err_put_udev:
+ usb_put_dev(udev);
++
+ return retval;
+ }
+
--- /dev/null
+From stable+bounces-289087-greg=kroah.com@vger.kernel.org Sat Jul 25 12:54:49 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sat, 25 Jul 2026 06:54:40 -0400
+Subject: mtd: maps: vmu-flash: fix fault in unaligned fixup
+To: stable@vger.kernel.org
+Cc: Florian Fuchs <fuchsfl@gmail.com>, Miquel Raynal <miquel.raynal@bootlin.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260725105440.1854110-1-sashal@kernel.org>
+
+From: Florian Fuchs <fuchsfl@gmail.com>
+
+[ Upstream commit 79d1661502c6e4b6f626185cef72cf2fa78116e1 ]
+
+Use kzalloc_obj() / kzalloc_objs() to allocate the memcard structs,
+instead of kmalloc_obj() / kmalloc_objs() to prevent access to
+uninitialized data.
+
+Fixes runtime error: Fault in unaligned fixup: 0000 [#1] at
+mtd_get_fact_prot_info.
+
+Fixes: 47a72688fae7 ("mtd: flash mapping support for Dreamcast VMU.")
+Cc: stable@vger.kernel.org
+Signed-off-by: Florian Fuchs <fuchsfl@gmail.com>
+Signed-off-by: Miquel Raynal <miquel.raynal@bootlin.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/mtd/maps/vmu-flash.c | 8 +++-----
+ 1 file changed, 3 insertions(+), 5 deletions(-)
+
+--- a/drivers/mtd/maps/vmu-flash.c
++++ b/drivers/mtd/maps/vmu-flash.c
+@@ -610,7 +610,7 @@ static int vmu_connect(struct maple_devi
+
+ basic_flash_data = be32_to_cpu(mdev->devinfo.function_data[c - 1]);
+
+- card = kmalloc(sizeof(struct memcard), GFP_KERNEL);
++ card = kzalloc_obj(struct memcard);
+ if (!card) {
+ error = -ENOMEM;
+ goto fail_nomem;
+@@ -628,15 +628,13 @@ static int vmu_connect(struct maple_devi
+ * Not sure there are actually any multi-partition devices in the
+ * real world, but the hardware supports them, so, so will we
+ */
+- card->parts = kmalloc_array(card->partitions, sizeof(struct vmupart),
+- GFP_KERNEL);
++ card->parts = kzalloc_objs(struct vmupart, card->partitions);
+ if (!card->parts) {
+ error = -ENOMEM;
+ goto fail_partitions;
+ }
+
+- card->mtd = kmalloc_array(card->partitions, sizeof(struct mtd_info),
+- GFP_KERNEL);
++ card->mtd = kzalloc_objs(struct mtd_info, card->partitions);
+ if (!card->mtd) {
+ error = -ENOMEM;
+ goto fail_mtd_info;
--- /dev/null
+From stable+bounces-290230-greg=kroah.com@vger.kernel.org Wed Jul 29 04:32:43 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 22:30:25 -0400
+Subject: net: ipa: fix SMEM state handle leaks in SMP2P init
+To: stable@vger.kernel.org
+Cc: Haoxiang Li <haoxiang_li2024@163.com>, Larysa Zaremba <larysa.zaremba@intel.com>, Alex Elder <elder@riscstar.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729023025.1114440-1-sashal@kernel.org>
+
+From: Haoxiang Li <haoxiang_li2024@163.com>
+
+[ Upstream commit 96ca1e658ae459276292bd6d971ab5d8c7e0379a ]
+
+ipa_smp2p_init() acquires two Qualcomm SMEM state handles with
+qcom_smem_state_get(). However, neither the init error paths
+nor ipa_smp2p_exit() release them.
+
+Release both handles with qcom_smem_state_put() in the init
+error paths and in ipa_smp2p_exit().
+
+Fixes: 530f9216a953 ("soc: qcom: ipa: AP/modem communications")
+Cc: stable@vger.kernel.org
+Signed-off-by: Haoxiang Li <haoxiang_li2024@163.com>
+Reviewed-by: Larysa Zaremba <larysa.zaremba@intel.com>
+Reviewed-by: Alex Elder <elder@riscstar.com>
+Link: https://patch.msgid.link/20260624065955.2822765-1-haoxiang_li2024@163.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+[ kzalloc_obj() context line kept as kzalloc(sizeof(*smp2p), GFP_KERNEL) since ipa_smp2p.c was not yet converted in this tree ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ipa/ipa_smp2p.c | 30 ++++++++++++++++++++++--------
+ 1 file changed, 22 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/ipa/ipa_smp2p.c
++++ b/drivers/net/ipa/ipa_smp2p.c
+@@ -234,19 +234,27 @@ ipa_smp2p_init(struct ipa *ipa, struct p
+ &valid_bit);
+ if (IS_ERR(valid_state))
+ return PTR_ERR(valid_state);
+- if (valid_bit >= 32) /* BITS_PER_U32 */
+- return -EINVAL;
++ if (valid_bit >= 32) { /* BITS_PER_U32 */
++ ret = -EINVAL;
++ goto err_valid_state_put;
++ }
+
+ enabled_state = qcom_smem_state_get(dev, "ipa-clock-enabled",
+ &enabled_bit);
+- if (IS_ERR(enabled_state))
+- return PTR_ERR(enabled_state);
+- if (enabled_bit >= 32) /* BITS_PER_U32 */
+- return -EINVAL;
++ if (IS_ERR(enabled_state)) {
++ ret = PTR_ERR(enabled_state);
++ goto err_valid_state_put;
++ }
++ if (enabled_bit >= 32) { /* BITS_PER_U32 */
++ ret = -EINVAL;
++ goto err_enabled_state_put;
++ }
+
+ smp2p = kzalloc(sizeof(*smp2p), GFP_KERNEL);
+- if (!smp2p)
+- return -ENOMEM;
++ if (!smp2p) {
++ ret = -ENOMEM;
++ goto err_enabled_state_put;
++ }
+
+ smp2p->ipa = ipa;
+
+@@ -291,6 +299,10 @@ err_null_smp2p:
+ ipa->smp2p = NULL;
+ mutex_destroy(&smp2p->mutex);
+ kfree(smp2p);
++err_enabled_state_put:
++ qcom_smem_state_put(enabled_state);
++err_valid_state_put:
++ qcom_smem_state_put(valid_state);
+
+ return ret;
+ }
+@@ -307,6 +319,8 @@ void ipa_smp2p_exit(struct ipa *ipa)
+ ipa_smp2p_power_release(ipa);
+ ipa->smp2p = NULL;
+ mutex_destroy(&smp2p->mutex);
++ qcom_smem_state_put(smp2p->enabled_state);
++ qcom_smem_state_put(smp2p->valid_state);
+ kfree(smp2p);
+ }
+
--- /dev/null
+From stable+bounces-289857-greg=kroah.com@vger.kernel.org Tue Jul 28 06:07:13 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 23:57:14 -0400
+Subject: net: mana: Optimize irq affinity for low vcpu configs
+To: stable@vger.kernel.org
+Cc: Shradha Gupta <shradhagupta@linux.microsoft.com>, Erni Sri Satya Vennela <ernis@linux.microsoft.com>, Haiyang Zhang <haiyangz@microsoft.com>, Simon Horman <horms@kernel.org>, Yury Norov <ynorov@nvidia.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728035714.3173126-1-sashal@kernel.org>
+
+From: Shradha Gupta <shradhagupta@linux.microsoft.com>
+
+[ Upstream commit 5316394b1752f6cf3f9901e7fefdec1cd1d97fd3 ]
+
+Before the commit 755391121038 ("net: mana: Allocate MSI-X vectors
+dynamically"), all the MANA IRQs were assigned statically and together
+during early driver load.
+
+After this commit, the IRQ allocation for MANA was done in two phases.
+HWC IRQ allocated earlier and then, queue IRQs dynamically added at a
+later point. By this time, the IRQ weights on vCPUs can become imbalanced
+and if IRQ count is greater than the vCPU count the topology aware IRQ
+distribution logic in MANA can cause multiple MANA IRQs to land on the
+same vCPUs, while other sibling vCPUs have none (case 1).
+
+On SMP enabled, low-vCPU systems, this becomes a bigger problem as the
+softIRQ handling overhead of two IRQs on the same vCPUs becomes much more
+than their overheads if they were spread across sibling vCPUs.
+
+In such cases when many parallel TCP connections are tested, the
+throughput drops significantly.
+
+Fix the affinity assignment logic, in cases where the IRQ count is greater
+than the vCPU count and when IRQs are added dynamically, by utilizing all
+the vCPUs irrespective of their NUMA/core bindings (case 2).
+
+The results of setting the affinity and hint to NULL were also studied,
+and we observed that, with this logic if there are pre-existing IRQs
+allocated on the VM (apart from MANA), during MANA IRQs allocation, it
+leads to clustering of the MANA queue IRQs again (case 3).
+
+=======================================================
+Case 1: without this patch
+=======================================================
+4 vcpu(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
+
+ TYPE effective vCPU aff
+=======================================================
+IRQ0: HWC 0
+IRQ1: mana_q1 0
+IRQ2: mana_q2 2
+IRQ3: mana_q3 0
+IRQ4: mana_q4 3
+
+%soft on each vCPU(mpstat -P ALL 1) on receiver
+vCPU 0 1 2 3
+=======================================================
+pass 1: 38.85 0.03 24.89 24.65
+pass 2: 39.15 0.03 24.57 25.28
+pass 3: 40.36 0.03 23.20 23.17
+
+=======================================================
+Case 2: with this patch
+=======================================================
+4 vcpu(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
+
+ TYPE effective vCPU aff
+=======================================================
+IRQ0: HWC 0
+IRQ1: mana_q1 0
+IRQ2: mana_q2 1
+IRQ3: mana_q3 2
+IRQ4: mana_q4 3
+
+%soft on each vCPU(mpstat -P ALL 1) on receiver
+vCPU 0 1 2 3
+=======================================================
+pass 1: 15.42 15.85 14.99 14.51
+pass 2: 15.53 15.94 15.81 15.93
+pass 3: 16.41 16.35 16.40 16.36
+
+=======================================================
+Case 3: with affinity set to NULL
+=======================================================
+4 vCPU(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
+
+ TYPE effective vCPU aff
+=======================================================
+IRQ0: HWC 0
+IRQ1: mana_q1 2
+IRQ2: mana_q2 3
+IRQ3: mana_q3 2
+IRQ4: mana_q4 3
+
+=======================================================
+Throughput Impact(in Gbps, same env)
+=======================================================
+TCP conn with patch w/o patch aff NULL
+20480 15.65 7.73 5.25
+10240 15.63 8.93 5.77
+8192 15.64 9.69 7.16
+6144 15.64 13.16 9.33
+4096 15.69 15.75 13.50
+2048 15.69 15.83 13.61
+1024 15.71 15.28 13.60
+
+Fixes: 755391121038 ("net: mana: Allocate MSI-X vectors dynamically")
+Cc: stable@vger.kernel.org
+Co-developed-by: Erni Sri Satya Vennela <ernis@linux.microsoft.com>
+Signed-off-by: Erni Sri Satya Vennela <ernis@linux.microsoft.com>
+Signed-off-by: Shradha Gupta <shradhagupta@linux.microsoft.com>
+Reviewed-by: Haiyang Zhang <haiyangz@microsoft.com>
+Reviewed-by: Simon Horman <horms@kernel.org>
+Reviewed-by: Yury Norov <ynorov@nvidia.com>
+Link: https://patch.msgid.link/20260624072138.1632849-1-shradhagupta@linux.microsoft.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+[ Kept 7.1's `int *irqs, irq, err, i;` declaration (minus the deleted `skip_first_cpu`) instead of upstream's `msi` variant, as this tree lacks the `mana_gd_get_gic()` refactor. ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/microsoft/mana/gdma_main.c | 78 +++++++++++++++++++-----
+ 1 file changed, 64 insertions(+), 14 deletions(-)
+
+--- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
++++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
+@@ -125,6 +125,8 @@ static int mana_gd_query_max_resources(s
+ } else {
+ /* If dynamic allocation is enabled we have already allocated
+ * hwc msi
++ * Also, we make sure in this case the following is always true
++ * (num_msix_usable - 1 HWC) <= num_online_cpus()
+ */
+ gc->num_msix_usable = min(resp.max_msix, num_online_cpus() + 1);
+ }
+@@ -1587,8 +1589,8 @@ void mana_gd_free_res_map(struct gdma_re
+ * do the same thing.
+ */
+
+-static int irq_setup(unsigned int *irqs, unsigned int len, int node,
+- bool skip_first_cpu)
++static int mana_irq_setup_numa_aware(unsigned int *irqs, unsigned int len,
++ int node, bool skip_first_cpu)
+ {
+ const struct cpumask *next, *prev = cpu_none_mask;
+ cpumask_var_t cpus __free(free_cpumask_var);
+@@ -1624,11 +1626,24 @@ done:
+ return 0;
+ }
+
++/* must be called with cpus_read_lock() held */
++static void mana_irq_setup_linear(unsigned int *irqs, unsigned int len)
++{
++ int cpu;
++
++ for_each_online_cpu(cpu) {
++ if (len == 0)
++ break;
++
++ irq_set_affinity_and_hint(*irqs++, cpumask_of(cpu));
++ len--;
++ }
++}
++
+ static int mana_gd_setup_dyn_irqs(struct pci_dev *pdev, int nvec)
+ {
+ struct gdma_context *gc = pci_get_drvdata(pdev);
+ struct gdma_irq_context *gic;
+- bool skip_first_cpu = false;
+ int *irqs, irq, err, i;
+
+ irqs = kmalloc_array(nvec, sizeof(int), GFP_KERNEL);
+@@ -1636,10 +1651,12 @@ static int mana_gd_setup_dyn_irqs(struct
+ return -ENOMEM;
+
+ /*
++ * In this function, num_msix_usable = HWC IRQ + Queue IRQ.
++ * nvec is only Queue IRQ (HWC already setup).
+ * While processing the next pci irq vector, we start with index 1,
+ * as IRQ vector at index 0 is already processed for HWC.
+ * However, the population of irqs array starts with index 0, to be
+- * further used in irq_setup()
++ * further used in mana_irq_setup_numa_aware()
+ */
+ for (i = 1; i <= nvec; i++) {
+ gic = kzalloc(sizeof(*gic), GFP_KERNEL);
+@@ -1669,18 +1686,51 @@ static int mana_gd_setup_dyn_irqs(struct
+ }
+
+ /*
+- * When calling irq_setup() for dynamically added IRQs, if number of
+- * CPUs is more than or equal to allocated MSI-X, we need to skip the
+- * first CPU sibling group since they are already affinitized to HWC IRQ
++ * When calling mana_irq_setup_numa_aware() for dynamically added IRQs,
++ * if number of CPUs is more than or equal to allocated MSI-X, we need to
++ * skip the first CPU sibling group since they are already affinitized to
++ * HWC IRQ
+ */
+ cpus_read_lock();
+- if (gc->num_msix_usable <= num_online_cpus())
+- skip_first_cpu = true;
++ if (gc->num_msix_usable <= num_online_cpus()) {
++ err = mana_irq_setup_numa_aware(irqs, nvec, gc->numa_node,
++ true);
++ if (err) {
++ cpus_read_unlock();
++ goto free_irq;
++ }
++ } else {
++ /*
++ * When num_msix_usable are more than num_online_cpus, our
++ * queue IRQs should be equal to num of online vCPUs.
++ * We try to make sure queue IRQs spread across all vCPUs.
++ * In such a case NUMA or CPU core affinity does not matter.
++ * Note: in this case the total mana IRQ should always be
++ * num_online_cpus + 1. The first HWC IRQ is already handled
++ * in HWC setup calls
++ * However, if CPUs went offline since num_msix_usable was
++ * computed, queue IRQs will be more than num_online_cpus().
++ * In such cases remaining extra IRQs will retain their default
++ * affinity.
++ */
++ int first_unassigned = num_online_cpus();
+
+- err = irq_setup(irqs, nvec, gc->numa_node, skip_first_cpu);
+- if (err) {
+- cpus_read_unlock();
+- goto free_irq;
++ if (nvec > first_unassigned) {
++ char buf[32];
++
++ if (first_unassigned == nvec - 1)
++ snprintf(buf, sizeof(buf), "%d",
++ first_unassigned);
++ else
++ snprintf(buf, sizeof(buf), "%d-%d",
++ first_unassigned, nvec - 1);
++
++ dev_dbg(&pdev->dev,
++ "MANA IRQ indices #%s will retain the default CPU affinity\n",
++ buf);
++ }
++
++ mana_irq_setup_linear(irqs, nvec);
+ }
+
+ cpus_read_unlock();
+@@ -1766,7 +1816,7 @@ static int mana_gd_setup_irqs(struct pci
+ nvec -= 1;
+ }
+
+- err = irq_setup(irqs, nvec, gc->numa_node, false);
++ err = mana_irq_setup_numa_aware(irqs, nvec, gc->numa_node, false);
+ if (err) {
+ cpus_read_unlock();
+ goto free_irq;
--- /dev/null
+From stable+bounces-289815-greg=kroah.com@vger.kernel.org Tue Jul 28 04:51:58 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 22:46:45 -0400
+Subject: net: mana: Validate the packet length reported by the NIC
+To: stable@vger.kernel.org
+Cc: Dexuan Cui <decui@microsoft.com>, Haiyang Zhang <haiyangz@microsoft.com>, Paolo Abeni <pabeni@redhat.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728024645.3059155-1-sashal@kernel.org>
+
+From: Dexuan Cui <decui@microsoft.com>
+
+[ Upstream commit 2e2a83b4998af4384e677d3b2ac08565274279bf ]
+
+Validate the packet length reported in the RX CQE before passing it
+to skb processing. The CQE is supplied by the NIC device and should
+not be blindly trusted.
+
+Cc: stable@vger.kernel.org
+Reviewed-by: Haiyang Zhang <haiyangz@microsoft.com>
+Signed-off-by: Dexuan Cui <decui@microsoft.com>
+Fixes: ca9c54d2d6a5 ("net: mana: Add a driver for Microsoft Azure Network Adapter (MANA)")
+Link: https://patch.msgid.link/20260702041237.617719-2-decui@microsoft.com
+Signed-off-by: Paolo Abeni <pabeni@redhat.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/microsoft/mana/mana_en.c | 13 +++++++++++++
+ 1 file changed, 13 insertions(+)
+
+--- a/drivers/net/ethernet/microsoft/mana/mana_en.c
++++ b/drivers/net/ethernet/microsoft/mana/mana_en.c
+@@ -2160,6 +2160,19 @@ static void mana_process_rx_cqe(struct m
+ rxbuf_oob = &rxq->rx_oobs[curr];
+ WARN_ON_ONCE(rxbuf_oob->wqe_inf.wqe_size_in_bu != 1);
+
++ if (unlikely(pktlen > rxq->datasize)) {
++ /* Increase it even if mana_rx_skb() isn't called. */
++ rxq->rx_cq.work_done++;
++
++ ++ndev->stats.rx_dropped;
++ netdev_warn_once(ndev,
++ "Dropped oversized RX packet: len=%u, datasize=%u\n",
++ pktlen, rxq->datasize);
++
++ /* Reuse the RX buffer since rxbuf_oob is unchanged. */
++ goto drop;
++ }
++
+ mana_refill_rx_oob(dev, rxq, rxbuf_oob, &old_buf, &old_fp);
+
+ /* Unsuccessful refill will have old_buf == NULL.
--- /dev/null
+From stable+bounces-288335-greg=kroah.com@vger.kernel.org Thu Jul 23 16:28:01 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:18:39 -0400
+Subject: netfilter: nf_conntrack_sip: remove net variable shadowing
+To: stable@vger.kernel.org
+Cc: Florian Westphal <fw@strlen.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723141840.3216559-1-sashal@kernel.org>
+
+From: Florian Westphal <fw@strlen.de>
+
+[ Upstream commit 7970d6aaf710db166de98c5356a260089896fae5 ]
+
+net is already set, derived from nf_conn.
+I don't see how the device could be living in a different netns
+than the conntrack entry.
+
+Remove the extra variable and re-use existing one.
+
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Stable-dep-of: e5e24a365a5e ("netfilter: nf_conntrack_sip: validate skb_dst() before accessing it")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/netfilter/nf_conntrack_sip.c | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/net/netfilter/nf_conntrack_sip.c
++++ b/net/netfilter/nf_conntrack_sip.c
+@@ -948,9 +948,8 @@ static int set_expected_rtp_rtcp(struct
+ saddr = &ct->tuplehash[!dir].tuple.src.u3;
+ } else if (sip_external_media) {
+ struct net_device *dev = skb_dst(skb)->dev;
+- struct net *net = dev_net(dev);
+- struct flowi fl;
+ struct dst_entry *dst = NULL;
++ struct flowi fl;
+
+ memset(&fl, 0, sizeof(fl));
+
--- /dev/null
+From stable+bounces-288336-greg=kroah.com@vger.kernel.org Thu Jul 23 16:20:14 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:18:40 -0400
+Subject: netfilter: nf_conntrack_sip: validate skb_dst() before accessing it
+To: stable@vger.kernel.org
+Cc: Pablo Neira Ayuso <pablo@netfilter.org>, Ren Wei <n05ec@lzu.edu.cn>, Florian Westphal <fw@strlen.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723141840.3216559-2-sashal@kernel.org>
+
+From: Pablo Neira Ayuso <pablo@netfilter.org>
+
+[ Upstream commit e5e24a365a5e024efef63cc49abb345fbd4852c5 ]
+
+tc ingress and openvswitch do not guarantee routing information to be
+available. These subsystems use the conntrack helper infrastructure, and
+the SIP helper relies on the skb_dst() to be present if
+sip_external_media is set to 1 (which is disabled by default as a module
+parameter).
+
+This effectively disables the sip_external_media toggle for these
+subsystems without resulting in a crash.
+
+Fixes: cae3a2627520 ("openvswitch: Allow attaching helpers to ct action")
+Fixes: b57dc7c13ea9 ("net/sched: Introduce action ct")
+Cc: stable@vger.kernel.org
+Reported-by: Ren Wei <n05ec@lzu.edu.cn>
+Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/netfilter/nf_conntrack_sip.c | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+--- a/net/netfilter/nf_conntrack_sip.c
++++ b/net/netfilter/nf_conntrack_sip.c
+@@ -947,7 +947,6 @@ static int set_expected_rtp_rtcp(struct
+ return NF_ACCEPT;
+ saddr = &ct->tuplehash[!dir].tuple.src.u3;
+ } else if (sip_external_media) {
+- struct net_device *dev = skb_dst(skb)->dev;
+ struct dst_entry *dst = NULL;
+ struct flowi fl;
+
+@@ -969,7 +968,11 @@ static int set_expected_rtp_rtcp(struct
+ * through the same interface as the signalling peer.
+ */
+ if (dst) {
+- bool external_media = (dst->dev == dev);
++ const struct dst_entry *this_dst = skb_dst(skb);
++ bool external_media = false;
++
++ if (this_dst && dst->dev == this_dst->dev)
++ external_media = true;
+
+ dst_release(dst);
+ if (external_media)
--- /dev/null
+From stable+bounces-288333-greg=kroah.com@vger.kernel.org Thu Jul 23 16:30:53 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:18:34 -0400
+Subject: netfilter: nf_tables: remove register tracking infrastructure
+To: stable@vger.kernel.org
+Cc: Florian Westphal <fw@strlen.de>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723141835.3216078-1-sashal@kernel.org>
+
+From: Florian Westphal <fw@strlen.de>
+
+[ Upstream commit 6b94d081f81dd524626f7aab2b98a9de335edb72 ]
+
+This facility was disabled in commit
+9e539c5b6d9c ("netfilter: nf_tables: disable expression reduction infra"),
+because not all nft_exprs guarantee they will update the destination
+register: some may set NFT_BREAK instead to cancel evaluation of the
+rule.
+
+This has been dead code ever since.
+There are no plans to salvage this at this time, so remove this.
+
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Link: https://patch.msgid.link/20260224205048.4718-10-fw@strlen.de
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: d07955dd34ec ("netfilter: nft_fib: reject fib expression on the netdev egress hook")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/net/netfilter/nf_tables.h | 32 ---------
+ include/net/netfilter/nft_fib.h | 2
+ include/net/netfilter/nft_meta.h | 3
+ net/bridge/netfilter/nft_meta_bridge.c | 20 -----
+ net/bridge/netfilter/nft_reject_bridge.c | 1
+ net/ipv4/netfilter/nft_dup_ipv4.c | 1
+ net/ipv4/netfilter/nft_fib_ipv4.c | 2
+ net/ipv4/netfilter/nft_reject_ipv4.c | 1
+ net/ipv6/netfilter/nft_dup_ipv6.c | 1
+ net/ipv6/netfilter/nft_fib_ipv6.c | 2
+ net/ipv6/netfilter/nft_reject_ipv6.c | 1
+ net/netfilter/nf_tables_api.c | 67 -------------------
+ net/netfilter/nft_bitwise.c | 104 -------------------------------
+ net/netfilter/nft_byteorder.c | 11 ---
+ net/netfilter/nft_cmp.c | 3
+ net/netfilter/nft_compat.c | 10 --
+ net/netfilter/nft_connlimit.c | 1
+ net/netfilter/nft_counter.c | 1
+ net/netfilter/nft_ct.c | 46 -------------
+ net/netfilter/nft_dup_netdev.c | 1
+ net/netfilter/nft_dynset.c | 1
+ net/netfilter/nft_exthdr.c | 34 ----------
+ net/netfilter/nft_fib.c | 42 ------------
+ net/netfilter/nft_fib_inet.c | 1
+ net/netfilter/nft_fib_netdev.c | 1
+ net/netfilter/nft_flow_offload.c | 1
+ net/netfilter/nft_fwd_netdev.c | 2
+ net/netfilter/nft_hash.c | 36 ----------
+ net/netfilter/nft_immediate.c | 12 ---
+ net/netfilter/nft_last.c | 1
+ net/netfilter/nft_limit.c | 2
+ net/netfilter/nft_log.c | 1
+ net/netfilter/nft_lookup.c | 12 ---
+ net/netfilter/nft_masq.c | 3
+ net/netfilter/nft_meta.c | 45 -------------
+ net/netfilter/nft_nat.c | 2
+ net/netfilter/nft_numgen.c | 22 ------
+ net/netfilter/nft_objref.c | 2
+ net/netfilter/nft_osf.c | 25 -------
+ net/netfilter/nft_payload.c | 47 --------------
+ net/netfilter/nft_queue.c | 2
+ net/netfilter/nft_quota.c | 1
+ net/netfilter/nft_range.c | 1
+ net/netfilter/nft_redir.c | 3
+ net/netfilter/nft_reject_inet.c | 1
+ net/netfilter/nft_reject_netdev.c | 1
+ net/netfilter/nft_rt.c | 1
+ net/netfilter/nft_socket.c | 26 -------
+ net/netfilter/nft_synproxy.c | 1
+ net/netfilter/nft_tproxy.c | 1
+ net/netfilter/nft_tunnel.c | 26 -------
+ net/netfilter/nft_xfrm.c | 27 --------
+ 52 files changed, 693 deletions(-)
+
+--- a/include/net/netfilter/nf_tables.h
++++ b/include/net/netfilter/nf_tables.h
+@@ -123,17 +123,6 @@ struct nft_regs {
+ };
+ };
+
+-struct nft_regs_track {
+- struct {
+- const struct nft_expr *selector;
+- const struct nft_expr *bitwise;
+- u8 num_reg;
+- } regs[NFT_REG32_NUM];
+-
+- const struct nft_expr *cur;
+- const struct nft_expr *last;
+-};
+-
+ /* Store/load an u8, u16 or u64 integer to/from the u32 data register.
+ *
+ * Note, when using concatenations, register allocation happens at 32-bit
+@@ -433,8 +422,6 @@ int nft_expr_clone(struct nft_expr *dst,
+ void nft_expr_destroy(const struct nft_ctx *ctx, struct nft_expr *expr);
+ int nft_expr_dump(struct sk_buff *skb, unsigned int attr,
+ const struct nft_expr *expr, bool reset);
+-bool nft_expr_reduce_bitwise(struct nft_regs_track *track,
+- const struct nft_expr *expr);
+
+ struct nft_set_ext;
+
+@@ -949,7 +936,6 @@ struct nft_offload_ctx;
+ * @destroy_clone: destruction clone function
+ * @dump: function to dump parameters
+ * @validate: validate expression, called during loop detection
+- * @reduce: reduce expression
+ * @gc: garbage collection expression
+ * @offload: hardware offload expression
+ * @offload_action: function to report true/false to allocate one slot or not in the flow
+@@ -983,8 +969,6 @@ struct nft_expr_ops {
+ bool reset);
+ int (*validate)(const struct nft_ctx *ctx,
+ const struct nft_expr *expr);
+- bool (*reduce)(struct nft_regs_track *track,
+- const struct nft_expr *expr);
+ bool (*gc)(struct net *net,
+ const struct nft_expr *expr);
+ int (*offload)(struct nft_offload_ctx *ctx,
+@@ -1967,20 +1951,4 @@ static inline u64 nft_net_tstamp(const s
+ return nft_pernet(net)->tstamp;
+ }
+
+-#define __NFT_REDUCE_READONLY 1UL
+-#define NFT_REDUCE_READONLY (void *)__NFT_REDUCE_READONLY
+-
+-void nft_reg_track_update(struct nft_regs_track *track,
+- const struct nft_expr *expr, u8 dreg, u8 len);
+-void nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg, u8 len);
+-void __nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg);
+-
+-static inline bool nft_reg_track_cmp(struct nft_regs_track *track,
+- const struct nft_expr *expr, u8 dreg)
+-{
+- return track->regs[dreg].selector &&
+- track->regs[dreg].selector->ops == expr->ops &&
+- track->regs[dreg].num_reg == 0;
+-}
+-
+ #endif /* _NET_NF_TABLES_H */
+--- a/include/net/netfilter/nft_fib.h
++++ b/include/net/netfilter/nft_fib.h
+@@ -66,6 +66,4 @@ void nft_fib6_eval(const struct nft_expr
+ void nft_fib_store_result(void *reg, const struct nft_fib *priv,
+ const struct net_device *dev);
+
+-bool nft_fib_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr);
+ #endif
+--- a/include/net/netfilter/nft_meta.h
++++ b/include/net/netfilter/nft_meta.h
+@@ -45,9 +45,6 @@ int nft_meta_get_validate(const struct n
+ int nft_meta_set_validate(const struct nft_ctx *ctx,
+ const struct nft_expr *expr);
+
+-bool nft_meta_get_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr);
+-
+ struct nft_inner_tun_ctx;
+ void nft_meta_inner_eval(const struct nft_expr *expr,
+ struct nft_regs *regs, const struct nft_pktinfo *pkt,
+--- a/net/bridge/netfilter/nft_meta_bridge.c
++++ b/net/bridge/netfilter/nft_meta_bridge.c
+@@ -134,7 +134,6 @@ static const struct nft_expr_ops nft_met
+ .init = nft_meta_bridge_get_init,
+ .validate = nft_meta_bridge_get_validate,
+ .dump = nft_meta_get_dump,
+- .reduce = nft_meta_get_reduce,
+ };
+
+ static void nft_meta_bridge_set_eval(const struct nft_expr *expr,
+@@ -181,24 +180,6 @@ static int nft_meta_bridge_set_init(cons
+ return 0;
+ }
+
+-static bool nft_meta_bridge_set_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- int i;
+-
+- for (i = 0; i < NFT_REG32_NUM; i++) {
+- if (!track->regs[i].selector)
+- continue;
+-
+- if (track->regs[i].selector->ops != &nft_meta_bridge_get_ops)
+- continue;
+-
+- __nft_reg_track_cancel(track, i);
+- }
+-
+- return false;
+-}
+-
+ static int nft_meta_bridge_set_validate(const struct nft_ctx *ctx,
+ const struct nft_expr *expr)
+ {
+@@ -223,7 +204,6 @@ static const struct nft_expr_ops nft_met
+ .init = nft_meta_bridge_set_init,
+ .destroy = nft_meta_set_destroy,
+ .dump = nft_meta_set_dump,
+- .reduce = nft_meta_bridge_set_reduce,
+ .validate = nft_meta_bridge_set_validate,
+ };
+
+--- a/net/bridge/netfilter/nft_reject_bridge.c
++++ b/net/bridge/netfilter/nft_reject_bridge.c
+@@ -184,7 +184,6 @@ static const struct nft_expr_ops nft_rej
+ .init = nft_reject_init,
+ .dump = nft_reject_dump,
+ .validate = nft_reject_bridge_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_reject_bridge_type __read_mostly = {
+--- a/net/ipv4/netfilter/nft_dup_ipv4.c
++++ b/net/ipv4/netfilter/nft_dup_ipv4.c
+@@ -76,7 +76,6 @@ static const struct nft_expr_ops nft_dup
+ .eval = nft_dup_ipv4_eval,
+ .init = nft_dup_ipv4_init,
+ .dump = nft_dup_ipv4_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nla_policy nft_dup_ipv4_policy[NFTA_DUP_MAX + 1] = {
+--- a/net/ipv4/netfilter/nft_fib_ipv4.c
++++ b/net/ipv4/netfilter/nft_fib_ipv4.c
+@@ -163,7 +163,6 @@ static const struct nft_expr_ops nft_fib
+ .init = nft_fib_init,
+ .dump = nft_fib_dump,
+ .validate = nft_fib_validate,
+- .reduce = nft_fib_reduce,
+ };
+
+ static const struct nft_expr_ops nft_fib4_ops = {
+@@ -173,7 +172,6 @@ static const struct nft_expr_ops nft_fib
+ .init = nft_fib_init,
+ .dump = nft_fib_dump,
+ .validate = nft_fib_validate,
+- .reduce = nft_fib_reduce,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/ipv4/netfilter/nft_reject_ipv4.c
++++ b/net/ipv4/netfilter/nft_reject_ipv4.c
+@@ -45,7 +45,6 @@ static const struct nft_expr_ops nft_rej
+ .init = nft_reject_init,
+ .dump = nft_reject_dump,
+ .validate = nft_reject_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_reject_ipv4_type __read_mostly = {
+--- a/net/ipv6/netfilter/nft_dup_ipv6.c
++++ b/net/ipv6/netfilter/nft_dup_ipv6.c
+@@ -74,7 +74,6 @@ static const struct nft_expr_ops nft_dup
+ .eval = nft_dup_ipv6_eval,
+ .init = nft_dup_ipv6_init,
+ .dump = nft_dup_ipv6_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nla_policy nft_dup_ipv6_policy[NFTA_DUP_MAX + 1] = {
+--- a/net/ipv6/netfilter/nft_fib_ipv6.c
++++ b/net/ipv6/netfilter/nft_fib_ipv6.c
+@@ -225,7 +225,6 @@ static const struct nft_expr_ops nft_fib
+ .init = nft_fib_init,
+ .dump = nft_fib_dump,
+ .validate = nft_fib_validate,
+- .reduce = nft_fib_reduce,
+ };
+
+ static const struct nft_expr_ops nft_fib6_ops = {
+@@ -235,7 +234,6 @@ static const struct nft_expr_ops nft_fib
+ .init = nft_fib_init,
+ .dump = nft_fib_dump,
+ .validate = nft_fib_validate,
+- .reduce = nft_fib_reduce,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/ipv6/netfilter/nft_reject_ipv6.c
++++ b/net/ipv6/netfilter/nft_reject_ipv6.c
+@@ -46,7 +46,6 @@ static const struct nft_expr_ops nft_rej
+ .init = nft_reject_init,
+ .dump = nft_reject_dump,
+ .validate = nft_reject_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_reject_ipv6_type __read_mostly = {
+--- a/net/netfilter/nf_tables_api.c
++++ b/net/netfilter/nf_tables_api.c
+@@ -939,58 +939,6 @@ static int nft_delflowtable(struct nft_c
+ return 0;
+ }
+
+-static void __nft_reg_track_clobber(struct nft_regs_track *track, u8 dreg)
+-{
+- int i;
+-
+- for (i = track->regs[dreg].num_reg; i > 0; i--)
+- __nft_reg_track_cancel(track, dreg - i);
+-}
+-
+-static void __nft_reg_track_update(struct nft_regs_track *track,
+- const struct nft_expr *expr,
+- u8 dreg, u8 num_reg)
+-{
+- track->regs[dreg].selector = expr;
+- track->regs[dreg].bitwise = NULL;
+- track->regs[dreg].num_reg = num_reg;
+-}
+-
+-void nft_reg_track_update(struct nft_regs_track *track,
+- const struct nft_expr *expr, u8 dreg, u8 len)
+-{
+- unsigned int regcount;
+- int i;
+-
+- __nft_reg_track_clobber(track, dreg);
+-
+- regcount = DIV_ROUND_UP(len, NFT_REG32_SIZE);
+- for (i = 0; i < regcount; i++, dreg++)
+- __nft_reg_track_update(track, expr, dreg, i);
+-}
+-EXPORT_SYMBOL_GPL(nft_reg_track_update);
+-
+-void nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg, u8 len)
+-{
+- unsigned int regcount;
+- int i;
+-
+- __nft_reg_track_clobber(track, dreg);
+-
+- regcount = DIV_ROUND_UP(len, NFT_REG32_SIZE);
+- for (i = 0; i < regcount; i++, dreg++)
+- __nft_reg_track_cancel(track, dreg);
+-}
+-EXPORT_SYMBOL_GPL(nft_reg_track_cancel);
+-
+-void __nft_reg_track_cancel(struct nft_regs_track *track, u8 dreg)
+-{
+- track->regs[dreg].selector = NULL;
+- track->regs[dreg].bitwise = NULL;
+- track->regs[dreg].num_reg = 0;
+-}
+-EXPORT_SYMBOL_GPL(__nft_reg_track_cancel);
+-
+ /*
+ * Tables
+ */
+@@ -10173,16 +10121,9 @@ void nf_tables_trans_destroy_flush_work(
+ }
+ EXPORT_SYMBOL_GPL(nf_tables_trans_destroy_flush_work);
+
+-static bool nft_expr_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- return false;
+-}
+-
+ static int nf_tables_commit_chain_prepare(struct net *net, struct nft_chain *chain)
+ {
+ const struct nft_expr *expr, *last;
+- struct nft_regs_track track = {};
+ unsigned int size, data_size;
+ void *data, *data_boundary;
+ struct nft_rule_dp *prule;
+@@ -10219,15 +10160,7 @@ static int nf_tables_commit_chain_prepar
+ return -ENOMEM;
+
+ size = 0;
+- track.last = nft_expr_last(rule);
+ nft_rule_for_each_expr(expr, last, rule) {
+- track.cur = expr;
+-
+- if (nft_expr_reduce(&track, expr)) {
+- expr = track.cur;
+- continue;
+- }
+-
+ if (WARN_ON_ONCE(data + size + expr->ops->size > data_boundary))
+ return -ENOMEM;
+
+--- a/net/netfilter/nft_bitwise.c
++++ b/net/netfilter/nft_bitwise.c
+@@ -402,61 +402,12 @@ static int nft_bitwise_offload(struct nf
+ return 0;
+ }
+
+-static bool nft_bitwise_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_bitwise *priv = nft_expr_priv(expr);
+- const struct nft_bitwise *bitwise;
+- unsigned int regcount;
+- u8 dreg;
+- int i;
+-
+- if (!track->regs[priv->sreg].selector)
+- return false;
+-
+- bitwise = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (track->regs[priv->sreg].selector == track->regs[priv->dreg].selector &&
+- track->regs[priv->sreg].num_reg == 0 &&
+- track->regs[priv->dreg].bitwise &&
+- track->regs[priv->dreg].bitwise->ops == expr->ops &&
+- priv->sreg == bitwise->sreg &&
+- priv->sreg2 == bitwise->sreg2 &&
+- priv->dreg == bitwise->dreg &&
+- priv->op == bitwise->op &&
+- priv->len == bitwise->len &&
+- !memcmp(&priv->mask, &bitwise->mask, sizeof(priv->mask)) &&
+- !memcmp(&priv->xor, &bitwise->xor, sizeof(priv->xor)) &&
+- !memcmp(&priv->data, &bitwise->data, sizeof(priv->data))) {
+- track->cur = expr;
+- return true;
+- }
+-
+- if (track->regs[priv->sreg].bitwise ||
+- track->regs[priv->sreg].num_reg != 0) {
+- nft_reg_track_cancel(track, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (priv->sreg != priv->dreg) {
+- nft_reg_track_update(track, track->regs[priv->sreg].selector,
+- priv->dreg, priv->len);
+- }
+-
+- dreg = priv->dreg;
+- regcount = DIV_ROUND_UP(priv->len, NFT_REG32_SIZE);
+- for (i = 0; i < regcount; i++, dreg++)
+- track->regs[dreg].bitwise = expr;
+-
+- return false;
+-}
+-
+ static const struct nft_expr_ops nft_bitwise_ops = {
+ .type = &nft_bitwise_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_bitwise)),
+ .eval = nft_bitwise_eval,
+ .init = nft_bitwise_init,
+ .dump = nft_bitwise_dump,
+- .reduce = nft_bitwise_reduce,
+ .offload = nft_bitwise_offload,
+ };
+
+@@ -559,48 +510,12 @@ static int nft_bitwise_fast_offload(stru
+ return 0;
+ }
+
+-static bool nft_bitwise_fast_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_bitwise_fast_expr *priv = nft_expr_priv(expr);
+- const struct nft_bitwise_fast_expr *bitwise;
+-
+- if (!track->regs[priv->sreg].selector)
+- return false;
+-
+- bitwise = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (track->regs[priv->sreg].selector == track->regs[priv->dreg].selector &&
+- track->regs[priv->dreg].bitwise &&
+- track->regs[priv->dreg].bitwise->ops == expr->ops &&
+- priv->sreg == bitwise->sreg &&
+- priv->dreg == bitwise->dreg &&
+- priv->mask == bitwise->mask &&
+- priv->xor == bitwise->xor) {
+- track->cur = expr;
+- return true;
+- }
+-
+- if (track->regs[priv->sreg].bitwise) {
+- nft_reg_track_cancel(track, priv->dreg, NFT_REG32_SIZE);
+- return false;
+- }
+-
+- if (priv->sreg != priv->dreg) {
+- track->regs[priv->dreg].selector =
+- track->regs[priv->sreg].selector;
+- }
+- track->regs[priv->dreg].bitwise = expr;
+-
+- return false;
+-}
+-
+ const struct nft_expr_ops nft_bitwise_fast_ops = {
+ .type = &nft_bitwise_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_bitwise_fast_expr)),
+ .eval = NULL, /* inlined */
+ .init = nft_bitwise_fast_init,
+ .dump = nft_bitwise_fast_dump,
+- .reduce = nft_bitwise_fast_reduce,
+ .offload = nft_bitwise_fast_offload,
+ };
+
+@@ -637,22 +552,3 @@ struct nft_expr_type nft_bitwise_type __
+ .maxattr = NFTA_BITWISE_MAX,
+ .owner = THIS_MODULE,
+ };
+-
+-bool nft_expr_reduce_bitwise(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_expr *last = track->last;
+- const struct nft_expr *next;
+-
+- if (expr == last)
+- return false;
+-
+- next = nft_expr_next(expr);
+- if (next->ops == &nft_bitwise_ops)
+- return nft_bitwise_reduce(track, next);
+- else if (next->ops == &nft_bitwise_fast_ops)
+- return nft_bitwise_fast_reduce(track, next);
+-
+- return false;
+-}
+-EXPORT_SYMBOL_GPL(nft_expr_reduce_bitwise);
+--- a/net/netfilter/nft_byteorder.c
++++ b/net/netfilter/nft_byteorder.c
+@@ -177,23 +177,12 @@ nla_put_failure:
+ return -1;
+ }
+
+-static bool nft_byteorder_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- struct nft_byteorder *priv = nft_expr_priv(expr);
+-
+- nft_reg_track_cancel(track, priv->dreg, priv->len);
+-
+- return false;
+-}
+-
+ static const struct nft_expr_ops nft_byteorder_ops = {
+ .type = &nft_byteorder_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_byteorder)),
+ .eval = nft_byteorder_eval,
+ .init = nft_byteorder_init,
+ .dump = nft_byteorder_dump,
+- .reduce = nft_byteorder_reduce,
+ };
+
+ struct nft_expr_type nft_byteorder_type __read_mostly = {
+--- a/net/netfilter/nft_cmp.c
++++ b/net/netfilter/nft_cmp.c
+@@ -190,7 +190,6 @@ static const struct nft_expr_ops nft_cmp
+ .eval = nft_cmp_eval,
+ .init = nft_cmp_init,
+ .dump = nft_cmp_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ .offload = nft_cmp_offload,
+ };
+
+@@ -282,7 +281,6 @@ const struct nft_expr_ops nft_cmp_fast_o
+ .eval = NULL, /* inlined */
+ .init = nft_cmp_fast_init,
+ .dump = nft_cmp_fast_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ .offload = nft_cmp_fast_offload,
+ };
+
+@@ -376,7 +374,6 @@ const struct nft_expr_ops nft_cmp16_fast
+ .eval = NULL, /* inlined */
+ .init = nft_cmp16_fast_init,
+ .dump = nft_cmp16_fast_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ .offload = nft_cmp16_fast_offload,
+ };
+
+--- a/net/netfilter/nft_compat.c
++++ b/net/netfilter/nft_compat.c
+@@ -794,14 +794,6 @@ static const struct nfnetlink_subsystem
+
+ static struct nft_expr_type nft_match_type;
+
+-static bool nft_match_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct xt_match *match = expr->ops->data;
+-
+- return strcmp(match->name, "comment") == 0;
+-}
+-
+ static const struct nft_expr_ops *
+ nft_match_select_ops(const struct nft_ctx *ctx,
+ const struct nlattr * const tb[])
+@@ -844,7 +836,6 @@ nft_match_select_ops(const struct nft_ct
+ ops->dump = nft_match_dump;
+ ops->validate = nft_match_validate;
+ ops->data = match;
+- ops->reduce = nft_match_reduce;
+
+ matchsize = NFT_EXPR_SIZE(XT_ALIGN(match->matchsize));
+ if (matchsize > NFT_MATCH_LARGE_THRESH) {
+@@ -933,7 +924,6 @@ nft_target_select_ops(const struct nft_c
+ ops->destroy = nft_target_destroy;
+ ops->dump = nft_target_dump;
+ ops->data = target;
+- ops->reduce = NFT_REDUCE_READONLY;
+
+ if (family == NFPROTO_BRIDGE) {
+ ops->eval = nft_target_eval_bridge;
+--- a/net/netfilter/nft_connlimit.c
++++ b/net/netfilter/nft_connlimit.c
+@@ -247,7 +247,6 @@ static const struct nft_expr_ops nft_con
+ .destroy_clone = nft_connlimit_destroy_clone,
+ .dump = nft_connlimit_dump,
+ .gc = nft_connlimit_gc,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_connlimit_type __read_mostly = {
+--- a/net/netfilter/nft_counter.c
++++ b/net/netfilter/nft_counter.c
+@@ -313,7 +313,6 @@ static const struct nft_expr_ops nft_cou
+ .destroy_clone = nft_counter_destroy,
+ .dump = nft_counter_dump,
+ .clone = nft_counter_clone,
+- .reduce = NFT_REDUCE_READONLY,
+ .offload = nft_counter_offload,
+ .offload_stats = nft_counter_offload_stats,
+ };
+--- a/net/netfilter/nft_ct.c
++++ b/net/netfilter/nft_ct.c
+@@ -698,29 +698,6 @@ nla_put_failure:
+ return -1;
+ }
+
+-static bool nft_ct_get_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_ct *priv = nft_expr_priv(expr);
+- const struct nft_ct *ct;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- ct = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->key != ct->key) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return nft_expr_reduce_bitwise(track, expr);
+-}
+-
+ static int nft_ct_set_dump(struct sk_buff *skb,
+ const struct nft_expr *expr, bool reset)
+ {
+@@ -755,27 +732,8 @@ static const struct nft_expr_ops nft_ct_
+ .init = nft_ct_get_init,
+ .destroy = nft_ct_get_destroy,
+ .dump = nft_ct_get_dump,
+- .reduce = nft_ct_get_reduce,
+ };
+
+-static bool nft_ct_set_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- int i;
+-
+- for (i = 0; i < NFT_REG32_NUM; i++) {
+- if (!track->regs[i].selector)
+- continue;
+-
+- if (track->regs[i].selector->ops != &nft_ct_get_ops)
+- continue;
+-
+- __nft_reg_track_cancel(track, i);
+- }
+-
+- return false;
+-}
+-
+ #ifdef CONFIG_MITIGATION_RETPOLINE
+ static const struct nft_expr_ops nft_ct_get_fast_ops = {
+ .type = &nft_ct_type,
+@@ -784,7 +742,6 @@ static const struct nft_expr_ops nft_ct_
+ .init = nft_ct_get_init,
+ .destroy = nft_ct_get_destroy,
+ .dump = nft_ct_get_dump,
+- .reduce = nft_ct_set_reduce,
+ };
+ #endif
+
+@@ -795,7 +752,6 @@ static const struct nft_expr_ops nft_ct_
+ .init = nft_ct_set_init,
+ .destroy = nft_ct_set_destroy,
+ .dump = nft_ct_set_dump,
+- .reduce = nft_ct_set_reduce,
+ };
+
+ #ifdef CONFIG_NF_CONNTRACK_ZONES
+@@ -806,7 +762,6 @@ static const struct nft_expr_ops nft_ct_
+ .init = nft_ct_set_init,
+ .destroy = nft_ct_set_destroy,
+ .dump = nft_ct_set_dump,
+- .reduce = nft_ct_set_reduce,
+ };
+ #endif
+
+@@ -876,7 +831,6 @@ static const struct nft_expr_ops nft_not
+ .type = &nft_notrack_type,
+ .size = NFT_EXPR_SIZE(0),
+ .eval = nft_notrack_eval,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_notrack_type __read_mostly = {
+--- a/net/netfilter/nft_dup_netdev.c
++++ b/net/netfilter/nft_dup_netdev.c
+@@ -80,7 +80,6 @@ static const struct nft_expr_ops nft_dup
+ .eval = nft_dup_netdev_eval,
+ .init = nft_dup_netdev_init,
+ .dump = nft_dup_netdev_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ .offload = nft_dup_netdev_offload,
+ .offload_action = nft_dup_netdev_offload_action,
+ };
+--- a/net/netfilter/nft_dynset.c
++++ b/net/netfilter/nft_dynset.c
+@@ -429,7 +429,6 @@ static const struct nft_expr_ops nft_dyn
+ .activate = nft_dynset_activate,
+ .deactivate = nft_dynset_deactivate,
+ .dump = nft_dynset_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ struct nft_expr_type nft_dynset_type __read_mostly = {
+--- a/net/netfilter/nft_exthdr.c
++++ b/net/netfilter/nft_exthdr.c
+@@ -705,40 +705,12 @@ static int nft_exthdr_dump_strip(struct
+ return nft_exthdr_dump_common(skb, priv);
+ }
+
+-static bool nft_exthdr_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_exthdr *priv = nft_expr_priv(expr);
+- const struct nft_exthdr *exthdr;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- exthdr = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->type != exthdr->type ||
+- priv->op != exthdr->op ||
+- priv->flags != exthdr->flags ||
+- priv->offset != exthdr->offset ||
+- priv->len != exthdr->len) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return nft_expr_reduce_bitwise(track, expr);
+-}
+-
+ static const struct nft_expr_ops nft_exthdr_ipv6_ops = {
+ .type = &nft_exthdr_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_exthdr)),
+ .eval = nft_exthdr_ipv6_eval,
+ .init = nft_exthdr_init,
+ .dump = nft_exthdr_dump,
+- .reduce = nft_exthdr_reduce,
+ };
+
+ static const struct nft_expr_ops nft_exthdr_ipv4_ops = {
+@@ -747,7 +719,6 @@ static const struct nft_expr_ops nft_ext
+ .eval = nft_exthdr_ipv4_eval,
+ .init = nft_exthdr_ipv4_init,
+ .dump = nft_exthdr_dump,
+- .reduce = nft_exthdr_reduce,
+ };
+
+ static const struct nft_expr_ops nft_exthdr_tcp_ops = {
+@@ -756,7 +727,6 @@ static const struct nft_expr_ops nft_ext
+ .eval = nft_exthdr_tcp_eval,
+ .init = nft_exthdr_init,
+ .dump = nft_exthdr_dump,
+- .reduce = nft_exthdr_reduce,
+ };
+
+ static const struct nft_expr_ops nft_exthdr_tcp_set_ops = {
+@@ -765,7 +735,6 @@ static const struct nft_expr_ops nft_ext
+ .eval = nft_exthdr_tcp_set_eval,
+ .init = nft_exthdr_tcp_set_init,
+ .dump = nft_exthdr_dump_set,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nft_expr_ops nft_exthdr_tcp_strip_ops = {
+@@ -774,7 +743,6 @@ static const struct nft_expr_ops nft_ext
+ .eval = nft_exthdr_tcp_strip_eval,
+ .init = nft_exthdr_tcp_strip_init,
+ .dump = nft_exthdr_dump_strip,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nft_expr_ops nft_exthdr_sctp_ops = {
+@@ -783,7 +751,6 @@ static const struct nft_expr_ops nft_ext
+ .eval = nft_exthdr_sctp_eval,
+ .init = nft_exthdr_init,
+ .dump = nft_exthdr_dump,
+- .reduce = nft_exthdr_reduce,
+ };
+
+ #ifdef CONFIG_NFT_EXTHDR_DCCP
+@@ -793,7 +760,6 @@ static const struct nft_expr_ops nft_ext
+ .eval = nft_exthdr_dccp_eval,
+ .init = nft_exthdr_dccp_init,
+ .dump = nft_exthdr_dump,
+- .reduce = nft_exthdr_reduce,
+ };
+ #endif
+
+--- a/net/netfilter/nft_fib.c
++++ b/net/netfilter/nft_fib.c
+@@ -168,48 +168,6 @@ void nft_fib_store_result(void *reg, con
+ }
+ EXPORT_SYMBOL_GPL(nft_fib_store_result);
+
+-bool nft_fib_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_fib *priv = nft_expr_priv(expr);
+- unsigned int len = NFT_REG32_SIZE;
+- const struct nft_fib *fib;
+-
+- switch (priv->result) {
+- case NFT_FIB_RESULT_OIF:
+- break;
+- case NFT_FIB_RESULT_OIFNAME:
+- if (priv->flags & NFTA_FIB_F_PRESENT)
+- len = NFT_REG32_SIZE;
+- else
+- len = IFNAMSIZ;
+- break;
+- case NFT_FIB_RESULT_ADDRTYPE:
+- break;
+- default:
+- WARN_ON_ONCE(1);
+- break;
+- }
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, len);
+- return false;
+- }
+-
+- fib = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->result != fib->result ||
+- priv->flags != fib->flags) {
+- nft_reg_track_update(track, expr, priv->dreg, len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return false;
+-}
+-EXPORT_SYMBOL_GPL(nft_fib_reduce);
+-
+ MODULE_LICENSE("GPL");
+ MODULE_DESCRIPTION("Query routing table from nftables");
+ MODULE_AUTHOR("Florian Westphal <fw@strlen.de>");
+--- a/net/netfilter/nft_fib_inet.c
++++ b/net/netfilter/nft_fib_inet.c
+@@ -49,7 +49,6 @@ static const struct nft_expr_ops nft_fib
+ .init = nft_fib_init,
+ .dump = nft_fib_dump,
+ .validate = nft_fib_validate,
+- .reduce = nft_fib_reduce,
+ };
+
+ static struct nft_expr_type nft_fib_inet_type __read_mostly = {
+--- a/net/netfilter/nft_fib_netdev.c
++++ b/net/netfilter/nft_fib_netdev.c
+@@ -58,7 +58,6 @@ static const struct nft_expr_ops nft_fib
+ .init = nft_fib_init,
+ .dump = nft_fib_dump,
+ .validate = nft_fib_validate,
+- .reduce = nft_fib_reduce,
+ };
+
+ static struct nft_expr_type nft_fib_netdev_type __read_mostly = {
+--- a/net/netfilter/nft_flow_offload.c
++++ b/net/netfilter/nft_flow_offload.c
+@@ -224,7 +224,6 @@ static const struct nft_expr_ops nft_flo
+ .destroy = nft_flow_offload_destroy,
+ .validate = nft_flow_offload_validate,
+ .dump = nft_flow_offload_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_flow_offload_type __read_mostly = {
+--- a/net/netfilter/nft_fwd_netdev.c
++++ b/net/netfilter/nft_fwd_netdev.c
+@@ -228,7 +228,6 @@ static const struct nft_expr_ops nft_fwd
+ .init = nft_fwd_neigh_init,
+ .dump = nft_fwd_neigh_dump,
+ .validate = nft_fwd_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nft_expr_ops nft_fwd_netdev_ops = {
+@@ -238,7 +237,6 @@ static const struct nft_expr_ops nft_fwd
+ .init = nft_fwd_netdev_init,
+ .dump = nft_fwd_netdev_dump,
+ .validate = nft_fwd_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ .offload = nft_fwd_netdev_offload,
+ .offload_action = nft_fwd_netdev_offload_action,
+ };
+--- a/net/netfilter/nft_hash.c
++++ b/net/netfilter/nft_hash.c
+@@ -166,16 +166,6 @@ nla_put_failure:
+ return -1;
+ }
+
+-static bool nft_jhash_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_jhash *priv = nft_expr_priv(expr);
+-
+- nft_reg_track_cancel(track, priv->dreg, sizeof(u32));
+-
+- return false;
+-}
+-
+ static int nft_symhash_dump(struct sk_buff *skb,
+ const struct nft_expr *expr, bool reset)
+ {
+@@ -196,30 +186,6 @@ nla_put_failure:
+ return -1;
+ }
+
+-static bool nft_symhash_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- struct nft_symhash *priv = nft_expr_priv(expr);
+- struct nft_symhash *symhash;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, sizeof(u32));
+- return false;
+- }
+-
+- symhash = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->offset != symhash->offset ||
+- priv->modulus != symhash->modulus) {
+- nft_reg_track_update(track, expr, priv->dreg, sizeof(u32));
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return false;
+-}
+-
+ static struct nft_expr_type nft_hash_type;
+ static const struct nft_expr_ops nft_jhash_ops = {
+ .type = &nft_hash_type,
+@@ -227,7 +193,6 @@ static const struct nft_expr_ops nft_jha
+ .eval = nft_jhash_eval,
+ .init = nft_jhash_init,
+ .dump = nft_jhash_dump,
+- .reduce = nft_jhash_reduce,
+ };
+
+ static const struct nft_expr_ops nft_symhash_ops = {
+@@ -236,7 +201,6 @@ static const struct nft_expr_ops nft_sym
+ .eval = nft_symhash_eval,
+ .init = nft_symhash_init,
+ .dump = nft_symhash_dump,
+- .reduce = nft_symhash_reduce,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_immediate.c
++++ b/net/netfilter/nft_immediate.c
+@@ -320,17 +320,6 @@ static bool nft_immediate_offload_action
+ return false;
+ }
+
+-static bool nft_immediate_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_immediate_expr *priv = nft_expr_priv(expr);
+-
+- if (priv->dreg != NFT_REG_VERDICT)
+- nft_reg_track_cancel(track, priv->dreg, priv->dlen);
+-
+- return false;
+-}
+-
+ static const struct nft_expr_ops nft_imm_ops = {
+ .type = &nft_imm_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_immediate_expr)),
+@@ -341,7 +330,6 @@ static const struct nft_expr_ops nft_imm
+ .destroy = nft_immediate_destroy,
+ .dump = nft_immediate_dump,
+ .validate = nft_immediate_validate,
+- .reduce = nft_immediate_reduce,
+ .offload = nft_immediate_offload,
+ .offload_action = nft_immediate_offload_action,
+ };
+--- a/net/netfilter/nft_last.c
++++ b/net/netfilter/nft_last.c
+@@ -125,7 +125,6 @@ static const struct nft_expr_ops nft_las
+ .destroy = nft_last_destroy,
+ .clone = nft_last_clone,
+ .dump = nft_last_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ struct nft_expr_type nft_last_type __read_mostly = {
+--- a/net/netfilter/nft_limit.c
++++ b/net/netfilter/nft_limit.c
+@@ -243,7 +243,6 @@ static const struct nft_expr_ops nft_lim
+ .destroy = nft_limit_pkts_destroy,
+ .clone = nft_limit_pkts_clone,
+ .dump = nft_limit_pkts_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static void nft_limit_bytes_eval(const struct nft_expr *expr,
+@@ -299,7 +298,6 @@ static const struct nft_expr_ops nft_lim
+ .dump = nft_limit_bytes_dump,
+ .clone = nft_limit_bytes_clone,
+ .destroy = nft_limit_bytes_destroy,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_log.c
++++ b/net/netfilter/nft_log.c
+@@ -291,7 +291,6 @@ static const struct nft_expr_ops nft_log
+ .init = nft_log_init,
+ .destroy = nft_log_destroy,
+ .dump = nft_log_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_log_type __read_mostly = {
+--- a/net/netfilter/nft_lookup.c
++++ b/net/netfilter/nft_lookup.c
+@@ -269,17 +269,6 @@ static int nft_lookup_validate(const str
+ return 0;
+ }
+
+-static bool nft_lookup_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_lookup *priv = nft_expr_priv(expr);
+-
+- if (priv->set->flags & NFT_SET_MAP)
+- nft_reg_track_cancel(track, priv->dreg, priv->set->dlen);
+-
+- return false;
+-}
+-
+ static const struct nft_expr_ops nft_lookup_ops = {
+ .type = &nft_lookup_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_lookup)),
+@@ -290,7 +279,6 @@ static const struct nft_expr_ops nft_loo
+ .destroy = nft_lookup_destroy,
+ .dump = nft_lookup_dump,
+ .validate = nft_lookup_validate,
+- .reduce = nft_lookup_reduce,
+ };
+
+ struct nft_expr_type nft_lookup_type __read_mostly = {
+--- a/net/netfilter/nft_masq.c
++++ b/net/netfilter/nft_masq.c
+@@ -143,7 +143,6 @@ static const struct nft_expr_ops nft_mas
+ .destroy = nft_masq_ipv4_destroy,
+ .dump = nft_masq_dump,
+ .validate = nft_masq_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_masq_ipv4_type __read_mostly = {
+@@ -171,7 +170,6 @@ static const struct nft_expr_ops nft_mas
+ .destroy = nft_masq_ipv6_destroy,
+ .dump = nft_masq_dump,
+ .validate = nft_masq_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_masq_ipv6_type __read_mostly = {
+@@ -213,7 +211,6 @@ static const struct nft_expr_ops nft_mas
+ .destroy = nft_masq_inet_destroy,
+ .dump = nft_masq_dump,
+ .validate = nft_masq_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_masq_inet_type __read_mostly = {
+--- a/net/netfilter/nft_meta.c
++++ b/net/netfilter/nft_meta.c
+@@ -743,60 +743,16 @@ static int nft_meta_get_offload(struct n
+ return 0;
+ }
+
+-bool nft_meta_get_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_meta *priv = nft_expr_priv(expr);
+- const struct nft_meta *meta;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- meta = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->key != meta->key ||
+- priv->dreg != meta->dreg) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return nft_expr_reduce_bitwise(track, expr);
+-}
+-EXPORT_SYMBOL_GPL(nft_meta_get_reduce);
+-
+ static const struct nft_expr_ops nft_meta_get_ops = {
+ .type = &nft_meta_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_meta)),
+ .eval = nft_meta_get_eval,
+ .init = nft_meta_get_init,
+ .dump = nft_meta_get_dump,
+- .reduce = nft_meta_get_reduce,
+ .validate = nft_meta_get_validate,
+ .offload = nft_meta_get_offload,
+ };
+
+-static bool nft_meta_set_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- int i;
+-
+- for (i = 0; i < NFT_REG32_NUM; i++) {
+- if (!track->regs[i].selector)
+- continue;
+-
+- if (track->regs[i].selector->ops != &nft_meta_get_ops)
+- continue;
+-
+- __nft_reg_track_cancel(track, i);
+- }
+-
+- return false;
+-}
+-
+ static const struct nft_expr_ops nft_meta_set_ops = {
+ .type = &nft_meta_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_meta)),
+@@ -804,7 +760,6 @@ static const struct nft_expr_ops nft_met
+ .init = nft_meta_set_init,
+ .destroy = nft_meta_set_destroy,
+ .dump = nft_meta_set_dump,
+- .reduce = nft_meta_set_reduce,
+ .validate = nft_meta_set_validate,
+ };
+
+--- a/net/netfilter/nft_nat.c
++++ b/net/netfilter/nft_nat.c
+@@ -320,7 +320,6 @@ static const struct nft_expr_ops nft_nat
+ .destroy = nft_nat_destroy,
+ .dump = nft_nat_dump,
+ .validate = nft_nat_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_nat_type __read_mostly = {
+@@ -351,7 +350,6 @@ static const struct nft_expr_ops nft_nat
+ .destroy = nft_nat_destroy,
+ .dump = nft_nat_dump,
+ .validate = nft_nat_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_inet_nat_type __read_mostly = {
+--- a/net/netfilter/nft_numgen.c
++++ b/net/netfilter/nft_numgen.c
+@@ -84,16 +84,6 @@ err:
+ return err;
+ }
+
+-static bool nft_ng_inc_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_ng_inc *priv = nft_expr_priv(expr);
+-
+- nft_reg_track_cancel(track, priv->dreg, NFT_REG32_SIZE);
+-
+- return false;
+-}
+-
+ static int nft_ng_dump(struct sk_buff *skb, enum nft_registers dreg,
+ u32 modulus, enum nft_ng_types type, u32 offset)
+ {
+@@ -178,16 +168,6 @@ static int nft_ng_random_dump(struct sk_
+ priv->offset);
+ }
+
+-static bool nft_ng_random_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_ng_random *priv = nft_expr_priv(expr);
+-
+- nft_reg_track_cancel(track, priv->dreg, NFT_REG32_SIZE);
+-
+- return false;
+-}
+-
+ static struct nft_expr_type nft_ng_type;
+ static const struct nft_expr_ops nft_ng_inc_ops = {
+ .type = &nft_ng_type,
+@@ -196,7 +176,6 @@ static const struct nft_expr_ops nft_ng_
+ .init = nft_ng_inc_init,
+ .destroy = nft_ng_inc_destroy,
+ .dump = nft_ng_inc_dump,
+- .reduce = nft_ng_inc_reduce,
+ };
+
+ static const struct nft_expr_ops nft_ng_random_ops = {
+@@ -205,7 +184,6 @@ static const struct nft_expr_ops nft_ng_
+ .eval = nft_ng_random_eval,
+ .init = nft_ng_random_init,
+ .dump = nft_ng_random_dump,
+- .reduce = nft_ng_random_reduce,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_objref.c
++++ b/net/netfilter/nft_objref.c
+@@ -123,7 +123,6 @@ static const struct nft_expr_ops nft_obj
+ .deactivate = nft_objref_deactivate,
+ .dump = nft_objref_dump,
+ .validate = nft_objref_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ struct nft_objref_map {
+@@ -245,7 +244,6 @@ static const struct nft_expr_ops nft_obj
+ .destroy = nft_objref_map_destroy,
+ .dump = nft_objref_map_dump,
+ .validate = nft_objref_map_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_osf.c
++++ b/net/netfilter/nft_osf.c
+@@ -131,30 +131,6 @@ static int nft_osf_validate(const struct
+ return nft_chain_validate_hooks(ctx->chain, hooks);
+ }
+
+-static bool nft_osf_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- struct nft_osf *priv = nft_expr_priv(expr);
+- struct nft_osf *osf;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, NFT_OSF_MAXGENRELEN);
+- return false;
+- }
+-
+- osf = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->flags != osf->flags ||
+- priv->ttl != osf->ttl) {
+- nft_reg_track_update(track, expr, priv->dreg, NFT_OSF_MAXGENRELEN);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return false;
+-}
+-
+ static struct nft_expr_type nft_osf_type;
+ static const struct nft_expr_ops nft_osf_op = {
+ .eval = nft_osf_eval,
+@@ -163,7 +139,6 @@ static const struct nft_expr_ops nft_osf
+ .dump = nft_osf_dump,
+ .type = &nft_osf_type,
+ .validate = nft_osf_validate,
+- .reduce = nft_osf_reduce,
+ };
+
+ static struct nft_expr_type nft_osf_type __read_mostly = {
+--- a/net/netfilter/nft_payload.c
++++ b/net/netfilter/nft_payload.c
+@@ -256,31 +256,6 @@ nla_put_failure:
+ return -1;
+ }
+
+-static bool nft_payload_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_payload *priv = nft_expr_priv(expr);
+- const struct nft_payload *payload;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- payload = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->base != payload->base ||
+- priv->offset != payload->offset ||
+- priv->len != payload->len) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return nft_expr_reduce_bitwise(track, expr);
+-}
+-
+ static bool nft_payload_offload_mask(struct nft_offload_reg *reg,
+ u32 priv_len, u32 field_len)
+ {
+@@ -584,7 +559,6 @@ static const struct nft_expr_ops nft_pay
+ .eval = nft_payload_eval,
+ .init = nft_payload_init,
+ .dump = nft_payload_dump,
+- .reduce = nft_payload_reduce,
+ .offload = nft_payload_offload,
+ };
+
+@@ -594,7 +568,6 @@ const struct nft_expr_ops nft_payload_fa
+ .eval = nft_payload_eval,
+ .init = nft_payload_init,
+ .dump = nft_payload_dump,
+- .reduce = nft_payload_reduce,
+ .offload = nft_payload_offload,
+ };
+
+@@ -1022,32 +995,12 @@ nla_put_failure:
+ return -1;
+ }
+
+-static bool nft_payload_set_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- int i;
+-
+- for (i = 0; i < NFT_REG32_NUM; i++) {
+- if (!track->regs[i].selector)
+- continue;
+-
+- if (track->regs[i].selector->ops != &nft_payload_ops &&
+- track->regs[i].selector->ops != &nft_payload_fast_ops)
+- continue;
+-
+- __nft_reg_track_cancel(track, i);
+- }
+-
+- return false;
+-}
+-
+ static const struct nft_expr_ops nft_payload_set_ops = {
+ .type = &nft_payload_type,
+ .size = NFT_EXPR_SIZE(sizeof(struct nft_payload_set)),
+ .eval = nft_payload_set_eval,
+ .init = nft_payload_set_init,
+ .dump = nft_payload_set_dump,
+- .reduce = nft_payload_set_reduce,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_queue.c
++++ b/net/netfilter/nft_queue.c
+@@ -191,7 +191,6 @@ static const struct nft_expr_ops nft_que
+ .init = nft_queue_init,
+ .dump = nft_queue_dump,
+ .validate = nft_queue_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nft_expr_ops nft_queue_sreg_ops = {
+@@ -201,7 +200,6 @@ static const struct nft_expr_ops nft_que
+ .init = nft_queue_sreg_init,
+ .dump = nft_queue_sreg_dump,
+ .validate = nft_queue_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static const struct nft_expr_ops *
+--- a/net/netfilter/nft_quota.c
++++ b/net/netfilter/nft_quota.c
+@@ -266,7 +266,6 @@ static const struct nft_expr_ops nft_quo
+ .destroy = nft_quota_destroy,
+ .clone = nft_quota_clone,
+ .dump = nft_quota_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_quota_type __read_mostly = {
+--- a/net/netfilter/nft_range.c
++++ b/net/netfilter/nft_range.c
+@@ -138,7 +138,6 @@ static const struct nft_expr_ops nft_ran
+ .eval = nft_range_eval,
+ .init = nft_range_init,
+ .dump = nft_range_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ struct nft_expr_type nft_range_type __read_mostly = {
+--- a/net/netfilter/nft_redir.c
++++ b/net/netfilter/nft_redir.c
+@@ -146,7 +146,6 @@ static const struct nft_expr_ops nft_red
+ .destroy = nft_redir_ipv4_destroy,
+ .dump = nft_redir_dump,
+ .validate = nft_redir_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_redir_ipv4_type __read_mostly = {
+@@ -174,7 +173,6 @@ static const struct nft_expr_ops nft_red
+ .destroy = nft_redir_ipv6_destroy,
+ .dump = nft_redir_dump,
+ .validate = nft_redir_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_redir_ipv6_type __read_mostly = {
+@@ -203,7 +201,6 @@ static const struct nft_expr_ops nft_red
+ .destroy = nft_redir_inet_destroy,
+ .dump = nft_redir_dump,
+ .validate = nft_redir_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_redir_inet_type __read_mostly = {
+--- a/net/netfilter/nft_reject_inet.c
++++ b/net/netfilter/nft_reject_inet.c
+@@ -79,7 +79,6 @@ static const struct nft_expr_ops nft_rej
+ .init = nft_reject_init,
+ .dump = nft_reject_dump,
+ .validate = nft_reject_inet_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_reject_inet_type __read_mostly = {
+--- a/net/netfilter/nft_reject_netdev.c
++++ b/net/netfilter/nft_reject_netdev.c
+@@ -158,7 +158,6 @@ static const struct nft_expr_ops nft_rej
+ .init = nft_reject_init,
+ .dump = nft_reject_dump,
+ .validate = nft_reject_netdev_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_reject_netdev_type __read_mostly = {
+--- a/net/netfilter/nft_rt.c
++++ b/net/netfilter/nft_rt.c
+@@ -195,7 +195,6 @@ static const struct nft_expr_ops nft_rt_
+ .init = nft_rt_get_init,
+ .dump = nft_rt_get_dump,
+ .validate = nft_rt_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ struct nft_expr_type nft_rt_type __read_mostly = {
+--- a/net/netfilter/nft_socket.c
++++ b/net/netfilter/nft_socket.c
+@@ -249,31 +249,6 @@ static int nft_socket_dump(struct sk_buf
+ return 0;
+ }
+
+-static bool nft_socket_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_socket *priv = nft_expr_priv(expr);
+- const struct nft_socket *socket;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- socket = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->key != socket->key ||
+- priv->dreg != socket->dreg ||
+- priv->level != socket->level) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return nft_expr_reduce_bitwise(track, expr);
+-}
+-
+ static int nft_socket_validate(const struct nft_ctx *ctx,
+ const struct nft_expr *expr)
+ {
+@@ -296,7 +271,6 @@ static const struct nft_expr_ops nft_soc
+ .init = nft_socket_init,
+ .dump = nft_socket_dump,
+ .validate = nft_socket_validate,
+- .reduce = nft_socket_reduce,
+ };
+
+ static struct nft_expr_type nft_socket_type __read_mostly = {
+--- a/net/netfilter/nft_synproxy.c
++++ b/net/netfilter/nft_synproxy.c
+@@ -290,7 +290,6 @@ static const struct nft_expr_ops nft_syn
+ .dump = nft_synproxy_dump,
+ .type = &nft_synproxy_type,
+ .validate = nft_synproxy_validate,
+- .reduce = NFT_REDUCE_READONLY,
+ };
+
+ static struct nft_expr_type nft_synproxy_type __read_mostly = {
+--- a/net/netfilter/nft_tproxy.c
++++ b/net/netfilter/nft_tproxy.c
+@@ -331,7 +331,6 @@ static const struct nft_expr_ops nft_tpr
+ .init = nft_tproxy_init,
+ .destroy = nft_tproxy_destroy,
+ .dump = nft_tproxy_dump,
+- .reduce = NFT_REDUCE_READONLY,
+ .validate = nft_tproxy_validate,
+ };
+
+--- a/net/netfilter/nft_tunnel.c
++++ b/net/netfilter/nft_tunnel.c
+@@ -124,31 +124,6 @@ nla_put_failure:
+ return -1;
+ }
+
+-static bool nft_tunnel_get_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_tunnel *priv = nft_expr_priv(expr);
+- const struct nft_tunnel *tunnel;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- tunnel = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->key != tunnel->key ||
+- priv->dreg != tunnel->dreg ||
+- priv->mode != tunnel->mode) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return false;
+-}
+-
+ static struct nft_expr_type nft_tunnel_type;
+ static const struct nft_expr_ops nft_tunnel_get_ops = {
+ .type = &nft_tunnel_type,
+@@ -156,7 +131,6 @@ static const struct nft_expr_ops nft_tun
+ .eval = nft_tunnel_get_eval,
+ .init = nft_tunnel_get_init,
+ .dump = nft_tunnel_get_dump,
+- .reduce = nft_tunnel_get_reduce,
+ };
+
+ static struct nft_expr_type nft_tunnel_type __read_mostly = {
+--- a/net/netfilter/nft_xfrm.c
++++ b/net/netfilter/nft_xfrm.c
+@@ -259,32 +259,6 @@ static int nft_xfrm_validate(const struc
+ return nft_chain_validate_hooks(ctx->chain, hooks);
+ }
+
+-static bool nft_xfrm_reduce(struct nft_regs_track *track,
+- const struct nft_expr *expr)
+-{
+- const struct nft_xfrm *priv = nft_expr_priv(expr);
+- const struct nft_xfrm *xfrm;
+-
+- if (!nft_reg_track_cmp(track, expr, priv->dreg)) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- xfrm = nft_expr_priv(track->regs[priv->dreg].selector);
+- if (priv->key != xfrm->key ||
+- priv->dreg != xfrm->dreg ||
+- priv->dir != xfrm->dir ||
+- priv->spnum != xfrm->spnum) {
+- nft_reg_track_update(track, expr, priv->dreg, priv->len);
+- return false;
+- }
+-
+- if (!track->regs[priv->dreg].bitwise)
+- return true;
+-
+- return nft_expr_reduce_bitwise(track, expr);
+-}
+-
+ static struct nft_expr_type nft_xfrm_type;
+ static const struct nft_expr_ops nft_xfrm_get_ops = {
+ .type = &nft_xfrm_type,
+@@ -293,7 +267,6 @@ static const struct nft_expr_ops nft_xfr
+ .init = nft_xfrm_get_init,
+ .dump = nft_xfrm_get_dump,
+ .validate = nft_xfrm_validate,
+- .reduce = nft_xfrm_reduce,
+ };
+
+ static struct nft_expr_type nft_xfrm_type __read_mostly = {
--- /dev/null
+From stable+bounces-288332-greg=kroah.com@vger.kernel.org Thu Jul 23 16:30:50 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 10:18:35 -0400
+Subject: netfilter: nft_fib: reject fib expression on the netdev egress hook
+To: stable@vger.kernel.org
+Cc: Theodor Arsenij Larionov-Trichkine <theodorlarionov@gmail.com>, Florian Westphal <fw@strlen.de>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723141835.3216078-2-sashal@kernel.org>
+
+From: Theodor Arsenij Larionov-Trichkine <theodorlarionov@gmail.com>
+
+[ Upstream commit d07955dd34ecae17d35d8c7d0a273a3fba653a8c ]
+
+A fib expression in a netdev egress base chain dereferences nft_in(pkt),
+NULL on the transmit path, causing a NULL pointer dereference at eval.
+nft_fib_validate() masks the hook with NF_INET_* values, but netdev hook
+numbers are a separate enum that aliases them (NF_NETDEV_EGRESS ==
+NF_INET_LOCAL_IN), so an egress chain passes validation and then faults.
+
+Add nft_fib_netdev_validate() that limits each result/flag to the netdev
+hook where the device it reads exists: the input-device cases (OIF,
+OIFNAME, ADDRTYPE with F_IIF) to ingress, the output-device case (ADDRTYPE
+with F_OIF) to egress, ADDRTYPE with no device flag to both. Also restrict
+nft_fib_validate() to NFPROTO_IPV4/IPV6/INET so its NF_INET_* masks are
+not applied to another family's hooks.
+
+Fixes: 42df6e1d221d ("netfilter: Introduce egress hook")
+Cc: stable@vger.kernel.org
+Link: https://lore.kernel.org/netfilter-devel/ajxsjcDOnwllMfoR@strlen.de/
+Signed-off-by: Theodor Arsenij Larionov-Trichkine <theodorlarionov@gmail.com>
+Signed-off-by: Florian Westphal <fw@strlen.de>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ net/netfilter/nft_fib.c | 9 +++++++++
+ net/netfilter/nft_fib_netdev.c | 29 ++++++++++++++++++++++++++++-
+ 2 files changed, 37 insertions(+), 1 deletion(-)
+
+--- a/net/netfilter/nft_fib.c
++++ b/net/netfilter/nft_fib.c
+@@ -31,6 +31,15 @@ int nft_fib_validate(const struct nft_ct
+ const struct nft_fib *priv = nft_expr_priv(expr);
+ unsigned int hooks;
+
++ switch (ctx->family) {
++ case NFPROTO_IPV4:
++ case NFPROTO_IPV6:
++ case NFPROTO_INET:
++ break;
++ default:
++ return -EOPNOTSUPP;
++ }
++
+ switch (priv->result) {
+ case NFT_FIB_RESULT_OIF:
+ case NFT_FIB_RESULT_OIFNAME:
+--- a/net/netfilter/nft_fib_netdev.c
++++ b/net/netfilter/nft_fib_netdev.c
+@@ -50,6 +50,33 @@ static void nft_fib_netdev_eval(const st
+ regs->verdict.code = NFT_BREAK;
+ }
+
++static int nft_fib_netdev_validate(const struct nft_ctx *ctx,
++ const struct nft_expr *expr)
++{
++ const struct nft_fib *priv = nft_expr_priv(expr);
++ unsigned int hooks;
++
++ switch (priv->result) {
++ case NFT_FIB_RESULT_OIF:
++ case NFT_FIB_RESULT_OIFNAME:
++ hooks = (1 << NF_NETDEV_INGRESS);
++ break;
++ case NFT_FIB_RESULT_ADDRTYPE:
++ if (priv->flags & NFTA_FIB_F_IIF)
++ hooks = (1 << NF_NETDEV_INGRESS);
++ else if (priv->flags & NFTA_FIB_F_OIF)
++ hooks = (1 << NF_NETDEV_EGRESS);
++ else
++ hooks = (1 << NF_NETDEV_INGRESS) |
++ (1 << NF_NETDEV_EGRESS);
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return nft_chain_validate_hooks(ctx->chain, hooks);
++}
++
+ static struct nft_expr_type nft_fib_netdev_type;
+ static const struct nft_expr_ops nft_fib_netdev_ops = {
+ .type = &nft_fib_netdev_type,
+@@ -57,7 +84,7 @@ static const struct nft_expr_ops nft_fib
+ .eval = nft_fib_netdev_eval,
+ .init = nft_fib_init,
+ .dump = nft_fib_dump,
+- .validate = nft_fib_validate,
++ .validate = nft_fib_netdev_validate,
+ };
+
+ static struct nft_expr_type nft_fib_netdev_type __read_mostly = {
--- /dev/null
+From stable+bounces-288492-greg=kroah.com@vger.kernel.org Thu Jul 23 20:27:08 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:19:06 -0400
+Subject: NFSD: pass nfsd_file to nfsd_iter_read()
+To: stable@vger.kernel.org
+Cc: Mike Snitzer <snitzer@kernel.org>, Jeff Layton <jlayton@kernel.org>, NeilBrown <neil@brown.name>, Christoph Hellwig <hch@lst.de>, Chuck Lever <chuck.lever@oracle.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723181911.3438372-1-sashal@kernel.org>
+
+From: Mike Snitzer <snitzer@kernel.org>
+
+[ Upstream commit 803bc849f0039291f546ba0e2237faebeb5c073e ]
+
+Prepare for nfsd_iter_read() to use the DIO alignment stored in
+nfsd_file by passing the nfsd_file to nfsd_iter_read() rather than
+just the file which is associaed with the nfsd_file.
+
+This means nfsd4_encode_readv() now also needs the nfsd_file rather
+than the file. Instead of changing the file arg to be the nfsd_file,
+we discard the file arg as the nfsd_file (and indeed the file) is
+already available via the "read" argument.
+
+Signed-off-by: Mike Snitzer <snitzer@kernel.org>
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Reviewed-by: NeilBrown <neil@brown.name>
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/nfsd/nfs4xdr.c | 8 ++++----
+ fs/nfsd/vfs.c | 7 ++++---
+ fs/nfsd/vfs.h | 2 +-
+ 3 files changed, 9 insertions(+), 8 deletions(-)
+
+--- a/fs/nfsd/nfs4xdr.c
++++ b/fs/nfsd/nfs4xdr.c
+@@ -4478,7 +4478,7 @@ out_err:
+
+ static __be32 nfsd4_encode_readv(struct nfsd4_compoundres *resp,
+ struct nfsd4_read *read,
+- struct file *file, unsigned long maxcount)
++ unsigned long maxcount)
+ {
+ struct xdr_stream *xdr = resp->xdr;
+ unsigned int base = xdr->buf->page_len & ~PAGE_MASK;
+@@ -4489,7 +4489,7 @@ static __be32 nfsd4_encode_readv(struct
+ if (xdr_reserve_space_vec(xdr, maxcount) < 0)
+ return nfserr_resource;
+
+- nfserr = nfsd_iter_read(resp->rqstp, read->rd_fhp, file,
++ nfserr = nfsd_iter_read(resp->rqstp, read->rd_fhp, read->rd_nf,
+ read->rd_offset, &maxcount, base,
+ &read->rd_eof);
+ read->rd_length = maxcount;
+@@ -4536,7 +4536,7 @@ nfsd4_encode_read(struct nfsd4_compoundr
+ if (file->f_op->splice_read && splice_ok)
+ nfserr = nfsd4_encode_splice_read(resp, read, file, maxcount);
+ else
+- nfserr = nfsd4_encode_readv(resp, read, file, maxcount);
++ nfserr = nfsd4_encode_readv(resp, read, maxcount);
+ if (nfserr) {
+ xdr_truncate_encode(xdr, eof_offset);
+ return nfserr;
+@@ -5432,7 +5432,7 @@ nfsd4_encode_read_plus_data(struct nfsd4
+ if (file->f_op->splice_read && splice_ok)
+ nfserr = nfsd4_encode_splice_read(resp, read, file, maxcount);
+ else
+- nfserr = nfsd4_encode_readv(resp, read, file, maxcount);
++ nfserr = nfsd4_encode_readv(resp, read, maxcount);
+ if (nfserr)
+ return nfserr;
+
+--- a/fs/nfsd/vfs.c
++++ b/fs/nfsd/vfs.c
+@@ -1078,7 +1078,7 @@ __be32 nfsd_splice_read(struct svc_rqst
+ * nfsd_iter_read - Perform a VFS read using an iterator
+ * @rqstp: RPC transaction context
+ * @fhp: file handle of file to be read
+- * @file: opened struct file of file to be read
++ * @nf: opened struct nfsd_file of file to be read
+ * @offset: starting byte offset
+ * @count: IN: requested number of bytes; OUT: number of bytes read
+ * @base: offset in first page of read buffer
+@@ -1091,9 +1091,10 @@ __be32 nfsd_splice_read(struct svc_rqst
+ * returned.
+ */
+ __be32 nfsd_iter_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
+- struct file *file, loff_t offset, unsigned long *count,
++ struct nfsd_file *nf, loff_t offset, unsigned long *count,
+ unsigned int base, u32 *eof)
+ {
++ struct file *file = nf->nf_file;
+ unsigned long v, total;
+ struct iov_iter iter;
+ struct kiocb kiocb;
+@@ -1346,7 +1347,7 @@ __be32 nfsd_read(struct svc_rqst *rqstp,
+ if (file->f_op->splice_read && nfsd_read_splice_ok(rqstp))
+ err = nfsd_splice_read(rqstp, fhp, file, offset, count, eof);
+ else
+- err = nfsd_iter_read(rqstp, fhp, file, offset, count, 0, eof);
++ err = nfsd_iter_read(rqstp, fhp, nf, offset, count, 0, eof);
+
+ nfsd_file_put(nf);
+ trace_nfsd_read_done(rqstp, fhp, offset, *count);
+--- a/fs/nfsd/vfs.h
++++ b/fs/nfsd/vfs.h
+@@ -122,7 +122,7 @@ __be32 nfsd_splice_read(struct svc_rqst
+ unsigned long *count,
+ u32 *eof);
+ __be32 nfsd_iter_read(struct svc_rqst *rqstp, struct svc_fh *fhp,
+- struct file *file, loff_t offset,
++ struct nfsd_file *nf, loff_t offset,
+ unsigned long *count, unsigned int base,
+ u32 *eof);
+ bool nfsd_read_splice_ok(struct svc_rqst *rqstp);
--- /dev/null
+From stable+bounces-289995-greg=kroah.com@vger.kernel.org Tue Jul 28 15:39:10 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 09:30:37 -0400
+Subject: octeontx2-af: cn10k: restrict VF LMTLINE sharing to its own PF
+To: stable@vger.kernel.org
+Cc: Junrui Luo <moonafterrain@outlook.com>, Yuhao Jiang <danisjiang@gmail.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728133037.3965153-2-sashal@kernel.org>
+
+From: Junrui Luo <moonafterrain@outlook.com>
+
+[ Upstream commit 8cdcf3d2caacdee7ddd363705fb4d93b0c1a0915 ]
+
+rvu_mbox_handler_lmtst_tbl_setup() uses req->base_pcifunc as a direct
+index into the LMT map table to read another function's LMTLINE
+physical base address and copy it into the caller's own LMT map table
+entry. The mailbox dispatcher authenticates req->hdr.pcifunc from the
+IRQ source, but req->base_pcifunc is a separate payload field and is
+not sanitized.
+
+Reject the request with -EPERM when a VF caller's base_pcifunc is not a
+valid function under its own PF. is_pf_func_valid() bounds the FUNC field
+to the PF's configured VF count, keeping the computed index inside the
+caller's own slot block.
+
+Fixes: 893ae97214c3 ("octeontx2-af: cn10k: Support configurable LMTST regions")
+Reported-by: Yuhao Jiang <danisjiang@gmail.com>
+Cc: stable@vger.kernel.org
+Signed-off-by: Junrui Luo <moonafterrain@outlook.com>
+Link: https://patch.msgid.link/SYBPR01MB78811656934E713B77DA6CEDAFE62@SYBPR01MB7881.ausprd01.prod.outlook.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/marvell/octeontx2/af/rvu_cn10k.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_cn10k.c
++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_cn10k.c
+@@ -178,6 +178,15 @@ int rvu_mbox_handler_lmtst_tbl_setup(str
+ * pcifunc (will be the one who is calling this mailbox).
+ */
+ if (req->base_pcifunc) {
++ /* A VF is untrusted and must not redirect its LMTLINE to
++ * another PF's region, so confine VF callers to their own PF.
++ */
++ if (is_vf(req->hdr.pcifunc) &&
++ (!is_pf_func_valid(rvu, req->base_pcifunc) ||
++ rvu_get_pf(rvu->pdev, req->hdr.pcifunc) !=
++ rvu_get_pf(rvu->pdev, req->base_pcifunc)))
++ return -EPERM;
++
+ /* Calculating the LMT table index equivalent to primary
+ * pcifunc.
+ */
--- /dev/null
+From stable+bounces-289996-greg=kroah.com@vger.kernel.org Tue Jul 28 15:39:12 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Tue, 28 Jul 2026 09:30:36 -0400
+Subject: octeontx2-af: validate body pcifunc in rvu_mbox_handler_rep_event_notify
+To: stable@vger.kernel.org
+Cc: Michael Bommarito <michael.bommarito@gmail.com>, Jakub Kicinski <kuba@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260728133037.3965153-1-sashal@kernel.org>
+
+From: Michael Bommarito <michael.bommarito@gmail.com>
+
+[ Upstream commit 2156a29aecfffa2eb7c558255690084efbe9f3b0 ]
+
+rvu_mbox_handler_rep_event_notify() in drivers/net/ethernet/marvell/
+octeontx2/af/rvu_rep.c queues a sender-controlled REP_EVENT_NOTIFY
+request body verbatim, and rvu_rep_up_notify() then forwards
+event->pcifunc (the nested body field, distinct from the
+AF-normalised header pcifunc) into rvu_get_pfvf(), rvu_get_pf() and
+the AF->PF mailbox device index without any bounds check.
+
+A VF attached to a PF that has been put into switchdev
+representor mode reaches this path: the VF mailbox handler
+otx2_pfvf_mbox_handler() forwards every message id including
+MBOX_MSG_REP_EVENT_NOTIFY to AF without an allowlist, and the AF
+dispatcher rewrites only msg->pcifunc, leaving struct
+rep_event::pcifunc attacker-controlled. The sibling
+rvu_mbox_handler_esw_cfg() refuses requests whose header pcifunc
+is not rvu->rep_pcifunc; this handler has no equivalent gate.
+
+An out-of-range body pcifunc selects an &rvu->pf[]/&rvu->hwvf[]
+element past the allocated array and, for RVU_EVENT_MAC_ADDR_CHANGE,
+turns into a six-byte attacker-chosen OOB ether_addr_copy() target
+inside the queued worker; KASAN reports a slab-out-of-bounds write
+in rvu_rep_wq_handler.
+
+Reject malformed requests at the handler entry by gating on
+is_pf_func_valid(), which is already the canonical PF/VF range check
+in this driver; expose it via rvu.h so callers in rvu_rep.c can use
+it instead of open-coding the same range arithmetic.
+
+Fixes: b8fea84a0468 ("octeontx2-pf: Add support to sync link state between representor and VFs")
+Cc: stable@vger.kernel.org
+Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
+Link: https://patch.msgid.link/20260520154157.1439319-1-michael.bommarito@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+Stable-dep-of: 8cdcf3d2caac ("octeontx2-af: cn10k: restrict VF LMTLINE sharing to its own PF")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/net/ethernet/marvell/octeontx2/af/rvu.c | 2 +-
+ drivers/net/ethernet/marvell/octeontx2/af/rvu.h | 1 +
+ drivers/net/ethernet/marvell/octeontx2/af/rvu_rep.c | 8 ++++++++
+ 3 files changed, 10 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.c
+@@ -435,7 +435,7 @@ struct rvu_pfvf *rvu_get_pfvf(struct rvu
+ return &rvu->pf[rvu_get_pf(rvu->pdev, pcifunc)];
+ }
+
+-static bool is_pf_func_valid(struct rvu *rvu, u16 pcifunc)
++bool is_pf_func_valid(struct rvu *rvu, u16 pcifunc)
+ {
+ int pf, vf, nvfs;
+ u64 cfg;
+--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu.h
+@@ -900,6 +900,7 @@ u16 rvu_get_rsrc_mapcount(struct rvu_pfv
+ struct rvu_pfvf *rvu_get_pfvf(struct rvu *rvu, int pcifunc);
+ void rvu_get_pf_numvfs(struct rvu *rvu, int pf, int *numvfs, int *hwvf);
+ bool is_block_implemented(struct rvu_hwinfo *hw, int blkaddr);
++bool is_pf_func_valid(struct rvu *rvu, u16 pcifunc);
+ bool is_pffunc_map_valid(struct rvu *rvu, u16 pcifunc, int blktype);
+ int rvu_get_lf(struct rvu *rvu, struct rvu_block *block, u16 pcifunc, u16 slot);
+ int rvu_lf_reset(struct rvu *rvu, struct rvu_block *block, int lf);
+--- a/drivers/net/ethernet/marvell/octeontx2/af/rvu_rep.c
++++ b/drivers/net/ethernet/marvell/octeontx2/af/rvu_rep.c
+@@ -97,6 +97,14 @@ int rvu_mbox_handler_rep_event_notify(st
+ {
+ struct rep_evtq_ent *qentry;
+
++ /* The mailbox dispatcher normalises only the header pcifunc; the
++ * nested struct rep_event::pcifunc body field is sender-controlled
++ * and is later used by rvu_rep_up_notify() to index rvu->pf[] /
++ * rvu->hwvf[]. Reject out-of-range body selectors before queueing.
++ */
++ if (!is_pf_func_valid(rvu, req->pcifunc))
++ return -EINVAL;
++
+ qentry = kmalloc(sizeof(*qentry), GFP_ATOMIC);
+ if (!qentry)
+ return -ENOMEM;
--- /dev/null
+From stable+bounces-289547-greg=kroah.com@vger.kernel.org Mon Jul 27 15:50:18 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 09:31:39 -0400
+Subject: ovl: use linked upper dentry in copy-up tmpfile
+To: stable@vger.kernel.org
+Cc: Souvik Banerjee <souvik@amlalabs.com>, Amir Goldstein <amir73il@gmail.com>, Miklos Szeredi <mszeredi@redhat.com>, "Christian Brauner (Amutable)" <brauner@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727133139.539439-1-sashal@kernel.org>
+
+From: Souvik Banerjee <souvik@amlalabs.com>
+
+[ Upstream commit e348eecd4d8fa8d18a5157ff59f7be1dc59c5928 ]
+
+ovl_copy_up_tmpfile() stores the disconnected O_TMPFILE dentry as the
+overlay's upper dentry reference via ovl_inode_update(). vfs_tmpfile()
+allocated this dentry via d_alloc(parentpath->dentry, &slash_name), so
+d_name is "/" and d_parent is c->workdir. Local upper filesystems
+(ext4, btrfs, xfs, ...) immediately rename it to "#<inum>" via
+d_mark_tmpfile() inside their ->tmpfile() op; FUSE and virtiofs do
+not, so both fields stay that way. Neither identifies the destination
+directory and filename where ovl_do_link() actually linked the file.
+
+When the upper filesystem implements ->d_revalidate() (e.g. FUSE or
+virtiofs), ovl_revalidate_real() calls it with the dentry's parent
+inode and a snapshot of d_name. The server tries to look up "/" inside
+c->workdir, fails, and overlayfs reports -ESTALE.
+
+This causes persistent ESTALE errors for any file that was copied up via
+the tmpfile path, breaking dpkg, apt, and other tools that do
+rename-over-existing on overlayfs with a FUSE/virtiofs upper.
+
+Before commit 6b52243f633e ("ovl: fold copy-up helpers into callers"),
+the tmpfile copy-up path used a dedicated helper ovl_link_tmpfile()
+that captured the linked destination dentry returned by ovl_do_link():
+
+ err = ovl_do_link(temp, udir, upper);
+ ...
+ if (!err)
+ *newdentry = dget(upper);
+
+and published it via ovl_inode_update(d_inode(c->dentry), newdentry).
+The fold inlined ovl_do_link() into ovl_copy_up_tmpfile() but dropped
+the dget(upper) capture, and rewrote the publish line as
+ovl_inode_update(d_inode(c->dentry), dget(temp)) — where temp is the
+disconnected O_TMPFILE dentry.
+
+Fix by keeping a reference to the linked destination dentry after
+ovl_do_link() succeeds, and publishing that dentry at the existing
+ovl_inode_update() call site. The non-tmpfile/workdir path continues to
+publish the renamed temporary dentry.
+
+Reproducer:
+ - Mount overlayfs with virtiofs (or a FUSE fs whose server advertises
+ FUSE_TMPFILE) as upper
+ - Run: dpkg -i <any .deb>
+ - Observe: "error installing new file '...': Stale file handle"
+
+Fixes: 6b52243f633e ("ovl: fold copy-up helpers into callers")
+Cc: stable@vger.kernel.org # v4.20+
+Signed-off-by: Souvik Banerjee <souvik@amlalabs.com>
+Link: https://patch.msgid.link/20260501232735.2610824-1-souvik@amlalabs.com
+Reviewed-by: Amir Goldstein <amir73il@gmail.com>
+Reviewed-by: Miklos Szeredi <mszeredi@redhat.com>
+Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
+[ adapted scoped credential and creation helpers to explicit credential, locking, lookup, and cleanup handling ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/overlayfs/copy_up.c | 12 ++++++++++--
+ 1 file changed, 10 insertions(+), 2 deletions(-)
+
+--- a/fs/overlayfs/copy_up.c
++++ b/fs/overlayfs/copy_up.c
+@@ -864,7 +864,7 @@ static int ovl_copy_up_tmpfile(struct ov
+ {
+ struct ovl_fs *ofs = OVL_FS(c->dentry->d_sb);
+ struct inode *udir = d_inode(c->destdir);
+- struct dentry *temp, *upper;
++ struct dentry *temp, *upper, *newdentry = NULL;
+ struct file *tmpfile;
+ struct ovl_cu_creds cc;
+ int err;
+@@ -901,6 +901,14 @@ static int ovl_copy_up_tmpfile(struct ov
+ err = PTR_ERR(upper);
+ if (!IS_ERR(upper)) {
+ err = ovl_do_link(ofs, temp, udir, upper);
++ if (!err) {
++ /*
++ * Record the linked dentry -- not the disconnected
++ * O_TMPFILE dentry -- so that ->d_revalidate() on
++ * the upper fs sees the real parent/name.
++ */
++ newdentry = dget(upper);
++ }
+ dput(upper);
+ }
+ inode_unlock(udir);
+@@ -916,7 +924,7 @@ static int ovl_copy_up_tmpfile(struct ov
+
+ if (!c->metacopy)
+ ovl_set_upperdata(d_inode(c->dentry));
+- ovl_inode_update(d_inode(c->dentry), dget(temp));
++ ovl_inode_update(d_inode(c->dentry), newdentry);
+
+ out:
+ ovl_end_write(c->dentry);
--- /dev/null
+From stable+bounces-288449-greg=kroah.com@vger.kernel.org Thu Jul 23 19:13:25 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 13:03:25 -0400
+Subject: pinctrl: renesas: rzg2l: Use -ENOTSUPP instead of -EOPNOTSUPP
+To: stable@vger.kernel.org
+Cc: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>, Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>, Geert Uytterhoeven <geert+renesas@glider.be>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723170325.3399838-1-sashal@kernel.org>
+
+From: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
+
+[ Upstream commit c1492da3939c89372929e062d731f328f7693f1e ]
+
+The pinctrl and GPIO core code make exceptions for the -ENOTSUPP error
+code. One such example is gpio_set_config_with_argument_optional(),
+which returns success when gpio_set_config_with_argument() returns
+-ENOTSUPP, but reports failure for all other error codes.
+
+Returning -EOPNOTSUPP from the pinctrl driver on the unsupported pinctrl
+operation may lead to boot failures when pinctrl drivers implements
+struct gpio_chip::set_config, the system uses GPIO hogs, and the
+struct gpio_chip::set_config implementation returns -EOPNOTSUPP for the
+unsupported operations.
+
+Return -ENOTSUPP for the unsupported pinctrl operation.
+
+Fixes: 560c633d378a ("pinctrl: renesas: rzg2l: Drop oen_read and oen_write callbacks")
+Fixes: c4c4637eb57f ("pinctrl: renesas: Add RZ/G2L pin and gpio controller driver")
+Cc: stable@vger.kernel.org
+Signed-off-by: Claudiu Beznea <claudiu.beznea.uj@bp.renesas.com>
+Reviewed-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
+Reviewed-by: Geert Uytterhoeven <geert+renesas@glider.be>
+Tested-by: Geert Uytterhoeven <geert+renesas@glider.be>
+Link: https://patch.msgid.link/20260515124008.2947838-2-claudiu.beznea@kernel.org
+Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pinctrl/renesas/pinctrl-rzg2l.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/pinctrl/renesas/pinctrl-rzg2l.c
++++ b/drivers/pinctrl/renesas/pinctrl-rzg2l.c
+@@ -1097,7 +1097,7 @@ static int rzg2l_read_oen(struct rzg2l_p
+ int bit;
+
+ if (!pctrl->data->pin_to_oen_bit)
+- return -EOPNOTSUPP;
++ return -ENOTSUPP;
+
+ bit = pctrl->data->pin_to_oen_bit(pctrl, _pin);
+ if (bit < 0)
+@@ -1115,7 +1115,7 @@ static int rzg2l_write_oen(struct rzg2l_
+ int bit;
+
+ if (!pctrl->data->pin_to_oen_bit)
+- return -EOPNOTSUPP;
++ return -ENOTSUPP;
+
+ bit = pctrl->data->pin_to_oen_bit(pctrl, _pin);
+ if (bit < 0)
+@@ -1552,7 +1552,7 @@ static int rzg2l_pinctrl_pinconf_set(str
+ break;
+
+ default:
+- return -EOPNOTSUPP;
++ return -ENOTSUPP;
+ }
+ }
+
+@@ -1634,7 +1634,7 @@ static int rzg2l_pinctrl_pinconf_group_g
+
+ /* Check config matching between to pin */
+ if (i && prev_config != *config)
+- return -EOPNOTSUPP;
++ return -ENOTSUPP;
+
+ prev_config = *config;
+ }
--- /dev/null
+From stable+bounces-290868-greg=kroah.com@vger.kernel.org Wed Jul 29 20:12:23 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 14:02:07 -0400
+Subject: pmdomain: imx93-blk-ctrl: convert to devm_* only
+To: stable@vger.kernel.org
+Cc: Marco Felsch <m.felsch@pengutronix.de>, Frank Li <Frank.Li@nxp.com>, Ulf Hansson <ulf.hansson@linaro.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729180208.3683189-1-sashal@kernel.org>
+
+From: Marco Felsch <m.felsch@pengutronix.de>
+
+[ Upstream commit 52becc142280eeef828d19f9cd01fd653b588786 ]
+
+Convert the driver to devm_ APIs only by making use of
+devm_add_action_or_reset() and devm_pm_runtime_enable() to simplify the
+probe error path and to drop the .remove() callback. This also ensures
+that the device release order equals the device probe error path order.
+
+Furthermore drop the dev_set_drvdata() usage since the only user was the
+.remove() callback which is removed by this commit.
+
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Signed-off-by: Marco Felsch <m.felsch@pengutronix.de>
+Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
+Stable-dep-of: 99611233f8cd ("pmdomain: imx93-blk-ctrl: Extract PHY as shared domain for DSI/CSI")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pmdomain/imx/imx93-blk-ctrl.c | 66 ++++++++++++++--------------------
+ 1 file changed, 29 insertions(+), 37 deletions(-)
+
+--- a/drivers/pmdomain/imx/imx93-blk-ctrl.c
++++ b/drivers/pmdomain/imx/imx93-blk-ctrl.c
+@@ -188,6 +188,20 @@ static int imx93_blk_ctrl_power_off(stru
+ return 0;
+ }
+
++static void imx93_release_genpd_provider(void *data)
++{
++ struct device_node *of_node = data;
++
++ of_genpd_del_provider(of_node);
++}
++
++static void imx93_release_pm_genpd(void *data)
++{
++ struct generic_pm_domain *genpd = data;
++
++ pm_genpd_remove(genpd);
++}
++
+ static struct lock_class_key blk_ctrl_genpd_lock_class;
+
+ static int imx93_blk_ctrl_probe(struct platform_device *pdev)
+@@ -258,10 +272,8 @@ static int imx93_blk_ctrl_probe(struct p
+ domain->clks[j].id = data->clk_names[j];
+
+ ret = devm_clk_bulk_get(dev, data->num_clks, domain->clks);
+- if (ret) {
+- dev_err_probe(dev, ret, "failed to get clock\n");
+- goto cleanup_pds;
+- }
++ if (ret)
++ return dev_err_probe(dev, ret, "failed to get clock\n");
+
+ domain->genpd.name = data->name;
+ domain->genpd.power_on = imx93_blk_ctrl_power_on;
+@@ -269,11 +281,12 @@ static int imx93_blk_ctrl_probe(struct p
+ domain->bc = bc;
+
+ ret = pm_genpd_init(&domain->genpd, NULL, true);
+- if (ret) {
+- dev_err_probe(dev, ret, "failed to init power domain\n");
+- goto cleanup_pds;
+- }
++ if (ret)
++ return dev_err_probe(dev, ret, "failed to init power domain\n");
+
++ ret = devm_add_action_or_reset(dev, imx93_release_pm_genpd, &domain->genpd);
++ if (ret)
++ return dev_err_probe(dev, ret, "failed to add pm_genpd release callback\n");
+ /*
+ * We use runtime PM to trigger power on/off of the upstream GPC
+ * domain, as a strict hierarchical parent/child power domain
+@@ -290,39 +303,19 @@ static int imx93_blk_ctrl_probe(struct p
+ bc->onecell_data.domains[i] = &domain->genpd;
+ }
+
+- pm_runtime_enable(dev);
++ ret = devm_pm_runtime_enable(dev);
++ if (ret)
++ return dev_err_probe(dev, ret, "failed to enable pm-runtime\n");
+
+ ret = of_genpd_add_provider_onecell(dev->of_node, &bc->onecell_data);
+- if (ret) {
+- dev_err_probe(dev, ret, "failed to add power domain provider\n");
+- goto cleanup_pds;
+- }
++ if (ret)
++ return dev_err_probe(dev, ret, "failed to add power domain provider\n");
+
+- dev_set_drvdata(dev, bc);
++ ret = devm_add_action_or_reset(dev, imx93_release_genpd_provider, dev->of_node);
++ if (ret)
++ return dev_err_probe(dev, ret, "failed to add genpd_provider release callback\n");
+
+ return 0;
+-
+-cleanup_pds:
+- for (i--; i >= 0; i--)
+- pm_genpd_remove(&bc->domains[i].genpd);
+-
+- return ret;
+-}
+-
+-static void imx93_blk_ctrl_remove(struct platform_device *pdev)
+-{
+- struct imx93_blk_ctrl *bc = dev_get_drvdata(&pdev->dev);
+- int i;
+-
+- of_genpd_del_provider(pdev->dev.of_node);
+-
+- pm_runtime_disable(&pdev->dev);
+-
+- for (i = 0; i < bc->onecell_data.num_domains; i++) {
+- struct imx93_blk_ctrl_domain *domain = &bc->domains[i];
+-
+- pm_genpd_remove(&domain->genpd);
+- }
+ }
+
+ static const struct imx93_blk_ctrl_domain_data imx93_media_blk_ctl_domain_data[] = {
+@@ -457,7 +450,6 @@ MODULE_DEVICE_TABLE(of, imx93_blk_ctrl_o
+
+ static struct platform_driver imx93_blk_ctrl_driver = {
+ .probe = imx93_blk_ctrl_probe,
+- .remove = imx93_blk_ctrl_remove,
+ .driver = {
+ .name = "imx93-blk-ctrl",
+ .of_match_table = imx93_blk_ctrl_of_match,
--- /dev/null
+From stable+bounces-290869-greg=kroah.com@vger.kernel.org Wed Jul 29 20:12:25 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 29 Jul 2026 14:02:08 -0400
+Subject: pmdomain: imx93-blk-ctrl: Extract PHY as shared domain for DSI/CSI
+To: stable@vger.kernel.org
+Cc: Guoniu Zhou <guoniu.zhou@oss.nxp.com>, Frank Li <Frank.Li@nxp.com>, Peng Fan <peng.fan@nxp.com>, Ulf Hansson <ulfh@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260729180208.3683189-2-sashal@kernel.org>
+
+From: Guoniu Zhou <guoniu.zhou@oss.nxp.com>
+
+[ Upstream commit 99611233f8cda833169fa6487d5dacdf189e5cb0 ]
+
+The MIPI DSI and CSI domains share control bits for clock and reset, which
+can lead to incorrect behavior if one domain disables the shared resource
+while the other is still active.
+
+To fix the issue, introduce a shared MIPI PHY power domain to own the
+common resources and make DSI and CSI its subdomains. This ensures the
+shared bits are properly managed and not disabled while still in use.
+
+Fixes: e9aa77d413c9 ("soc: imx: add i.MX93 media blk ctrl driver")
+Cc: stable@vger.kernel.org
+Signed-off-by: Guoniu Zhou <guoniu.zhou@oss.nxp.com>
+Reviewed-by: Frank Li <Frank.Li@nxp.com>
+Reviewed-by: Peng Fan <peng.fan@nxp.com>
+Signed-off-by: Ulf Hansson <ulfh@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/pmdomain/imx/imx93-blk-ctrl.c | 60 ++++++++++++++++++++++++++++++++--
+ 1 file changed, 58 insertions(+), 2 deletions(-)
+
+--- a/drivers/pmdomain/imx/imx93-blk-ctrl.c
++++ b/drivers/pmdomain/imx/imx93-blk-ctrl.c
+@@ -47,6 +47,8 @@
+
+ #define PRIO(X) (X)
+
++#define BLK_CTRL_NO_PARENT UINT_MAX
++
+ struct imx93_blk_ctrl_domain;
+
+ struct imx93_blk_ctrl {
+@@ -67,12 +69,18 @@ struct imx93_blk_ctrl_qos {
+ u32 cfg_prio;
+ };
+
++struct imx93_blk_ctrl_subdomain_link {
++ struct generic_pm_domain *parent;
++ struct generic_pm_domain *subdomain;
++};
++
+ struct imx93_blk_ctrl_domain_data {
+ const char *name;
+ const char * const *clk_names;
+ int num_clks;
+ u32 rst_mask;
+ u32 clk_mask;
++ u32 parent;
+ int num_qos;
+ struct imx93_blk_ctrl_qos qos[DOMAIN_MAX_QOS];
+ };
+@@ -202,6 +210,13 @@ static void imx93_release_pm_genpd(void
+ pm_genpd_remove(genpd);
+ }
+
++static void imx93_release_subdomain(void *data)
++{
++ struct imx93_blk_ctrl_subdomain_link *link = data;
++
++ pm_genpd_remove_subdomain(link->parent, link->subdomain);
++}
++
+ static struct lock_class_key blk_ctrl_genpd_lock_class;
+
+ static int imx93_blk_ctrl_probe(struct platform_device *pdev)
+@@ -303,6 +318,34 @@ static int imx93_blk_ctrl_probe(struct p
+ bc->onecell_data.domains[i] = &domain->genpd;
+ }
+
++ for (i = 0; i < bc_data->num_domains; i++) {
++ struct imx93_blk_ctrl_domain *domain = &bc->domains[i];
++ const struct imx93_blk_ctrl_domain_data *data = domain->data;
++ struct imx93_blk_ctrl_subdomain_link *link;
++
++ if (bc_data->skip_mask & BIT(i) ||
++ data->parent == BLK_CTRL_NO_PARENT)
++ continue;
++
++ link = devm_kzalloc(dev, sizeof(*link), GFP_KERNEL);
++ if (!link)
++ return -ENOMEM;
++
++ link->parent = &bc->domains[data->parent].genpd;
++ link->subdomain = &domain->genpd;
++
++ ret = pm_genpd_add_subdomain(&bc->domains[data->parent].genpd,
++ &domain->genpd);
++ if (ret)
++ return dev_err_probe(dev, ret, "failed to add subdomain %s\n",
++ domain->genpd.name);
++
++ ret = devm_add_action_or_reset(dev, imx93_release_subdomain, link);
++ if (ret)
++ return dev_err_probe(dev, ret,
++ "failed to add subdomain release callback\n");
++ }
++
+ ret = devm_pm_runtime_enable(dev);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to enable pm-runtime\n");
+@@ -323,8 +366,9 @@ static const struct imx93_blk_ctrl_domai
+ .name = "mediablk-mipi-dsi",
+ .clk_names = (const char *[]){ "dsi" },
+ .num_clks = 1,
+- .rst_mask = BIT(11) | BIT(12),
+- .clk_mask = BIT(11) | BIT(12),
++ .rst_mask = BIT(11),
++ .clk_mask = BIT(11),
++ .parent = IMX93_MEDIABLK_PD_MIPI_PHY,
+ },
+ [IMX93_MEDIABLK_PD_MIPI_CSI] = {
+ .name = "mediablk-mipi-csi",
+@@ -332,6 +376,7 @@ static const struct imx93_blk_ctrl_domai
+ .num_clks = 2,
+ .rst_mask = BIT(9) | BIT(10),
+ .clk_mask = BIT(9) | BIT(10),
++ .parent = IMX93_MEDIABLK_PD_MIPI_PHY,
+ },
+ [IMX93_MEDIABLK_PD_PXP] = {
+ .name = "mediablk-pxp",
+@@ -339,6 +384,7 @@ static const struct imx93_blk_ctrl_domai
+ .num_clks = 1,
+ .rst_mask = BIT(7) | BIT(8),
+ .clk_mask = BIT(7) | BIT(8),
++ .parent = BLK_CTRL_NO_PARENT,
+ .num_qos = 2,
+ .qos = {
+ {
+@@ -360,6 +406,7 @@ static const struct imx93_blk_ctrl_domai
+ .num_clks = 2,
+ .rst_mask = BIT(4) | BIT(5) | BIT(6),
+ .clk_mask = BIT(4) | BIT(5) | BIT(6),
++ .parent = BLK_CTRL_NO_PARENT,
+ .num_qos = 1,
+ .qos = {
+ {
+@@ -376,6 +423,7 @@ static const struct imx93_blk_ctrl_domai
+ .num_clks = 1,
+ .rst_mask = BIT(2) | BIT(3),
+ .clk_mask = BIT(2) | BIT(3),
++ .parent = BLK_CTRL_NO_PARENT,
+ .num_qos = 4,
+ .qos = {
+ {
+@@ -401,6 +449,14 @@ static const struct imx93_blk_ctrl_domai
+ }
+ }
+ },
++ [IMX93_MEDIABLK_PD_MIPI_PHY] = {
++ .name = "mediablk-mipi-phy",
++ .clk_names = NULL,
++ .num_clks = 0,
++ .rst_mask = BIT(12),
++ .clk_mask = BIT(12),
++ .parent = BLK_CTRL_NO_PARENT,
++ },
+ };
+
+ static const struct regmap_range imx93_media_blk_ctl_yes_ranges[] = {
--- /dev/null
+From stable+bounces-288526-greg=kroah.com@vger.kernel.org Thu Jul 23 22:00:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 15:58:58 -0400
+Subject: remoteproc: xlnx: Check remote core state
+To: stable@vger.kernel.org
+Cc: Tanmay Shah <tanmay.shah@amd.com>, Beleswar Padhi <b-padhi@ti.com>, Michal Simek <michal.simek@amd.com>, Mathieu Poirier <mathieu.poirier@linaro.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723195858.3684427-1-sashal@kernel.org>
+
+From: Tanmay Shah <tanmay.shah@amd.com>
+
+[ Upstream commit a48df51d23138388900995add2854cda4aa68e55 ]
+
+The remote state is set to RPROC_DETACHED if the resource table is found
+in the memory. However, this can be wrong if the remote is not started,
+but firmware is still loaded in the memory. Use PM_GET_NODE_STATUS call
+to the firmware to request the state of the RPU node. If the RPU is
+actually out of reset and running, only then move the remote state to
+RPROC_DETACHED, otherwise keep the remote state to RPROC_OFFLINE.
+
+Signed-off-by: Tanmay Shah <tanmay.shah@amd.com>
+Fixes: bca4b02ef92e ("remoteproc: xlnx: Add attach detach support")
+Reviewed-by: Beleswar Padhi <b-padhi@ti.com>
+Acked-by: Michal Simek <michal.simek@amd.com>
+Cc: stable@vger.kernel.org
+Link: https://lore.kernel.org/r/20260428221855.313752-1-tanmay.shah@amd.com
+Signed-off-by: Mathieu Poirier <mathieu.poirier@linaro.org>
+[ replaced the unavailable zynqmp_pm_get_node_status() helper with a direct zynqmp_pm_invoke_fn() call and exported it for modular builds. ]
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/firmware/xilinx/zynqmp.c | 1
+ drivers/remoteproc/xlnx_r5_remoteproc.c | 50 +++++++++++++++++++++++++-------
+ include/linux/firmware/xlnx-zynqmp.h | 13 ++++++++
+ 3 files changed, 54 insertions(+), 10 deletions(-)
+
+--- a/drivers/firmware/xilinx/zynqmp.c
++++ b/drivers/firmware/xilinx/zynqmp.c
+@@ -461,6 +461,7 @@ int zynqmp_pm_invoke_fn(u32 pm_api_id, u
+ return do_fw_call(ret_payload, 8, smc_arg[0], smc_arg[1], smc_arg[2], smc_arg[3],
+ smc_arg[4], smc_arg[5], smc_arg[6], smc_arg[7]);
+ }
++EXPORT_SYMBOL_GPL(zynqmp_pm_invoke_fn);
+
+ static u32 pm_api_version;
+ static u32 pm_tz_version;
+--- a/drivers/remoteproc/xlnx_r5_remoteproc.c
++++ b/drivers/remoteproc/xlnx_r5_remoteproc.c
+@@ -959,16 +959,6 @@ static struct zynqmp_r5_core *zynqmp_r5_
+ goto free_rproc;
+ }
+
+- /*
+- * If firmware is already available in the memory then move rproc state
+- * to DETACHED. Firmware can be preloaded via debugger or by any other
+- * agent (processors) in the system.
+- * If firmware isn't available in the memory and resource table isn't
+- * found, then rproc state remains OFFLINE.
+- */
+- if (!zynqmp_r5_get_rsc_table_va(r5_core))
+- r5_rproc->state = RPROC_DETACHED;
+-
+ r5_core->rproc = r5_rproc;
+ return r5_core;
+
+@@ -1221,6 +1211,7 @@ static int zynqmp_r5_core_init(struct zy
+ {
+ struct device *dev = cluster->dev;
+ struct zynqmp_r5_core *r5_core;
++ u32 payload[PAYLOAD_ARG_CNT];
+ int ret = -EINVAL, i;
+
+ r5_core = cluster->r5_cores[0];
+@@ -1266,6 +1257,45 @@ static int zynqmp_r5_core_init(struct zy
+ ret = zynqmp_r5_get_sram_banks(r5_core);
+ if (ret)
+ return ret;
++
++ /*
++ * It is possible that firmware is loaded into the memory, but
++ * RPU (remote) is not running. In such case, RPU state will be
++ * moved to RPROC_DETACHED wrongfully. To avoid it first make
++ * sure RPU is power-on and out of reset before parsing for the
++ * resource table.
++ */
++ ret = zynqmp_pm_feature(PM_GET_NODE_STATUS);
++ if (ret < PM_API_VERSION_2)
++ ret = -EOPNOTSUPP;
++ else
++ ret = zynqmp_pm_invoke_fn(PM_GET_NODE_STATUS, payload, 1,
++ r5_core->pm_domain_id);
++ if (ret) {
++ dev_warn(r5_core->dev,
++ "failed to get rpu node status, err %d\n", ret);
++ continue;
++ }
++
++ /*
++ * If RPU state is power on and out of reset i.e. running, then
++ * assign RPROC_DETACHED state. If the RPU is not out of reset
++ * then do not attempt to attach to the remote processor.
++ */
++ if (payload[1] == PM_NODE_RUNNING) {
++ /*
++ * Not all the firmware that is running on the remote
++ * core is expected to have the resource table. The
++ * firmware might not use RPMsg at all, and in that case
++ * resource table becomes irrelevant. However, we still
++ * need to make sure that running core is not reported
++ * as offline. so do not decide remote core state based
++ * on the resource table availability
++ */
++ if (zynqmp_r5_get_rsc_table_va(r5_core))
++ dev_dbg(r5_core->dev, "rsc tbl not found\n");
++ r5_core->rproc->state = RPROC_DETACHED;
++ }
+ }
+
+ return 0;
+--- a/include/linux/firmware/xlnx-zynqmp.h
++++ b/include/linux/firmware/xlnx-zynqmp.h
+@@ -164,6 +164,7 @@ enum pm_api_cb_id {
+ enum pm_api_id {
+ PM_API_FEATURES = 0,
+ PM_GET_API_VERSION = 1,
++ PM_GET_NODE_STATUS = 3,
+ PM_REGISTER_NOTIFIER = 5,
+ PM_FORCE_POWERDOWN = 8,
+ PM_REQUEST_WAKEUP = 10,
+@@ -545,6 +546,18 @@ enum pm_gem_config_type {
+ };
+
+ /**
++ * enum pm_node_status - Device node status provided by xilpm fw
++ * @PM_NODE_UNUSED: Device is not used
++ * @PM_NODE_RUNNING: Device is power-on and out of reset
++ * @PM_NODE_HALT: Device is power-on but in the reset state
++ */
++enum pm_node_status {
++ PM_NODE_UNUSED = 0,
++ PM_NODE_RUNNING = 1,
++ PM_NODE_HALT = 12,
++};
++
++/**
+ * struct zynqmp_pm_query_data - PM query data
+ * @qid: query ID
+ * @arg1: Argument 1 of query data
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
--- /dev/null
+From stable+bounces-288493-greg=kroah.com@vger.kernel.org Thu Jul 23 20:21:07 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:19:08 -0400
+Subject: SUNRPC: Add helpers to convert xdr_buf byte ranges to scatterlists
+To: stable@vger.kernel.org
+Cc: Chuck Lever <chuck.lever@oracle.com>, Jeff Layton <jlayton@kernel.org>, Anna Schumaker <anna.schumaker@hammerspace.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723181911.3438372-3-sashal@kernel.org>
+
+From: Chuck Lever <chuck.lever@oracle.com>
+
+[ Upstream commit e9be933959b581effd426f93b86654f5fbf0c574 ]
+
+The crypto/krb5 library accepts data in scatterlist form, but
+the GSS-API layer presents RPC payloads as struct xdr_buf.
+Bridge that gap with a pair of helper functions:
+
+ xdr_buf_to_sg() - populate a caller-supplied scatterlist
+ array from a byte range
+ xdr_buf_to_sg_alloc() - populate a caller-supplied inline
+ scatterlist, chaining to a heap-
+ allocated overflow for large payloads
+
+The inline array (typically stack-allocated at eight entries)
+covers the common case of small RPCs with no heap allocation
+on the encrypt/decrypt path. Only buffers spanning many pages
+incur a kmalloc for the chained extension.
+
+The segment-walking logic follows the same head, page array,
+tail traversal as xdr_process_buf(), but populates a
+scatterlist directly rather than invoking a per-segment
+callback. sg_next() traversal makes the walker safe for
+chained scatterlists. Once subsequent patches reroute all
+per-message crypto operations through crypto/krb5,
+xdr_process_buf() loses its last callers and is removed.
+
+Assisted-by: Claude:claude-opus-4-6
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Acked-by: Anna Schumaker <anna.schumaker@hammerspace.com>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/sunrpc/xdr.h | 15 +++
+ net/sunrpc/xdr.c | 199 +++++++++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 214 insertions(+)
+
+--- a/include/linux/sunrpc/xdr.h
++++ b/include/linux/sunrpc/xdr.h
+@@ -140,6 +140,21 @@ int xdr_alloc_bvec(struct xdr_buf *buf,
+ void xdr_free_bvec(struct xdr_buf *buf);
+ unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
+ const struct xdr_buf *xdr);
++int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset,
++ unsigned int len, struct scatterlist *sg, unsigned int nsg);
++int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset,
++ unsigned int len, struct scatterlist *sg_head,
++ unsigned int sg_head_nents,
++ struct scatterlist **sg_overflow, gfp_t gfp);
++
++/*
++ * Inline scatterlist entries for xdr_buf_to_sg_alloc(). Sized to cover the
++ * head kvec, tail kvec, and a few page fragments without any heap allocation.
++ */
++enum {
++ XDR_BUF_TO_SG_NENTS = 8,
++};
++
+
+ static inline __be32 *xdr_encode_array(__be32 *p, const void *s, unsigned int len)
+ {
+--- a/net/sunrpc/xdr.c
++++ b/net/sunrpc/xdr.c
+@@ -192,6 +192,205 @@ bvec_overflow:
+ EXPORT_SYMBOL_GPL(xdr_buf_to_bvec);
+
+ /**
++ * xdr_buf_to_sg - Populate a scatterlist from an xdr_buf range
++ * @buf: xdr_buf to map
++ * @offset: starting byte offset within @buf
++ * @len: number of bytes to cover
++ * @sg: scatterlist array initialized with sg_init_table()
++ * @nsg: number of entries available in @sg
++ *
++ * @sg is traversed with sg_next(), so callers may pass a list
++ * assembled with sg_chain().
++ *
++ * Return: on success, the number of scatterlist entries used; the
++ * last used entry is marked with sg_mark_end(). On failure, a
++ * negative errno.
++ */
++int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset,
++ unsigned int len, struct scatterlist *sg, unsigned int nsg)
++{
++ unsigned int page_len, thislen, page_offset;
++ struct scatterlist *cur = sg, *prev = NULL;
++ int nents = 0;
++ int i;
++
++ if (len == 0)
++ return 0;
++
++ if (offset >= buf->head[0].iov_len) {
++ offset -= buf->head[0].iov_len;
++ } else {
++ thislen = min_t(unsigned int,
++ buf->head[0].iov_len - offset, len);
++ if (nents >= nsg)
++ return -ENOSPC;
++ sg_set_buf(cur, buf->head[0].iov_base + offset,
++ thislen);
++ prev = cur;
++ cur = sg_next(cur);
++ nents++;
++ len -= thislen;
++ offset = 0;
++ }
++ if (len == 0)
++ goto done;
++
++ if (offset >= buf->page_len) {
++ offset -= buf->page_len;
++ } else {
++ page_len = min(buf->page_len - offset, len);
++ len -= page_len;
++ page_offset = (offset + buf->page_base) & (PAGE_SIZE - 1);
++ i = (offset + buf->page_base) >> PAGE_SHIFT;
++ thislen = PAGE_SIZE - page_offset;
++ do {
++ if (thislen > page_len)
++ thislen = page_len;
++ if (nents >= nsg)
++ return -ENOSPC;
++ sg_set_page(cur, buf->pages[i],
++ thislen, page_offset);
++ prev = cur;
++ cur = sg_next(cur);
++ nents++;
++ page_len -= thislen;
++ i++;
++ page_offset = 0;
++ thislen = PAGE_SIZE;
++ } while (page_len != 0);
++ offset = 0;
++ }
++ if (len == 0)
++ goto done;
++
++ if (offset < buf->tail[0].iov_len) {
++ thislen = min_t(unsigned int,
++ buf->tail[0].iov_len - offset, len);
++ if (nents >= nsg)
++ return -ENOSPC;
++ sg_set_buf(cur, buf->tail[0].iov_base + offset,
++ thislen);
++ prev = cur;
++ nents++;
++ len -= thislen;
++ }
++ if (len != 0)
++ return -EINVAL;
++
++done:
++ if (prev)
++ sg_mark_end(prev);
++ return nents;
++}
++EXPORT_SYMBOL_GPL(xdr_buf_to_sg);
++
++/*
++ * Count the scatterlist entries needed to cover [offset, offset + len)
++ * within @buf. Mirrors the walk in xdr_buf_to_sg() so the caller can
++ * size an allocation that matches the requested sub-range rather than
++ * the full xdr_buf.
++ */
++static unsigned int xdr_buf_sg_nents(const struct xdr_buf *buf,
++ unsigned int offset, unsigned int len)
++{
++ unsigned int nsg = 0, thislen, page_offset;
++
++ if (len == 0)
++ return 0;
++
++ if (offset < buf->head[0].iov_len) {
++ thislen = min_t(unsigned int,
++ buf->head[0].iov_len - offset, len);
++ nsg++;
++ len -= thislen;
++ offset = 0;
++ } else {
++ offset -= buf->head[0].iov_len;
++ }
++ if (len == 0)
++ return nsg;
++
++ if (offset < buf->page_len) {
++ thislen = min(buf->page_len - offset, len);
++ page_offset = (offset + buf->page_base) & (PAGE_SIZE - 1);
++ nsg += DIV_ROUND_UP(page_offset + thislen, PAGE_SIZE);
++ len -= thislen;
++ offset = 0;
++ } else {
++ offset -= buf->page_len;
++ }
++ if (len == 0)
++ return nsg;
++
++ if (offset < buf->tail[0].iov_len)
++ nsg++;
++ return nsg;
++}
++
++/**
++ * xdr_buf_to_sg_alloc - Populate a scatterlist for an xdr_buf range
++ * @buf: xdr_buf to map
++ * @offset: starting byte offset within @buf
++ * @len: number of bytes to cover
++ * @sg_head: caller-provided scatterlist array (typically stack-allocated)
++ * @sg_head_nents: number of entries in @sg_head
++ * @sg_overflow: OUT: chained extension, or NULL when @sg_head sufficed
++ * @gfp: memory allocation flags for overflow
++ *
++ * Populates @sg_head directly when the xdr_buf fits. When more
++ * entries are needed, an overflow scatterlist is allocated and
++ * chained from @sg_head so that the result is traversable with
++ * sg_next().
++ *
++ * Return: on success, the number of populated scatterlist entries
++ * (counting only data entries, not chain entries). @sg_head is
++ * the head of the resulting list. Caller must kfree @sg_overflow
++ * when done. On failure, a negative errno.
++ */
++int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset,
++ unsigned int len, struct scatterlist *sg_head,
++ unsigned int sg_head_nents,
++ struct scatterlist **sg_overflow, gfp_t gfp)
++{
++ unsigned int nsg;
++ int ret;
++
++ *sg_overflow = NULL;
++ if (len == 0)
++ return 0;
++
++ nsg = xdr_buf_sg_nents(buf, offset, len);
++ if (nsg == 0)
++ return -EINVAL;
++
++ if (nsg <= sg_head_nents) {
++ sg_init_table(sg_head, nsg);
++ } else {
++ /* +1 replaces the slot sg_chain() consumes as the link. */
++ unsigned int overflow_nents = nsg - sg_head_nents + 1;
++ struct scatterlist *overflow;
++
++ overflow = kmalloc_array(overflow_nents, sizeof(*overflow),
++ gfp);
++ if (!overflow)
++ return -ENOMEM;
++
++ sg_init_table(sg_head, sg_head_nents);
++ sg_init_table(overflow, overflow_nents);
++ sg_chain(sg_head, sg_head_nents, overflow);
++ *sg_overflow = overflow;
++ }
++
++ ret = xdr_buf_to_sg(buf, offset, len, sg_head, nsg);
++ if (ret < 0) {
++ kfree(*sg_overflow);
++ *sg_overflow = NULL;
++ }
++ return ret;
++}
++EXPORT_SYMBOL_GPL(xdr_buf_to_sg_alloc);
++
++/**
+ * xdr_inline_pages - Prepare receive buffer for a large reply
+ * @xdr: xdr_buf into which reply will be placed
+ * @offset: expected offset where data payload will start, in bytes
--- /dev/null
+From stable+bounces-288491-greg=kroah.com@vger.kernel.org Thu Jul 23 20:21:30 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:19:07 -0400
+Subject: sunrpc: allocate a separate bvec array for socket sends
+To: stable@vger.kernel.org
+Cc: Jeff Layton <jlayton@kernel.org>, NeilBrown <neil@brown.name>, Chuck Lever <chuck.lever@oracle.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723181911.3438372-2-sashal@kernel.org>
+
+From: Jeff Layton <jlayton@kernel.org>
+
+[ Upstream commit 6b3b697d65d46a0f640216a3f6c72856c159c567 ]
+
+svc_tcp_sendmsg() calls xdr_buf_to_bvec() with the second slot of
+rq_bvec as the start, but doesn't reduce the array length by one, which
+could lead to an array overrun. Also, rq_bvec is always rq_maxpages in
+length, which can be too short in some cases, since the TCP record
+marker consumes a slot.
+
+Fix both problems by adding a separate bvec array to the svc_sock that
+is specifically for sending. For TCP, make this array one slot longer
+than rq_maxpages, to account for the record marker. For UDP, only
+allocate as large an array as we need since it's limited to 64k of
+payload.
+
+Signed-off-by: Jeff Layton <jlayton@kernel.org>
+Reviewed-by: NeilBrown <neil@brown.name>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Stable-dep-of: 18c1cc698861 ("SUNRPC: Return an error from xdr_buf_to_bvec() on overflow")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/sunrpc/svcsock.h | 3 ++
+ net/sunrpc/svcsock.c | 55 +++++++++++++++++++++++++++++++++++------
+ 2 files changed, 51 insertions(+), 7 deletions(-)
+
+--- a/include/linux/sunrpc/svcsock.h
++++ b/include/linux/sunrpc/svcsock.h
+@@ -26,6 +26,9 @@ struct svc_sock {
+ void (*sk_odata)(struct sock *);
+ void (*sk_owspace)(struct sock *);
+
++ /* For sends (protected by xpt_mutex) */
++ struct bio_vec *sk_bvec;
++
+ /* private TCP part */
+ /* On-the-wire fragment header: */
+ __be32 sk_marker;
+--- a/net/sunrpc/svcsock.c
++++ b/net/sunrpc/svcsock.c
+@@ -68,6 +68,17 @@
+
+ #define RPCDBG_FACILITY RPCDBG_SVCXPRT
+
++/*
++ * For UDP:
++ * 1 for header page
++ * enough pages for RPCSVC_MAXPAYLOAD_UDP
++ * 1 in case payload is not aligned
++ * 1 for tail page
++ */
++enum {
++ SUNRPC_MAX_UDP_SENDPAGES = 1 + RPCSVC_MAXPAYLOAD_UDP / PAGE_SIZE + 1 + 1
++};
++
+ /* To-do: to avoid tying up an nfsd thread while waiting for a
+ * handshake request, the request could instead be deferred.
+ */
+@@ -750,14 +761,14 @@ static int svc_udp_sendto(struct svc_rqs
+ if (svc_xprt_is_dead(xprt))
+ goto out_notconn;
+
+- count = xdr_buf_to_bvec(rqstp->rq_bvec, rqstp->rq_maxpages, xdr);
++ count = xdr_buf_to_bvec(svsk->sk_bvec, SUNRPC_MAX_UDP_SENDPAGES, xdr);
+
+- iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, rqstp->rq_bvec,
++ iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+ count, rqstp->rq_res.len);
+ err = sock_sendmsg(svsk->sk_sock, &msg);
+ if (err == -ECONNREFUSED) {
+ /* ICMP error on earlier request. */
+- iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, rqstp->rq_bvec,
++ iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+ count, rqstp->rq_res.len);
+ err = sock_sendmsg(svsk->sk_sock, &msg);
+ }
+@@ -1245,19 +1256,19 @@ static int svc_tcp_sendmsg(struct svc_so
+ int ret;
+
+ /* The stream record marker is copied into a temporary page
+- * fragment buffer so that it can be included in rq_bvec.
++ * fragment buffer so that it can be included in sk_bvec.
+ */
+ buf = page_frag_alloc(&svsk->sk_frag_cache, sizeof(marker),
+ GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+ memcpy(buf, &marker, sizeof(marker));
+- bvec_set_virt(rqstp->rq_bvec, buf, sizeof(marker));
++ bvec_set_virt(svsk->sk_bvec, buf, sizeof(marker));
+
+- count = xdr_buf_to_bvec(rqstp->rq_bvec + 1, rqstp->rq_maxpages,
++ count = xdr_buf_to_bvec(svsk->sk_bvec + 1, rqstp->rq_maxpages,
+ &rqstp->rq_res);
+
+- iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, rqstp->rq_bvec,
++ iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+ 1 + count, sizeof(marker) + rqstp->rq_res.len);
+ ret = sock_sendmsg(svsk->sk_sock, &msg);
+ page_frag_free(buf);
+@@ -1402,6 +1413,20 @@ void svc_sock_update_bufs(struct svc_ser
+ spin_unlock_bh(&serv->sv_lock);
+ }
+
++static int svc_sock_sendpages(struct svc_serv *serv, struct socket *sock, int flags)
++{
++ switch (sock->type) {
++ case SOCK_STREAM:
++ /* +1 for TCP record marker */
++ if (flags & SVC_SOCK_TEMPORARY)
++ return svc_serv_maxpages(serv) + 1;
++ return 0;
++ case SOCK_DGRAM:
++ return SUNRPC_MAX_UDP_SENDPAGES;
++ }
++ return -EINVAL;
++}
++
+ /*
+ * Initialize socket for RPC use and create svc_sock struct
+ */
+@@ -1412,12 +1437,26 @@ static struct svc_sock *svc_setup_socket
+ struct svc_sock *svsk;
+ struct sock *inet;
+ int pmap_register = !(flags & SVC_SOCK_ANONYMOUS);
++ int sendpages;
+ unsigned long pages;
+
++ sendpages = svc_sock_sendpages(serv, sock, flags);
++ if (sendpages < 0)
++ return ERR_PTR(sendpages);
++
+ pages = svc_serv_maxpages(serv);
+ svsk = kzalloc(struct_size(svsk, sk_pages, pages), GFP_KERNEL);
+ if (!svsk)
+ return ERR_PTR(-ENOMEM);
++
++ if (sendpages) {
++ svsk->sk_bvec = kcalloc(sendpages, sizeof(*svsk->sk_bvec), GFP_KERNEL);
++ if (!svsk->sk_bvec) {
++ kfree(svsk);
++ return ERR_PTR(-ENOMEM);
++ }
++ }
++
+ svsk->sk_maxpages = pages;
+
+ inet = sock->sk;
+@@ -1429,6 +1468,7 @@ static struct svc_sock *svc_setup_socket
+ inet->sk_protocol,
+ ntohs(inet_sk(inet)->inet_sport));
+ if (err < 0) {
++ kfree(svsk->sk_bvec);
+ kfree(svsk);
+ return ERR_PTR(err);
+ }
+@@ -1646,5 +1686,6 @@ static void svc_sock_free(struct svc_xpr
+ sock_release(sock);
+
+ page_frag_cache_drain(&svsk->sk_frag_cache);
++ kfree(svsk->sk_bvec);
+ kfree(svsk);
+ }
--- /dev/null
+From stable+bounces-288495-greg=kroah.com@vger.kernel.org Thu Jul 23 20:27:21 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Thu, 23 Jul 2026 14:19:10 -0400
+Subject: SUNRPC: Return an error from xdr_buf_to_bvec() on overflow
+To: stable@vger.kernel.org
+Cc: Chuck Lever <chuck.lever@oracle.com>, Chris Mason <clm@meta.com>, Jeff Layton <jlayton@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260723181911.3438372-5-sashal@kernel.org>
+
+From: Chuck Lever <chuck.lever@oracle.com>
+
+[ Upstream commit 18c1cc69886192e33536498289d26dba6894e3d5 ]
+
+xdr_buf_to_bvec() returns a slot count even when the caller's bvec
+budget is exhausted partway through the xdr_buf. Callers feed that
+count into iov_iter_bvec() and continue as if the conversion had
+succeeded, silently sending or writing fewer bytes than the data
+length declares. For an NFS WRITE the server reports the truncated
+transfer to the client as full success.
+
+The overflow represents an internal invariant violation: a higher
+layer reserved a bvec budget too small for the xdr_buf it then
+asked the encoder to convert. That is a server-side fault, not a
+media I/O failure and not a malformed client argument.
+
+Change xdr_buf_to_bvec() to return a signed int and have the
+overflow label return -ESERVERFAULT. Update the three callers to
+detect the negative return and fail the request: nfsd_vfs_write()
+folds the error into host_err, which nfserrno() translates to
+nfserr_serverfault for the WRITE reply; svc_udp_sendto() and
+svc_tcp_sendmsg() propagate the error out of the send path.
+
+Reported-by: Chris Mason <clm@meta.com>
+Fixes: 2eb2b9358181 ("SUNRPC: Convert svc_tcp_sendmsg to use bio_vecs directly")
+Cc: stable@vger.kernel.org
+Reviewed-by: Jeff Layton <jlayton@kernel.org>
+Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/nfsd/vfs.c | 8 +++++++-
+ include/linux/sunrpc/xdr.h | 4 ++--
+ net/sunrpc/svcsock.c | 14 ++++++++++++--
+ net/sunrpc/xdr.c | 11 ++++++-----
+ 4 files changed, 27 insertions(+), 10 deletions(-)
+
+--- a/fs/nfsd/vfs.c
++++ b/fs/nfsd/vfs.c
+@@ -1203,7 +1203,7 @@ nfsd_vfs_write(struct svc_rqst *rqstp, s
+ unsigned long exp_op_flags = 0;
+ unsigned int pflags = current->flags;
+ bool restore_flags = false;
+- unsigned int nvecs;
++ int nvecs;
+
+ trace_nfsd_write_opened(rqstp, fhp, offset, *cnt);
+
+@@ -1243,7 +1243,13 @@ nfsd_vfs_write(struct svc_rqst *rqstp, s
+ }
+
+ nvecs = xdr_buf_to_bvec(rqstp->rq_bvec, rqstp->rq_maxpages, payload);
++ if (nvecs < 0) {
++ host_err = nvecs;
++ goto out_nfserr;
++ }
++
+ iov_iter_bvec(&iter, ITER_SOURCE, rqstp->rq_bvec, nvecs, *cnt);
++
+ since = READ_ONCE(file->f_wb_err);
+ if (verf)
+ nfsd_copy_write_verifier(verf, nn);
+--- a/include/linux/sunrpc/xdr.h
++++ b/include/linux/sunrpc/xdr.h
+@@ -138,8 +138,8 @@ void xdr_terminate_string(const struct x
+ size_t xdr_buf_pagecount(const struct xdr_buf *buf);
+ int xdr_alloc_bvec(struct xdr_buf *buf, gfp_t gfp);
+ void xdr_free_bvec(struct xdr_buf *buf);
+-unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
+- const struct xdr_buf *xdr);
++int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
++ const struct xdr_buf *xdr);
+ int xdr_buf_to_sg(const struct xdr_buf *buf, unsigned int offset,
+ unsigned int len, struct scatterlist *sg, unsigned int nsg);
+ int xdr_buf_to_sg_alloc(const struct xdr_buf *buf, unsigned int offset,
+--- a/net/sunrpc/svcsock.c
++++ b/net/sunrpc/svcsock.c
+@@ -748,7 +748,7 @@ static int svc_udp_sendto(struct svc_rqs
+ .msg_flags = MSG_SPLICE_PAGES,
+ .msg_controllen = sizeof(buffer),
+ };
+- unsigned int count;
++ int count;
+ int err;
+
+ svc_udp_release_ctxt(xprt, rqstp->rq_xprt_ctxt);
+@@ -762,6 +762,10 @@ static int svc_udp_sendto(struct svc_rqs
+ goto out_notconn;
+
+ count = xdr_buf_to_bvec(svsk->sk_bvec, SUNRPC_MAX_UDP_SENDPAGES, xdr);
++ if (count < 0) {
++ err = count;
++ goto out_trace;
++ }
+
+ iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+ count, rqstp->rq_res.len);
+@@ -773,6 +777,7 @@ static int svc_udp_sendto(struct svc_rqs
+ err = sock_sendmsg(svsk->sk_sock, &msg);
+ }
+
++out_trace:
+ trace_svcsock_udp_send(xprt, err);
+
+ mutex_unlock(&xprt->xpt_mutex);
+@@ -1251,7 +1256,7 @@ static int svc_tcp_sendmsg(struct svc_so
+ struct msghdr msg = {
+ .msg_flags = MSG_SPLICE_PAGES,
+ };
+- unsigned int count;
++ int count;
+ void *buf;
+ int ret;
+
+@@ -1267,10 +1272,15 @@ static int svc_tcp_sendmsg(struct svc_so
+
+ count = xdr_buf_to_bvec(svsk->sk_bvec + 1, rqstp->rq_maxpages,
+ &rqstp->rq_res);
++ if (count < 0) {
++ ret = count;
++ goto out;
++ }
+
+ iov_iter_bvec(&msg.msg_iter, ITER_SOURCE, svsk->sk_bvec,
+ 1 + count, sizeof(marker) + rqstp->rq_res.len);
+ ret = sock_sendmsg(svsk->sk_sock, &msg);
++out:
+ page_frag_free(buf);
+ return ret;
+ }
+--- a/net/sunrpc/xdr.c
++++ b/net/sunrpc/xdr.c
+@@ -139,13 +139,14 @@ xdr_free_bvec(struct xdr_buf *buf)
+ /**
+ * xdr_buf_to_bvec - Copy components of an xdr_buf into a bio_vec array
+ * @bvec: bio_vec array to populate
+- * @bvec_size: element count of @bio_vec
++ * @bvec_size: element count of @bvec
+ * @xdr: xdr_buf to be copied
+ *
+- * Returns the number of entries consumed in @bvec.
++ * Returns the number of entries consumed in @bvec on success, or
++ * -ESERVERFAULT when @xdr does not fit within @bvec_size entries.
+ */
+-unsigned int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
+- const struct xdr_buf *xdr)
++int xdr_buf_to_bvec(struct bio_vec *bvec, unsigned int bvec_size,
++ const struct xdr_buf *xdr)
+ {
+ const struct kvec *head = xdr->head;
+ const struct kvec *tail = xdr->tail;
+@@ -187,7 +188,7 @@ unsigned int xdr_buf_to_bvec(struct bio_
+
+ bvec_overflow:
+ pr_warn_once("%s: bio_vec array overflow\n", __func__);
+- return count;
++ return -ESERVERFAULT;
+ }
+ EXPORT_SYMBOL_GPL(xdr_buf_to_bvec);
+
--- /dev/null
+From stable+bounces-289230-greg=kroah.com@vger.kernel.org Sun Jul 26 14:00:55 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:00:47 -0400
+Subject: thunderbolt: Keep XDomain reference during the lifetime of a service
+To: stable@vger.kernel.org
+Cc: Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726120050.4138299-1-sashal@kernel.org>
+
+From: Mika Westerberg <mika.westerberg@linux.intel.com>
+
+[ Upstream commit 8b4060998637f06975fceee9b73845d8672d411e ]
+
+This is needed because we release the service ID in tb_service_release()
+and the ID array is owned by the parent XDomain.
+
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/xdomain.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -1012,6 +1012,7 @@ static void tb_service_release(struct de
+ ida_free(&xd->service_ids, svc->id);
+ kfree(svc->key);
+ kfree(svc);
++ tb_xdomain_put(xd);
+ }
+
+ const struct device_type tb_service_type = {
+@@ -1120,7 +1121,7 @@ static void enumerate_services(struct tb
+ svc->id = id;
+ svc->dev.bus = &tb_bus_type;
+ svc->dev.type = &tb_service_type;
+- svc->dev.parent = &xd->dev;
++ svc->dev.parent = get_device(&xd->dev);
+ dev_set_name(&svc->dev, "%s.%d", dev_name(&xd->dev), svc->id);
+
+ tb_service_debugfs_init(svc);
--- /dev/null
+From stable+bounces-289233-greg=kroah.com@vger.kernel.org Sun Jul 26 14:03:26 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:00:50 -0400
+Subject: thunderbolt: Prevent XDomain delayed work use-after-free on disconnect
+To: stable@vger.kernel.org
+Cc: Michael Bommarito <michael.bommarito@gmail.com>, Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726120050.4138299-4-sashal@kernel.org>
+
+From: Michael Bommarito <michael.bommarito@gmail.com>
+
+[ Upstream commit 2c5d2d3c3f70cde2565d7b279b544893a2035842 ]
+
+tb_xdp_handle_request() runs on system_wq and queues
+xd->state_work via queue_delayed_work() in three request handlers:
+PROPERTIES_CHANGED_REQUEST, UUID_REQUEST (via start_handshake),
+and LINK_STATE_CHANGE_REQUEST. Similarly, update_xdomain() queues
+xd->properties_changed_work when local properties change.
+
+Concurrently, tb_xdomain_remove() calls stop_handshake() which does
+cancel_delayed_work_sync() on both delayed works. Later,
+tb_xdomain_unregister() calls device_unregister() which eventually
+frees the xdomain. Since commit 559c1e1e0134 ("thunderbolt: Run
+tb_xdp_handle_request() in system workqueue") moved the request
+handler off tb->wq, the handler and the remove path are no longer
+serialized. If queue_delayed_work() executes after
+cancel_delayed_work_sync() but before the xdomain is freed, the
+delayed work fires on a freed object.
+
+Add xd->removing that tb_xdomain_remove() sets under xd->lock
+before calling stop_handshake(). Each external queue site holds
+the same lock and checks removing before calling
+queue_delayed_work(). This provides the mutual exclusion needed:
+either the queue site acquires the lock first and queues work that
+the subsequent cancel will see, or the remove path acquires the
+lock first and the queue site observes removing == true and skips
+the queue.
+
+Fixes: 559c1e1e0134 ("thunderbolt: Run tb_xdp_handle_request() in system workqueue")
+Cc: stable@vger.kernel.org
+Assisted-by: Claude:claude-opus-4-7
+Signed-off-by: Michael Bommarito <michael.bommarito@gmail.com>
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/xdomain.c | 26 +++++++++++++-------------
+ 1 file changed, 13 insertions(+), 13 deletions(-)
+
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -909,6 +909,19 @@ void tb_unregister_service_driver(struct
+ }
+ EXPORT_SYMBOL_GPL(tb_unregister_service_driver);
+
++static int update_xdomain(struct device *dev, void *data)
++{
++ struct tb_xdomain *xd;
++
++ xd = tb_to_xdomain(dev);
++ if (xd) {
++ queue_delayed_work(xd->tb->wq, &xd->properties_changed_work,
++ msecs_to_jiffies(50));
++ }
++
++ return 0;
++}
++
+ static ssize_t key_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+ {
+@@ -2500,19 +2513,6 @@ bool tb_xdomain_handle_request(struct tb
+ return ret > 0;
+ }
+
+-static int update_xdomain(struct device *dev, void *data)
+-{
+- struct tb_xdomain *xd;
+-
+- xd = tb_to_xdomain(dev);
+- if (xd) {
+- queue_delayed_work(xd->tb->wq, &xd->properties_changed_work,
+- msecs_to_jiffies(50));
+- }
+-
+- return 0;
+-}
+-
+ static void update_all_xdomains(void)
+ {
+ bus_for_each_dev(&tb_bus_type, NULL, NULL, update_xdomain);
--- /dev/null
+From stable+bounces-289231-greg=kroah.com@vger.kernel.org Sun Jul 26 14:00:57 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:00:48 -0400
+Subject: thunderbolt: Remove service debugfs entries during unregister
+To: stable@vger.kernel.org
+Cc: Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726120050.4138299-2-sashal@kernel.org>
+
+From: Mika Westerberg <mika.westerberg@linux.intel.com>
+
+[ Upstream commit 4d5fc3f4068568dfcb8cbe2852b4adc56394aa26 ]
+
+We add them as part of the register path so to keep it symmetric remove
+them as part of the unregister path. This also removes them even if the
+service itself is not yet released (but is unregistered), thus allowing
+new register with the same service name to happen.
+
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/xdomain.c | 14 +++++++++++---
+ 1 file changed, 11 insertions(+), 3 deletions(-)
+
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -1008,7 +1008,6 @@ static void tb_service_release(struct de
+ struct tb_service *svc = container_of(dev, struct tb_service, dev);
+ struct tb_xdomain *xd = tb_service_parent(svc);
+
+- tb_service_debugfs_remove(svc);
+ ida_free(&xd->service_ids, svc->id);
+ kfree(svc->key);
+ kfree(svc);
+@@ -1023,6 +1022,14 @@ const struct device_type tb_service_type
+ };
+ EXPORT_SYMBOL_GPL(tb_service_type);
+
++static void __unregister_service(struct device *dev)
++{
++ struct tb_service *svc = tb_to_service(dev);
++
++ tb_service_debugfs_remove(svc);
++ device_unregister(&svc->dev);
++}
++
+ static int remove_missing_service(struct device *dev, void *data)
+ {
+ struct tb_xdomain *xd = data;
+@@ -1034,7 +1041,7 @@ static int remove_missing_service(struct
+
+ if (!tb_property_find(xd->remote_properties, svc->key,
+ TB_PROPERTY_TYPE_DIRECTORY))
+- device_unregister(dev);
++ __unregister_service(dev);
+
+ return 0;
+ }
+@@ -1127,6 +1134,7 @@ static void enumerate_services(struct tb
+ tb_service_debugfs_init(svc);
+
+ if (device_register(&svc->dev)) {
++ tb_service_debugfs_remove(svc);
+ put_device(&svc->dev);
+ break;
+ }
+@@ -2059,7 +2067,7 @@ void tb_xdomain_add(struct tb_xdomain *x
+
+ static int unregister_service(struct device *dev, void *data)
+ {
+- device_unregister(dev);
++ __unregister_service(dev);
+ return 0;
+ }
+
--- /dev/null
+From stable+bounces-289232-greg=kroah.com@vger.kernel.org Sun Jul 26 14:00:59 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Sun, 26 Jul 2026 08:00:49 -0400
+Subject: thunderbolt: Remove XDomain from the bus without holding tb->lock
+To: stable@vger.kernel.org
+Cc: Mika Westerberg <mika.westerberg@linux.intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260726120050.4138299-3-sashal@kernel.org>
+
+From: Mika Westerberg <mika.westerberg@linux.intel.com>
+
+[ Upstream commit a8937f35cf39c39c64325aa84d0463d866850857 ]
+
+Currently we call device_unregister() for services and the XDomain
+itself with tb->lock held. This prevents the service drivers from
+calling any functions that may take it. For this reason separate
+removing the XDomain from the topology data structures (where we need
+the lock) from unregistering the device from the bus (where remove
+callbacks of the drivers are being called).
+
+Signed-off-by: Mika Westerberg <mika.westerberg@linux.intel.com>
+Stable-dep-of: 2c5d2d3c3f70 ("thunderbolt: Prevent XDomain delayed work use-after-free on disconnect")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/thunderbolt/debugfs.c | 2 +
+ drivers/thunderbolt/domain.c | 30 +++++++++++++++++++++
+ drivers/thunderbolt/icm.c | 5 +++
+ drivers/thunderbolt/switch.c | 14 +++++++++
+ drivers/thunderbolt/tb.c | 59 ++++++++++++++++++++----------------------
+ drivers/thunderbolt/tb.h | 2 +
+ drivers/thunderbolt/xdomain.c | 53 +++++++++++++++++++++++--------------
+ 7 files changed, 115 insertions(+), 50 deletions(-)
+
+--- a/drivers/thunderbolt/debugfs.c
++++ b/drivers/thunderbolt/debugfs.c
+@@ -1786,6 +1786,8 @@ static void margining_port_remove(struct
+
+ if (!port->usb4)
+ return;
++ if (!port->usb4->margining)
++ return;
+
+ snprintf(dir_name, sizeof(dir_name), "port%d", port->port);
+ parent = debugfs_lookup(dir_name, port->sw->debugfs_dir);
+--- a/drivers/thunderbolt/domain.c
++++ b/drivers/thunderbolt/domain.c
+@@ -850,6 +850,36 @@ int tb_domain_disconnect_all_paths(struc
+ return bus_for_each_dev(&tb_bus_type, NULL, tb, disconnect_xdomain);
+ }
+
++struct unregister_context {
++ const struct tb *tb;
++ int n;
++};
++
++static int unregister_unplugged_xdomain(struct device *dev, void *data)
++{
++ struct unregister_context *ctx = data;
++ struct tb_xdomain *xd;
++
++ xd = tb_to_xdomain(dev);
++ if (xd && xd->tb == ctx->tb && xd->is_unplugged) {
++ tb_xdomain_unregister(xd);
++ ctx->n++;
++ }
++ return 0;
++}
++
++int tb_domain_unregister_unplugged_xdomains(struct tb *tb)
++{
++ struct unregister_context ctx;
++
++ ctx.tb = tb_domain_get(tb);
++ ctx.n = 0;
++ bus_for_each_dev(&tb_bus_type, NULL, &ctx, unregister_unplugged_xdomain);
++ tb_domain_put(tb);
++
++ return ctx.n;
++}
++
+ int tb_domain_init(void)
+ {
+ int ret;
+--- a/drivers/thunderbolt/icm.c
++++ b/drivers/thunderbolt/icm.c
+@@ -738,6 +738,7 @@ static void remove_xdomain(struct tb_xdo
+
+ sw = tb_to_switch(xd->dev.parent);
+ tb_port_at(xd->route, sw)->xdomain = NULL;
++ xd->is_unplugged = true;
+ tb_xdomain_remove(xd);
+ }
+
+@@ -1762,6 +1763,8 @@ static void icm_handle_notification(stru
+
+ kfree(n->pkg);
+ kfree(n);
++
++ tb_domain_unregister_unplugged_xdomains(tb);
+ }
+
+ static void icm_handle_event(struct tb *tb, enum tb_cfg_pkg_type type,
+@@ -2112,6 +2115,8 @@ static void icm_rescan_work(struct work_
+ if (tb->root_switch)
+ icm_free_unplugged_children(tb->root_switch);
+ mutex_unlock(&tb->lock);
++
++ tb_domain_unregister_unplugged_xdomains(tb);
+ }
+
+ static void icm_complete(struct tb *tb)
+--- a/drivers/thunderbolt/switch.c
++++ b/drivers/thunderbolt/switch.c
+@@ -3603,6 +3603,20 @@ int tb_switch_resume(struct tb_switch *s
+ tb_port_warn(port,
+ "lost during suspend, disconnecting\n");
+ tb_sw_set_unplugged(port->remote->sw);
++ } else if (port->xdomain) {
++ /*
++ * If the user replaced the XDomain with
++ * another router, this will succeed in
++ * which case we must remove the XDomain
++ * before adding the new router.
++ */
++ err = tb_cfg_get_upstream_port(sw->tb->ctl,
++ port->xdomain->route);
++ if (err > 0) {
++ tb_port_warn(port,
++ "XDomain was disconnected\n");
++ port->xdomain->is_unplugged = true;
++ }
+ }
+ }
+ }
+--- a/drivers/thunderbolt/tb.c
++++ b/drivers/thunderbolt/tb.c
+@@ -2524,6 +2524,8 @@ put_sw:
+ out:
+ mutex_unlock(&tb->lock);
+
++ tb_domain_unregister_unplugged_xdomains(tb);
++
+ pm_runtime_mark_last_busy(&tb->dev);
+ pm_runtime_put_autosuspend(&tb->dev);
+
+@@ -3110,6 +3112,24 @@ static void tb_restore_children(struct t
+ }
+ }
+
++static void tb_free_unplugged_xdomains(struct tb_switch *sw)
++{
++ struct tb_port *port;
++
++ tb_switch_for_each_port(sw, port) {
++ if (tb_is_upstream_port(port))
++ continue;
++ if (port->xdomain && port->xdomain->is_unplugged) {
++ tb_retimer_remove_all(port);
++ tb_xdomain_remove(port->xdomain);
++ tb_port_unconfigure_xdomain(port);
++ port->xdomain = NULL;
++ } else if (port->remote) {
++ tb_free_unplugged_xdomains(port->remote->sw);
++ }
++ }
++}
++
+ static int tb_resume_noirq(struct tb *tb)
+ {
+ struct tb_cm *tcm = tb_priv(tb);
+@@ -3129,6 +3149,7 @@ static int tb_resume_noirq(struct tb *tb
+ tb_switch_resume(tb->root_switch, false);
+ tb_free_invalid_tunnels(tb);
+ tb_free_unplugged_children(tb->root_switch);
++ tb_free_unplugged_xdomains(tb->root_switch);
+ tb_restore_children(tb->root_switch);
+
+ /*
+@@ -3171,28 +3192,6 @@ static int tb_resume_noirq(struct tb *tb
+ return 0;
+ }
+
+-static int tb_free_unplugged_xdomains(struct tb_switch *sw)
+-{
+- struct tb_port *port;
+- int ret = 0;
+-
+- tb_switch_for_each_port(sw, port) {
+- if (tb_is_upstream_port(port))
+- continue;
+- if (port->xdomain && port->xdomain->is_unplugged) {
+- tb_retimer_remove_all(port);
+- tb_xdomain_remove(port->xdomain);
+- tb_port_unconfigure_xdomain(port);
+- port->xdomain = NULL;
+- ret++;
+- } else if (port->remote) {
+- ret += tb_free_unplugged_xdomains(port->remote->sw);
+- }
+- }
+-
+- return ret;
+-}
+-
+ static int tb_freeze_noirq(struct tb *tb)
+ {
+ struct tb_cm *tcm = tb_priv(tb);
+@@ -3212,14 +3211,14 @@ static int tb_thaw_noirq(struct tb *tb)
+ static void tb_complete(struct tb *tb)
+ {
+ /*
+- * Release any unplugged XDomains and if there is a case where
++ * Unregister unplugged XDomains and if there is a case where
+ * another domain is swapped in place of unplugged XDomain we
+ * need to run another rescan.
+ */
+- mutex_lock(&tb->lock);
+- if (tb_free_unplugged_xdomains(tb->root_switch))
+- tb_scan_switch(tb->root_switch);
+- mutex_unlock(&tb->lock);
++ if (tb_domain_unregister_unplugged_xdomains(tb)) {
++ scoped_guard(mutex, &tb->lock)
++ tb_scan_switch(tb->root_switch);
++ }
+ }
+
+ static int tb_runtime_suspend(struct tb *tb)
+@@ -3246,11 +3245,11 @@ static void tb_remove_work(struct work_s
+ struct tb *tb = tcm_to_tb(tcm);
+
+ mutex_lock(&tb->lock);
+- if (tb->root_switch) {
++ if (tb->root_switch)
+ tb_free_unplugged_children(tb->root_switch);
+- tb_free_unplugged_xdomains(tb->root_switch);
+- }
+ mutex_unlock(&tb->lock);
++
++ tb_free_unplugged_xdomains(tb->root_switch);
+ }
+
+ static int tb_runtime_resume(struct tb *tb)
+--- a/drivers/thunderbolt/tb.h
++++ b/drivers/thunderbolt/tb.h
+@@ -792,6 +792,7 @@ int tb_domain_disconnect_xdomain_paths(s
+ int transmit_path, int transmit_ring,
+ int receive_path, int receive_ring);
+ int tb_domain_disconnect_all_paths(struct tb *tb);
++int tb_domain_unregister_unplugged_xdomains(struct tb *tb);
+
+ static inline struct tb *tb_domain_get(struct tb *tb)
+ {
+@@ -1262,6 +1263,7 @@ struct tb_xdomain *tb_xdomain_alloc(stru
+ const uuid_t *remote_uuid);
+ void tb_xdomain_add(struct tb_xdomain *xd);
+ void tb_xdomain_remove(struct tb_xdomain *xd);
++void tb_xdomain_unregister(struct tb_xdomain *xd);
+ struct tb_xdomain *tb_xdomain_find_by_link_depth(struct tb *tb, u8 link,
+ u8 depth);
+
+--- a/drivers/thunderbolt/xdomain.c
++++ b/drivers/thunderbolt/xdomain.c
+@@ -2072,41 +2072,54 @@ static int unregister_service(struct dev
+ }
+
+ /**
+- * tb_xdomain_remove() - Remove XDomain from the bus
++ * tb_xdomain_remove() - Remove XDomain
+ * @xd: XDomain to remove
+ *
+- * This will stop all ongoing configuration work and remove the XDomain
+- * along with any services from the bus. When the last reference to @xd
+- * is released the object will be released as well.
++ * This will stop all ongoing configuration work. XDomain is not removed
++ * from the bus if it was added. That needs to be done separately by
++ * calling tb_xdomain_unregister().
++ *
++ * Called with @tb->lock held.
+ */
+ void tb_xdomain_remove(struct tb_xdomain *xd)
+ {
+ tb_xdomain_debugfs_remove(xd);
+-
+ stop_handshake(xd);
+-
+- device_for_each_child_reverse(&xd->dev, xd, unregister_service);
+-
+ tb_xdomain_link_exit(xd);
+
+- /*
+- * Undo runtime PM here explicitly because it is possible that
+- * the XDomain was never added to the bus and thus device_del()
+- * is not called for it (device_del() would handle this otherwise).
+- */
+- pm_runtime_disable(&xd->dev);
+- pm_runtime_put_noidle(&xd->dev);
+- pm_runtime_set_suspended(&xd->dev);
+-
+ if (!device_is_registered(&xd->dev)) {
++ /*
++ * Undo runtime PM here explicitly because it is
++ * possible that the XDomain was never added to the bus
++ * and thus device_del() is not called for it
++ * (device_del() would handle this otherwise).
++ */
++ pm_runtime_disable(&xd->dev);
++ pm_runtime_put_noidle(&xd->dev);
++ pm_runtime_set_suspended(&xd->dev);
+ put_device(&xd->dev);
+- } else {
+- dev_info(&xd->dev, "host disconnected\n");
+- device_unregister(&xd->dev);
+ }
+ }
+
+ /**
++ * tb_xdomain_unregister() - Unregister XDomain
++ * @xd: XDomain to unregister
++ *
++ * This will unregister the XDomain along with any services from the
++ * bus. When the last reference to @xd is released the object will be
++ * released as well.
++ */
++void tb_xdomain_unregister(struct tb_xdomain *xd)
++{
++ lockdep_assert_not_held(&xd->tb->lock);
++
++ device_for_each_child_reverse(&xd->dev, xd, unregister_service);
++
++ dev_info(&xd->dev, "host disconnected\n");
++ device_unregister(&xd->dev);
++}
++
++/**
+ * tb_xdomain_lane_bonding_enable() - Enable lane bonding on XDomain
+ * @xd: XDomain connection
+ *
--- /dev/null
+From stable+bounces-287927-greg=kroah.com@vger.kernel.org Wed Jul 22 18:08:44 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:07:31 -0400
+Subject: VDUSE: avoid leaking information to userspace
+To: stable@vger.kernel.org
+Cc: "Jason Wang" <jasowang@redhat.com>, "Xie Yongji" <xieyongji@bytedance.com>, "Eugenio Pérez" <eperezma@redhat.com>, "Michael S. Tsirkin" <mst@redhat.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260722160731.1739521-4-sashal@kernel.org>
+
+From: Jason Wang <jasowang@redhat.com>
+
+[ Upstream commit 9c1523803445ee0348f62b77793266dd981596e0 ]
+
+The bounceing is not necessarily page aligned, so current VDUSE can
+leak kernel information through mapping bounce pages to
+userspace. Allocate bounce pages with __GFP_ZERO to avoid leaking
+information to userspace.
+
+Fixes: 8c773d53fb7b ("vduse: Implement an MMU-based software IOTLB")
+Cc: stable@vger.kernel.org
+Signed-off-by: Jason Wang <jasowang@redhat.com>
+Reviewed-by: Xie Yongji <xieyongji@bytedance.com>
+Reviewed-by: Eugenio Pérez <eperezma@redhat.com>
+Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
+Message-ID: <20260130050750.4050-1-jasowang@redhat.com>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vdpa/vdpa_user/iova_domain.c | 2 +-
+ drivers/vdpa/vdpa_user/vduse_dev.c | 2 +-
+ 2 files changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/vdpa/vdpa_user/iova_domain.c
++++ b/drivers/vdpa/vdpa_user/iova_domain.c
+@@ -124,7 +124,7 @@ static int vduse_domain_map_bounce_page(
+ if (!map->bounce_page) {
+ head_map = &domain->bounce_maps[(iova & PAGE_MASK) >> BOUNCE_MAP_SHIFT];
+ if (!head_map->bounce_page) {
+- tmp_page = alloc_page(GFP_ATOMIC);
++ tmp_page = alloc_page(GFP_ATOMIC | __GFP_ZERO);
+ if (!tmp_page)
+ return -ENOMEM;
+ if (cmpxchg(&head_map->bounce_page, NULL, tmp_page))
+--- a/drivers/vdpa/vdpa_user/vduse_dev.c
++++ b/drivers/vdpa/vdpa_user/vduse_dev.c
+@@ -930,7 +930,7 @@ static void *vduse_dev_alloc_coherent(un
+ if (!token.group)
+ return NULL;
+
+- addr = alloc_pages_exact(size, flag);
++ addr = alloc_pages_exact(size, flag | __GFP_ZERO);
+ if (!addr)
+ return NULL;
+
--- /dev/null
+From stable+bounces-287925-greg=kroah.com@vger.kernel.org Wed Jul 22 18:34:33 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:07:29 -0400
+Subject: vduse: remove unused vaddr parameter of vduse_domain_free_coherent
+To: stable@vger.kernel.org
+Cc: "Eugenio Pérez" <eperezma@redhat.com>, "Michael S. Tsirkin" <mst@redhat.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260722160731.1739521-2-sashal@kernel.org>
+
+From: Eugenio Pérez <eperezma@redhat.com>
+
+[ Upstream commit 766e1749c0ef6a09651be9b8a8283d508c322b58 ]
+
+We will modify the function in next patches so let's clean it first.
+
+Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
+Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
+Message-Id: <20260119143306.1818855-9-eperezma@redhat.com>
+Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vdpa/vdpa_user/iova_domain.c | 3 +--
+ drivers/vdpa/vdpa_user/iova_domain.h | 3 +--
+ drivers/vdpa/vdpa_user/vduse_dev.c | 2 +-
+ 3 files changed, 3 insertions(+), 5 deletions(-)
+
+--- a/drivers/vdpa/vdpa_user/iova_domain.c
++++ b/drivers/vdpa/vdpa_user/iova_domain.c
+@@ -521,8 +521,7 @@ err:
+ }
+
+ void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
+- void *vaddr, dma_addr_t dma_addr,
+- unsigned long attrs)
++ dma_addr_t dma_addr, unsigned long attrs)
+ {
+ struct iova_domain *iovad = &domain->consistent_iovad;
+ struct vhost_iotlb_map *map;
+--- a/drivers/vdpa/vdpa_user/iova_domain.h
++++ b/drivers/vdpa/vdpa_user/iova_domain.h
+@@ -71,8 +71,7 @@ dma_addr_t vduse_domain_alloc_coherent(s
+ size_t size, void *orig);
+
+ void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
+- void *vaddr, dma_addr_t dma_addr,
+- unsigned long attrs);
++ dma_addr_t dma_addr, unsigned long attrs);
+
+ void vduse_domain_reset_bounce_map(struct vduse_iova_domain *domain);
+
+--- a/drivers/vdpa/vdpa_user/vduse_dev.c
++++ b/drivers/vdpa/vdpa_user/vduse_dev.c
+@@ -960,7 +960,7 @@ static void vduse_dev_free_coherent(unio
+ vdev = token.group->dev;
+ domain = vdev->domain;
+
+- vduse_domain_free_coherent(domain, size, vaddr, dma_addr, attrs);
++ vduse_domain_free_coherent(domain, size, dma_addr, attrs);
+ }
+
+ static bool vduse_dev_need_sync(union virtio_map token, dma_addr_t dma_addr)
--- /dev/null
+From stable+bounces-287924-greg=kroah.com@vger.kernel.org Wed Jul 22 18:29:31 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:07:28 -0400
+Subject: vduse: return internal vq group struct as map token
+To: stable@vger.kernel.org
+Cc: "Eugenio Pérez" <eperezma@redhat.com>, "Jason Wang" <jasowang@redhat.com>, "Michael S. Tsirkin" <mst@redhat.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260722160731.1739521-1-sashal@kernel.org>
+
+From: Eugenio Pérez <eperezma@redhat.com>
+
+[ Upstream commit 02e3f7ffe2906033da73b7c7ea8180b131d0cdbc ]
+
+Return the internal struct that represents the vq group as virtqueue map
+token, instead of the device. This allows the map functions to access
+the information per group.
+
+At this moment all the virtqueues share the same vq group, that only
+can point to ASID 0. This change prepares the infrastructure for actual
+per-group address space handling
+
+Acked-by: Jason Wang <jasowang@redhat.com>
+Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
+Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
+Message-Id: <20260119143306.1818855-5-eperezma@redhat.com>
+Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vdpa/vdpa_user/iova_domain.c | 21 ++----
+ drivers/vdpa/vdpa_user/iova_domain.h | 5 -
+ drivers/vdpa/vdpa_user/vduse_dev.c | 109 ++++++++++++++++++++++++++++++-----
+ include/linux/virtio.h | 6 -
+ 4 files changed, 108 insertions(+), 33 deletions(-)
+
+--- a/drivers/vdpa/vdpa_user/iova_domain.c
++++ b/drivers/vdpa/vdpa_user/iova_domain.c
+@@ -493,17 +493,15 @@ void vduse_domain_unmap_page(struct vdus
+ vduse_domain_free_iova(iovad, dma_addr, size);
+ }
+
+-void *vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
+- size_t size, dma_addr_t *dma_addr,
+- gfp_t flag)
++dma_addr_t vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
++ size_t size, void *orig)
+ {
+ struct iova_domain *iovad = &domain->consistent_iovad;
+ unsigned long limit = domain->iova_limit;
+ dma_addr_t iova = vduse_domain_alloc_iova(iovad, size, limit);
+- void *orig = alloc_pages_exact(size, flag);
+
+- if (!iova || !orig)
+- goto err;
++ if (!iova)
++ return DMA_MAPPING_ERROR;
+
+ spin_lock(&domain->iotlb_lock);
+ if (vduse_iotlb_add_range(domain, (u64)iova, (u64)iova + size - 1,
+@@ -514,17 +512,12 @@ void *vduse_domain_alloc_coherent(struct
+ }
+ spin_unlock(&domain->iotlb_lock);
+
+- *dma_addr = iova;
++ return iova;
+
+- return orig;
+ err:
+- *dma_addr = DMA_MAPPING_ERROR;
+- if (orig)
+- free_pages_exact(orig, size);
+- if (iova)
+- vduse_domain_free_iova(iovad, iova, size);
++ vduse_domain_free_iova(iovad, iova, size);
+
+- return NULL;
++ return DMA_MAPPING_ERROR;
+ }
+
+ void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
+--- a/drivers/vdpa/vdpa_user/iova_domain.h
++++ b/drivers/vdpa/vdpa_user/iova_domain.h
+@@ -67,9 +67,8 @@ void vduse_domain_unmap_page(struct vdus
+ dma_addr_t dma_addr, size_t size,
+ enum dma_data_direction dir, unsigned long attrs);
+
+-void *vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
+- size_t size, dma_addr_t *dma_addr,
+- gfp_t flag);
++dma_addr_t vduse_domain_alloc_coherent(struct vduse_iova_domain *domain,
++ size_t size, void *orig);
+
+ void vduse_domain_free_coherent(struct vduse_iova_domain *domain, size_t size,
+ void *vaddr, dma_addr_t dma_addr,
+--- a/drivers/vdpa/vdpa_user/vduse_dev.c
++++ b/drivers/vdpa/vdpa_user/vduse_dev.c
+@@ -22,6 +22,7 @@
+ #include <linux/uio.h>
+ #include <linux/vdpa.h>
+ #include <linux/nospec.h>
++#include <linux/virtio.h>
+ #include <linux/vmalloc.h>
+ #include <linux/sched/mm.h>
+ #include <uapi/linux/vduse.h>
+@@ -83,6 +84,10 @@ struct vduse_umem {
+ struct mm_struct *mm;
+ };
+
++struct vduse_vq_group {
++ struct vduse_dev *dev;
++};
++
+ struct vduse_dev {
+ struct vduse_vdpa *vdev;
+ struct device *dev;
+@@ -115,6 +120,7 @@ struct vduse_dev {
+ u32 vq_num;
+ u32 vq_align;
+ struct vduse_umem *umem;
++ struct vduse_vq_group *groups;
+ struct mutex mem_lock;
+ unsigned int bounce_size;
+ struct mutex domain_lock;
+@@ -615,6 +621,16 @@ static int vduse_vdpa_set_vq_state(struc
+ return 0;
+ }
+
++static union virtio_map vduse_get_vq_map(struct vdpa_device *vdpa, u16 idx)
++{
++ struct vduse_dev *dev = vdpa_to_vduse(vdpa);
++ union virtio_map ret = {
++ .group = &dev->groups[0],
++ };
++
++ return ret;
++}
++
+ static int vduse_vdpa_get_vq_state(struct vdpa_device *vdpa, u16 idx,
+ struct vdpa_vq_state *state)
+ {
+@@ -834,6 +850,7 @@ static const struct vdpa_config_ops vdus
+ .get_vq_affinity = vduse_vdpa_get_vq_affinity,
+ .reset = vduse_vdpa_reset,
+ .set_map = vduse_vdpa_set_map,
++ .get_vq_map = vduse_get_vq_map,
+ .free = vduse_vdpa_free,
+ };
+
+@@ -841,7 +858,14 @@ static void vduse_dev_sync_single_for_de
+ dma_addr_t dma_addr, size_t size,
+ enum dma_data_direction dir)
+ {
+- struct vduse_iova_domain *domain = token.iova_domain;
++ struct vduse_dev *vdev;
++ struct vduse_iova_domain *domain;
++
++ if (!token.group)
++ return;
++
++ vdev = token.group->dev;
++ domain = vdev->domain;
+
+ vduse_domain_sync_single_for_device(domain, dma_addr, size, dir);
+ }
+@@ -850,7 +874,14 @@ static void vduse_dev_sync_single_for_cp
+ dma_addr_t dma_addr, size_t size,
+ enum dma_data_direction dir)
+ {
+- struct vduse_iova_domain *domain = token.iova_domain;
++ struct vduse_dev *vdev;
++ struct vduse_iova_domain *domain;
++
++ if (!token.group)
++ return;
++
++ vdev = token.group->dev;
++ domain = vdev->domain;
+
+ vduse_domain_sync_single_for_cpu(domain, dma_addr, size, dir);
+ }
+@@ -860,7 +891,14 @@ static dma_addr_t vduse_dev_map_page(uni
+ enum dma_data_direction dir,
+ unsigned long attrs)
+ {
+- struct vduse_iova_domain *domain = token.iova_domain;
++ struct vduse_dev *vdev;
++ struct vduse_iova_domain *domain;
++
++ if (!token.group)
++ return DMA_MAPPING_ERROR;
++
++ vdev = token.group->dev;
++ domain = vdev->domain;
+
+ return vduse_domain_map_page(domain, page, offset, size, dir, attrs);
+ }
+@@ -869,7 +907,14 @@ static void vduse_dev_unmap_page(union v
+ size_t size, enum dma_data_direction dir,
+ unsigned long attrs)
+ {
+- struct vduse_iova_domain *domain = token.iova_domain;
++ struct vduse_dev *vdev;
++ struct vduse_iova_domain *domain;
++
++ if (!token.group)
++ return;
++
++ vdev = token.group->dev;
++ domain = vdev->domain;
+
+ return vduse_domain_unmap_page(domain, dma_addr, size, dir, attrs);
+ }
+@@ -877,33 +922,57 @@ static void vduse_dev_unmap_page(union v
+ static void *vduse_dev_alloc_coherent(union virtio_map token, size_t size,
+ dma_addr_t *dma_addr, gfp_t flag)
+ {
+- struct vduse_iova_domain *domain = token.iova_domain;
+- unsigned long iova;
++ struct vduse_dev *vdev;
++ struct vduse_iova_domain *domain;
+ void *addr;
+
+ *dma_addr = DMA_MAPPING_ERROR;
+- addr = vduse_domain_alloc_coherent(domain, size,
+- (dma_addr_t *)&iova, flag);
++ if (!token.group)
++ return NULL;
++
++ addr = alloc_pages_exact(size, flag);
+ if (!addr)
+ return NULL;
+
+- *dma_addr = (dma_addr_t)iova;
++ vdev = token.group->dev;
++ domain = vdev->domain;
++ *dma_addr = vduse_domain_alloc_coherent(domain, size, addr);
++ if (*dma_addr == DMA_MAPPING_ERROR)
++ goto err;
+
+ return addr;
++
++err:
++ free_pages_exact(addr, size);
++ return NULL;
+ }
+
+ static void vduse_dev_free_coherent(union virtio_map token, size_t size,
+ void *vaddr, dma_addr_t dma_addr,
+ unsigned long attrs)
+ {
+- struct vduse_iova_domain *domain = token.iova_domain;
++ struct vduse_dev *vdev;
++ struct vduse_iova_domain *domain;
++
++ if (!token.group)
++ return;
++
++ vdev = token.group->dev;
++ domain = vdev->domain;
+
+ vduse_domain_free_coherent(domain, size, vaddr, dma_addr, attrs);
+ }
+
+ static bool vduse_dev_need_sync(union virtio_map token, dma_addr_t dma_addr)
+ {
+- struct vduse_iova_domain *domain = token.iova_domain;
++ struct vduse_dev *vdev;
++ struct vduse_iova_domain *domain;
++
++ if (!token.group)
++ return false;
++
++ vdev = token.group->dev;
++ domain = vdev->domain;
+
+ return dma_addr < domain->bounce_size;
+ }
+@@ -917,7 +986,14 @@ static int vduse_dev_mapping_error(union
+
+ static size_t vduse_dev_max_mapping_size(union virtio_map token)
+ {
+- struct vduse_iova_domain *domain = token.iova_domain;
++ struct vduse_dev *vdev;
++ struct vduse_iova_domain *domain;
++
++ if (!token.group)
++ return 0;
++
++ vdev = token.group->dev;
++ domain = vdev->domain;
+
+ return domain->bounce_size;
+ }
+@@ -1716,6 +1792,7 @@ static int vduse_destroy_dev(char *name)
+ if (dev->domain)
+ vduse_domain_destroy(dev->domain);
+ kfree(dev->name);
++ kfree(dev->groups);
+ vduse_dev_destroy(dev);
+ module_put(THIS_MODULE);
+
+@@ -1874,6 +1951,11 @@ static int vduse_create_dev(struct vduse
+ dev->device_features = config->features;
+ dev->device_id = config->device_id;
+ dev->vendor_id = config->vendor_id;
++ dev->groups = kcalloc(1, sizeof(dev->groups[0]), GFP_KERNEL);
++ if (!dev->groups)
++ goto err_vq_groups;
++ dev->groups[0].dev = dev;
++
+ dev->name = kstrdup(config->name, GFP_KERNEL);
+ if (!dev->name)
+ goto err_str;
+@@ -1910,6 +1992,8 @@ err_dev:
+ err_idr:
+ kfree(dev->name);
+ err_str:
++ kfree(dev->groups);
++err_vq_groups:
+ vduse_dev_destroy(dev);
+ err:
+ return ret;
+@@ -2071,7 +2155,6 @@ static int vdpa_dev_add(struct vdpa_mgmt
+ return -ENOMEM;
+ }
+
+- dev->vdev->vdpa.vmap.iova_domain = dev->domain;
+ ret = _vdpa_register_device(&dev->vdev->vdpa, dev->vq_num);
+ if (ret) {
+ put_device(&dev->vdev->vdpa.dev);
+--- a/include/linux/virtio.h
++++ b/include/linux/virtio.h
+@@ -41,13 +41,13 @@ struct virtqueue {
+ void *priv;
+ };
+
+-struct vduse_iova_domain;
++struct vduse_vq_group;
+
+ union virtio_map {
+ /* Device that performs DMA */
+ struct device *dma_dev;
+- /* VDUSE specific mapping data */
+- struct vduse_iova_domain *iova_domain;
++ /* VDUSE specific virtqueue group for doing map */
++ struct vduse_vq_group *group;
+ };
+
+ int virtqueue_add_outbuf(struct virtqueue *vq,
--- /dev/null
+From stable+bounces-287926-greg=kroah.com@vger.kernel.org Wed Jul 22 18:08:40 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 12:07:30 -0400
+Subject: vduse: take out allocations from vduse_dev_alloc_coherent
+To: stable@vger.kernel.org
+Cc: "Eugenio Pérez" <eperezma@redhat.com>, "Jason Wang" <jasowang@redhat.com>, "Michael S. Tsirkin" <mst@redhat.com>, "Sasha Levin" <sashal@kernel.org>
+Message-ID: <20260722160731.1739521-3-sashal@kernel.org>
+
+From: Eugenio Pérez <eperezma@redhat.com>
+
+[ Upstream commit 489d76520612abf9a4ede4344349105406c91a73 ]
+
+The function vduse_dev_alloc_coherent will be called under rwlock in
+next patches. Make it out of the lock to avoid increasing its fail
+rate.
+
+Acked-by: Jason Wang <jasowang@redhat.com>
+Signed-off-by: Eugenio Pérez <eperezma@redhat.com>
+Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
+Message-Id: <20260119143306.1818855-10-eperezma@redhat.com>
+Stable-dep-of: 9c1523803445 ("VDUSE: avoid leaking information to userspace")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ drivers/vdpa/vdpa_user/iova_domain.c | 3 ---
+ drivers/vdpa/vdpa_user/vduse_dev.c | 1 +
+ 2 files changed, 1 insertion(+), 3 deletions(-)
+
+--- a/drivers/vdpa/vdpa_user/iova_domain.c
++++ b/drivers/vdpa/vdpa_user/iova_domain.c
+@@ -526,7 +526,6 @@ void vduse_domain_free_coherent(struct v
+ struct iova_domain *iovad = &domain->consistent_iovad;
+ struct vhost_iotlb_map *map;
+ struct vdpa_map_file *map_file;
+- phys_addr_t pa;
+
+ spin_lock(&domain->iotlb_lock);
+ map = vhost_iotlb_itree_first(domain->iotlb, (u64)dma_addr,
+@@ -538,12 +537,10 @@ void vduse_domain_free_coherent(struct v
+ map_file = (struct vdpa_map_file *)map->opaque;
+ fput(map_file->file);
+ kfree(map_file);
+- pa = map->addr;
+ vhost_iotlb_map_free(domain->iotlb, map);
+ spin_unlock(&domain->iotlb_lock);
+
+ vduse_domain_free_iova(iovad, dma_addr, size);
+- free_pages_exact(phys_to_virt(pa), size);
+ }
+
+ static vm_fault_t vduse_domain_mmap_fault(struct vm_fault *vmf)
+--- a/drivers/vdpa/vdpa_user/vduse_dev.c
++++ b/drivers/vdpa/vdpa_user/vduse_dev.c
+@@ -961,6 +961,7 @@ static void vduse_dev_free_coherent(unio
+ domain = vdev->domain;
+
+ vduse_domain_free_coherent(domain, size, dma_addr, attrs);
++ free_pages_exact(vaddr, size);
+ }
+
+ static bool vduse_dev_need_sync(union virtio_map token, dma_addr_t dma_addr)
--- /dev/null
+From stable+bounces-289734-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:32 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:17 -0400
+Subject: x86,fs/resctrl: Rename some L3 specific functions
+To: stable@vger.kernel.org
+Cc: Tony Luck <tony.luck@intel.com>, "Borislav Petkov (AMD)" <bp@alien8.de>, Reinette Chatre <reinette.chatre@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727233421.2423844-3-sashal@kernel.org>
+
+From: Tony Luck <tony.luck@intel.com>
+
+[ Upstream commit 9c214d10c50990c7a61b95887493df9ae713eec5 ]
+
+With the arrival of monitor events tied to new domains associated with a
+different resource it would be clearer if the L3 resource specific functions
+are more accurately named.
+
+Rename three groups of functions:
+
+Functions that allocate/free architecture per-RMID MBM state information:
+arch_domain_mbm_alloc() -> l3_mon_domain_mbm_alloc()
+mon_domain_free() -> l3_mon_domain_free()
+
+Functions that allocate/free filesystem per-RMID MBM state information:
+domain_setup_mon_state() -> domain_setup_l3_mon_state()
+domain_destroy_mon_state() -> domain_destroy_l3_mon_state()
+
+Initialization/exit:
+rdt_get_mon_l3_config() -> rdt_get_l3_mon_config()
+resctrl_mon_resource_init() -> resctrl_l3_mon_resource_init()
+resctrl_mon_resource_exit() -> resctrl_l3_mon_resource_exit()
+
+Ensure kernel-doc descriptions of these functions' return values are present
+and correctly formatted.
+
+Signed-off-by: Tony Luck <tony.luck@intel.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Reviewed-by: Reinette Chatre <reinette.chatre@intel.com>
+Link: https://lore.kernel.org/20251217172121.12030-1-tony.luck@intel.com
+Stable-dep-of: 52fce648607e ("fs/resctrl: Fix use-after-free during unmount")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/x86/kernel/cpu/resctrl/core.c | 20 +++++++++++---------
+ arch/x86/kernel/cpu/resctrl/internal.h | 2 +-
+ arch/x86/kernel/cpu/resctrl/monitor.c | 2 +-
+ fs/resctrl/internal.h | 6 +++---
+ fs/resctrl/monitor.c | 8 ++++----
+ fs/resctrl/rdtgroup.c | 24 ++++++++++++------------
+ 6 files changed, 32 insertions(+), 30 deletions(-)
+
+--- a/arch/x86/kernel/cpu/resctrl/core.c
++++ b/arch/x86/kernel/cpu/resctrl/core.c
+@@ -363,7 +363,7 @@ static void ctrl_domain_free(struct rdt_
+ kfree(hw_dom);
+ }
+
+-static void mon_domain_free(struct rdt_hw_l3_mon_domain *hw_dom)
++static void l3_mon_domain_free(struct rdt_hw_l3_mon_domain *hw_dom)
+ {
+ int idx;
+
+@@ -396,11 +396,13 @@ static int domain_setup_ctrlval(struct r
+ }
+
+ /**
+- * arch_domain_mbm_alloc() - Allocate arch private storage for the MBM counters
++ * l3_mon_domain_mbm_alloc() - Allocate arch private storage for the MBM counters
+ * @num_rmid: The size of the MBM counter array
+ * @hw_dom: The domain that owns the allocated arrays
++ *
++ * Return: 0 for success, or -ENOMEM.
+ */
+-static int arch_domain_mbm_alloc(u32 num_rmid, struct rdt_hw_l3_mon_domain *hw_dom)
++static int l3_mon_domain_mbm_alloc(u32 num_rmid, struct rdt_hw_l3_mon_domain *hw_dom)
+ {
+ size_t tsize = sizeof(*hw_dom->arch_mbm_states[0]);
+ enum resctrl_event_id eventid;
+@@ -536,7 +538,7 @@ static void domain_add_cpu_mon(int cpu,
+ ci = get_cpu_cacheinfo_level(cpu, RESCTRL_L3_CACHE);
+ if (!ci) {
+ pr_warn_once("Can't find L3 cache for CPU:%d resource %s\n", cpu, r->name);
+- mon_domain_free(hw_dom);
++ l3_mon_domain_free(hw_dom);
+ return;
+ }
+ d->ci_id = ci->id;
+@@ -548,8 +550,8 @@ static void domain_add_cpu_mon(int cpu,
+
+ arch_mon_domain_online(r, d);
+
+- if (arch_domain_mbm_alloc(r->mon.num_rmid, hw_dom)) {
+- mon_domain_free(hw_dom);
++ if (l3_mon_domain_mbm_alloc(r->mon.num_rmid, hw_dom)) {
++ l3_mon_domain_free(hw_dom);
+ return;
+ }
+
+@@ -559,7 +561,7 @@ static void domain_add_cpu_mon(int cpu,
+ if (err) {
+ list_del_rcu(&d->hdr.list);
+ synchronize_rcu();
+- mon_domain_free(hw_dom);
++ l3_mon_domain_free(hw_dom);
+ }
+ }
+
+@@ -650,7 +652,7 @@ static void domain_remove_cpu_mon(int cp
+ resctrl_offline_mon_domain(r, d);
+ list_del_rcu(&d->hdr.list);
+ synchronize_rcu();
+- mon_domain_free(hw_dom);
++ l3_mon_domain_free(hw_dom);
+
+ return;
+ }
+@@ -897,7 +899,7 @@ static __init bool get_rdt_mon_resources
+ if (!ret)
+ return false;
+
+- return !rdt_get_mon_l3_config(r);
++ return !rdt_get_l3_mon_config(r);
+ }
+
+ static __init void __check_quirks_intel(void)
+--- a/arch/x86/kernel/cpu/resctrl/internal.h
++++ b/arch/x86/kernel/cpu/resctrl/internal.h
+@@ -211,7 +211,7 @@ union l3_qos_abmc_cfg {
+
+ void rdt_ctrl_update(void *arg);
+
+-int rdt_get_mon_l3_config(struct rdt_resource *r);
++int rdt_get_l3_mon_config(struct rdt_resource *r);
+
+ bool rdt_cpu_has(int flag);
+
+--- a/arch/x86/kernel/cpu/resctrl/monitor.c
++++ b/arch/x86/kernel/cpu/resctrl/monitor.c
+@@ -423,7 +423,7 @@ static __init int snc_get_config(void)
+ return ret;
+ }
+
+-int __init rdt_get_mon_l3_config(struct rdt_resource *r)
++int __init rdt_get_l3_mon_config(struct rdt_resource *r)
+ {
+ unsigned int mbm_offset = boot_cpu_data.x86_cache_mbm_width_offset;
+ struct rdt_hw_resource *hw_res = resctrl_to_arch_res(r);
+--- a/fs/resctrl/internal.h
++++ b/fs/resctrl/internal.h
+@@ -355,7 +355,9 @@ int alloc_rmid(u32 closid);
+
+ void free_rmid(u32 closid, u32 rmid);
+
+-void resctrl_mon_resource_exit(void);
++int resctrl_l3_mon_resource_init(void);
++
++void resctrl_l3_mon_resource_exit(void);
+
+ void mon_event_count(void *info);
+
+@@ -365,8 +367,6 @@ void mon_event_read(struct rmid_read *rr
+ struct rdt_l3_mon_domain *d, struct rdtgroup *rdtgrp,
+ cpumask_t *cpumask, int evtid, int first);
+
+-int resctrl_mon_resource_init(void);
+-
+ void mbm_setup_overflow_handler(struct rdt_l3_mon_domain *dom,
+ unsigned long delay_ms,
+ int exclude_cpu);
+--- a/fs/resctrl/monitor.c
++++ b/fs/resctrl/monitor.c
+@@ -1758,7 +1758,7 @@ ssize_t mbm_L3_assignments_write(struct
+ }
+
+ /**
+- * resctrl_mon_resource_init() - Initialise global monitoring structures.
++ * resctrl_l3_mon_resource_init() - Initialise global monitoring structures.
+ *
+ * Allocate and initialise global monitor resources that do not belong to a
+ * specific domain. i.e. the rmid_ptrs[] used for the limbo and free lists.
+@@ -1767,9 +1767,9 @@ ssize_t mbm_L3_assignments_write(struct
+ * Resctrl's cpuhp callbacks may be called before this point to bring a domain
+ * online.
+ *
+- * Returns 0 for success, or -ENOMEM.
++ * Return: 0 for success, or -ENOMEM.
+ */
+-int resctrl_mon_resource_init(void)
++int resctrl_l3_mon_resource_init(void)
+ {
+ struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+ int ret;
+@@ -1819,7 +1819,7 @@ int resctrl_mon_resource_init(void)
+ return 0;
+ }
+
+-void resctrl_mon_resource_exit(void)
++void resctrl_l3_mon_resource_exit(void)
+ {
+ struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+
+--- a/fs/resctrl/rdtgroup.c
++++ b/fs/resctrl/rdtgroup.c
+@@ -4170,7 +4170,7 @@ static void rdtgroup_setup_default(void)
+ mutex_unlock(&rdtgroup_mutex);
+ }
+
+-static void domain_destroy_mon_state(struct rdt_l3_mon_domain *d)
++static void domain_destroy_l3_mon_state(struct rdt_l3_mon_domain *d)
+ {
+ int idx;
+
+@@ -4218,13 +4218,13 @@ void resctrl_offline_mon_domain(struct r
+ cancel_delayed_work(&d->cqm_limbo);
+ }
+
+- domain_destroy_mon_state(d);
++ domain_destroy_l3_mon_state(d);
+
+ mutex_unlock(&rdtgroup_mutex);
+ }
+
+ /**
+- * domain_setup_mon_state() - Initialise domain monitoring structures.
++ * domain_setup_l3_mon_state() - Initialise domain monitoring structures.
+ * @r: The resource for the newly online domain.
+ * @d: The newly online domain.
+ *
+@@ -4232,11 +4232,11 @@ void resctrl_offline_mon_domain(struct r
+ * Called when the first CPU of a domain comes online, regardless of whether
+ * the filesystem is mounted.
+ * During boot this may be called before global allocations have been made by
+- * resctrl_mon_resource_init().
++ * resctrl_l3_mon_resource_init().
+ *
+- * Returns 0 for success, or -ENOMEM.
++ * Return: 0 for success, or -ENOMEM.
+ */
+-static int domain_setup_mon_state(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
++static int domain_setup_l3_mon_state(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+ u32 idx_limit = resctrl_arch_system_num_rmid_idx();
+ size_t tsize = sizeof(*d->mbm_states[0]);
+@@ -4298,7 +4298,7 @@ int resctrl_online_mon_domain(struct rdt
+
+ mutex_lock(&rdtgroup_mutex);
+
+- err = domain_setup_mon_state(r, d);
++ err = domain_setup_l3_mon_state(r, d);
+ if (err)
+ goto out_unlock;
+
+@@ -4413,13 +4413,13 @@ int resctrl_init(void)
+
+ thread_throttle_mode_init();
+
+- ret = resctrl_mon_resource_init();
++ ret = resctrl_l3_mon_resource_init();
+ if (ret)
+ return ret;
+
+ ret = sysfs_create_mount_point(fs_kobj, "resctrl");
+ if (ret) {
+- resctrl_mon_resource_exit();
++ resctrl_l3_mon_resource_exit();
+ return ret;
+ }
+
+@@ -4454,7 +4454,7 @@ int resctrl_init(void)
+
+ cleanup_mountpoint:
+ sysfs_remove_mount_point(fs_kobj, "resctrl");
+- resctrl_mon_resource_exit();
++ resctrl_l3_mon_resource_exit();
+
+ return ret;
+ }
+@@ -4490,7 +4490,7 @@ static bool resctrl_online_domains_exist
+ * When called by the architecture code, all CPUs and resctrl domains must be
+ * offline. This ensures the limbo and overflow handlers are not scheduled to
+ * run, meaning the data structures they access can be freed by
+- * resctrl_mon_resource_exit().
++ * resctrl_l3_mon_resource_exit().
+ *
+ * After resctrl_exit() returns, the architecture code should return an
+ * error from all resctrl_arch_ functions that can do this.
+@@ -4517,5 +4517,5 @@ void resctrl_exit(void)
+ * it can be used to umount resctrl.
+ */
+
+- resctrl_mon_resource_exit();
++ resctrl_l3_mon_resource_exit();
+ }
--- /dev/null
+From stable+bounces-289735-greg=kroah.com@vger.kernel.org Tue Jul 28 01:34:31 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Mon, 27 Jul 2026 19:34:16 -0400
+Subject: x86,fs/resctrl: Rename struct rdt_mon_domain and rdt_hw_mon_domain
+To: stable@vger.kernel.org
+Cc: Tony Luck <tony.luck@intel.com>, "Borislav Petkov (AMD)" <bp@alien8.de>, Reinette Chatre <reinette.chatre@intel.com>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260727233421.2423844-2-sashal@kernel.org>
+
+From: Tony Luck <tony.luck@intel.com>
+
+[ Upstream commit 4bc3ef46ff41d5e7ba557e56e9cd2031527cd7f8 ]
+
+The upcoming telemetry event monitoring is not tied to the L3 resource and
+will have a new domain structure.
+
+Rename the L3 resource specific domain data structures to include "l3_"
+in their names to avoid confusion between the different resource specific
+domain structures:
+rdt_mon_domain -> rdt_l3_mon_domain
+rdt_hw_mon_domain -> rdt_hw_l3_mon_domain
+
+No functional change.
+
+Signed-off-by: Tony Luck <tony.luck@intel.com>
+Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
+Reviewed-by: Reinette Chatre <reinette.chatre@intel.com>
+Link: https://lore.kernel.org/20251217172121.12030-1-tony.luck@intel.com
+Stable-dep-of: 52fce648607e ("fs/resctrl: Fix use-after-free during unmount")
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ arch/x86/kernel/cpu/resctrl/core.c | 16 ++++----
+ arch/x86/kernel/cpu/resctrl/internal.h | 16 ++++----
+ arch/x86/kernel/cpu/resctrl/monitor.c | 34 +++++++++---------
+ fs/resctrl/ctrlmondata.c | 6 +--
+ fs/resctrl/internal.h | 12 +++---
+ fs/resctrl/monitor.c | 62 ++++++++++++++++-----------------
+ fs/resctrl/rdtgroup.c | 32 ++++++++---------
+ include/linux/resctrl.h | 28 +++++++-------
+ 8 files changed, 103 insertions(+), 103 deletions(-)
+
+--- a/arch/x86/kernel/cpu/resctrl/core.c
++++ b/arch/x86/kernel/cpu/resctrl/core.c
+@@ -363,7 +363,7 @@ static void ctrl_domain_free(struct rdt_
+ kfree(hw_dom);
+ }
+
+-static void mon_domain_free(struct rdt_hw_mon_domain *hw_dom)
++static void mon_domain_free(struct rdt_hw_l3_mon_domain *hw_dom)
+ {
+ int idx;
+
+@@ -400,7 +400,7 @@ static int domain_setup_ctrlval(struct r
+ * @num_rmid: The size of the MBM counter array
+ * @hw_dom: The domain that owns the allocated arrays
+ */
+-static int arch_domain_mbm_alloc(u32 num_rmid, struct rdt_hw_mon_domain *hw_dom)
++static int arch_domain_mbm_alloc(u32 num_rmid, struct rdt_hw_l3_mon_domain *hw_dom)
+ {
+ size_t tsize = sizeof(*hw_dom->arch_mbm_states[0]);
+ enum resctrl_event_id eventid;
+@@ -499,9 +499,9 @@ static void domain_add_cpu_mon(int cpu,
+ {
+ int id = get_domain_id_from_scope(cpu, r->mon_scope);
+ struct list_head *add_pos = NULL;
+- struct rdt_hw_mon_domain *hw_dom;
++ struct rdt_hw_l3_mon_domain *hw_dom;
+ struct rdt_domain_hdr *hdr;
+- struct rdt_mon_domain *d;
++ struct rdt_l3_mon_domain *d;
+ struct cacheinfo *ci;
+ int err;
+
+@@ -517,7 +517,7 @@ static void domain_add_cpu_mon(int cpu,
+ if (hdr) {
+ if (WARN_ON_ONCE(hdr->type != RESCTRL_MON_DOMAIN))
+ return;
+- d = container_of(hdr, struct rdt_mon_domain, hdr);
++ d = container_of(hdr, struct rdt_l3_mon_domain, hdr);
+
+ cpumask_set_cpu(cpu, &d->hdr.cpu_mask);
+ /* Update the mbm_assign_mode state for the CPU if supported */
+@@ -620,9 +620,9 @@ static void domain_remove_cpu_ctrl(int c
+ static void domain_remove_cpu_mon(int cpu, struct rdt_resource *r)
+ {
+ int id = get_domain_id_from_scope(cpu, r->mon_scope);
+- struct rdt_hw_mon_domain *hw_dom;
++ struct rdt_hw_l3_mon_domain *hw_dom;
+ struct rdt_domain_hdr *hdr;
+- struct rdt_mon_domain *d;
++ struct rdt_l3_mon_domain *d;
+
+ lockdep_assert_held(&domain_list_lock);
+
+@@ -642,7 +642,7 @@ static void domain_remove_cpu_mon(int cp
+ if (WARN_ON_ONCE(hdr->type != RESCTRL_MON_DOMAIN))
+ return;
+
+- d = container_of(hdr, struct rdt_mon_domain, hdr);
++ d = container_of(hdr, struct rdt_l3_mon_domain, hdr);
+ hw_dom = resctrl_to_arch_mon_dom(d);
+
+ cpumask_clear_cpu(cpu, &d->hdr.cpu_mask);
+--- a/arch/x86/kernel/cpu/resctrl/internal.h
++++ b/arch/x86/kernel/cpu/resctrl/internal.h
+@@ -63,17 +63,17 @@ struct rdt_hw_ctrl_domain {
+ };
+
+ /**
+- * struct rdt_hw_mon_domain - Arch private attributes of a set of CPUs that share
+- * a resource for a monitor function
+- * @d_resctrl: Properties exposed to the resctrl file system
++ * struct rdt_hw_l3_mon_domain - Arch private attributes of a set of CPUs sharing
++ * RDT_RESOURCE_L3 monitoring
++ * @d_resctrl: Properties exposed to the resctrl file system
+ * @arch_mbm_states: Per-event pointer to the MBM event's saved state.
+ * An MBM event's state is an array of struct arch_mbm_state
+ * indexed by RMID on x86.
+ *
+ * Members of this structure are accessed via helpers that provide abstraction.
+ */
+-struct rdt_hw_mon_domain {
+- struct rdt_mon_domain d_resctrl;
++struct rdt_hw_l3_mon_domain {
++ struct rdt_l3_mon_domain d_resctrl;
+ struct arch_mbm_state *arch_mbm_states[QOS_NUM_L3_MBM_EVENTS];
+ };
+
+@@ -82,9 +82,9 @@ static inline struct rdt_hw_ctrl_domain
+ return container_of(r, struct rdt_hw_ctrl_domain, d_resctrl);
+ }
+
+-static inline struct rdt_hw_mon_domain *resctrl_to_arch_mon_dom(struct rdt_mon_domain *r)
++static inline struct rdt_hw_l3_mon_domain *resctrl_to_arch_mon_dom(struct rdt_l3_mon_domain *r)
+ {
+- return container_of(r, struct rdt_hw_mon_domain, d_resctrl);
++ return container_of(r, struct rdt_hw_l3_mon_domain, d_resctrl);
+ }
+
+ /**
+@@ -138,7 +138,7 @@ static inline struct rdt_hw_resource *re
+
+ extern struct rdt_hw_resource rdt_resources_all[];
+
+-void arch_mon_domain_online(struct rdt_resource *r, struct rdt_mon_domain *d);
++void arch_mon_domain_online(struct rdt_resource *r, struct rdt_l3_mon_domain *d);
+
+ /* CPUID.(EAX=10H, ECX=ResID=1).EAX */
+ union cpuid_0x10_1_eax {
+--- a/arch/x86/kernel/cpu/resctrl/monitor.c
++++ b/arch/x86/kernel/cpu/resctrl/monitor.c
+@@ -109,7 +109,7 @@ static inline u64 get_corrected_mbm_coun
+ *
+ * In RMID sharing mode there are fewer "logical RMID" values available
+ * to accumulate data ("physical RMIDs" are divided evenly between SNC
+- * nodes that share an L3 cache). Linux creates an rdt_mon_domain for
++ * nodes that share an L3 cache). Linux creates an rdt_l3_mon_domain for
+ * each SNC node.
+ *
+ * The value loaded into IA32_PQR_ASSOC is the "logical RMID".
+@@ -157,7 +157,7 @@ static int __rmid_read_phys(u32 prmid, e
+ return 0;
+ }
+
+-static struct arch_mbm_state *get_arch_mbm_state(struct rdt_hw_mon_domain *hw_dom,
++static struct arch_mbm_state *get_arch_mbm_state(struct rdt_hw_l3_mon_domain *hw_dom,
+ u32 rmid,
+ enum resctrl_event_id eventid)
+ {
+@@ -171,11 +171,11 @@ static struct arch_mbm_state *get_arch_m
+ return state ? &state[rmid] : NULL;
+ }
+
+-void resctrl_arch_reset_rmid(struct rdt_resource *r, struct rdt_mon_domain *d,
++void resctrl_arch_reset_rmid(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ u32 unused, u32 rmid,
+ enum resctrl_event_id eventid)
+ {
+- struct rdt_hw_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
++ struct rdt_hw_l3_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
+ int cpu = cpumask_any(&d->hdr.cpu_mask);
+ struct arch_mbm_state *am;
+ u32 prmid;
+@@ -194,9 +194,9 @@ void resctrl_arch_reset_rmid(struct rdt_
+ * Assumes that hardware counters are also reset and thus that there is
+ * no need to record initial non-zero counts.
+ */
+-void resctrl_arch_reset_rmid_all(struct rdt_resource *r, struct rdt_mon_domain *d)
++void resctrl_arch_reset_rmid_all(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+- struct rdt_hw_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
++ struct rdt_hw_l3_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
+ enum resctrl_event_id eventid;
+ int idx;
+
+@@ -217,10 +217,10 @@ static u64 mbm_overflow_count(u64 prev_m
+ return chunks >> shift;
+ }
+
+-static u64 get_corrected_val(struct rdt_resource *r, struct rdt_mon_domain *d,
++static u64 get_corrected_val(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ u32 rmid, enum resctrl_event_id eventid, u64 msr_val)
+ {
+- struct rdt_hw_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
++ struct rdt_hw_l3_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
+ struct rdt_hw_resource *hw_res = resctrl_to_arch_res(r);
+ struct arch_mbm_state *am;
+ u64 chunks;
+@@ -238,11 +238,11 @@ static u64 get_corrected_val(struct rdt_
+ return chunks * hw_res->mon_scale;
+ }
+
+-int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_mon_domain *d,
++int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ u32 unused, u32 rmid, enum resctrl_event_id eventid,
+ u64 *val, void *ignored)
+ {
+- struct rdt_hw_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
++ struct rdt_hw_l3_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
+ struct arch_mbm_state *am;
+ u64 msr_val;
+ u32 prmid;
+@@ -308,11 +308,11 @@ static int __cntr_id_read(u32 cntr_id, u
+ return 0;
+ }
+
+-void resctrl_arch_reset_cntr(struct rdt_resource *r, struct rdt_mon_domain *d,
++void resctrl_arch_reset_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ u32 unused, u32 rmid, int cntr_id,
+ enum resctrl_event_id eventid)
+ {
+- struct rdt_hw_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
++ struct rdt_hw_l3_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
+ struct arch_mbm_state *am;
+
+ am = get_arch_mbm_state(hw_dom, rmid, eventid);
+@@ -324,7 +324,7 @@ void resctrl_arch_reset_cntr(struct rdt_
+ }
+ }
+
+-int resctrl_arch_cntr_read(struct rdt_resource *r, struct rdt_mon_domain *d,
++int resctrl_arch_cntr_read(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ u32 unused, u32 rmid, int cntr_id,
+ enum resctrl_event_id eventid, u64 *val)
+ {
+@@ -354,7 +354,7 @@ int resctrl_arch_cntr_read(struct rdt_re
+ * must adjust RMID counter numbers based on SNC node. See
+ * logical_rmid_to_physical_rmid() for code that does this.
+ */
+-void arch_mon_domain_online(struct rdt_resource *r, struct rdt_mon_domain *d)
++void arch_mon_domain_online(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+ if (snc_nodes_per_l3_cache > 1)
+ msr_clear_bit(MSR_RMID_SNC_CONFIG, 0);
+@@ -515,7 +515,7 @@ static void resctrl_abmc_set_one_amd(voi
+ */
+ static void _resctrl_abmc_enable(struct rdt_resource *r, bool enable)
+ {
+- struct rdt_mon_domain *d;
++ struct rdt_l3_mon_domain *d;
+
+ lockdep_assert_cpus_held();
+
+@@ -554,11 +554,11 @@ static void resctrl_abmc_config_one_amd(
+ /*
+ * Send an IPI to the domain to assign the counter to RMID, event pair.
+ */
+-void resctrl_arch_config_cntr(struct rdt_resource *r, struct rdt_mon_domain *d,
++void resctrl_arch_config_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ enum resctrl_event_id evtid, u32 rmid, u32 closid,
+ u32 cntr_id, bool assign)
+ {
+- struct rdt_hw_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
++ struct rdt_hw_l3_mon_domain *hw_dom = resctrl_to_arch_mon_dom(d);
+ union l3_qos_abmc_cfg abmc_cfg = { 0 };
+ struct arch_mbm_state *am;
+
+--- a/fs/resctrl/ctrlmondata.c
++++ b/fs/resctrl/ctrlmondata.c
+@@ -547,7 +547,7 @@ struct rdt_domain_hdr *resctrl_find_doma
+ }
+
+ void mon_event_read(struct rmid_read *rr, struct rdt_resource *r,
+- struct rdt_mon_domain *d, struct rdtgroup *rdtgrp,
++ struct rdt_l3_mon_domain *d, struct rdtgroup *rdtgrp,
+ cpumask_t *cpumask, int evtid, int first)
+ {
+ int cpu;
+@@ -596,9 +596,9 @@ int rdtgroup_mondata_show(struct seq_fil
+ struct kernfs_open_file *of = m->private;
+ enum resctrl_res_level resid;
+ enum resctrl_event_id evtid;
++ struct rdt_l3_mon_domain *d;
+ struct rdt_domain_hdr *hdr;
+ struct rmid_read rr = {0};
+- struct rdt_mon_domain *d;
+ struct rdtgroup *rdtgrp;
+ int domid, cpu, ret = 0;
+ struct rdt_resource *r;
+@@ -653,7 +653,7 @@ int rdtgroup_mondata_show(struct seq_fil
+ ret = -ENOENT;
+ goto out;
+ }
+- d = container_of(hdr, struct rdt_mon_domain, hdr);
++ d = container_of(hdr, struct rdt_l3_mon_domain, hdr);
+ mon_event_read(&rr, r, d, rdtgrp, &d->hdr.cpu_mask, evtid, false);
+ }
+
+--- a/fs/resctrl/internal.h
++++ b/fs/resctrl/internal.h
+@@ -123,7 +123,7 @@ struct mon_data {
+ struct rmid_read {
+ struct rdtgroup *rgrp;
+ struct rdt_resource *r;
+- struct rdt_mon_domain *d;
++ struct rdt_l3_mon_domain *d;
+ enum resctrl_event_id evtid;
+ bool first;
+ struct cacheinfo *ci;
+@@ -362,12 +362,12 @@ void mon_event_count(void *info);
+ int rdtgroup_mondata_show(struct seq_file *m, void *arg);
+
+ void mon_event_read(struct rmid_read *rr, struct rdt_resource *r,
+- struct rdt_mon_domain *d, struct rdtgroup *rdtgrp,
++ struct rdt_l3_mon_domain *d, struct rdtgroup *rdtgrp,
+ cpumask_t *cpumask, int evtid, int first);
+
+ int resctrl_mon_resource_init(void);
+
+-void mbm_setup_overflow_handler(struct rdt_mon_domain *dom,
++void mbm_setup_overflow_handler(struct rdt_l3_mon_domain *dom,
+ unsigned long delay_ms,
+ int exclude_cpu);
+
+@@ -375,14 +375,14 @@ void mbm_handle_overflow(struct work_str
+
+ bool is_mba_sc(struct rdt_resource *r);
+
+-void cqm_setup_limbo_handler(struct rdt_mon_domain *dom, unsigned long delay_ms,
++void cqm_setup_limbo_handler(struct rdt_l3_mon_domain *dom, unsigned long delay_ms,
+ int exclude_cpu);
+
+ void cqm_handle_limbo(struct work_struct *work);
+
+-bool has_busy_rmid(struct rdt_mon_domain *d);
++bool has_busy_rmid(struct rdt_l3_mon_domain *d);
+
+-void __check_limbo(struct rdt_mon_domain *d, bool force_free);
++void __check_limbo(struct rdt_l3_mon_domain *d, bool force_free);
+
+ void resctrl_file_fflags_init(const char *config, unsigned long fflags);
+
+--- a/fs/resctrl/monitor.c
++++ b/fs/resctrl/monitor.c
+@@ -130,7 +130,7 @@ static void limbo_release_entry(struct r
+ * decrement the count. If the busy count gets to zero on an RMID, we
+ * free the RMID
+ */
+-void __check_limbo(struct rdt_mon_domain *d, bool force_free)
++void __check_limbo(struct rdt_l3_mon_domain *d, bool force_free)
+ {
+ struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+ u32 idx_limit = resctrl_arch_system_num_rmid_idx();
+@@ -193,7 +193,7 @@ void __check_limbo(struct rdt_mon_domain
+ resctrl_arch_mon_ctx_free(r, QOS_L3_OCCUP_EVENT_ID, arch_mon_ctx);
+ }
+
+-bool has_busy_rmid(struct rdt_mon_domain *d)
++bool has_busy_rmid(struct rdt_l3_mon_domain *d)
+ {
+ u32 idx_limit = resctrl_arch_system_num_rmid_idx();
+
+@@ -294,7 +294,7 @@ int alloc_rmid(u32 closid)
+ static void add_rmid_to_limbo(struct rmid_entry *entry)
+ {
+ struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+- struct rdt_mon_domain *d;
++ struct rdt_l3_mon_domain *d;
+ u32 idx;
+
+ lockdep_assert_held(&rdtgroup_mutex);
+@@ -347,7 +347,7 @@ void free_rmid(u32 closid, u32 rmid)
+ list_add_tail(&entry->list, &rmid_free_lru);
+ }
+
+-static struct mbm_state *get_mbm_state(struct rdt_mon_domain *d, u32 closid,
++static struct mbm_state *get_mbm_state(struct rdt_l3_mon_domain *d, u32 closid,
+ u32 rmid, enum resctrl_event_id evtid)
+ {
+ u32 idx = resctrl_arch_rmid_idx_encode(closid, rmid);
+@@ -367,7 +367,7 @@ static struct mbm_state *get_mbm_state(s
+ * Return:
+ * Valid counter ID on success, or -ENOENT on failure.
+ */
+-static int mbm_cntr_get(struct rdt_resource *r, struct rdt_mon_domain *d,
++static int mbm_cntr_get(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ struct rdtgroup *rdtgrp, enum resctrl_event_id evtid)
+ {
+ int cntr_id;
+@@ -394,7 +394,7 @@ static int mbm_cntr_get(struct rdt_resou
+ * Return:
+ * Valid counter ID on success, or -ENOSPC on failure.
+ */
+-static int mbm_cntr_alloc(struct rdt_resource *r, struct rdt_mon_domain *d,
++static int mbm_cntr_alloc(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ struct rdtgroup *rdtgrp, enum resctrl_event_id evtid)
+ {
+ int cntr_id;
+@@ -413,7 +413,7 @@ static int mbm_cntr_alloc(struct rdt_res
+ /*
+ * mbm_cntr_free() - Clear the counter ID configuration details in the domain @d.
+ */
+-static void mbm_cntr_free(struct rdt_mon_domain *d, int cntr_id)
++static void mbm_cntr_free(struct rdt_l3_mon_domain *d, int cntr_id)
+ {
+ memset(&d->cntr_cfg[cntr_id], 0, sizeof(*d->cntr_cfg));
+ }
+@@ -423,7 +423,7 @@ static int __l3_mon_event_count(struct r
+ int cpu = smp_processor_id();
+ u32 closid = rdtgrp->closid;
+ u32 rmid = rdtgrp->mon.rmid;
+- struct rdt_mon_domain *d;
++ struct rdt_l3_mon_domain *d;
+ int cntr_id = -ENOENT;
+ struct mbm_state *m;
+ int err, ret;
+@@ -628,7 +628,7 @@ static struct rdt_ctrl_domain *get_ctrl_
+ * throttle MSRs already have low percentage values. To avoid
+ * unnecessarily restricting such rdtgroups, we also increase the bandwidth.
+ */
+-static void update_mba_bw(struct rdtgroup *rgrp, struct rdt_mon_domain *dom_mbm)
++static void update_mba_bw(struct rdtgroup *rgrp, struct rdt_l3_mon_domain *dom_mbm)
+ {
+ u32 closid, rmid, cur_msr_val, new_msr_val;
+ struct mbm_state *pmbm_data, *cmbm_data;
+@@ -696,7 +696,7 @@ static void update_mba_bw(struct rdtgrou
+ resctrl_arch_update_one(r_mba, dom_mba, closid, CDP_NONE, new_msr_val);
+ }
+
+-static void mbm_update_one_event(struct rdt_resource *r, struct rdt_mon_domain *d,
++static void mbm_update_one_event(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ struct rdtgroup *rdtgrp, enum resctrl_event_id evtid)
+ {
+ struct rmid_read rr = {0};
+@@ -728,7 +728,7 @@ static void mbm_update_one_event(struct
+ resctrl_arch_mon_ctx_free(rr.r, rr.evtid, rr.arch_mon_ctx);
+ }
+
+-static void mbm_update(struct rdt_resource *r, struct rdt_mon_domain *d,
++static void mbm_update(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ struct rdtgroup *rdtgrp)
+ {
+ /*
+@@ -749,12 +749,12 @@ static void mbm_update(struct rdt_resour
+ void cqm_handle_limbo(struct work_struct *work)
+ {
+ unsigned long delay = msecs_to_jiffies(CQM_LIMBOCHECK_INTERVAL);
+- struct rdt_mon_domain *d;
++ struct rdt_l3_mon_domain *d;
+
+ cpus_read_lock();
+ mutex_lock(&rdtgroup_mutex);
+
+- d = container_of(work, struct rdt_mon_domain, cqm_limbo.work);
++ d = container_of(work, struct rdt_l3_mon_domain, cqm_limbo.work);
+
+ __check_limbo(d, false);
+
+@@ -777,7 +777,7 @@ void cqm_handle_limbo(struct work_struct
+ * @exclude_cpu: Which CPU the handler should not run on,
+ * RESCTRL_PICK_ANY_CPU to pick any CPU.
+ */
+-void cqm_setup_limbo_handler(struct rdt_mon_domain *dom, unsigned long delay_ms,
++void cqm_setup_limbo_handler(struct rdt_l3_mon_domain *dom, unsigned long delay_ms,
+ int exclude_cpu)
+ {
+ unsigned long delay = msecs_to_jiffies(delay_ms);
+@@ -794,7 +794,7 @@ void mbm_handle_overflow(struct work_str
+ {
+ unsigned long delay = msecs_to_jiffies(MBM_OVERFLOW_INTERVAL);
+ struct rdtgroup *prgrp, *crgrp;
+- struct rdt_mon_domain *d;
++ struct rdt_l3_mon_domain *d;
+ struct list_head *head;
+ struct rdt_resource *r;
+
+@@ -809,7 +809,7 @@ void mbm_handle_overflow(struct work_str
+ goto out_unlock;
+
+ r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+- d = container_of(work, struct rdt_mon_domain, mbm_over.work);
++ d = container_of(work, struct rdt_l3_mon_domain, mbm_over.work);
+
+ list_for_each_entry(prgrp, &rdt_all_groups, rdtgroup_list) {
+ mbm_update(r, d, prgrp);
+@@ -843,7 +843,7 @@ out_unlock:
+ * @exclude_cpu: Which CPU the handler should not run on,
+ * RESCTRL_PICK_ANY_CPU to pick any CPU.
+ */
+-void mbm_setup_overflow_handler(struct rdt_mon_domain *dom, unsigned long delay_ms,
++void mbm_setup_overflow_handler(struct rdt_l3_mon_domain *dom, unsigned long delay_ms,
+ int exclude_cpu)
+ {
+ unsigned long delay = msecs_to_jiffies(delay_ms);
+@@ -1098,7 +1098,7 @@ out_unlock:
+ * mbm_cntr_free_all() - Clear all the counter ID configuration details in the
+ * domain @d. Called when mbm_assign_mode is changed.
+ */
+-static void mbm_cntr_free_all(struct rdt_resource *r, struct rdt_mon_domain *d)
++static void mbm_cntr_free_all(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+ memset(d->cntr_cfg, 0, sizeof(*d->cntr_cfg) * r->mon.num_mbm_cntrs);
+ }
+@@ -1107,7 +1107,7 @@ static void mbm_cntr_free_all(struct rdt
+ * resctrl_reset_rmid_all() - Reset all non-architecture states for all the
+ * supported RMIDs.
+ */
+-static void resctrl_reset_rmid_all(struct rdt_resource *r, struct rdt_mon_domain *d)
++static void resctrl_reset_rmid_all(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+ u32 idx_limit = resctrl_arch_system_num_rmid_idx();
+ enum resctrl_event_id evt;
+@@ -1128,7 +1128,7 @@ static void resctrl_reset_rmid_all(struc
+ * Assign the counter if @assign is true else unassign the counter. Reset the
+ * associated non-architectural state.
+ */
+-static void rdtgroup_assign_cntr(struct rdt_resource *r, struct rdt_mon_domain *d,
++static void rdtgroup_assign_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ enum resctrl_event_id evtid, u32 rmid, u32 closid,
+ u32 cntr_id, bool assign)
+ {
+@@ -1148,7 +1148,7 @@ static void rdtgroup_assign_cntr(struct
+ * Return:
+ * 0 on success, < 0 on failure.
+ */
+-static int rdtgroup_alloc_assign_cntr(struct rdt_resource *r, struct rdt_mon_domain *d,
++static int rdtgroup_alloc_assign_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ struct rdtgroup *rdtgrp, struct mon_evt *mevt)
+ {
+ int cntr_id;
+@@ -1183,7 +1183,7 @@ static int rdtgroup_alloc_assign_cntr(st
+ * Return:
+ * 0 on success, < 0 on failure.
+ */
+-static int rdtgroup_assign_cntr_event(struct rdt_mon_domain *d, struct rdtgroup *rdtgrp,
++static int rdtgroup_assign_cntr_event(struct rdt_l3_mon_domain *d, struct rdtgroup *rdtgrp,
+ struct mon_evt *mevt)
+ {
+ struct rdt_resource *r = resctrl_arch_get_resource(mevt->rid);
+@@ -1233,7 +1233,7 @@ void rdtgroup_assign_cntrs(struct rdtgro
+ * rdtgroup_free_unassign_cntr() - Unassign and reset the counter ID configuration
+ * for the event pointed to by @mevt within the domain @d and resctrl group @rdtgrp.
+ */
+-static void rdtgroup_free_unassign_cntr(struct rdt_resource *r, struct rdt_mon_domain *d,
++static void rdtgroup_free_unassign_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ struct rdtgroup *rdtgrp, struct mon_evt *mevt)
+ {
+ int cntr_id;
+@@ -1254,7 +1254,7 @@ static void rdtgroup_free_unassign_cntr(
+ * the event structure @mevt from the domain @d and the group @rdtgrp. Unassign
+ * the counters from all the domains if @d is NULL else unassign from @d.
+ */
+-static void rdtgroup_unassign_cntr_event(struct rdt_mon_domain *d, struct rdtgroup *rdtgrp,
++static void rdtgroup_unassign_cntr_event(struct rdt_l3_mon_domain *d, struct rdtgroup *rdtgrp,
+ struct mon_evt *mevt)
+ {
+ struct rdt_resource *r = resctrl_arch_get_resource(mevt->rid);
+@@ -1329,7 +1329,7 @@ next_config:
+ static void rdtgroup_update_cntr_event(struct rdt_resource *r, struct rdtgroup *rdtgrp,
+ enum resctrl_event_id evtid)
+ {
+- struct rdt_mon_domain *d;
++ struct rdt_l3_mon_domain *d;
+ int cntr_id;
+
+ list_for_each_entry(d, &r->mon_domains, hdr.list) {
+@@ -1435,7 +1435,7 @@ ssize_t resctrl_mbm_assign_mode_write(st
+ size_t nbytes, loff_t off)
+ {
+ struct rdt_resource *r = rdt_kn_parent_priv(of->kn);
+- struct rdt_mon_domain *d;
++ struct rdt_l3_mon_domain *d;
+ int ret = 0;
+ bool enable;
+
+@@ -1508,7 +1508,7 @@ int resctrl_num_mbm_cntrs_show(struct ke
+ struct seq_file *s, void *v)
+ {
+ struct rdt_resource *r = rdt_kn_parent_priv(of->kn);
+- struct rdt_mon_domain *dom;
++ struct rdt_l3_mon_domain *dom;
+ bool sep = false;
+
+ cpus_read_lock();
+@@ -1532,7 +1532,7 @@ int resctrl_available_mbm_cntrs_show(str
+ struct seq_file *s, void *v)
+ {
+ struct rdt_resource *r = rdt_kn_parent_priv(of->kn);
+- struct rdt_mon_domain *dom;
++ struct rdt_l3_mon_domain *dom;
+ bool sep = false;
+ u32 cntrs, i;
+ int ret = 0;
+@@ -1573,7 +1573,7 @@ out_unlock:
+ int mbm_L3_assignments_show(struct kernfs_open_file *of, struct seq_file *s, void *v)
+ {
+ struct rdt_resource *r = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+- struct rdt_mon_domain *d;
++ struct rdt_l3_mon_domain *d;
+ struct rdtgroup *rdtgrp;
+ struct mon_evt *mevt;
+ int ret = 0;
+@@ -1636,7 +1636,7 @@ static struct mon_evt *mbm_get_mon_event
+ return NULL;
+ }
+
+-static int rdtgroup_modify_assign_state(char *assign, struct rdt_mon_domain *d,
++static int rdtgroup_modify_assign_state(char *assign, struct rdt_l3_mon_domain *d,
+ struct rdtgroup *rdtgrp, struct mon_evt *mevt)
+ {
+ int ret = 0;
+@@ -1662,7 +1662,7 @@ static int rdtgroup_modify_assign_state(
+ static int resctrl_parse_mbm_assignment(struct rdt_resource *r, struct rdtgroup *rdtgrp,
+ char *event, char *tok)
+ {
+- struct rdt_mon_domain *d;
++ struct rdt_l3_mon_domain *d;
+ unsigned long dom_id = 0;
+ char *dom_str, *id_str;
+ struct mon_evt *mevt;
+--- a/fs/resctrl/rdtgroup.c
++++ b/fs/resctrl/rdtgroup.c
+@@ -1620,7 +1620,7 @@ static void mondata_config_read(struct r
+ static int mbm_config_show(struct seq_file *s, struct rdt_resource *r, u32 evtid)
+ {
+ struct resctrl_mon_config_info mon_info;
+- struct rdt_mon_domain *dom;
++ struct rdt_l3_mon_domain *dom;
+ bool sep = false;
+
+ cpus_read_lock();
+@@ -1668,7 +1668,7 @@ static int mbm_local_bytes_config_show(s
+ }
+
+ static void mbm_config_write_domain(struct rdt_resource *r,
+- struct rdt_mon_domain *d, u32 evtid, u32 val)
++ struct rdt_l3_mon_domain *d, u32 evtid, u32 val)
+ {
+ struct resctrl_mon_config_info mon_info = {0};
+
+@@ -1710,7 +1710,7 @@ static int mon_config_write(struct rdt_r
+ {
+ char *dom_str = NULL, *id_str;
+ unsigned long dom_id, val;
+- struct rdt_mon_domain *d;
++ struct rdt_l3_mon_domain *d;
+
+ /* Walking r->domains, ensure it can't race with cpuhp */
+ lockdep_assert_cpus_held();
+@@ -2718,7 +2718,7 @@ static int rdt_get_tree(struct fs_contex
+ {
+ struct rdt_fs_context *ctx = rdt_fc2context(fc);
+ unsigned long flags = RFTYPE_CTRL_BASE;
+- struct rdt_mon_domain *dom;
++ struct rdt_l3_mon_domain *dom;
+ struct rdt_resource *r;
+ int ret;
+
+@@ -3169,7 +3169,7 @@ static void mon_rmdir_one_subdir(struct
+ * when last domain being summed is removed.
+ */
+ static void rmdir_mondata_subdir_allrdtgrp(struct rdt_resource *r,
+- struct rdt_mon_domain *d)
++ struct rdt_l3_mon_domain *d)
+ {
+ struct rdtgroup *prgrp, *crgrp;
+ char subname[32];
+@@ -3189,7 +3189,7 @@ static void rmdir_mondata_subdir_allrdtg
+ }
+ }
+
+-static int mon_add_all_files(struct kernfs_node *kn, struct rdt_mon_domain *d,
++static int mon_add_all_files(struct kernfs_node *kn, struct rdt_l3_mon_domain *d,
+ struct rdt_resource *r, struct rdtgroup *prgrp,
+ bool do_sum)
+ {
+@@ -3218,7 +3218,7 @@ static int mon_add_all_files(struct kern
+ }
+
+ static int mkdir_mondata_subdir(struct kernfs_node *parent_kn,
+- struct rdt_mon_domain *d,
++ struct rdt_l3_mon_domain *d,
+ struct rdt_resource *r, struct rdtgroup *prgrp)
+ {
+ struct kernfs_node *kn, *ckn;
+@@ -3280,7 +3280,7 @@ out_destroy:
+ * and "monitor" groups with given domain id.
+ */
+ static void mkdir_mondata_subdir_allrdtgrp(struct rdt_resource *r,
+- struct rdt_mon_domain *d)
++ struct rdt_l3_mon_domain *d)
+ {
+ struct kernfs_node *parent_kn;
+ struct rdtgroup *prgrp, *crgrp;
+@@ -3302,7 +3302,7 @@ static int mkdir_mondata_subdir_alldom(s
+ struct rdt_resource *r,
+ struct rdtgroup *prgrp)
+ {
+- struct rdt_mon_domain *dom;
++ struct rdt_l3_mon_domain *dom;
+ int ret;
+
+ /* Walking r->domains, ensure it can't race with cpuhp */
+@@ -4170,7 +4170,7 @@ static void rdtgroup_setup_default(void)
+ mutex_unlock(&rdtgroup_mutex);
+ }
+
+-static void domain_destroy_mon_state(struct rdt_mon_domain *d)
++static void domain_destroy_mon_state(struct rdt_l3_mon_domain *d)
+ {
+ int idx;
+
+@@ -4192,7 +4192,7 @@ void resctrl_offline_ctrl_domain(struct
+ mutex_unlock(&rdtgroup_mutex);
+ }
+
+-void resctrl_offline_mon_domain(struct rdt_resource *r, struct rdt_mon_domain *d)
++void resctrl_offline_mon_domain(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+ mutex_lock(&rdtgroup_mutex);
+
+@@ -4236,7 +4236,7 @@ void resctrl_offline_mon_domain(struct r
+ *
+ * Returns 0 for success, or -ENOMEM.
+ */
+-static int domain_setup_mon_state(struct rdt_resource *r, struct rdt_mon_domain *d)
++static int domain_setup_mon_state(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+ u32 idx_limit = resctrl_arch_system_num_rmid_idx();
+ size_t tsize = sizeof(*d->mbm_states[0]);
+@@ -4292,7 +4292,7 @@ int resctrl_online_ctrl_domain(struct rd
+ return err;
+ }
+
+-int resctrl_online_mon_domain(struct rdt_resource *r, struct rdt_mon_domain *d)
++int resctrl_online_mon_domain(struct rdt_resource *r, struct rdt_l3_mon_domain *d)
+ {
+ int err;
+
+@@ -4344,10 +4344,10 @@ static void clear_childcpus(struct rdtgr
+ }
+ }
+
+-static struct rdt_mon_domain *get_mon_domain_from_cpu(int cpu,
++static struct rdt_l3_mon_domain *get_mon_domain_from_cpu(int cpu,
+ struct rdt_resource *r)
+ {
+- struct rdt_mon_domain *d;
++ struct rdt_l3_mon_domain *d;
+
+ lockdep_assert_cpus_held();
+
+@@ -4363,7 +4363,7 @@ static struct rdt_mon_domain *get_mon_do
+ void resctrl_offline_cpu(unsigned int cpu)
+ {
+ struct rdt_resource *l3 = resctrl_arch_get_resource(RDT_RESOURCE_L3);
+- struct rdt_mon_domain *d;
++ struct rdt_l3_mon_domain *d;
+ struct rdtgroup *rdtgrp;
+
+ mutex_lock(&rdtgroup_mutex);
+--- a/include/linux/resctrl.h
++++ b/include/linux/resctrl.h
+@@ -169,7 +169,7 @@ struct mbm_cntr_cfg {
+ };
+
+ /**
+- * struct rdt_mon_domain - group of CPUs sharing a resctrl monitor resource
++ * struct rdt_l3_mon_domain - group of CPUs sharing RDT_RESOURCE_L3 monitoring
+ * @hdr: common header for different domain types
+ * @ci_id: cache info id for this domain
+ * @rmid_busy_llc: bitmap of which limbo RMIDs are above threshold
+@@ -183,7 +183,7 @@ struct mbm_cntr_cfg {
+ * @cntr_cfg: array of assignable counters' configuration (indexed
+ * by counter ID)
+ */
+-struct rdt_mon_domain {
++struct rdt_l3_mon_domain {
+ struct rdt_domain_hdr hdr;
+ unsigned int ci_id;
+ unsigned long *rmid_busy_llc;
+@@ -355,10 +355,10 @@ struct resctrl_cpu_defaults {
+ };
+
+ struct resctrl_mon_config_info {
+- struct rdt_resource *r;
+- struct rdt_mon_domain *d;
+- u32 evtid;
+- u32 mon_config;
++ struct rdt_resource *r;
++ struct rdt_l3_mon_domain *d;
++ u32 evtid;
++ u32 mon_config;
+ };
+
+ /**
+@@ -495,9 +495,9 @@ int resctrl_arch_update_one(struct rdt_r
+ u32 resctrl_arch_get_config(struct rdt_resource *r, struct rdt_ctrl_domain *d,
+ u32 closid, enum resctrl_conf_type type);
+ int resctrl_online_ctrl_domain(struct rdt_resource *r, struct rdt_ctrl_domain *d);
+-int resctrl_online_mon_domain(struct rdt_resource *r, struct rdt_mon_domain *d);
++int resctrl_online_mon_domain(struct rdt_resource *r, struct rdt_l3_mon_domain *d);
+ void resctrl_offline_ctrl_domain(struct rdt_resource *r, struct rdt_ctrl_domain *d);
+-void resctrl_offline_mon_domain(struct rdt_resource *r, struct rdt_mon_domain *d);
++void resctrl_offline_mon_domain(struct rdt_resource *r, struct rdt_l3_mon_domain *d);
+ void resctrl_online_cpu(unsigned int cpu);
+ void resctrl_offline_cpu(unsigned int cpu);
+
+@@ -526,7 +526,7 @@ void resctrl_offline_cpu(unsigned int cp
+ * Return:
+ * 0 on success, or -EIO, -EINVAL etc on error.
+ */
+-int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_mon_domain *d,
++int resctrl_arch_rmid_read(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ u32 closid, u32 rmid, enum resctrl_event_id eventid,
+ u64 *val, void *arch_mon_ctx);
+
+@@ -573,7 +573,7 @@ struct rdt_domain_hdr *resctrl_find_doma
+ *
+ * This can be called from any CPU.
+ */
+-void resctrl_arch_reset_rmid(struct rdt_resource *r, struct rdt_mon_domain *d,
++void resctrl_arch_reset_rmid(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ u32 closid, u32 rmid,
+ enum resctrl_event_id eventid);
+
+@@ -586,7 +586,7 @@ void resctrl_arch_reset_rmid(struct rdt_
+ *
+ * This can be called from any CPU.
+ */
+-void resctrl_arch_reset_rmid_all(struct rdt_resource *r, struct rdt_mon_domain *d);
++void resctrl_arch_reset_rmid_all(struct rdt_resource *r, struct rdt_l3_mon_domain *d);
+
+ /**
+ * resctrl_arch_reset_all_ctrls() - Reset the control for each CLOSID to its
+@@ -612,7 +612,7 @@ void resctrl_arch_reset_all_ctrls(struct
+ *
+ * This can be called from any CPU.
+ */
+-void resctrl_arch_config_cntr(struct rdt_resource *r, struct rdt_mon_domain *d,
++void resctrl_arch_config_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ enum resctrl_event_id evtid, u32 rmid, u32 closid,
+ u32 cntr_id, bool assign);
+
+@@ -635,7 +635,7 @@ void resctrl_arch_config_cntr(struct rdt
+ * Return:
+ * 0 on success, or -EIO, -EINVAL etc on error.
+ */
+-int resctrl_arch_cntr_read(struct rdt_resource *r, struct rdt_mon_domain *d,
++int resctrl_arch_cntr_read(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ u32 closid, u32 rmid, int cntr_id,
+ enum resctrl_event_id eventid, u64 *val);
+
+@@ -650,7 +650,7 @@ int resctrl_arch_cntr_read(struct rdt_re
+ *
+ * This can be called from any CPU.
+ */
+-void resctrl_arch_reset_cntr(struct rdt_resource *r, struct rdt_mon_domain *d,
++void resctrl_arch_reset_cntr(struct rdt_resource *r, struct rdt_l3_mon_domain *d,
+ u32 closid, u32 rmid, int cntr_id,
+ enum resctrl_event_id eventid);
+
--- /dev/null
+From stable+bounces-287818-greg=kroah.com@vger.kernel.org Wed Jul 22 13:40:52 2026
+From: Sasha Levin <sashal@kernel.org>
+Date: Wed, 22 Jul 2026 07:33:53 -0400
+Subject: xfs: don't replace the wrong part of the cow fork
+To: stable@vger.kernel.org
+Cc: "Darrick J. Wong" <djwong@kernel.org>, Christoph Hellwig <hch@lst.de>, Carlos Maiolino <cem@kernel.org>, Sasha Levin <sashal@kernel.org>
+Message-ID: <20260722113353.1151198-1-sashal@kernel.org>
+
+From: "Darrick J. Wong" <djwong@kernel.org>
+
+[ Upstream commit a1caeeadbf57ff86dfc3454398c46de86056a74e ]
+
+LOLLM points out that xfs_iext_lookup_extent can return a @got where
+got->br_startoff < startoff. In this case, xrep_cow_replace_range
+replaces the entire mapping instead of just the part that had been
+marked bad in the bitmap, but advances the bitmap cursor in
+xrep_cow_replace by the amount replaced. As a result, we fail to
+replace the end of the bad range, and replace part of the good range.
+
+Fix this by rewriting the replace method to handle replacing the middle
+of a cow fork mapping. This we do by returning both the current mapping
+as @got, and the subset of the mapping that we want to replace as @rep,
+using @rep to store the results of the new allocation, and comparing
+@rep to @got to figure out the exact transformations needed.
+
+Cc: stable@vger.kernel.org # v6.8
+Fixes: dbbdbd0086320a ("xfs: repair problems in CoW forks")
+Signed-off-by: "Darrick J. Wong" <djwong@kernel.org>
+Assisted-by: LOLLM # finding obvious bugs
+Reviewed-by: Christoph Hellwig <hch@lst.de>
+Signed-off-by: Carlos Maiolino <cem@kernel.org>
+Signed-off-by: Sasha Levin <sashal@kernel.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ fs/xfs/scrub/cow_repair.c | 203 +++++++++++++++++++++++++++++-----------------
+ fs/xfs/scrub/trace.h | 28 +++---
+ 2 files changed, 148 insertions(+), 83 deletions(-)
+
+--- a/fs/xfs/scrub/cow_repair.c
++++ b/fs/xfs/scrub/cow_repair.c
+@@ -80,12 +80,6 @@ struct xrep_cow {
+ unsigned int next_bno;
+ };
+
+-/* CoW staging extent. */
+-struct xrep_cow_extent {
+- xfs_fsblock_t fsbno;
+- xfs_extlen_t len;
+-};
+-
+ /*
+ * Mark the part of the file range that corresponds to the given physical
+ * space. Caller must ensure that the physical range is within xc->irec.
+@@ -401,22 +395,21 @@ out_rtg:
+ STATIC int
+ xrep_cow_alloc(
+ struct xfs_scrub *sc,
+- xfs_extlen_t maxlen,
+- struct xrep_cow_extent *repl)
++ struct xfs_bmbt_irec *del)
+ {
+ struct xfs_alloc_arg args = {
+ .tp = sc->tp,
+ .mp = sc->mp,
+ .oinfo = XFS_RMAP_OINFO_SKIP_UPDATE,
+ .minlen = 1,
+- .maxlen = maxlen,
++ .maxlen = del->br_blockcount,
+ .prod = 1,
+ .resv = XFS_AG_RESV_NONE,
+ .datatype = XFS_ALLOC_USERDATA,
+ };
+ int error;
+
+- error = xfs_trans_reserve_more(sc->tp, maxlen, 0);
++ error = xfs_trans_reserve_more(sc->tp, del->br_blockcount, 0);
+ if (error)
+ return error;
+
+@@ -429,8 +422,8 @@ xrep_cow_alloc(
+
+ xfs_refcount_alloc_cow_extent(sc->tp, false, args.fsbno, args.len);
+
+- repl->fsbno = args.fsbno;
+- repl->len = args.len;
++ del->br_startblock = args.fsbno;
++ del->br_blockcount = args.len;
+ return 0;
+ }
+
+@@ -441,10 +434,12 @@ xrep_cow_alloc(
+ STATIC int
+ xrep_cow_alloc_rt(
+ struct xfs_scrub *sc,
+- xfs_extlen_t maxlen,
+- struct xrep_cow_extent *repl)
++ struct xfs_bmbt_irec *del)
+ {
+- xfs_rtxlen_t maxrtx = xfs_rtb_to_rtx(sc->mp, maxlen);
++ xfs_fsblock_t fsbno;
++ xfs_rtxlen_t maxrtx =
++ min(U32_MAX, xfs_blen_to_rtbxlen(sc->mp, del->br_blockcount));
++ xfs_extlen_t len;
+ int error;
+
+ error = xfs_trans_reserve_more(sc->tp, 0, maxrtx);
+@@ -452,11 +447,14 @@ xrep_cow_alloc_rt(
+ return error;
+
+ error = xfs_rtallocate_rtgs(sc->tp, NULLRTBLOCK, 1, maxrtx, 1, false,
+- false, &repl->fsbno, &repl->len);
++ false, &fsbno, &len);
+ if (error)
+ return error;
+
+- xfs_refcount_alloc_cow_extent(sc->tp, true, repl->fsbno, repl->len);
++ xfs_refcount_alloc_cow_extent(sc->tp, true, fsbno, len);
++
++ del->br_startblock = fsbno;
++ del->br_blockcount = len;
+ return 0;
+ }
+
+@@ -470,19 +468,19 @@ static inline int
+ xrep_cow_find_mapping(
+ struct xrep_cow *xc,
+ struct xfs_iext_cursor *icur,
+- xfs_fileoff_t startoff,
+- struct xfs_bmbt_irec *got)
++ xfs_fileoff_t badoff,
++ xfs_extlen_t badlen,
++ struct xfs_bmbt_irec *got,
++ struct xfs_bmbt_irec *rep)
+ {
+ struct xfs_inode *ip = xc->sc->ip;
+ struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
+
+- if (!xfs_iext_lookup_extent(ip, ifp, startoff, icur, got))
++ if (!xfs_iext_lookup_extent(ip, ifp, badoff, icur, got))
+ goto bad;
++ memcpy(rep, got, sizeof(*rep));
+
+- if (got->br_startoff > startoff)
+- goto bad;
+-
+- if (got->br_blockcount == 0)
++ if (got->br_startoff > badoff)
+ goto bad;
+
+ if (isnullstartblock(got->br_startblock))
+@@ -491,6 +489,24 @@ xrep_cow_find_mapping(
+ if (xfs_bmap_is_written_extent(got))
+ goto bad;
+
++ if (got->br_startoff < badoff) {
++ const int64_t delta = badoff - got->br_startoff;
++
++ rep->br_blockcount -= delta;
++ rep->br_startoff += delta;
++ rep->br_startblock += delta;
++ }
++
++ if (got->br_startoff + got->br_blockcount > badoff + badlen) {
++ const int64_t delta = (got->br_startoff + got->br_blockcount) -
++ (badoff + badlen);
++
++ rep->br_blockcount -= delta;
++ }
++
++ if (got->br_blockcount == 0)
++ goto bad;
++
+ return 0;
+ bad:
+ ASSERT(0);
+@@ -501,46 +517,92 @@ bad:
+ #define REPLACE_RIGHT_SIDE (1U << 1)
+
+ /*
+- * Given a CoW fork mapping @got and a replacement mapping @repl, remap the
+- * beginning of @got with the space described by @rep.
++ * Given a CoW fork mapping @got and a replacement mapping @rep, map the space
++ * described by @rep into the cow fork, pushing aside @got as necessary. @icur
++ * must point to iext tree leaf containing @got.
+ */
+ static inline void
+ xrep_cow_replace_mapping(
+- struct xfs_inode *ip,
+- struct xfs_iext_cursor *icur,
+- const struct xfs_bmbt_irec *got,
+- const struct xrep_cow_extent *repl)
++ struct xfs_inode *ip,
++ struct xfs_iext_cursor *icur,
++ struct xfs_bmbt_irec *got,
++ struct xfs_bmbt_irec *rep)
+ {
+- struct xfs_bmbt_irec new = *got; /* struct copy */
++ struct xfs_ifork *ifp = xfs_ifork_ptr(ip, XFS_COW_FORK);
++ xfs_fileoff_t rep_endoff =
++ rep->br_startoff + rep->br_blockcount;
++ xfs_fileoff_t got_endoff =
++ got->br_startoff + got->br_blockcount;
++ uint32_t state = BMAP_COWFORK;
+
+- ASSERT(repl->len > 0);
++ ASSERT(rep->br_blockcount > 0);
+ ASSERT(!isnullstartblock(got->br_startblock));
++ ASSERT(got->br_startoff <= rep->br_startoff);
++ ASSERT(got_endoff >= rep_endoff);
+
+- trace_xrep_cow_replace_mapping(ip, got, repl->fsbno, repl->len);
++ trace_xrep_cow_replace_mapping(ip, got, rep);
+
+- if (got->br_blockcount == repl->len) {
++ if (got->br_startoff == rep->br_startoff)
++ state |= BMAP_LEFT_FILLING;
++ if (got_endoff == rep_endoff)
++ state |= BMAP_RIGHT_FILLING;
++
++ switch (state & (BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING)) {
++ case BMAP_LEFT_FILLING | BMAP_RIGHT_FILLING:
+ /*
+- * The new extent is a complete replacement for the existing
+- * extent. Update the COW fork record.
++ * Replacement matches the whole mapping, update the record.
+ */
+- new.br_startblock = repl->fsbno;
+- xfs_iext_update_extent(ip, BMAP_COWFORK, icur, &new);
+- return;
+- }
++ xfs_iext_update_extent(ip, state, icur, rep);
++ break;
++ case BMAP_LEFT_FILLING:
++ /*
++ * Replace the first part of the mapping: Update the cursor
++ * position with the new mapping, then add a record with the
++ * tail of the old mapping.
++ */
++ got->br_startoff = rep_endoff;
++ got->br_blockcount -= rep->br_blockcount;
++ got->br_startblock += rep->br_blockcount;
++
++ xfs_iext_update_extent(ip, state, icur, rep);
++ xfs_iext_next(ifp, icur);
++ xfs_iext_insert(ip, icur, got, state);
++ break;
++ case BMAP_RIGHT_FILLING:
++ /*
++ * Replacing the last part of the mapping. Shorten the current
++ * mapping then add a record with the new mapping.
++ */
++ got->br_blockcount -= rep->br_blockcount;
+
+- /*
+- * The new extent can replace the beginning of the COW fork record.
+- * Move the left side of @got upwards, then insert the new record.
+- */
+- new.br_startoff += repl->len;
+- new.br_startblock += repl->len;
+- new.br_blockcount -= repl->len;
+- xfs_iext_update_extent(ip, BMAP_COWFORK, icur, &new);
+-
+- new.br_startoff = got->br_startoff;
+- new.br_startblock = repl->fsbno;
+- new.br_blockcount = repl->len;
+- xfs_iext_insert(ip, icur, &new, BMAP_COWFORK);
++ xfs_iext_update_extent(ip, state, icur, got);
++ xfs_iext_next(ifp, icur);
++ xfs_iext_insert(ip, icur, rep, state);
++ break;
++ case 0:
++ /*
++ * Replacing the middle of the extent. Shorten the current
++ * mapping, add a new record with the new mapping, and add a
++ * second new record with the tail of the old mapping.
++ */
++ got->br_blockcount = rep->br_startoff - got->br_startoff;
++
++ struct xfs_bmbt_irec new = {
++ .br_startoff = rep_endoff,
++ .br_blockcount = got_endoff - rep_endoff,
++ .br_state = got->br_state,
++ .br_startblock = got->br_startblock +
++ rep->br_blockcount +
++ got->br_blockcount,
++ };
++
++ xfs_iext_update_extent(ip, state, icur, got);
++ xfs_iext_next(ifp, icur);
++ xfs_iext_insert(ip, icur, rep, state);
++ xfs_iext_next(ifp, icur);
++ xfs_iext_insert(ip, icur, &new, state);
++ break;
++ }
+ }
+
+ /*
+@@ -554,33 +616,30 @@ xrep_cow_replace_range(
+ xfs_extlen_t *blockcount)
+ {
+ struct xfs_iext_cursor icur;
+- struct xrep_cow_extent repl;
+- struct xfs_bmbt_irec got;
++ struct xfs_bmbt_irec got, rep;
+ struct xfs_scrub *sc = xc->sc;
+- xfs_fileoff_t nextoff;
+- xfs_extlen_t alloc_len;
++ xfs_fsblock_t old_fsbno;
+ int error;
+
+ /*
+- * Put the existing CoW fork mapping in @got. If @got ends before
+- * @rep, truncate @rep so we only replace one extent mapping at a time.
++ * Put the existing CoW fork mapping in @got, and put in @rep the
++ * contents of @got trimmed to @startoff/@blockcount. We only want
++ * to replace the bad region, and only one mapping at a time.
+ */
+- error = xrep_cow_find_mapping(xc, &icur, startoff, &got);
++ error = xrep_cow_find_mapping(xc, &icur, startoff, *blockcount, &got,
++ &rep);
+ if (error)
+ return error;
+- nextoff = min(startoff + *blockcount,
+- got.br_startoff + got.br_blockcount);
++ old_fsbno = rep.br_startblock;
+
+ /*
+ * Allocate a replacement extent. If we don't fill all the blocks,
+ * shorten the quantity that will be deleted in this step.
+ */
+- alloc_len = min_t(xfs_fileoff_t, XFS_MAX_BMBT_EXTLEN,
+- nextoff - startoff);
+ if (XFS_IS_REALTIME_INODE(sc->ip))
+- error = xrep_cow_alloc_rt(sc, alloc_len, &repl);
++ error = xrep_cow_alloc_rt(sc, &rep);
+ else
+- error = xrep_cow_alloc(sc, alloc_len, &repl);
++ error = xrep_cow_alloc(sc, &rep);
+ if (error)
+ return error;
+
+@@ -588,7 +647,7 @@ xrep_cow_replace_range(
+ * Replace the old mapping with the new one, and commit the metadata
+ * changes made so far.
+ */
+- xrep_cow_replace_mapping(sc->ip, &icur, &got, &repl);
++ xrep_cow_replace_mapping(sc->ip, &icur, &got, &rep);
+
+ xfs_inode_set_cowblocks_tag(sc->ip);
+ error = xfs_defer_finish(&sc->tp);
+@@ -597,15 +656,15 @@ xrep_cow_replace_range(
+
+ /* Note the old CoW staging extents; we'll reap them all later. */
+ if (XFS_IS_REALTIME_INODE(sc->ip))
+- error = xrtb_bitmap_set(&xc->old_cowfork_rtblocks,
+- got.br_startblock, repl.len);
++ error = xrtb_bitmap_set(&xc->old_cowfork_rtblocks, old_fsbno,
++ rep.br_blockcount);
+ else
+- error = xfsb_bitmap_set(&xc->old_cowfork_fsblocks,
+- got.br_startblock, repl.len);
++ error = xfsb_bitmap_set(&xc->old_cowfork_fsblocks, old_fsbno,
++ rep.br_blockcount);
+ if (error)
+ return error;
+
+- *blockcount = repl.len;
++ *blockcount = rep.br_blockcount;
+ return 0;
+ }
+
+--- a/fs/xfs/scrub/trace.h
++++ b/fs/xfs/scrub/trace.h
+@@ -2672,9 +2672,9 @@ TRACE_EVENT(xrep_cow_mark_file_range,
+ );
+
+ TRACE_EVENT(xrep_cow_replace_mapping,
+- TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *irec,
+- xfs_fsblock_t new_startblock, xfs_extlen_t new_blockcount),
+- TP_ARGS(ip, irec, new_startblock, new_blockcount),
++ TP_PROTO(struct xfs_inode *ip, const struct xfs_bmbt_irec *got,
++ const struct xfs_bmbt_irec *rep),
++ TP_ARGS(ip, got, rep),
+ TP_STRUCT__entry(
+ __field(dev_t, dev)
+ __field(xfs_ino_t, ino)
+@@ -2682,28 +2682,34 @@ TRACE_EVENT(xrep_cow_replace_mapping,
+ __field(xfs_fileoff_t, startoff)
+ __field(xfs_filblks_t, blockcount)
+ __field(xfs_exntst_t, state)
++ __field(xfs_fileoff_t, new_startoff)
+ __field(xfs_fsblock_t, new_startblock)
+ __field(xfs_extlen_t, new_blockcount)
++ __field(xfs_exntst_t, new_state)
+ ),
+ TP_fast_assign(
+ __entry->dev = ip->i_mount->m_super->s_dev;
+ __entry->ino = ip->i_ino;
+- __entry->startoff = irec->br_startoff;
+- __entry->startblock = irec->br_startblock;
+- __entry->blockcount = irec->br_blockcount;
+- __entry->state = irec->br_state;
+- __entry->new_startblock = new_startblock;
+- __entry->new_blockcount = new_blockcount;
++ __entry->startoff = got->br_startoff;
++ __entry->startblock = got->br_startblock;
++ __entry->blockcount = got->br_blockcount;
++ __entry->state = got->br_state;
++ __entry->new_startoff = rep->br_startoff;
++ __entry->new_startblock = rep->br_startblock;
++ __entry->new_blockcount = rep->br_blockcount;
++ __entry->new_state = rep->br_state;
+ ),
+- TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startblock 0x%llx new_fsbcount 0x%x",
++ TP_printk("dev %d:%d ino 0x%llx startoff 0x%llx startblock 0x%llx fsbcount 0x%llx state 0x%x new_startoff 0x%llx new_startblock 0x%llx new_fsbcount 0x%x new_state 0x%x",
+ MAJOR(__entry->dev), MINOR(__entry->dev),
+ __entry->ino,
+ __entry->startoff,
+ __entry->startblock,
+ __entry->blockcount,
+ __entry->state,
++ __entry->new_startoff,
+ __entry->new_startblock,
+- __entry->new_blockcount)
++ __entry->new_blockcount,
++ __entry->new_state)
+ );
+
+ TRACE_EVENT(xrep_cow_free_staging,