[PATCH v2 2/4] pmdomain: mediatek: Respect PD relationships during error cleanup
AngeloGioacchino Del Regno
angelogioacchino.delregno at collabora.com
Tue Jul 7 08:55:33 PDT 2026
In case any probe error occurs (usually, a probe deferral) the
power domains shall be cleaned up while respecting their child
to parent relationship, or the system may freeze.
In order to do that without any memory footprint impacts after
the fact, allocate a temporary array in the probe function and
use it to store the indices of the added power domains in the
correct order.
This will be used in the error cleanup path and will be freed
at the end regardless of the probe status as, when the probing
succeeds, the genpd API takes care of unregistering all PDs in
the correct order anyway.
Reviewed-by: Matthias Brugger <matthias.bgg at gmail.com>
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno at collabora.com>
---
drivers/pmdomain/mediatek/mtk-pm-domains.c | 43 +++++++++++++++++-----
1 file changed, 33 insertions(+), 10 deletions(-)
diff --git a/drivers/pmdomain/mediatek/mtk-pm-domains.c b/drivers/pmdomain/mediatek/mtk-pm-domains.c
index e1cfd4223473..db543d4b1813 100644
--- a/drivers/pmdomain/mediatek/mtk-pm-domains.c
+++ b/drivers/pmdomain/mediatek/mtk-pm-domains.c
@@ -738,7 +738,8 @@ static int scpsys_power_off(struct generic_pm_domain *genpd)
}
static struct
-generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_node *node)
+generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_node *node,
+ u8 *domains_idx, u8 *num_domains)
{
const struct scpsys_domain_data *domain_data;
const struct scpsys_hwv_domain_data *hwv_domain_data;
@@ -906,6 +907,7 @@ generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_no
else
pm_genpd_init(&pd->genpd, NULL, false);
+ domains_idx[(*num_domains)++] = (u8) id;
scpsys->domains[id] = &pd->genpd;
return scpsys->pd_data.domains[id];
@@ -917,7 +919,8 @@ generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_no
return ERR_PTR(ret);
}
-static int scpsys_add_subdomain(struct scpsys *scpsys, struct device_node *parent)
+static int scpsys_add_subdomain(struct scpsys *scpsys, struct device_node *parent,
+ u8 *domains_idx, u8 *num_domains)
{
struct generic_pm_domain *child_pd, *parent_pd;
struct device_node *child;
@@ -940,7 +943,7 @@ static int scpsys_add_subdomain(struct scpsys *scpsys, struct device_node *paren
parent_pd = scpsys->pd_data.domains[id];
- child_pd = scpsys_add_one_domain(scpsys, child);
+ child_pd = scpsys_add_one_domain(scpsys, child, domains_idx, num_domains);
if (IS_ERR(child_pd)) {
ret = PTR_ERR(child_pd);
dev_err_probe(scpsys->dev, ret, "%pOF: failed to get child domain id\n",
@@ -949,7 +952,7 @@ static int scpsys_add_subdomain(struct scpsys *scpsys, struct device_node *paren
}
/* recursive call to add all subdomains */
- ret = scpsys_add_subdomain(scpsys, child);
+ ret = scpsys_add_subdomain(scpsys, child, domains_idx, num_domains);
if (ret)
goto err_put_node;
@@ -991,14 +994,16 @@ static void scpsys_remove_one_domain(struct scpsys_domain *pd)
clk_bulk_put(pd->num_subsys_clks, pd->subsys_clks);
}
-static void scpsys_domain_cleanup(struct scpsys *scpsys)
+static void scpsys_domain_cleanup(struct scpsys *scpsys, u8 *domains_idx, u8 num_probed)
{
struct generic_pm_domain *genpd;
struct scpsys_domain *pd;
int i;
- for (i = scpsys->pd_data.num_domains - 1; i >= 0; i--) {
- genpd = scpsys->pd_data.domains[i];
+ for (i = num_probed - 1; i >= 0; i--) {
+ u8 pd_idx = domains_idx[i];
+
+ genpd = scpsys->pd_data.domains[pd_idx];
if (genpd) {
pd = to_scpsys_domain(genpd);
scpsys_remove_one_domain(pd);
@@ -1215,6 +1220,8 @@ static int scpsys_probe(struct platform_device *pdev)
struct device *parent;
struct scpsys *scpsys;
int num_domains, ret;
+ u8 num_added_pds = 0;
+ u8 *added_pds_idx;
soc = of_device_get_match_data(&pdev->dev);
if (!soc) {
@@ -1228,6 +1235,19 @@ static int scpsys_probe(struct platform_device *pdev)
if (!scpsys)
return -ENOMEM;
+ /*
+ * Temporarily store the IDs of the power domains that are added as in
+ * case of a probe deferral this can be used to correctly cleanup all
+ * of what was added before.
+ *
+ * Note that this array is used only in the probe function and must be
+ * freed at the end, regardless of whether all of the power domains were
+ * probed successfully or any failure happened.
+ */
+ added_pds_idx = devm_kmalloc_array(dev, num_domains, sizeof(*added_pds_idx), GFP_KERNEL);
+ if (!added_pds_idx)
+ return -ENOMEM;
+
scpsys->dev = dev;
scpsys->soc_data = soc;
@@ -1258,13 +1278,15 @@ static int scpsys_probe(struct platform_device *pdev)
for_each_available_child_of_node_scoped(np, node) {
struct generic_pm_domain *domain;
- domain = scpsys_add_one_domain(scpsys, node);
+ domain = scpsys_add_one_domain(scpsys, node,
+ added_pds_idx, &num_added_pds);
if (IS_ERR(domain)) {
ret = PTR_ERR(domain);
goto err_cleanup_domains;
}
- ret = scpsys_add_subdomain(scpsys, node);
+ ret = scpsys_add_subdomain(scpsys, node,
+ added_pds_idx, &num_added_pds);
if (ret)
goto err_cleanup_domains;
}
@@ -1280,10 +1302,11 @@ static int scpsys_probe(struct platform_device *pdev)
goto err_cleanup_domains;
}
+ devm_kfree(dev, added_pds_idx);
return 0;
err_cleanup_domains:
- scpsys_domain_cleanup(scpsys);
+ scpsys_domain_cleanup(scpsys, added_pds_idx, num_added_pds);
return ret;
}
--
2.54.0
More information about the linux-arm-kernel
mailing list