* Re: [PATCH v3] remoteproc: xlnx: check remote core state
From: Michal Simek @ 2026-04-29 6:41 UTC (permalink / raw)
To: Tanmay Shah, andersson, mathieu.poirier
Cc: linux-arm-kernel, linux-kernel, linux-remoteproc
In-Reply-To: <20260428221855.313752-1-tanmay.shah@amd.com>
On 4/29/26 00:18, Tanmay Shah wrote:
> 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.
>
> Fixes: bca4b02ef92e ("remoteproc: xlnx: Add attach detach support")
> Signed-off-by: Tanmay Shah <tanmay.shah@amd.com>
> ---
>
> Changes in v3:
> - add fixes tag
>
> Changes in v2:
> - fix subject line: %s/node/core/
> - add comment explaining optional resource table availability in the
>
> drivers/firmware/xilinx/zynqmp.c | 28 +++++++++++++++
> drivers/remoteproc/xlnx_r5_remoteproc.c | 46 +++++++++++++++++++------
> include/linux/firmware/xlnx-zynqmp.h | 21 +++++++++++
> 3 files changed, 85 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c
> index fbe8510f4927..af838b2dc327 100644
> --- a/drivers/firmware/xilinx/zynqmp.c
> +++ b/drivers/firmware/xilinx/zynqmp.c
> @@ -1450,6 +1450,34 @@ int zynqmp_pm_get_node_status(const u32 node, u32 *const status,
> }
> EXPORT_SYMBOL_GPL(zynqmp_pm_get_node_status);
>
> +/**
> + * zynqmp_pm_get_rpu_node_status - PM call to request a RPU node's current power state
> + * @node: ID of the RPU component or sub-system in question
> + * @status: Current operating state of the requested RPU node.
> + * @requirements: Current requirements asserted on the RPU node.
> + * @usage: Usage information, used for RPU slave nodes only:
> + * PM_USAGE_NO_MASTER - No master is currently using
> + * the node
> + * PM_USAGE_CURRENT_MASTER - Only requesting master is
> + * currently using the node
> + * PM_USAGE_OTHER_MASTER - Only other masters are
> + * currently using the node
> + * PM_USAGE_BOTH_MASTERS - Both the current and at least
> + * one other master is currently
> + * using the node
> + *
> + * Return: Returns status, either success or error+reason
> + */
> +int zynqmp_pm_get_rpu_node_status(const u32 node, u32 *const status,
> + u32 *const requirements, u32 *const usage)
> +{
> + if (zynqmp_pm_feature(PM_GET_NODE_STATUS) < PM_API_VERSION_2)
> + return -EOPNOTSUPP;
> +
> + return zynqmp_pm_get_node_status(node, status, requirements, usage);
> +}
> +EXPORT_SYMBOL_GPL(zynqmp_pm_get_rpu_node_status);
> +
> /**
> * zynqmp_pm_force_pwrdwn - PM call to request for another PU or subsystem to
> * be powered down forcefully
> diff --git a/drivers/remoteproc/xlnx_r5_remoteproc.c b/drivers/remoteproc/xlnx_r5_remoteproc.c
> index 50a9974f3202..45a62cb98072 100644
> --- a/drivers/remoteproc/xlnx_r5_remoteproc.c
> +++ b/drivers/remoteproc/xlnx_r5_remoteproc.c
> @@ -948,16 +948,6 @@ static struct zynqmp_r5_core *zynqmp_r5_add_rproc_core(struct device *cdev)
> 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;
>
> @@ -1210,6 +1200,7 @@ static int zynqmp_r5_core_init(struct zynqmp_r5_cluster *cluster,
> {
> struct device *dev = cluster->dev;
> struct zynqmp_r5_core *r5_core;
> + u32 req, usage, status;
> int ret = -EINVAL, i;
>
> r5_core = cluster->r5_cores[0];
> @@ -1255,6 +1246,41 @@ static int zynqmp_r5_core_init(struct zynqmp_r5_cluster *cluster,
> 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_get_rpu_node_status(r5_core->pm_domain_id,
> + &status, &req, &usage);
> + 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 (status == 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;
> diff --git a/include/linux/firmware/xlnx-zynqmp.h b/include/linux/firmware/xlnx-zynqmp.h
> index d70dcd462b44..7e27b0f7bf7e 100644
> --- a/include/linux/firmware/xlnx-zynqmp.h
> +++ b/include/linux/firmware/xlnx-zynqmp.h
> @@ -542,6 +542,18 @@ enum pm_gem_config_type {
> GEM_CONFIG_FIXED = 2,
> };
>
> +/**
> + * 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
> @@ -630,6 +642,8 @@ int zynqmp_pm_set_rpu_mode(u32 node_id, enum rpu_oper_mode rpu_mode);
> int zynqmp_pm_set_tcm_config(u32 node_id, enum rpu_tcm_comb tcm_mode);
> int zynqmp_pm_get_node_status(const u32 node, u32 *const status,
> u32 *const requirements, u32 *const usage);
> +int zynqmp_pm_get_rpu_node_status(const u32 node, u32 *const status,
> + u32 *const requirements, u32 *const usage);
> int zynqmp_pm_set_sd_config(u32 node, enum pm_sd_config_type config, u32 value);
> int zynqmp_pm_set_gem_config(u32 node, enum pm_gem_config_type config,
> u32 value);
> @@ -939,6 +953,13 @@ static inline int zynqmp_pm_get_node_status(const u32 node, u32 *const status,
> return -ENODEV;
> }
>
> +static inline int zynqmp_pm_get_rpu_node_status(const u32 node, u32 *const status,
> + u32 *const requirements,
> + u32 *const usage)
> +{
> + return -ENODEV;
> +}
> +
> static inline int zynqmp_pm_set_sd_config(u32 node,
> enum pm_sd_config_type config,
> u32 value)
>
> base-commit: fcdf2df56d34a3f04cab0725c5bc3abdaa73c2be
Acked-by: Michal Simek <michal.simek@amd.com>
Thanks,
Michal
^ permalink raw reply
* [PATCH v5 0/5] PCI: qcom: Add D3cold support
From: Krishna Chaitanya Chundru @ 2026-04-29 6:42 UTC (permalink / raw)
To: Jingoo Han, Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Bjorn Helgaas,
Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson, Krishna Chaitanya Chundru
This series adds support for putting Qualcomm PCIe host bridges into D3cold
when downstream conditions allow it, and introduces a small common helper
to determine D3cold eligibility based on endpoint state.
On Qualcomm platforms, PCIe host controllers are currently kept powered
even when there are no active endpoints (i.e. all endpoints are already in
PCI_D3hot). This prevents the SoC from entering deeper low‑power states
such as CXPC.
While PCIe D3cold support exists in the PCI core, host controller drivers
lack a common mechanism to determine whether it is safe to power off the
host bridge without breaking active devices or wakeup functionality.
As a result, controllers either avoid entering D3cold or depend on rough,
driver‑specific workarounds.
This series addresses that gap.
1. Introduces pci_host_common_can_enter_d3cold(), a helper that determines
whether a host bridge may enter D3cold based on downstream PCIe endpoint
state. The helper permits D3cold only when all *active* endpoints are
already in PCI_D3hot, and any wakeup‑enabled endpoint supports PME
from D3cold.
2. Updates the Designware PCIe host driver to use this helper in the
suspend_noirq() path, replacing the existing heuristic that blocked
D3cold whenever L1 ASPM was enabled.
3. Enables D3cold support for Qualcomm PCIe controllers by wiring them into
the DesignWare common suspend/resume flow and explicitly powering down
controller resources when all endpoints are in D3hot.
The immediate outcome of this series is that Qualcomm PCIe host bridges can
enter D3cold when all endpoints are in D3hot.
This is a necessary but not sufficient step toward unblocking CXPC. With
this series applied, CXPC can be achieved on systems with no attached NVMe
devices. Support for NVMe‑attached systems requires additional changes
in NVMe driver, which are being worked on separately.
Tested on:
- Qualcomm Lemans EVK, Monaco & sc7280 platforms.
Validation steps:
- Boot without NVMe attach:
* PCIe host enters D3cold during suspend
* SoC is able to reach CXPC provided other drivers also remove
their votes as part of suspend.
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
Changes in v5:
- Add additional checks for legacy, integrated endpoints also as pointed
by sashiko.
- for older platforms we need to read LTSSM state from ELBI regitsers,
updated the code to read elbi regitser by sashiko.
- Couple of nits by sashiko.
- Link to v4: https://lore.kernel.org/r/20260407-d3cold-v4-0-bb171f75b465@oss.qualcomm.com
Changes in v4:
- Added new argument to the API to know if there is any device with
wakeup enabled and pme can be generated in D3cold. we need this info
to decide to turn off power to device or not.
- Couple of nits in commit text (Mani).
- Link to v3: https://lore.kernel.org/r/20260311-d3cold-v3-0-4d85dc7c2695@oss.qualcomm.com
Changes in v3:
- Changed the function name from pci_host_common_can_enter_d3cold() to
pci_host_common_d3cold_possible() (Mani).
- Couple of nits for commit text, newlines etc(Mani).
- Removed -ETIMEDOUT check and added -ENODEV & -EIO(Mani).
- Link to v2: https://lore.kernel.org/r/20260217-d3cold-v2-0-89b322864043@oss.qualcomm.com
Changes in v2:
- Updated the cover letter (Bjorn Andersson)
- Add get_ltssm helper function to read LTSSM state from parf.
- Allow D3cold if there is no driver enabled for a endpoint.
- Added a seperate patch to make phy down in deinit part to avoid power
leakage.
- Revert icc bw voting if resume fails(Bjorn Andersson).
- Link to v1: https://lore.kernel.org/r/20260128-d3cold-v1-0-dd8f3f0ce824@oss.qualcomm.com
To: Will Deacon <will@kernel.org>
To: Lorenzo Pieralisi <lpieralisi@kernel.org>
To: Krzysztof Wilczyński <kwilczynski@kernel.org>
To: Manivannan Sadhasivam <mani@kernel.org>
To: Rob Herring <robh@kernel.org>
To: Bjorn Helgaas <bhelgaas@google.com>
To: Jingoo Han <jingoohan1@gmail.com>
Cc: linux-pci@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-msm@vger.kernel.org
---
Krishna Chaitanya Chundru (5):
PCI: host-common: Add helper to determine host bridge D3cold eligibility
PCI: qcom: Add .get_ltssm() helper
PCI: qcom: Power down PHY via PARF_PHY_CTRL before disabling rails/clocks
PCI: dwc: Use common D3cold eligibility helper in suspend path
PCI: qcom: Add D3cold support
drivers/pci/controller/dwc/pcie-designware-host.c | 15 +-
drivers/pci/controller/dwc/pcie-designware.h | 1 +
drivers/pci/controller/dwc/pcie-qcom.c | 224 ++++++++++++++++------
drivers/pci/controller/pci-host-common.c | 71 +++++++
drivers/pci/controller/pci-host-common.h | 2 +
5 files changed, 242 insertions(+), 71 deletions(-)
---
base-commit: 3b3bea6d4b9c162f9e555905d96b8c1da67ecd5b
change-id: 20251229-d3cold-bf99921960bb
Best regards,
--
Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
^ permalink raw reply
* [PATCH v5 1/5] PCI: host-common: Add helper to determine host bridge D3cold eligibility
From: Krishna Chaitanya Chundru @ 2026-04-29 6:42 UTC (permalink / raw)
To: Jingoo Han, Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Bjorn Helgaas,
Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson, Krishna Chaitanya Chundru
In-Reply-To: <20260429-d3cold-v5-0-89e9735b9df6@oss.qualcomm.com>
Add a common helper, pci_host_common_d3cold_possible(), to determine
whether PCIe devices under host bridge can safely transition to D3cold.
This helper is intended to be used by PCI host controller drivers to
decide whether they may safely put the host bridge into D3cold based on
the power state and wakeup capabilities of downstream endpoints.
The helper walks all devices on the all bridge buses and only allows
the devices to enter D3cold if all PCIe endpoints are already in
PCI_D3hot. This ensures that we do not power off the host bridge while
any active endpoint still requires the link to remain powered.
For devices that may wake the system, the helper additionally requires
that the device supports PME wake from D3cold (via WAKE#). Devices that
do not have wakeup enabled are not restricted by this check and do not
block the devices under host bridge from entering D3cold.
Devices without a bound driver and with PCI not enabled via sysfs are
treated as inactive and therefore do not prevent the devices under host
bridge from entering D3cold. This allows controllers to power down more
aggressively when there are no actively managed endpoints.
Some devices (e.g. M.2 without auxiliary power) lose PME detection when
main power is removed. Even if such devices advertise PME-from-D3cold
capability, entering D3cold may break wakeup. So, return PME-from-D3cold
capability via an output parameter so PCIe controller drivers can apply
platform-specific handling to preserve wakeup functionality.
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/pci/controller/pci-host-common.c | 71 ++++++++++++++++++++++++++++++++
drivers/pci/controller/pci-host-common.h | 2 +
2 files changed, 73 insertions(+)
diff --git a/drivers/pci/controller/pci-host-common.c b/drivers/pci/controller/pci-host-common.c
index d6258c1cffe5..09432d69175c 100644
--- a/drivers/pci/controller/pci-host-common.c
+++ b/drivers/pci/controller/pci-host-common.c
@@ -17,6 +17,9 @@
#include "pci-host-common.h"
+#define PCI_HOST_D3COLD_ALLOWED BIT(0)
+#define PCI_HOST_PME_D3COLD_CAPABLE BIT(1)
+
static void gen_pci_unmap_cfg(void *ptr)
{
pci_ecam_free((struct pci_config_window *)ptr);
@@ -106,5 +109,73 @@ void pci_host_common_remove(struct platform_device *pdev)
}
EXPORT_SYMBOL_GPL(pci_host_common_remove);
+static int __pci_host_common_d3cold_possible(struct pci_dev *pdev, void *userdata)
+{
+ u32 *flags = userdata;
+ int type;
+
+ /* Ignore conventional PCI devices */
+ if (!pci_is_pcie(pdev))
+ return 0;
+
+ type = pci_pcie_type(pdev);
+ if (type != PCI_EXP_TYPE_ENDPOINT &&
+ type != PCI_EXP_TYPE_LEG_END &&
+ type != PCI_EXP_TYPE_RC_END)
+ return 0;
+
+ if (!pdev->dev.driver && !pci_is_enabled(pdev))
+ return 0;
+
+ if (pdev->current_state != PCI_D3hot)
+ goto exit;
+
+ if (device_may_wakeup(&pdev->dev)) {
+ if (!pci_pme_capable(pdev, PCI_D3cold))
+ goto exit;
+ else
+ *flags |= PCI_HOST_PME_D3COLD_CAPABLE;
+ }
+
+ return 0;
+
+exit:
+ *flags &= ~PCI_HOST_D3COLD_ALLOWED;
+
+ return -EOPNOTSUPP;
+}
+
+/**
+ * pci_host_common_d3cold_possible - Determine whether the host bridge can transition the
+ * devices into D3Cold.
+ *
+ * @bridge: PCI host bridge to check
+ * @pme_capable: Pointer to update if there is any device which is capable of generating
+ * PME from D3cold.
+ *
+ * Walk downstream PCIe endpoint devices and determine whether the host bridge
+ * is permitted to transition the devices into D3cold.
+ *
+ * Devices under host bridge can enter D3cold only if all active PCIe endpoints are in
+ * PCI_D3hot and any wakeup-enabled endpoint is capable of generating PME from D3cold.
+ * Inactive endpoints are ignored.
+ *
+ * The @pme_capable output allows PCIe controller drivers to apply
+ * platform-specific handling to preserve wakeup functionality.
+ *
+ * Return: %true if the host bridge may enter D3cold, otherwise %false.
+ */
+bool pci_host_common_d3cold_possible(struct pci_host_bridge *bridge, bool *pme_capable)
+{
+ u32 flags = PCI_HOST_D3COLD_ALLOWED;
+
+ pci_walk_bus(bridge->bus, __pci_host_common_d3cold_possible, &flags);
+
+ *pme_capable = !!(flags & PCI_HOST_PME_D3COLD_CAPABLE);
+
+ return !!(flags & PCI_HOST_D3COLD_ALLOWED);
+}
+EXPORT_SYMBOL_GPL(pci_host_common_d3cold_possible);
+
MODULE_DESCRIPTION("Common library for PCI host controller drivers");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/pci/controller/pci-host-common.h b/drivers/pci/controller/pci-host-common.h
index b5075d4bd7eb..7eb5599b9ce4 100644
--- a/drivers/pci/controller/pci-host-common.h
+++ b/drivers/pci/controller/pci-host-common.h
@@ -20,4 +20,6 @@ void pci_host_common_remove(struct platform_device *pdev);
struct pci_config_window *pci_host_common_ecam_create(struct device *dev,
struct pci_host_bridge *bridge, const struct pci_ecam_ops *ops);
+
+bool pci_host_common_d3cold_possible(struct pci_host_bridge *bridge, bool *pme_capable);
#endif
--
2.34.1
^ permalink raw reply related
* [PATCH v5 2/5] PCI: qcom: Add .get_ltssm() helper
From: Krishna Chaitanya Chundru @ 2026-04-29 6:42 UTC (permalink / raw)
To: Jingoo Han, Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Bjorn Helgaas,
Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson, Krishna Chaitanya Chundru
In-Reply-To: <20260429-d3cold-v5-0-89e9735b9df6@oss.qualcomm.com>
For older targets like sc7280, we see reading DBI after sending PME
turn off message is causing NOC error.
To avoid unsafe DBI accesses, introduce qcom_pcie_get_ltssm() to retrieve
the LTSSM state. For newer platforms, the LTSSM state is read from the
PARF_LTSSM register, while older platforms continue to retrieve it from
ELBI_SYS_STTS.
This helper is used in place of direct DBI-based link state checks in
the D3cold path after sending PME turn-off message, ensuring the LTSSM
state can be queried safely even after DBI access is no longer valid.
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/pci/controller/dwc/pcie-qcom.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index af6bf5cce65b..085300c1d1ec 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -71,6 +71,7 @@
/* ELBI registers */
#define ELBI_SYS_CTRL 0x04
+#define ELBI_SYS_STTS 0x08
/* DBI registers */
#define AXI_MSTR_RESP_COMP_CTRL0 0x818
@@ -131,6 +132,7 @@
/* PARF_LTSSM register fields */
#define LTSSM_EN BIT(8)
+#define PARF_LTSSM_STATE_MASK GENMASK(5, 0)
/* PARF_NO_SNOOP_OVERRIDE register fields */
#define WR_NO_SNOOP_OVERRIDE_EN BIT(1)
@@ -145,6 +147,9 @@
/* ELBI_SYS_CTRL register fields */
#define ELBI_SYS_CTRL_LT_ENABLE BIT(0)
+/* ELBI_SYS_STTS register fields */
+#define ELBI_SYS_STTS_LTSSM_STATE_MASK GENMASK(17, 12)
+
/* AXI_MSTR_RESP_COMP_CTRL0 register fields */
#define CFG_REMOTE_RD_REQ_BRIDGE_SIZE_2K 0x4
#define CFG_REMOTE_RD_REQ_BRIDGE_SIZE_4K 0x5
@@ -245,6 +250,7 @@ struct qcom_pcie_ops {
void (*deinit)(struct qcom_pcie *pcie);
void (*ltssm_enable)(struct qcom_pcie *pcie);
int (*config_sid)(struct qcom_pcie *pcie);
+ enum dw_pcie_ltssm (*get_ltssm)(struct qcom_pcie *pcie);
};
/**
@@ -428,6 +434,15 @@ static void qcom_pcie_2_1_0_ltssm_enable(struct qcom_pcie *pcie)
writel(val, pci->elbi_base + ELBI_SYS_CTRL);
}
+static enum dw_pcie_ltssm qcom_pcie_2_1_0_get_ltssm(struct qcom_pcie *pcie)
+{
+ struct dw_pcie *pci = pcie->pci;
+ u32 val;
+
+ val = readl(pci->elbi_base + ELBI_SYS_STTS);
+ return (enum dw_pcie_ltssm)FIELD_GET(ELBI_SYS_STTS_LTSSM_STATE_MASK, val);
+}
+
static int qcom_pcie_get_resources_2_1_0(struct qcom_pcie *pcie)
{
struct qcom_pcie_resources_2_1_0 *res = &pcie->res.v2_1_0;
@@ -1260,6 +1275,19 @@ static bool qcom_pcie_link_up(struct dw_pcie *pci)
return val & PCI_EXP_LNKSTA_DLLLA;
}
+static enum dw_pcie_ltssm qcom_pcie_get_ltssm(struct dw_pcie *pci)
+{
+ struct qcom_pcie *pcie = to_qcom_pcie(pci);
+ u32 val;
+
+ if (pcie->cfg->ops->ltssm_enable)
+ return pcie->cfg->ops->get_ltssm(pcie);
+
+ val = readl(pcie->parf + PARF_LTSSM);
+
+ return (enum dw_pcie_ltssm)FIELD_GET(PARF_LTSSM_STATE_MASK, val);
+}
+
static void qcom_pcie_phy_power_off(struct qcom_pcie *pcie)
{
struct qcom_pcie_port *port;
@@ -1385,6 +1413,7 @@ static const struct qcom_pcie_ops ops_2_1_0 = {
.post_init = qcom_pcie_post_init_2_1_0,
.deinit = qcom_pcie_deinit_2_1_0,
.ltssm_enable = qcom_pcie_2_1_0_ltssm_enable,
+ .get_ltssm = qcom_pcie_2_1_0_get_ltssm,
};
/* Qcom IP rev.: 1.0.0 Synopsys IP rev.: 4.11a */
@@ -1394,6 +1423,7 @@ static const struct qcom_pcie_ops ops_1_0_0 = {
.post_init = qcom_pcie_post_init_1_0_0,
.deinit = qcom_pcie_deinit_1_0_0,
.ltssm_enable = qcom_pcie_2_1_0_ltssm_enable,
+ .get_ltssm = qcom_pcie_2_1_0_get_ltssm,
};
/* Qcom IP rev.: 2.3.2 Synopsys IP rev.: 4.21a */
@@ -1512,6 +1542,7 @@ static const struct qcom_pcie_cfg cfg_fw_managed = {
static const struct dw_pcie_ops dw_pcie_ops = {
.link_up = qcom_pcie_link_up,
.start_link = qcom_pcie_start_link,
+ .get_ltssm = qcom_pcie_get_ltssm,
};
static int qcom_pcie_icc_init(struct qcom_pcie *pcie)
--
2.34.1
^ permalink raw reply related
* [PATCH v5 3/5] PCI: qcom: Power down PHY via PARF_PHY_CTRL before disabling rails/clocks
From: Krishna Chaitanya Chundru @ 2026-04-29 6:42 UTC (permalink / raw)
To: Jingoo Han, Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Bjorn Helgaas,
Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson, Krishna Chaitanya Chundru
In-Reply-To: <20260429-d3cold-v5-0-89e9735b9df6@oss.qualcomm.com>
Some Qcom PCIe controller variants bring the PHY out of test power-down
(PHY_TEST_PWR_DOWN) during init. When the link is later transitioned
towards D3cold and the driver disables PCIe clocks and/or regulators
without explicitly re-asserting PHY_TEST_PWR_DOWN, the PHY can remain
partially powered, leading to avoidable power leakage.
Update the init-path comments to reflect that PARF_PHY_CTRL is used to
power the PHY on. Also, for controller revisions that enable PHY power
in init (2.3.2, 2.3.3, 2.4.0, 2.7.0 and 2.9.0), explicitly power the PHY
down via PARF_PHY_CTRL in the deinit path before disabling clocks or
regulators.
This ensures the PHY is put into a defined low-power state prior to
removing its supplies, preventing leakage when entering D3cold.
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/pci/controller/dwc/pcie-qcom.c | 38 +++++++++++++++++++++++++++++++---
1 file changed, 35 insertions(+), 3 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index 085300c1d1ec..9dd808e85409 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -532,7 +532,7 @@ static int qcom_pcie_post_init_2_1_0(struct qcom_pcie *pcie)
u32 val;
int ret;
- /* enable PCIe clocks and resets */
+ /* Force PHY out of lowest power state */
val = readl(pcie->parf + PARF_PHY_CTRL);
val &= ~PHY_TEST_PWR_DOWN;
writel(val, pcie->parf + PARF_PHY_CTRL);
@@ -699,6 +699,12 @@ static int qcom_pcie_get_resources_2_3_2(struct qcom_pcie *pcie)
static void qcom_pcie_deinit_2_3_2(struct qcom_pcie *pcie)
{
struct qcom_pcie_resources_2_3_2 *res = &pcie->res.v2_3_2;
+ u32 val;
+
+ /* Force PHY to lowest power state*/
+ val = readl(pcie->parf + PARF_PHY_CTRL);
+ val |= PHY_TEST_PWR_DOWN;
+ writel(val, pcie->parf + PARF_PHY_CTRL);
clk_bulk_disable_unprepare(res->num_clks, res->clks);
regulator_bulk_disable(ARRAY_SIZE(res->supplies), res->supplies);
@@ -731,7 +737,7 @@ static int qcom_pcie_post_init_2_3_2(struct qcom_pcie *pcie)
{
u32 val;
- /* enable PCIe clocks and resets */
+ /* Force PHY out of lowest power state */
val = readl(pcie->parf + PARF_PHY_CTRL);
val &= ~PHY_TEST_PWR_DOWN;
writel(val, pcie->parf + PARF_PHY_CTRL);
@@ -795,6 +801,12 @@ static int qcom_pcie_get_resources_2_4_0(struct qcom_pcie *pcie)
static void qcom_pcie_deinit_2_4_0(struct qcom_pcie *pcie)
{
struct qcom_pcie_resources_2_4_0 *res = &pcie->res.v2_4_0;
+ u32 val;
+
+ /* Force PHY to lowest power state*/
+ val = readl(pcie->parf + PARF_PHY_CTRL);
+ val |= PHY_TEST_PWR_DOWN;
+ writel(val, pcie->parf + PARF_PHY_CTRL);
reset_control_bulk_assert(res->num_resets, res->resets);
clk_bulk_disable_unprepare(res->num_clks, res->clks);
@@ -863,6 +875,12 @@ static int qcom_pcie_get_resources_2_3_3(struct qcom_pcie *pcie)
static void qcom_pcie_deinit_2_3_3(struct qcom_pcie *pcie)
{
struct qcom_pcie_resources_2_3_3 *res = &pcie->res.v2_3_3;
+ u32 val;
+
+ /* Force PHY to lowest power state */
+ val = readl(pcie->parf + PARF_PHY_CTRL);
+ val |= PHY_TEST_PWR_DOWN;
+ writel(val, pcie->parf + PARF_PHY_CTRL);
clk_bulk_disable_unprepare(res->num_clks, res->clks);
}
@@ -918,6 +936,7 @@ static int qcom_pcie_post_init_2_3_3(struct qcom_pcie *pcie)
u16 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
u32 val;
+ /* Force PHY out of lowest power state */
val = readl(pcie->parf + PARF_PHY_CTRL);
val &= ~PHY_TEST_PWR_DOWN;
writel(val, pcie->parf + PARF_PHY_CTRL);
@@ -1013,7 +1032,7 @@ static int qcom_pcie_init_2_7_0(struct qcom_pcie *pcie)
/* configure PCIe to RC mode */
writel(DEVICE_TYPE_RC, pcie->parf + PARF_DEVICE_TYPE);
- /* enable PCIe clocks and resets */
+ /* Force PHY out of lowest power state */
val = readl(pcie->parf + PARF_PHY_CTRL);
val &= ~PHY_TEST_PWR_DOWN;
writel(val, pcie->parf + PARF_PHY_CTRL);
@@ -1084,6 +1103,12 @@ static void qcom_pcie_host_post_init_2_7_0(struct qcom_pcie *pcie)
static void qcom_pcie_deinit_2_7_0(struct qcom_pcie *pcie)
{
struct qcom_pcie_resources_2_7_0 *res = &pcie->res.v2_7_0;
+ u32 val;
+
+ /* Force PHY to lowest power state */
+ val = readl(pcie->parf + PARF_PHY_CTRL);
+ val |= PHY_TEST_PWR_DOWN;
+ writel(val, pcie->parf + PARF_PHY_CTRL);
clk_bulk_disable_unprepare(res->num_clks, res->clks);
@@ -1188,6 +1213,12 @@ static int qcom_pcie_get_resources_2_9_0(struct qcom_pcie *pcie)
static void qcom_pcie_deinit_2_9_0(struct qcom_pcie *pcie)
{
struct qcom_pcie_resources_2_9_0 *res = &pcie->res.v2_9_0;
+ u32 val;
+
+ /* Force PHY to lowest power state */
+ val = readl(pcie->parf + PARF_PHY_CTRL);
+ val |= PHY_TEST_PWR_DOWN;
+ writel(val, pcie->parf + PARF_PHY_CTRL);
clk_bulk_disable_unprepare(res->num_clks, res->clks);
}
@@ -1228,6 +1259,7 @@ static int qcom_pcie_post_init_2_9_0(struct qcom_pcie *pcie)
u32 val;
int i;
+ /* Force PHY out of lowest power state */
val = readl(pcie->parf + PARF_PHY_CTRL);
val &= ~PHY_TEST_PWR_DOWN;
writel(val, pcie->parf + PARF_PHY_CTRL);
--
2.34.1
^ permalink raw reply related
* [PATCH v5 4/5] PCI: dwc: Use common D3cold eligibility helper in suspend path
From: Krishna Chaitanya Chundru @ 2026-04-29 6:42 UTC (permalink / raw)
To: Jingoo Han, Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Bjorn Helgaas,
Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson, Krishna Chaitanya Chundru
In-Reply-To: <20260429-d3cold-v5-0-89e9735b9df6@oss.qualcomm.com>
Previously, the driver skipped putting the link into L2/device state in
D3cold whenever L1 ASPM was enabled, since some devices (e.g. NVMe) expect
low resume latency and may not tolerate deeper power states. However, such
devices typically remain in D0 and are already covered by the new helper's
requirement that all endpoints be in D3hot before the devices under host
bridge may enter D3cold.
So, replace the local L1/L1SS-based check in dw_pcie_suspend_noirq() with
the shared pci_host_common_d3cold_possible() helper to decide whether the
devices under host bridge can safely transition to D3cold.
In addition, propagate PME-from-D3cold capability information from the
helper and record it in skip_pwrctrl_off. Some devices (e.g. M.2 cards
without auxiliary power) may lose PME detection when main power is
removed, even if they advertise PME-from-D3cold support. This allows
controller power-off to be skipped when required to preserve wakeup
functionality.
Update the suspended flag in dw_pcie_resume_noirq() only after the PCIe
link resumes successfully, to avoid marking the controller active when
link resume fails.
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/pci/controller/dwc/pcie-designware-host.c | 15 +++++++--------
drivers/pci/controller/dwc/pcie-designware.h | 1 +
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-designware-host.c b/drivers/pci/controller/dwc/pcie-designware-host.c
index c9517a348836..9e409a1909e6 100644
--- a/drivers/pci/controller/dwc/pcie-designware-host.c
+++ b/drivers/pci/controller/dwc/pcie-designware-host.c
@@ -16,9 +16,11 @@
#include <linux/msi.h>
#include <linux/of_address.h>
#include <linux/of_pci.h>
+#include <linux/pci.h>
#include <linux/pci_regs.h>
#include <linux/platform_device.h>
+#include "../pci-host-common.h"
#include "../../pci.h"
#include "pcie-designware.h"
@@ -1218,18 +1220,14 @@ static int dw_pcie_pme_turn_off(struct dw_pcie *pci)
int dw_pcie_suspend_noirq(struct dw_pcie *pci)
{
- u8 offset = dw_pcie_find_capability(pci, PCI_CAP_ID_EXP);
+ bool pme_capable = false;
int ret = 0;
u32 val;
if (!dw_pcie_link_up(pci))
goto stop_link;
- /*
- * If L1SS is supported, then do not put the link into L2 as some
- * devices such as NVMe expect low resume latency.
- */
- if (dw_pcie_readw_dbi(pci, offset + PCI_EXP_LNKCTL) & PCI_EXP_LNKCTL_ASPM_L1)
+ if (!pci_host_common_d3cold_possible(pci->pp.bridge, &pme_capable))
return 0;
if (pci->pp.ops->pme_turn_off) {
@@ -1273,6 +1271,7 @@ int dw_pcie_suspend_noirq(struct dw_pcie *pci)
udelay(1);
stop_link:
+ pci->pp.skip_pwrctrl_off = pme_capable;
dw_pcie_stop_link(pci);
if (pci->pp.ops->deinit)
pci->pp.ops->deinit(&pci->pp);
@@ -1290,8 +1289,6 @@ int dw_pcie_resume_noirq(struct dw_pcie *pci)
if (!pci->suspended)
return 0;
- pci->suspended = false;
-
if (pci->pp.ops->init) {
ret = pci->pp.ops->init(&pci->pp);
if (ret) {
@@ -1313,6 +1310,8 @@ int dw_pcie_resume_noirq(struct dw_pcie *pci)
if (pci->pp.ops->post_init)
pci->pp.ops->post_init(&pci->pp);
+ pci->suspended = false;
+
return 0;
err_stop_link:
diff --git a/drivers/pci/controller/dwc/pcie-designware.h b/drivers/pci/controller/dwc/pcie-designware.h
index 3e69ef60165b..e759c5c7257e 100644
--- a/drivers/pci/controller/dwc/pcie-designware.h
+++ b/drivers/pci/controller/dwc/pcie-designware.h
@@ -450,6 +450,7 @@ struct dw_pcie_rp {
bool ecam_enabled;
bool native_ecam;
bool skip_l23_ready;
+ bool skip_pwrctrl_off;
};
struct dw_pcie_ep_ops {
--
2.34.1
^ permalink raw reply related
* [PATCH v5 5/5] PCI: qcom: Add D3cold support
From: Krishna Chaitanya Chundru @ 2026-04-29 6:42 UTC (permalink / raw)
To: Jingoo Han, Manivannan Sadhasivam, Lorenzo Pieralisi,
Krzysztof Wilczyński, Rob Herring, Bjorn Helgaas,
Will Deacon
Cc: linux-pci, linux-kernel, linux-arm-msm, linux-arm-kernel,
jonathanh, bjorn.andersson, Krishna Chaitanya Chundru
In-Reply-To: <20260429-d3cold-v5-0-89e9735b9df6@oss.qualcomm.com>
Add support for transitioning PCIe endpoints under host bridge into
D3cold by integrating with the DWC core suspend/resume helpers.
Implement PME_TurnOff message generation via ELBI_SYS_CTRL and hook it
into the DWC host operations so the controller follows the standard
PME_TurnOff-based power-down sequence before entering D3cold.
When the device is suspended into D3cold, fully tear down interconnect
bandwidth, OPP votes. If D3cold is not entered, retain existing behavior
by keeping the required interconnect and OPP votes.
Use dw_pcie::skip_pwrctrl_off to avoid powering off devices during suspend
to preserve wakeup capability of the devices and also not to power on the
devices in the init path.
Drop the qcom_pcie::suspended flag and rely on the existing
dw_pcie::suspended state, which now drives both the power-management
flow and the interconnect/OPP handling.
Signed-off-by: Krishna Chaitanya Chundru <krishna.chundru@oss.qualcomm.com>
---
drivers/pci/controller/dwc/pcie-qcom.c | 155 ++++++++++++++++++++-------------
1 file changed, 95 insertions(+), 60 deletions(-)
diff --git a/drivers/pci/controller/dwc/pcie-qcom.c b/drivers/pci/controller/dwc/pcie-qcom.c
index 9dd808e85409..94403be5a05e 100644
--- a/drivers/pci/controller/dwc/pcie-qcom.c
+++ b/drivers/pci/controller/dwc/pcie-qcom.c
@@ -146,6 +146,7 @@
/* ELBI_SYS_CTRL register fields */
#define ELBI_SYS_CTRL_LT_ENABLE BIT(0)
+#define ELBI_SYS_CTRL_PME_TURNOFF_MSG BIT(4)
/* ELBI_SYS_STTS register fields */
#define ELBI_SYS_STTS_LTSSM_STATE_MASK GENMASK(17, 12)
@@ -288,7 +289,6 @@ struct qcom_pcie {
const struct qcom_pcie_cfg *cfg;
struct dentry *debugfs;
struct list_head ports;
- bool suspended;
bool use_pm_opp;
};
@@ -1364,13 +1364,17 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
if (ret)
goto err_deinit;
- ret = pci_pwrctrl_create_devices(pci->dev);
- if (ret)
- goto err_disable_phy;
+ if (!pci->suspended) {
+ ret = pci_pwrctrl_create_devices(pci->dev);
+ if (ret)
+ goto err_disable_phy;
+ }
- ret = pci_pwrctrl_power_on_devices(pci->dev);
- if (ret)
- goto err_pwrctrl_destroy;
+ if (!pp->skip_pwrctrl_off) {
+ ret = pci_pwrctrl_power_on_devices(pci->dev);
+ if (ret)
+ goto err_pwrctrl_destroy;
+ }
if (pcie->cfg->ops->post_init) {
ret = pcie->cfg->ops->post_init(pcie);
@@ -1395,9 +1399,10 @@ static int qcom_pcie_host_init(struct dw_pcie_rp *pp)
err_assert_reset:
qcom_pcie_perst_assert(pcie);
err_pwrctrl_power_off:
- pci_pwrctrl_power_off_devices(pci->dev);
+ if (!pp->skip_pwrctrl_off)
+ pci_pwrctrl_power_off_devices(pci->dev);
err_pwrctrl_destroy:
- if (ret != -EPROBE_DEFER)
+ if (ret != -EPROBE_DEFER && !pci->suspended)
pci_pwrctrl_destroy_devices(pci->dev);
err_disable_phy:
qcom_pcie_phy_power_off(pcie);
@@ -1414,11 +1419,14 @@ static void qcom_pcie_host_deinit(struct dw_pcie_rp *pp)
qcom_pcie_perst_assert(pcie);
- /*
- * No need to destroy pwrctrl devices as this function only gets called
- * during system suspend as of now.
- */
- pci_pwrctrl_power_off_devices(pci->dev);
+ if (!pci->pp.skip_pwrctrl_off) {
+ /*
+ * No need to destroy pwrctrl devices as this function only gets called
+ * during system suspend as of now.
+ */
+ pci_pwrctrl_power_off_devices(pci->dev);
+ }
+
qcom_pcie_phy_power_off(pcie);
pcie->cfg->ops->deinit(pcie);
}
@@ -1432,10 +1440,18 @@ static void qcom_pcie_host_post_init(struct dw_pcie_rp *pp)
pcie->cfg->ops->host_post_init(pcie);
}
+static void qcom_pcie_host_pme_turn_off(struct dw_pcie_rp *pp)
+{
+ struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
+
+ writel(ELBI_SYS_CTRL_PME_TURNOFF_MSG, pci->elbi_base + ELBI_SYS_CTRL);
+}
+
static const struct dw_pcie_host_ops qcom_pcie_dw_ops = {
.init = qcom_pcie_host_init,
.deinit = qcom_pcie_host_deinit,
.post_init = qcom_pcie_host_post_init,
+ .pme_turn_off = qcom_pcie_host_pme_turn_off,
};
/* Qcom IP rev.: 2.1.0 Synopsys IP rev.: 4.01a */
@@ -2102,53 +2118,51 @@ static int qcom_pcie_suspend_noirq(struct device *dev)
if (!pcie)
return 0;
- /*
- * Set minimum bandwidth required to keep data path functional during
- * suspend.
- */
- if (pcie->icc_mem) {
- ret = icc_set_bw(pcie->icc_mem, 0, kBps_to_icc(1));
- if (ret) {
- dev_err(dev,
- "Failed to set bandwidth for PCIe-MEM interconnect path: %d\n",
- ret);
- return ret;
- }
- }
+ ret = dw_pcie_suspend_noirq(pcie->pci);
+ if (ret)
+ return ret;
- /*
- * Turn OFF the resources only for controllers without active PCIe
- * devices. For controllers with active devices, the resources are kept
- * ON and the link is expected to be in L0/L1 (sub)states.
- *
- * Turning OFF the resources for controllers with active PCIe devices
- * will trigger access violation during the end of the suspend cycle,
- * as kernel tries to access the PCIe devices config space for masking
- * MSIs.
- *
- * Also, it is not desirable to put the link into L2/L3 state as that
- * implies VDD supply will be removed and the devices may go into
- * powerdown state. This will affect the lifetime of the storage devices
- * like NVMe.
- */
- if (!dw_pcie_link_up(pcie->pci)) {
- qcom_pcie_host_deinit(&pcie->pci->pp);
- pcie->suspended = true;
- }
+ if (pcie->pci->suspended) {
+ ret = icc_disable(pcie->icc_mem);
+ if (ret)
+ dev_err(dev, "Failed to disable PCIe-MEM interconnect path: %d\n", ret);
- /*
- * Only disable CPU-PCIe interconnect path if the suspend is non-S2RAM.
- * Because on some platforms, DBI access can happen very late during the
- * S2RAM and a non-active CPU-PCIe interconnect path may lead to NoC
- * error.
- */
- if (pm_suspend_target_state != PM_SUSPEND_MEM) {
ret = icc_disable(pcie->icc_cpu);
if (ret)
dev_err(dev, "Failed to disable CPU-PCIe interconnect path: %d\n", ret);
if (pcie->use_pm_opp)
dev_pm_opp_set_opp(pcie->pci->dev, NULL);
+ } else {
+ /*
+ * Set minimum bandwidth required to keep data path functional during
+ * suspend.
+ */
+ if (pcie->icc_mem) {
+ ret = icc_set_bw(pcie->icc_mem, 0, kBps_to_icc(1));
+ if (ret) {
+ dev_err(dev,
+ "Failed to set bandwidth for PCIe-MEM interconnect path: %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ /*
+ * Only disable CPU-PCIe interconnect path if the suspend is non-S2RAM.
+ * Because on some platforms, DBI access can happen very late during the
+ * S2RAM and a non-active CPU-PCIe interconnect path may lead to NoC
+ * error.
+ */
+ if (pm_suspend_target_state != PM_SUSPEND_MEM) {
+ ret = icc_disable(pcie->icc_cpu);
+ if (ret)
+ dev_err(dev, "Failed to disable CPU-PCIe interconnect path: %d\n",
+ ret);
+
+ if (pcie->use_pm_opp)
+ dev_pm_opp_set_opp(pcie->pci->dev, NULL);
+ }
}
return ret;
}
@@ -2162,25 +2176,46 @@ static int qcom_pcie_resume_noirq(struct device *dev)
if (!pcie)
return 0;
- if (pm_suspend_target_state != PM_SUSPEND_MEM) {
+ if (pcie->pci->suspended) {
ret = icc_enable(pcie->icc_cpu);
if (ret) {
dev_err(dev, "Failed to enable CPU-PCIe interconnect path: %d\n", ret);
return ret;
}
- }
- if (pcie->suspended) {
- ret = qcom_pcie_host_init(&pcie->pci->pp);
- if (ret)
- return ret;
+ ret = icc_enable(pcie->icc_mem);
+ if (ret) {
+ dev_err(dev, "Failed to enable PCIe-MEM interconnect path: %d\n", ret);
+ goto disable_icc_cpu;
+ }
- pcie->suspended = false;
+ /*
+ * Ignore -ENODEV & -EIO here since it is expected when no endpoint is
+ * connected to the PCIe link.
+ */
+ ret = dw_pcie_resume_noirq(pcie->pci);
+ if (ret && ret != -ENODEV && ret != -EIO)
+ goto disable_icc_mem;
+ } else {
+ if (pm_suspend_target_state != PM_SUSPEND_MEM) {
+ ret = icc_enable(pcie->icc_cpu);
+ if (ret) {
+ dev_err(dev, "Failed to enable CPU-PCIe interconnect path: %d\n",
+ ret);
+ return ret;
+ }
+ }
}
qcom_pcie_icc_opp_update(pcie);
return 0;
+disable_icc_mem:
+ icc_disable(pcie->icc_mem);
+disable_icc_cpu:
+ icc_disable(pcie->icc_cpu);
+
+ return ret;
}
static const struct of_device_id qcom_pcie_match[] = {
--
2.34.1
^ permalink raw reply related
* [PATCH v7 0/3] Support configurable fan PWM at shutdown
From: florin.leotescu @ 2026-04-29 6:59 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Michael Shych, linux-hwmon, devicetree, linux-kernel
Cc: daniel.baluta, viorel.suman, linux-arm-kernel, imx, festevam,
Florin Leotescu
From: Florin Leotescu <florin.leotescu@nxp.com>
Hi,
This series adds support for configuring the fan shutdown PWM value
via Device Tree and improves the robustness of the emc2305 driver.
Some platforms require fans to transition to a predefined safe state
during shutdown or reboot handoff until firmware or the next boot stage
reconfigures the controller.
The new optional Device Tree property "fan-shutdown-percent" allows the
shutdown PWM duty cycle to be configured per fan output.
Thanks for the review and apologies for the delay.
Changes in v7:
- Use the DT "reg" property when registering thermal cooling devices
instead of a sequential child index
- Validate the "reg" value against the number of available PWM channels
in both DT parsing and probe paths
- Address feedback from Guenter Roeck regarding channel index handling
Changes in v6:
- Split fan channel index validation into a separate patch.
Validate fan channel index agains the number of available channels.
- Refine dt-binding commit message to refer to PWM duty cycle
instead of fan speed.
Changes in v5:
- Add fan channel index bound check after reg property read
to prevent out-of-bounds access.
- Refine fan-shutdown-percent description.
Changes in v4:
- Initialize pwm_shudown array to EMC2305_PWM_SHUTDOWN_UNSET in probe,
to avoid treating unconfigured channels as valid and written 0
during shutdown
Changes in v3:
- Rebased on current upstream
- Dropped already upstreamed of_node_put(child) fix
Changes in v2:
- Address feedback from Guenter Roeck
- Make shutdown behavior configurable via Device Tree
- Add optional fan-shutdown-percent property
- Apply shutdown PWM only for channels defining the property
Florin Leotescu (3):
hwmon: emc2305: Fix fan channel index handling
dt-bindings: hwmon: emc2305: Add fan-shutdown-percent property
hwmon: emc2305: Support configurable fan PWM at shutdown
.../bindings/hwmon/microchip,emc2305.yaml | 8 +++
drivers/hwmon/emc2305.c | 54 +++++++++++++++++--
2 files changed, 59 insertions(+), 3 deletions(-)
--
2.34.1
^ permalink raw reply
* [PATCH v7 2/3] dt-bindings: hwmon: emc2305: Add fan-shutdown-percent property
From: florin.leotescu @ 2026-04-29 6:59 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Michael Shych, linux-hwmon, devicetree, linux-kernel
Cc: daniel.baluta, viorel.suman, linux-arm-kernel, imx, festevam,
Florin Leotescu
In-Reply-To: <20260429065955.2113012-1-florin.leotescu@oss.nxp.com>
From: Florin Leotescu <florin.leotescu@nxp.com>
The EMC2305 fan controller supports multiple independent PWM fan
outputs. Some systems require fans to enter a defined safe state
during system shutdown or reboot handoff, until firmware or the next
boot stage reconfigures the controller.
Add an optional "fan-shutdown-percent" property to fan child nodes
allowing the PWM duty cycle applied during shutdown to be configured
per fan output.
Signed-off-by: Florin Leotescu <florin.leotescu@nxp.com>
---
.../devicetree/bindings/hwmon/microchip,emc2305.yaml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/devicetree/bindings/hwmon/microchip,emc2305.yaml b/Documentation/devicetree/bindings/hwmon/microchip,emc2305.yaml
index d3f06ebc19fa..8c2548539d7f 100644
--- a/Documentation/devicetree/bindings/hwmon/microchip,emc2305.yaml
+++ b/Documentation/devicetree/bindings/hwmon/microchip,emc2305.yaml
@@ -54,6 +54,12 @@ patternProperties:
The fan number used to determine the associated PWM channel.
maxItems: 1
+ fan-shutdown-percent:
+ description:
+ PWM duty cycle in percent applied to the fan during shutdown.
+ minimum: 0
+ maximum: 100
+
required:
- reg
@@ -80,12 +86,14 @@ examples:
fan@0 {
reg = <0x0>;
pwms = <&fan_controller 26000 PWM_POLARITY_INVERTED 1>;
+ fan-shutdown-percent = <100>;
#cooling-cells = <2>;
};
fan@1 {
reg = <0x1>;
pwms = <&fan_controller 26000 0 1>;
+ fan-shutdown-percent = <50>;
#cooling-cells = <2>;
};
--
2.34.1
^ permalink raw reply related
* [PATCH v7 1/3] hwmon: emc2305: Fix fan channel index handling
From: florin.leotescu @ 2026-04-29 6:59 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Michael Shych, linux-hwmon, devicetree, linux-kernel
Cc: daniel.baluta, viorel.suman, linux-arm-kernel, imx, festevam,
Florin Leotescu
In-Reply-To: <20260429065955.2113012-1-florin.leotescu@oss.nxp.com>
From: Florin Leotescu <florin.leotescu@nxp.com>
The fan channel index is used to access per-channel data structures.
Validate the index against the number of available channels
before use to prevent out-of-bounds access if an invalid
value is provided.
The thermal registration path currently uses a sequential child index,
which may not match the validated channel from DT. Use the DT "reg"
property when registering cooling devices to ensure consistent
channel handling
Signed-off-by: Florin Leotescu <florin.leotescu@nxp.com>
---
drivers/hwmon/emc2305.c | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c
index 64b213e1451e..f71a0e265924 100644
--- a/drivers/hwmon/emc2305.c
+++ b/drivers/hwmon/emc2305.c
@@ -548,6 +548,12 @@ static int emc2305_of_parse_pwm_child(struct device *dev,
return ret;
}
+ if (ch >= data->pwm_num) {
+ dev_err(dev, "invalid reg %u for node %pOF (valid range 0-%u)\n", ch, child,
+ data->pwm_num - 1);
+ return -EINVAL;
+ }
+
ret = of_parse_phandle_with_args(child, "pwms", "#pwm-cells", 0, &args);
if (ret)
@@ -612,6 +618,7 @@ static int emc2305_probe(struct i2c_client *client)
int ret;
int i;
int pwm_childs;
+ u32 ch;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA))
return -ENODEV;
@@ -680,12 +687,18 @@ static int emc2305_probe(struct i2c_client *client)
if (IS_REACHABLE(CONFIG_THERMAL)) {
/* Parse and check for the available PWM child nodes */
if (pwm_childs > 0) {
- i = 0;
for_each_child_of_node_scoped(dev->of_node, child) {
- ret = emc2305_set_single_tz(dev, child, i);
+ ret = of_property_read_u32(child, "reg", &ch);
+ if (ret || ch >= data->pwm_num)
+ continue;
+
+ /*
+ * emc2305_set_single_tz() uses 0 for the common cooling
+ * device and 1..pwm_num for individual fan channels.
+ */
+ ret = emc2305_set_single_tz(dev, child, ch + 1);
if (ret != 0)
return ret;
- i++;
}
} else {
ret = emc2305_set_tz(dev);
--
2.34.1
^ permalink raw reply related
* [PATCH v7 3/3] hwmon: emc2305: Support configurable fan PWM at shutdown
From: florin.leotescu @ 2026-04-29 6:59 UTC (permalink / raw)
To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Michael Shych, linux-hwmon, devicetree, linux-kernel
Cc: daniel.baluta, viorel.suman, linux-arm-kernel, imx, festevam,
Florin Leotescu
In-Reply-To: <20260429065955.2113012-1-florin.leotescu@oss.nxp.com>
From: Florin Leotescu <florin.leotescu@nxp.com>
Some systems require fans to enter in a defined safe state during system
shutdown or reboot handoff.
Add support for the optional Device Tree property "fan-shutdown-percent"
to configure the shutdown PWM duty cycle per fan output.
If the property is present for a fan channel, the driver converts the
configured percentage value to the corresponding PWM duty cycle and
applies it during driver shutdown.
If the property is not present, the fan state remains unchanged.
Signed-off-by: Florin Leotescu <florin.leotescu@nxp.com>
---
drivers/hwmon/emc2305.c | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/drivers/hwmon/emc2305.c b/drivers/hwmon/emc2305.c
index f71a0e265924..c5a0b2146478 100644
--- a/drivers/hwmon/emc2305.c
+++ b/drivers/hwmon/emc2305.c
@@ -32,6 +32,7 @@
#define EMC2305_REG_DRIVE_PWM_OUT 0x2b
#define EMC2305_OPEN_DRAIN 0x0
#define EMC2305_PUSH_PULL 0x1
+#define EMC2305_PWM_SHUTDOWN_UNSET -1
#define EMC2305_PWM_DUTY2STATE(duty, max_state, pwm_max) \
DIV_ROUND_CLOSEST((duty) * (max_state), (pwm_max))
@@ -104,6 +105,7 @@ struct emc2305_cdev_data {
* @pwm_output_mask: PWM output mask
* @pwm_polarity_mask: PWM polarity mask
* @pwm_separate: separate PWM settings for every channel
+ * @pwm_shutdown: Set shutdown PWM.
* @pwm_min: array of minimum PWM per channel
* @pwm_freq: array of PWM frequency per channel
* @cdev_data: array of cooling devices data
@@ -116,6 +118,7 @@ struct emc2305_data {
u8 pwm_output_mask;
u8 pwm_polarity_mask;
bool pwm_separate;
+ s16 pwm_shutdown[EMC2305_PWM_MAX];
u8 pwm_min[EMC2305_PWM_MAX];
u16 pwm_freq[EMC2305_PWM_MAX];
struct emc2305_cdev_data cdev_data[EMC2305_PWM_MAX];
@@ -539,6 +542,7 @@ static int emc2305_of_parse_pwm_child(struct device *dev,
struct device_node *child,
struct emc2305_data *data)
{ u32 ch;
+ u32 pwm_shutdown_percent;
int ret;
struct of_phandle_args args;
@@ -585,6 +589,16 @@ static int emc2305_of_parse_pwm_child(struct device *dev,
}
of_node_put(args.np);
+
+ ret = of_property_read_u32(child, "fan-shutdown-percent",
+ &pwm_shutdown_percent);
+
+ if (!ret) {
+ pwm_shutdown_percent = clamp(pwm_shutdown_percent, 0, 100);
+ data->pwm_shutdown[ch] =
+ DIV_ROUND_CLOSEST(pwm_shutdown_percent * EMC2305_FAN_MAX, 100);
+ }
+
return 0;
}
@@ -638,6 +652,9 @@ static int emc2305_probe(struct i2c_client *client)
if (ret)
return ret;
+ for (i = 0; i < EMC2305_PWM_MAX; i++)
+ data->pwm_shutdown[i] = EMC2305_PWM_SHUTDOWN_UNSET;
+
pwm_childs = emc2305_probe_childs_from_dt(dev);
pdata = dev_get_platdata(&client->dev);
@@ -727,6 +744,23 @@ static int emc2305_probe(struct i2c_client *client)
return 0;
}
+static void emc2305_shutdown(struct i2c_client *client)
+{
+ int i;
+ int ret;
+ struct emc2305_data *data = i2c_get_clientdata(client);
+
+ for (i = 0; i < data->pwm_num; i++) {
+ if (data->pwm_shutdown[i] != EMC2305_PWM_SHUTDOWN_UNSET) {
+ ret = i2c_smbus_write_byte_data(client, EMC2305_REG_FAN_DRIVE(i),
+ data->pwm_shutdown[i]);
+ if (ret < 0)
+ dev_warn(&client->dev,
+ "Failed to set shutdown PWM for ch %d\n", i);
+ }
+ }
+}
+
static const struct of_device_id of_emc2305_match_table[] = {
{ .compatible = "microchip,emc2305", },
{},
@@ -739,6 +773,7 @@ static struct i2c_driver emc2305_driver = {
.of_match_table = of_emc2305_match_table,
},
.probe = emc2305_probe,
+ .shutdown = emc2305_shutdown,
.id_table = emc2305_ids,
};
--
2.34.1
^ permalink raw reply related
* [PATCH 5/6] arm64: dts: rockchip: Enable USB for Khadas Edge 2L
From: Gray Huang @ 2026-04-29 6:49 UTC (permalink / raw)
To: Heiko Stuebner, robh, krzk+dt, conor+dt
Cc: devicetree, linux-arm-kernel, linux-rockchip, linux-kernel, nick
In-Reply-To: <5673086.MHq7AAxBmi@phil>
On 4/27/26 20:27, Heiko Stuebner wrote:
> Am Mittwoch, 25. März 2026, 06:46:13 Mitteleuropäische Sommerzeit schrieb Gray Huang:
>> The Khadas Edge 2L board provides one USB 3.0 Host port and one
>> USB 2.0 port (connected via an internal hub). Enable the
>> corresponding DWC3 controllers and PHYs.
>>
>> Signed-off-by: Gray Huang <gray.huang@wesion.com>
>> ---
>> .../dts/rockchip/rk3576-khadas-edge-2l.dts | 47 +++++++++++++++++++
>> 1 file changed, 47 insertions(+)
>>
>> diff --git a/arch/arm64/boot/dts/rockchip/rk3576-khadas-edge-2l.dts b/arch/arm64/boot/dts/rockchip/rk3576-khadas-edge-2l.dts
>> index 003342fd69a7..8ecd00f6645d 100644
>> --- a/arch/arm64/boot/dts/rockchip/rk3576-khadas-edge-2l.dts
>> +++ b/arch/arm64/boot/dts/rockchip/rk3576-khadas-edge-2l.dts
>> @@ -30,6 +30,29 @@ hdmi_con_in: endpoint {
>> };
>> };
>>
>> + vcc5v0_device: regulator-vcc5v0-device {
>> + compatible = "regulator-fixed";
>> + regulator-name = "vcc5v0_device";
>> + regulator-always-on;
>> + regulator-boot-on;
>> + regulator-min-microvolt = <5000000>;
>> + regulator-max-microvolt = <5000000>;
>> + };
>> +
>> + vcc5v0_host: regulator-vcc5v0-host {
>> + compatible = "regulator-fixed";
>> + regulator-name = "vcc5v0_host";
>> + regulator-boot-on;
>> + regulator-always-on;
>> + regulator-min-microvolt = <5000000>;
>> + regulator-max-microvolt = <5000000>;
>> + enable-active-high;
>> + gpio = <&gpio2 RK_PD3 GPIO_ACTIVE_HIGH>;
>> + vin-supply = <&vcc5v0_device>;
>> + pinctrl-names = "default";
>> + pinctrl-0 = <&usb_host_pwren>;
>> + };
>> +
>> vcc_sys: regulator-vcc5v0-sys {
>> compatible = "regulator-fixed";
>> regulator-name = "vcc_sys";
>> @@ -531,6 +554,12 @@ pcie0_rst: pcie0-rst {
>> };
>> };
>>
>> + usb {
>> + usb_host_pwren: usb-host-pwren {
>> + rockchip,pins = <2 RK_PD3 RK_FUNC_GPIO &pcfg_pull_none>;
>> + };
>> + };
>> +
>> wifi {
>> wifi_reg_on: wifi-reg-on {
>> rockchip,pins = <4 RK_PA2 RK_FUNC_GPIO &pcfg_pull_up>;
>> @@ -542,6 +571,24 @@ wifi_wake_host: wifi-wake-host {
>> };
>> };
>>
>> +&usb_drd1_dwc3 {
>> + dr_mode = "host";
>> + status = "okay";
>> +};
>> +
>> +&u2phy1 {
>> + status = "okay";
>> +};
>> +
>> +&u2phy1_otg {
>> + phy-supply = <&vcc5v0_host>;
>> + status = "okay";
>> +};
>> +
>> +&combphy1_psu {
>> + status = "okay";
>> +};
> same here, u-something and c-something between pinctrl and sdhci
> is the wrong place.
>
> Heko
Has been sorted alphabetically in patch of v2.
Gray
>> +
>> &sdhci {
>> bus-width = <8>;
>> no-sdio;
>>
>
>
>
^ permalink raw reply
* [PATCH 6/6] arm64: dts: rockchip: Add Bluetooth support for Khadas Edge 2L
From: Gray Huang @ 2026-04-29 6:51 UTC (permalink / raw)
To: Heiko Stuebner, robh, krzk+dt, conor+dt
Cc: devicetree, linux-arm-kernel, linux-rockchip, linux-kernel, nick
In-Reply-To: <3716011.atdPhlSkOF@phil>
On 4/27/26 20:27, Heiko Stuebner wrote:
> Am Mittwoch, 25. März 2026, 06:46:14 Mitteleuropäische Sommerzeit schrieb Gray Huang:
>> Enable Bluetooth support for the Ampak AP6275P module on the
>> Khadas Edge 2L. This involves enabling the UART5 interface for
>> HCI communication and defining the required regulators and
>> power-sequence pins.
>>
>> Signed-off-by: Gray Huang <gray.huang@wesion.com>
>> ---
>> .../dts/rockchip/rk3576-khadas-edge-2l.dts | 35 +++++++++++++++++++
>> 1 file changed, 35 insertions(+)
>>
>> diff --git a/arch/arm64/boot/dts/rockchip/rk3576-khadas-edge-2l.dts b/arch/arm64/boot/dts/rockchip/rk3576-khadas-edge-2l.dts
>> index 8ecd00f6645d..345dc4c981c1 100644
>> --- a/arch/arm64/boot/dts/rockchip/rk3576-khadas-edge-2l.dts
>> +++ b/arch/arm64/boot/dts/rockchip/rk3576-khadas-edge-2l.dts
>> @@ -560,6 +560,20 @@ usb_host_pwren: usb-host-pwren {
>> };
>> };
>>
>> + bluetooth {
>> + bt_reg_on: bt-reg-on {
>> + rockchip,pins = <4 RK_PB2 RK_FUNC_GPIO &pcfg_pull_up>;
>> + };
>> +
>> + bt_wake_host: bt-wake-host {
>> + rockchip,pins = <0 RK_PB1 RK_FUNC_GPIO &pcfg_pull_down>;
>> + };
>> +
>> + host_wake_bt: host-wake-bt {
>> + rockchip,pins = <0 RK_PA5 RK_FUNC_GPIO &pcfg_pull_up>;
>> + };
>> + };
> same sorting problem with bluetooth between usb-foo and wifi-foo.
>
>> +
>> wifi {
>> wifi_reg_on: wifi-reg-on {
>> rockchip,pins = <4 RK_PA2 RK_FUNC_GPIO &pcfg_pull_up>;
> Heiko
Has been sorted alphabetically in patch of v2.
Gray
^ permalink raw reply
* [PATCH v2] media: mediatek: vcodec: free working buf on error path in vdec_vp9_slice_setup_lat()
From: Haoxiang Li @ 2026-04-29 6:52 UTC (permalink / raw)
To: tiffany.lin, andrew-ct.chen, yunfei.dong, mchehab, matthias.bgg,
angelogioacchino.delregno, laurent.pinchart, hverkuil+cisco,
benjamin.gaignard, p.zabel, george.sun, nicolas
Cc: linux-media, linux-kernel, linux-arm-kernel, linux-mediatek,
Haoxiang Li, stable
Add an error path label in vdec_vp9_slice_setup_lat()
and call vdec_vp9_slice_free_working_buffer() to free
working buffer to prevent potential memory leak.
Fixes: 5d418351ca8f ("media: mediatek: vcodec: support stateless VP9 decoding")
Cc: stable@vger.kernel.org
Signed-off-by: Haoxiang Li <lihaoxiang@isrc.iscas.ac.cn>
---
Changes in v2:
- Remove two functions' return value check and error
code, since these never fails. Thanks, Nicolas!
---
.../vcodec/decoder/vdec/vdec_vp9_req_lat_if.c | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c
index cd1935014d76..5a424d738fed 100644
--- a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c
+++ b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c
@@ -1166,24 +1166,22 @@ static int vdec_vp9_slice_setup_lat(struct vdec_vp9_slice_instance *instance,
if (ret)
goto err;
- ret = vdec_vp9_slice_setup_lat_buffer(instance, vsi, bs, lat_buf);
- if (ret)
- goto err;
+ vdec_vp9_slice_setup_lat_buffer(instance, vsi, bs, lat_buf);
vdec_vp9_slice_setup_seg_buffer(instance, vsi, &instance->seg[0]);
/* setup prob/tile buffers for LAT */
- ret = vdec_vp9_slice_setup_prob_buffer(instance, vsi);
- if (ret)
- goto err;
+ vdec_vp9_slice_setup_prob_buffer(instance, vsi);
ret = vdec_vp9_slice_setup_tile_buffer(instance, vsi, bs);
if (ret)
- goto err;
+ goto alloc_err;
return 0;
+alloc_err:
+ vdec_vp9_slice_free_working_buffer(instance);
err:
return ret;
}
--
2.25.1
^ permalink raw reply related
* Re: [PATCH v3 1/3] dt-bindings: net: add st,stlc45xx/p54spi binding
From: Krzysztof Kozlowski @ 2026-04-29 6:55 UTC (permalink / raw)
To: Arnd Bergmann
Cc: Arnd Bergmann, Aaro Koskinen, Andreas Kemnade,
Bartosz Golaszewski, Benoît Cousson, David S. Miller,
Dmitry Torokhov, Eric Dumazet, Felipe Balbi, Jakub Kicinski,
Johannes Berg, Kevin Hilman, Krzysztof Kozlowski, Linus Walleij,
Paolo Abeni, Rob Herring, Roger Quadros, Tony Lindgren,
linux-wireless, netdev, devicetree, linux-kernel,
linux-arm-kernel, linux-gpio, linux-omap, Christian Lamparter
In-Reply-To: <20260427142355.2532714-2-arnd@kernel.org>
On Mon, Apr 27, 2026 at 04:23:53PM +0200, Arnd Bergmann wrote:
> From: Arnd Bergmann <arnd@arndb.de>
>
> The SPI version of Prism54 was sold under a couple of different
> names and supported by the Linux p54spi driver, but there was
> never a DT binding for it.
>
> Document the four known names of this device and the properties
> that are sufficient for its use on the Nokia N8x0 tablet.
>
> As I don't have this hardware or documentation for it, this is
> purely based on existing usage in the driver.
>
> Link: https://lore.kernel.org/all/e8dc9acb-6f85-e0a9-a145-d101ca6da201@gmail.com/
> Acked-by: Christian Lamparter <chunkeey@gmail.com>
> Signed-off-by: Arnd Bergmann <arnd@arndb.de>
> ---
> .../bindings/net/wireless/st,stlc45xx.yaml | 58 +++++++++++++++++++
> MAINTAINERS | 1 +
> 2 files changed, 59 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/net/wireless/st,stlc45xx.yaml
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH v2 0/5] remoteproc: add Cortex-A Core remoteproc support on i.MX platforms
From: Daniel Baluta @ 2026-04-29 6:59 UTC (permalink / raw)
To: Jiafei Pan, andersson, mathieu.poirier, peng.fan, Frank.Li,
s.hauer, kernel, festevam, imx, linux-arm-kernel, linux-kernel
Cc: Zhiqiang.Hou, mingkai.hu, linux-remoteproc, devicetree
In-Reply-To: <20260429031047.30893-1-Jiafei.Pan@nxp.com>
On 4/29/26 06:10, Jiafei Pan wrote:
> This patch series is to add remoteproc support on Cortex-A Core of i.MX platforms:
> 1. Adding dts binding for Cortex-A Core remoteproc
> 2. Enable Cortex-A Core remoteproc support in remoteproc driver
> 3. Adding dts example on imx93 platforms.
> 4. Updating remoteproc driver to use prcoessor name as device node in sys filesystem
>
> Signed-off-by: Jiafei Pan <Jiafei.Pan@nxp.com>
Thanks Jiafei for the series! As Mathieu pointed out let's do internal review first.
@all, please ignore this series.
Daniel.
^ permalink raw reply
* [PATCH 00/11] kdump: reduce vmcore size and capture time via linux,no-dump
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
This series has two parts:
- Patches 1-4 are OF reserved_mem bug fixes and small hardening
changes. They stand on their own and at the same time prepare the
ground for the feature work that follows (accurate region counts,
consistent two-pass save/reserve state, and an early-out when the
array is empty).
- Patches 5-11 introduce a new 'linux,no-dump' reserved-memory
device tree property and the kdump plumbing to honour it, split
further as:
* Patches 5-7: core OF changes - parse 'linux,no-dump' on
/reserved-memory/ children, save /memreserve/ firmware regions
into reserved_mem[] with no_dump defaulted on, and add generic
no-dump crash_mem exclusion helpers.
* Patches 8-10: arch kdump consumers - arm64, riscv and
loongarch each call the helpers from patch 7 in their
prepare_elf_headers() so that 'linux,no-dump' /reserved-memory/
children and /memreserve/ regions are filtered out of the
vmcore ELF PT_LOAD segments.
* Patch 11: user-facing documentation in
Documentation/admin-guide/kdump/kdump.rst.
Motivation
==========
On SoCs that carve out large firmware-owned reserved memory (GPU
firmware, DSP, modem, camera ISP, NPU, ...), kdump currently dumps
those carveouts as part of system RAM even though their contents are
firmware state that is not useful for kernel crash analysis. On a
machine with several hundred MiB of such carveouts, the overhead per
vmcore is substantial.
This series adds a declarative way for DT authors to mark such
regions:
reserved-memory {
npu_fw@a0000000 {
reg = <0x0 0xa0000000 0x0 0x10000000>;
linux,no-dump;
};
};
and also defaults /memreserve/ firmware regions (Trusted Firmware /
BL31 images, secondary-CPU spin-table pens, bootloader scratch per
Documentation/arch/arm64/booting.rst and upstream DTS files) to
no_dump=true.
Interaction with existing reserved-memory flags is kept simple:
'linux,no-dump' is an OS hint, it is redundant (but harmless) when
combined with 'no-map' and silently ignored on 'reusable' (CMA)
regions whose contents are relevant for crash analysis. The 'linux,'
prefix follows existing precedents like 'linux,cma-default' since
kdump is a Linux-specific concept.
Benefits
========
- Smaller vmcore. The excluded firmware carveouts are omitted from
the ELF PT_LOAD segments entirely, so the resulting dump file is
smaller by roughly the sum of the tagged regions - on SoCs with
hundreds of MiB of GPU/DSP/modem/NPU carveouts this is a
substantial saving, both on disk and in transit to a dump server.
- Faster kdump. The dump-capture kernel writes less data to storage
or over the network, which directly shortens the crash-to-dump
turnaround. Tools that walk the dump (makedumpfile, crash) also
spend less time on regions that were never going to be useful
anyway.
- No existing behaviour change for DTs that do not opt in: regions
without 'linux,no-dump' and systems without /memreserve/ entries
are dumped exactly as before.
DT binding
==========
The 'linux,no-dump' property is maintained in dt-schema
(reserved-memory.yaml moved there from the kernel tree).
Corresponding PR:
https://github.com/devicetree-org/dt-schema/pull/193
Follow-ups
==========
- powerpc also uses kexec_file and /reserved-memory/, but its
arch/powerpc/kexec/ranges.c uses the _guarded variant of
crash_exclude_mem_range with dynamic realloc and collects
additional RTAS/OPAL firmware ranges. Adapting it needs a small
extra helper; left as a follow-up.
---
Chen Wandun (11):
of: reserved_mem: fix region count for nodes with multiple reg entries
of: reserved_mem: reject reserved memory outside physical address
range
of: reserved_mem: avoid unconditional save of reg entries in
fdt_scan_reserved_mem_late()
of: reserved_mem: skip reserved_mem array allocation when there is
nothing to save
of: reserved_mem: add linux,no-dump property support for reserved
memory regions
of: reserved_mem: save /memreserve/ entries into reserved_mem array
of: reserved_mem: add no-dump crash_mem exclusion helpers
arm64: kdump: exclude no-dump reserved memory regions from vmcore
riscv: kdump: exclude no-dump reserved memory regions from vmcore
loongarch: kdump: exclude no-dump reserved memory regions from vmcore
Documentation: admin-guide: kdump: document linux,no-dump DT property
Documentation/admin-guide/kdump/kdump.rst | 59 ++++++
arch/arm64/kernel/machine_kexec_file.c | 6 +
arch/loongarch/kernel/machine_kexec_file.c | 6 +
arch/riscv/kernel/machine_kexec_file.c | 4 +
drivers/of/of_reserved_mem.c | 233 ++++++++++++++++++---
include/linux/of_reserved_mem.h | 16 ++
6 files changed, 295 insertions(+), 29 deletions(-)
--
2.43.0
^ permalink raw reply
* [PATCH 01/11] of: reserved_mem: fix region count for nodes with multiple reg entries
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-1-chenwandun@lixiang.com>
When a reserved-memory node contains multiple reg entries (e.g.,
reg = <base1 size1>, <base2 size2>), the count used for
total_reserved_mem_cnt is wrong in two places:
1) __reserved_mem_reserve_reg() returns 0 on success regardless of how
many regions it reserved in memblock. The caller in
fdt_scan_reserved_mem() then increments count by just 1.
2) fdt_scan_reserved_mem_late() uses of_flat_dt_get_addr_size() which
only reads the first reg entry. Subsequent entries are never
initialized via fdt_init_reserved_mem_node(), so their metadata is
lost.
Fix both issues:
- Make __reserved_mem_reserve_reg() return the actual number of
regions successfully reserved. Update the caller to accumulate
the returned count.
- Rewrite fdt_scan_reserved_mem_late() to use
of_flat_dt_get_addr_size_prop() and iterate all reg entries,
initializing each one via fdt_init_reserved_mem_node().
Fixes: 8a6e02d0c00e ("of: reserved_mem: Restructure how the reserved memory regions are processed")
Fixes: 00c9a452a235 ("of: reserved_mem: Add code to dynamically allocate reserved_mem array")
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
Tested-by: Zhao Meijing <zhaomeijing@lixiang.com>
---
drivers/of/of_reserved_mem.c | 37 +++++++++++++++++++++++-------------
1 file changed, 24 insertions(+), 13 deletions(-)
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 8d5777cb5d1b..9d1b0193864c 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -129,6 +129,8 @@ static int __init early_init_dt_reserve_memory(phys_addr_t base,
/*
* __reserved_mem_reserve_reg() - reserve all memory described in 'reg' property
+ *
+ * Returns: number of regions successfully reserved, or negative error code
*/
static int __init __reserved_mem_reserve_reg(unsigned long node,
const char *uname)
@@ -137,6 +139,7 @@ static int __init __reserved_mem_reserve_reg(unsigned long node,
int i, len, err;
const __be32 *prop;
bool nomap;
+ int reserved_count = 0;
prop = of_flat_dt_get_addr_size_prop(node, "reg", &len);
if (!prop)
@@ -160,12 +163,13 @@ static int __init __reserved_mem_reserve_reg(unsigned long node,
fdt_fixup_reserved_mem_node(node, base, size);
pr_debug("Reserved memory: reserved region for node '%s': base %pa, size %lu MiB\n",
uname, &base, (unsigned long)(size / SZ_1M));
+ reserved_count++;
} else {
pr_err("Reserved memory: failed to reserve memory for node '%s': base %pa, size %lu MiB\n",
uname, &base, (unsigned long)(size / SZ_1M));
}
}
- return 0;
+ return reserved_count;
}
/*
@@ -275,25 +279,32 @@ void __init fdt_scan_reserved_mem_late(void)
fdt_for_each_subnode(child, fdt, node) {
const char *uname;
- u64 b, s;
+ int i, len;
+ const __be32 *prop;
int ret;
if (!of_fdt_device_is_available(fdt, child))
continue;
- if (!of_flat_dt_get_addr_size(child, "reg", &b, &s))
+ prop = of_flat_dt_get_addr_size_prop(child, "reg", &len);
+ if (!prop)
continue;
ret = fdt_validate_reserved_mem_node(child, NULL);
if (ret && ret != -ENODEV)
continue;
- base = b;
- size = s;
+ uname = fdt_get_name(fdt, child, NULL);
+ for (i = 0; i < len; i++) {
+ u64 b, s;
- if (size) {
- uname = fdt_get_name(fdt, child, NULL);
- fdt_init_reserved_mem_node(child, uname, base, size);
+ of_flat_dt_read_addr_size(prop, i, &b, &s);
+
+ base = b;
+ size = s;
+
+ if (size)
+ fdt_init_reserved_mem_node(child, uname, base, size);
}
}
@@ -331,16 +342,16 @@ int __init fdt_scan_reserved_mem(void)
fdt_for_each_subnode(child, fdt, node) {
const char *uname;
- int err;
+ int ret;
if (!of_fdt_device_is_available(fdt, child))
continue;
uname = fdt_get_name(fdt, child, NULL);
- err = __reserved_mem_reserve_reg(child, uname);
- if (!err)
- count++;
+ ret = __reserved_mem_reserve_reg(child, uname);
+ if (ret > 0)
+ count += ret;
/*
* Save the nodes for the dynamically-placed regions
* into an array which will be used for allocation right
@@ -348,7 +359,7 @@ int __init fdt_scan_reserved_mem(void)
* or marked as no-map. This is done to avoid dynamically
* allocating from one of the statically-placed regions.
*/
- if (err == -ENOENT && of_get_flat_dt_prop(child, "size", NULL)) {
+ if (ret == -ENOENT && of_get_flat_dt_prop(child, "size", NULL)) {
dynamic_nodes[dynamic_nodes_cnt] = child;
dynamic_nodes_cnt++;
}
--
2.43.0
^ permalink raw reply related
* [PATCH 02/11] of: reserved_mem: reject reserved memory outside physical address range
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-1-chenwandun@lixiang.com>
early_init_dt_reserve_memory() does not validate whether the region
falls within physical memory. If a device tree incorrectly specifies a
reserved memory region outside the physical address range:
- For the non-nomap path, memblock_reserve() blindly adds the region
to memblock.reserved, creating a stale entry that refers to
non-existent memory.
- For the nomap path, memblock_mark_nomap() silently fails to match
any region in memblock.memory, but still returns success.
Add a memblock_overlaps_region() check at the entry of
early_init_dt_reserve_memory() to reject such regions before any
memblock operation takes place. This also simplifies the existing nomap
guard: the original "overlaps && is_reserved" condition reduces to just
"is_reserved", since the overlap with physical memory is already
guaranteed by the new check.
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
Tested-by: Zhao Meijing <zhaomeijing@lixiang.com>
---
drivers/of/of_reserved_mem.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 9d1b0193864c..03c676052dab 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -112,14 +112,21 @@ static int fdt_fixup_reserved_mem_node(unsigned long node,
static int __init early_init_dt_reserve_memory(phys_addr_t base,
phys_addr_t size, bool nomap)
{
+ if (!memblock_overlaps_region(&memblock.memory, base, size)) {
+ phys_addr_t end = base + size - 1;
+
+ pr_warn("Reserved memory region %pa..%pa is outside of physical memory\n",
+ &base, &end);
+ return -EINVAL;
+ }
+
if (nomap) {
/*
* If the memory is already reserved (by another region), we
- * should not allow it to be marked nomap, but don't worry
- * if the region isn't memory as it won't be mapped.
+ * should not allow it to be marked nomap. The region being
+ * physical memory is guaranteed by the overlap check above.
*/
- if (memblock_overlaps_region(&memblock.memory, base, size) &&
- memblock_is_region_reserved(base, size))
+ if (memblock_is_region_reserved(base, size))
return -EBUSY;
return memblock_mark_nomap(base, size);
--
2.43.0
^ permalink raw reply related
* [PATCH 03/11] of: reserved_mem: avoid unconditional save of reg entries in fdt_scan_reserved_mem_late()
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-1-chenwandun@lixiang.com>
fdt_scan_reserved_mem_late() iterates all reg entries of every
/reserved-memory child and unconditionally initialises each via
fdt_init_reserved_mem_node(), while fdt_scan_reserved_mem() in the
first pass may have rejected individual entries in
early_init_dt_reserve_memory() (e.g. outside physical memory or, on
the no-map path, overlapping an existing reservation).
When a single node mixes failing and succeeding reg entries, the
first-pass counter only accounts for the successful ones, and the
second-pass save then overflows into the wrong slots: the failing
entry may be written to reserved_mem[] while the succeeding one is
dropped by the "not enough space" guard in fdt_init_reserved_mem_node().
The stored entry does not correspond to any real memblock reservation
and misleads consumers such as of_reserved_mem_lookup().
Mirror early_init_dt_reserve_memory()'s preconditions in the
per-reg-entry save loop:
- skip the entry if it does not overlap memblock.memory;
- for nomap entries, skip if the region is already reserved.
This keeps reserved_mem[] strictly consistent with the regions that
were actually reserved.
Fixes: 8a6e02d0c00e ("of: reserved_mem: Restructure how the reserved memory regions are processed")
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
Tested-by: Zhao Meijing <zhaomeijing@lixiang.com>
---
drivers/of/of_reserved_mem.c | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 03c676052dab..807b222fce5f 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -288,6 +288,7 @@ void __init fdt_scan_reserved_mem_late(void)
const char *uname;
int i, len;
const __be32 *prop;
+ bool nomap;
int ret;
if (!of_fdt_device_is_available(fdt, child))
@@ -301,6 +302,7 @@ void __init fdt_scan_reserved_mem_late(void)
if (ret && ret != -ENODEV)
continue;
+ nomap = of_get_flat_dt_prop(child, "no-map", NULL) != NULL;
uname = fdt_get_name(fdt, child, NULL);
for (i = 0; i < len; i++) {
u64 b, s;
@@ -310,8 +312,23 @@ void __init fdt_scan_reserved_mem_late(void)
base = b;
size = s;
- if (size)
- fdt_init_reserved_mem_node(child, uname, base, size);
+ if (!size)
+ continue;
+
+ /*
+ * Save only entries that were successfully reserved
+ * in the first pass. Mirrors the preconditions in
+ * early_init_dt_reserve_memory() so that a per-reg
+ * entry failure (outside RAM, or nomap rejected due
+ * to an existing reservation) does not leave a
+ * ghost slot in reserved_mem[].
+ */
+ if (!memblock_overlaps_region(&memblock.memory, base, size))
+ continue;
+ if (nomap && memblock_is_region_reserved(base, size))
+ continue;
+
+ fdt_init_reserved_mem_node(child, uname, base, size);
}
}
--
2.43.0
^ permalink raw reply related
* [PATCH 04/11] of: reserved_mem: skip reserved_mem array allocation when there is nothing to save
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-1-chenwandun@lixiang.com>
fdt_scan_reserved_mem_late() unconditionally calls
alloc_reserved_mem_array() after confirming /reserved-memory exists.
Two issues with that:
- When __reserved_mem_check_root() subsequently fails, the call
returns right away, leaving the freshly allocated array unused.
- When /reserved-memory exists but fdt_scan_reserved_mem() found no
entries to save (total_reserved_mem_cnt stays at its freshly-set
value of zero, e.g. empty node or all children disabled),
alloc_reserved_mem_array() ends up calling memblock_alloc() with
zero size, which returns NULL and logs an "Failed to allocate
memory for reserved_mem array" error even though nothing was
expected to be allocated.
Move alloc_reserved_mem_array() past the root-node check and gate it
on total_reserved_mem_cnt, so the array is only allocated when there
is at least one entry that needs a slot.
Fixes: 00c9a452a235 ("of: reserved_mem: Add code to dynamically allocate reserved_mem array")
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
Tested-by: Zhao Meijing <zhaomeijing@lixiang.com>
---
drivers/of/of_reserved_mem.c | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 807b222fce5f..93585af9f8a3 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -276,14 +276,22 @@ void __init fdt_scan_reserved_mem_late(void)
return;
}
- /* Attempt dynamic allocation of a new reserved_mem array */
- alloc_reserved_mem_array();
-
if (__reserved_mem_check_root(node)) {
pr_err("Reserved memory: unsupported node format, ignoring\n");
return;
}
+ /*
+ * fdt_scan_reserved_mem() sets total_reserved_mem_cnt to the
+ * number of entries that need a slot in reserved_mem[]. If it is
+ * zero there is nothing to allocate or save.
+ */
+ if (!total_reserved_mem_cnt)
+ return;
+
+ /* Attempt dynamic allocation of a new reserved_mem array */
+ alloc_reserved_mem_array();
+
fdt_for_each_subnode(child, fdt, node) {
const char *uname;
int i, len;
--
2.43.0
^ permalink raw reply related
* [PATCH 05/11] of: reserved_mem: add linux,no-dump property support for reserved memory regions
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-1-chenwandun@lixiang.com>
Add a 'no_dump' field to struct reserved_mem and parse the
'linux,no-dump' device tree property during reserved memory node
initialization. This property allows device tree authors to mark
specific reserved memory regions that should be excluded from kdump
vmcore dumps.
Reserved memory regions used by device firmware (e.g., GPU, DSP, modem)
typically contain data that is not useful for kernel crash analysis and
can significantly increase vmcore size. The 'linux,no-dump' property
provides a declarative way to indicate these regions should be filtered
out when constructing the elfcorehdr for kdump.
The property is named with a 'linux,' prefix because kdump/vmcore is
Linux-specific and the property is an OS hint rather than a hardware
description, matching existing properties such as 'linux,cma-default'
and 'linux,usable-memory-range'.
The 'linux,no-dump' property is only effective when the region:
- Does not have 'no-map': these regions are already excluded from
vmcore since they are removed from the linear mapping (MEMBLOCK_NOMAP).
- Does not have 'reusable': CMA reusable regions are actively used by
the kernel for movable page allocations, and their contents are
valuable for crash analysis.
The no-dump status is also printed in the boot log alongside the
existing nomap and reusable flags for diagnostic purposes.
Corresponding dt-schema binding update:
https://github.com/devicetree-org/dt-schema/pull/193
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
Tested-by: Zhao Meijing <zhaomeijing@lixiang.com>
---
drivers/of/of_reserved_mem.c | 13 ++++++++-----
include/linux/of_reserved_mem.h | 1 +
2 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 93585af9f8a3..ac3d8b837d61 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -654,6 +654,7 @@ static void __init fdt_init_reserved_mem_node(unsigned long node, const char *un
{
int err = 0;
bool nomap;
+ bool reusable;
struct reserved_mem *rmem = &reserved_mem[reserved_mem_count];
@@ -662,11 +663,14 @@ static void __init fdt_init_reserved_mem_node(unsigned long node, const char *un
return;
}
+ nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
+ reusable = of_get_flat_dt_prop(node, "reusable", NULL) != NULL;
+
rmem->name = uname;
rmem->base = base;
rmem->size = size;
-
- nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL;
+ rmem->no_dump = !nomap && !reusable &&
+ of_get_flat_dt_prop(node, "linux,no-dump", NULL) != NULL;
err = __reserved_mem_init_node(rmem, node);
if (err != 0 && err != -ENODEV) {
@@ -680,13 +684,12 @@ static void __init fdt_init_reserved_mem_node(unsigned long node, const char *un
return;
} else {
phys_addr_t end = rmem->base + rmem->size - 1;
- bool reusable =
- (of_get_flat_dt_prop(node, "reusable", NULL)) != NULL;
- pr_info("%pa..%pa (%lu KiB) %s %s %s\n",
+ pr_info("%pa..%pa (%lu KiB) %s %s %s %s\n",
&rmem->base, &end, (unsigned long)(rmem->size / SZ_1K),
nomap ? "nomap" : "map",
reusable ? "reusable" : "non-reusable",
+ rmem->no_dump ? "no-dump" : "dump",
rmem->name ? rmem->name : "unknown");
}
diff --git a/include/linux/of_reserved_mem.h b/include/linux/of_reserved_mem.h
index e8b20b29fa68..29674f572673 100644
--- a/include/linux/of_reserved_mem.h
+++ b/include/linux/of_reserved_mem.h
@@ -15,6 +15,7 @@ struct reserved_mem {
phys_addr_t base;
phys_addr_t size;
void *priv;
+ bool no_dump;
};
struct reserved_mem_ops {
--
2.43.0
^ permalink raw reply related
* [PATCH 06/11] of: reserved_mem: save /memreserve/ entries into reserved_mem array
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-1-chenwandun@lixiang.com>
Save /memreserve/ entries from the FDT header into the reserved_mem
array so they can be consumed as vmcore filtering metadata by kdump.
/memreserve/ regions hold firmware or bootloader state that is not
useful for kernel crash analysis, so saved /memreserve/ entries
default to no_dump=true and are tagged with name="memreserve" so
consumers can distinguish them from /reserved-memory/ child nodes.
Some DTBs declare the same or overlapping range in both
/memreserve/ and a /reserved-memory/ child. Commit b41328187629
("of: fdt: Scan /memreserve/ last") describes one such case on
Khadas Vim3 where the range is in /memreserve/ and also in a
/reserved-memory/ child carrying no-map. The /reserved-memory/
node's attributes (no-map, reusable, linux,no-dump) are the
explicit declaration and must win over the firmware default,
fdt_reserved_mem_save_memreserve() therefore inherits no_dump from
the overlapping /reserved-memory/ entry rather than silently
applying no_dump=true.
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
Tested-by: Zhao Meijing <zhaomeijing@lixiang.com>
---
drivers/of/of_reserved_mem.c | 107 +++++++++++++++++++++++++++++------
1 file changed, 91 insertions(+), 16 deletions(-)
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index ac3d8b837d61..4b80420da2d2 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -252,6 +252,49 @@ static void __init __rmem_check_for_overlap(void)
}
}
+/**
+ * fdt_reserved_mem_save_memreserve() - save a /memreserve/ entry
+ * @base: base address
+ * @size: size
+ *
+ * Save a /memreserve/ range into reserved_mem[] with no_dump=true
+ * as the firmware default. If the range overlaps a /reserved-memory/
+ * child already saved in this pass, inherit that entry's no_dump so
+ * node-level attributes (no-map, reusable, linux,no-dump) win over
+ * the firmware default.
+ */
+static void __init fdt_reserved_mem_save_memreserve(phys_addr_t base,
+ phys_addr_t size)
+{
+ struct reserved_mem *rmem;
+ phys_addr_t end = base + size;
+ bool no_dump = true;
+ int i;
+
+ for (i = 0; i < reserved_mem_count; i++) {
+ phys_addr_t r_base = reserved_mem[i].base;
+ phys_addr_t r_end = r_base + reserved_mem[i].size;
+
+ if (base < r_end && end > r_base) {
+ no_dump = reserved_mem[i].no_dump;
+ break;
+ }
+ }
+
+ if (reserved_mem_count == total_reserved_mem_cnt) {
+ pr_err("not enough space for all defined regions.\n");
+ return;
+ }
+
+ rmem = &reserved_mem[reserved_mem_count];
+ rmem->name = "memreserve";
+ rmem->base = base;
+ rmem->size = size;
+ rmem->no_dump = no_dump;
+
+ reserved_mem_count++;
+}
+
/**
* fdt_scan_reserved_mem_late() - Scan FDT and initialize remaining reserved
* memory regions.
@@ -260,6 +303,9 @@ static void __init __rmem_check_for_overlap(void)
* "static" reserved memory regions, that are defined using the "reg"
* property. Each such region is then initialized with its specific init
* function and stored in the global reserved_mem array.
+ *
+ * In addition, /memreserve/ entries from the FDT header are saved into
+ * the reserved_mem array so they can be consumed as vmcore metadata.
*/
void __init fdt_scan_reserved_mem_late(void)
{
@@ -270,28 +316,32 @@ void __init fdt_scan_reserved_mem_late(void)
if (!fdt)
return;
+ /*
+ * fdt_scan_reserved_mem() has set total_reserved_mem_cnt to the
+ * total number of entries to be saved (reg-based + /memreserve/).
+ * If it is zero there is nothing to allocate, save or check.
+ */
+ if (!total_reserved_mem_cnt)
+ return;
+
+ /*
+ * Allocate up front: /memreserve/ saves below may run on any
+ * path and must write into a memblock-backed array, not the
+ * __initdata reserved_mem_array which is freed at free_initmem().
+ */
+ alloc_reserved_mem_array();
+
node = fdt_path_offset(fdt, "/reserved-memory");
if (node < 0) {
pr_info("Reserved memory: No reserved-memory node in the DT\n");
- return;
+ goto memreserve;
}
if (__reserved_mem_check_root(node)) {
pr_err("Reserved memory: unsupported node format, ignoring\n");
- return;
+ goto memreserve;
}
- /*
- * fdt_scan_reserved_mem() sets total_reserved_mem_cnt to the
- * number of entries that need a slot in reserved_mem[]. If it is
- * zero there is nothing to allocate or save.
- */
- if (!total_reserved_mem_cnt)
- return;
-
- /* Attempt dynamic allocation of a new reserved_mem array */
- alloc_reserved_mem_array();
-
fdt_for_each_subnode(child, fdt, node) {
const char *uname;
int i, len;
@@ -342,6 +392,18 @@ void __init fdt_scan_reserved_mem_late(void)
/* check for overlapping reserved regions */
__rmem_check_for_overlap();
+
+memreserve:
+ /* Save /memreserve/ entries (independent of /reserved-memory) */
+ for (int i = 0; ; i++) {
+ u64 mbase, msize;
+
+ if (fdt_get_mem_rsv(fdt, i, &mbase, &msize))
+ break;
+ if (!msize)
+ break;
+ fdt_reserved_mem_save_memreserve(mbase, msize);
+ }
}
static int __init __reserved_mem_alloc_size(unsigned long node, const char *uname);
@@ -365,11 +427,11 @@ int __init fdt_scan_reserved_mem(void)
node = fdt_path_offset(fdt, "/reserved-memory");
if (node < 0)
- return -ENODEV;
+ goto memreserve;
if (__reserved_mem_check_root(node) != 0) {
pr_err("Reserved memory: unsupported node format, ignoring\n");
- return -EINVAL;
+ goto memreserve;
}
fdt_for_each_subnode(child, fdt, node) {
@@ -406,8 +468,21 @@ int __init fdt_scan_reserved_mem(void)
if (!err)
count++;
}
+
+memreserve:
+ /* Count /memreserve/ entries (independent of /reserved-memory) */
+ for (int i = 0; ; i++) {
+ u64 base, size;
+
+ if (fdt_get_mem_rsv(fdt, i, &base, &size))
+ break;
+ if (!size)
+ break;
+ count++;
+ }
+
total_reserved_mem_cnt = count;
- return 0;
+ return count ? 0 : -ENODEV;
}
/*
--
2.43.0
^ permalink raw reply related
* [PATCH 07/11] of: reserved_mem: add no-dump crash_mem exclusion helpers
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-1-chenwandun@lixiang.com>
Provide two kdump-oriented helpers so that arch kexec_file code does
not have to open-code the no-dump filtering loop:
- of_reserved_mem_no_dump_nr_ranges() returns the number of reserved
regions flagged with linux,no-dump. Each exclusion may split one
existing crash_mem range into two, so callers use this count to
pre-size their crash_mem allocation.
- of_reserved_mem_exclude_no_dump() walks the reserved_mem[] array
and calls crash_exclude_mem_range() for each no-dump region.
Both helpers are guarded by CONFIG_KEXEC_FILE; empty inline stubs are
provided for the !KEXEC_FILE case so architecture code can call them
unconditionally.
The consumers are added in the following arm64, riscv and loongarch
patches in this series.
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
Tested-by: Zhao Meijing <zhaomeijing@lixiang.com>
---
drivers/of/of_reserved_mem.c | 54 +++++++++++++++++++++++++++++++++
include/linux/of_reserved_mem.h | 15 +++++++++
2 files changed, 69 insertions(+)
diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
index 4b80420da2d2..038056a6408a 100644
--- a/drivers/of/of_reserved_mem.c
+++ b/drivers/of/of_reserved_mem.c
@@ -27,6 +27,10 @@
#include "of_private.h"
+#ifdef CONFIG_KEXEC_FILE
+#include <linux/crash_core.h>
+#endif
+
static struct reserved_mem reserved_mem_array[MAX_RESERVED_REGIONS] __initdata;
static struct reserved_mem *reserved_mem __refdata = reserved_mem_array;
static int total_reserved_mem_cnt = MAX_RESERVED_REGIONS;
@@ -916,6 +920,56 @@ struct reserved_mem *of_reserved_mem_lookup(struct device_node *np)
}
EXPORT_SYMBOL_GPL(of_reserved_mem_lookup);
+#ifdef CONFIG_KEXEC_FILE
+/**
+ * of_reserved_mem_no_dump_nr_ranges() - count reserved regions flagged
+ * with the linux,no-dump property.
+ *
+ * Each such region may split an existing crash_mem range into two when
+ * it is excluded, so callers can use this count to pre-size their
+ * crash_mem allocation.
+ */
+unsigned int of_reserved_mem_no_dump_nr_ranges(void)
+{
+ unsigned int i, n = 0;
+
+ for (i = 0; i < reserved_mem_count; i++)
+ if (reserved_mem[i].no_dump)
+ n++;
+ return n;
+}
+
+/**
+ * of_reserved_mem_exclude_no_dump() - exclude no-dump reserved regions
+ * from a crash_mem list.
+ * @cmem: crash memory list to modify
+ *
+ * Walks the reserved_mem[] array and calls crash_exclude_mem_range() for
+ * every region with no_dump set. Intended to be called from arch kdump
+ * code when constructing the elfcorehdr.
+ *
+ * Returns 0 on success, or a negative error returned by
+ * crash_exclude_mem_range() on the first failure.
+ */
+int of_reserved_mem_exclude_no_dump(struct crash_mem *cmem)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < reserved_mem_count; i++) {
+ struct reserved_mem *r = &reserved_mem[i];
+
+ if (!r->no_dump || !r->size)
+ continue;
+ ret = crash_exclude_mem_range(cmem, r->base,
+ r->base + r->size - 1);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+#endif /* CONFIG_KEXEC_FILE */
+
/**
* of_reserved_mem_region_to_resource() - Get a reserved memory region as a resource
* @np: node containing 'memory-region' property
diff --git a/include/linux/of_reserved_mem.h b/include/linux/of_reserved_mem.h
index 29674f572673..cd0f88f29579 100644
--- a/include/linux/of_reserved_mem.h
+++ b/include/linux/of_reserved_mem.h
@@ -8,6 +8,7 @@
struct of_phandle_args;
struct reserved_mem_ops;
struct resource;
+struct crash_mem;
struct reserved_mem {
const char *name;
@@ -94,6 +95,20 @@ static inline int of_reserved_mem_region_count(const struct device_node *np)
}
#endif
+#if defined(CONFIG_OF_RESERVED_MEM) && defined(CONFIG_KEXEC_FILE)
+unsigned int of_reserved_mem_no_dump_nr_ranges(void);
+int of_reserved_mem_exclude_no_dump(struct crash_mem *cmem);
+#else
+static inline unsigned int of_reserved_mem_no_dump_nr_ranges(void)
+{
+ return 0;
+}
+static inline int of_reserved_mem_exclude_no_dump(struct crash_mem *cmem)
+{
+ return 0;
+}
+#endif
+
/**
* of_reserved_mem_device_init() - assign reserved memory region to given device
* @dev: Pointer to the device to configure
--
2.43.0
^ permalink raw reply related
* [PATCH 08/11] arm64: kdump: exclude no-dump reserved memory regions from vmcore
From: Chen Wandun @ 2026-04-29 6:58 UTC (permalink / raw)
To: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree
Cc: akpm, bhe, rppt, pasha.tatashin, pratyush, ruirui.yang, corbet,
skhan, catalin.marinas, will, chenhuacai, kernel, pjw, palmer,
aou, robh, saravanak, chenwandun, zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-1-chenwandun@lixiang.com>
Exclude reserved memory regions marked with the linux,no-dump property
from the elfcorehdr PT_LOAD segments when preparing kdump vmcore.
Device firmware memory regions (e.g., GPU, DSP, modem) reserved via
the device tree typically contain data that is not useful for kernel
crash analysis and can significantly increase vmcore size. By honoring
the no_dump flag in the reserved_mem array, these regions are filtered
out from the crash dump, resulting in smaller and more focused vmcore
files.
Use the common of_reserved_mem_exclude_no_dump() helper to perform the
exclusion, and pre-size the crash_mem array via
of_reserved_mem_no_dump_nr_ranges().
Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
Tested-by: Zhao Meijing <zhaomeijing@lixiang.com>
---
arch/arm64/kernel/machine_kexec_file.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
index e31fabed378a..7c9c38096c60 100644
--- a/arch/arm64/kernel/machine_kexec_file.c
+++ b/arch/arm64/kernel/machine_kexec_file.c
@@ -17,6 +17,7 @@
#include <linux/memblock.h>
#include <linux/of.h>
#include <linux/of_fdt.h>
+#include <linux/of_reserved_mem.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/types.h>
@@ -51,6 +52,7 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
nr_ranges = 2; /* for exclusion of crashkernel region */
for_each_mem_range(i, &start, &end)
nr_ranges++;
+ nr_ranges += of_reserved_mem_no_dump_nr_ranges();
cmem = kmalloc_flex(*cmem, ranges, nr_ranges);
if (!cmem)
@@ -75,6 +77,10 @@ static int prepare_elf_headers(void **addr, unsigned long *sz)
goto out;
}
+ ret = of_reserved_mem_exclude_no_dump(cmem);
+ if (ret)
+ goto out;
+
ret = crash_prepare_elf64_headers(cmem, true, addr, sz);
out:
--
2.43.0
^ permalink raw reply related
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox