]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
pmdomain: mediatek: Fix possible nullptr KP in HWV cleanup/on-check
authorAngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Thu, 9 Jul 2026 08:46:32 +0000 (10:46 +0200)
committerUlf Hansson <ulfh@kernel.org>
Mon, 13 Jul 2026 10:42:17 +0000 (12:42 +0200)
Should probe fail for HW_VOTER type power domains, this driver was
unconditionally trying to perform cleanup for DIRECT_CTL domains,
but only after checking if the target domain is powered on... with
the DIRECT_CTL scpsys_domain_is_on() code again.

And there's more: the scpsys_domain_is_on() function is also being
unconditionally used in the probe path, for any power domain that
has flag MTK_SCPD_KEEP_DEFAULT_OFF!

This bug was never experienced by anyone because the HWV domains
never failed probe, and because none of those is declared with the
aforementioned flag - but it's still something critical.

In order to fix this, add a check for MTCMOS Type and, based on
that, call the correct functions for an "is on" check, and also
do the same for the cleanup path, calling the correct functions
for the "power off" action.

For the latter, since there's a call to pm_genpd_remove() right
before calling power_off, be cautious and add a variation of the
power off functions (with a _internal suffix) for those to get a
pointer to scpsys_domain instead of one to generic_pm_domain as,
even if that's still working, this is way too much fragile and
would break at some point.

Fixes: 88914db077b6 ("pmdomain: mediatek: Add support for Hardware Voter power domains")
Signed-off-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Cc: stable@vger.kernel.org
Signed-off-by: Ulf Hansson <ulfh@kernel.org>
drivers/pmdomain/mediatek/mtk-pm-domains.c

index e1cfd42234734f3c13872e79001840743b1b8bd4..f0a6339affd7588290fb4a2d875c708ebefed3e6 100644 (file)
@@ -393,9 +393,8 @@ err_infra:
        return ret;
 };
 
-static int scpsys_hwv_power_off(struct generic_pm_domain *genpd)
+static int scpsys_hwv_power_off_internal(struct scpsys_domain *pd)
 {
-       struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd);
        const struct scpsys_hwv_domain_data *hwv = pd->hwv_data;
        struct scpsys *scpsys = pd->scpsys;
        u32 val;
@@ -464,6 +463,13 @@ err_infra:
        return ret;
 };
 
+static int scpsys_hwv_power_off(struct generic_pm_domain *genpd)
+{
+       struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd);
+
+       return scpsys_hwv_power_off_internal(pd);
+}
+
 static int scpsys_ctl_pwrseq_on(struct scpsys_domain *pd)
 {
        struct scpsys *scpsys = pd->scpsys;
@@ -694,9 +700,8 @@ err_reg:
        return ret;
 }
 
-static int scpsys_power_off(struct generic_pm_domain *genpd)
+static int scpsys_power_off_internal(struct scpsys_domain *pd)
 {
-       struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd);
        struct scpsys *scpsys = pd->scpsys;
        bool tmp;
        int ret;
@@ -737,6 +742,13 @@ static int scpsys_power_off(struct generic_pm_domain *genpd)
        return 0;
 }
 
+static int scpsys_power_off(struct generic_pm_domain *genpd)
+{
+       struct scpsys_domain *pd = container_of(genpd, struct scpsys_domain, genpd);
+
+       return scpsys_power_off_internal(pd);
+}
+
 static struct
 generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_node *node)
 {
@@ -884,7 +896,14 @@ generic_pm_domain *scpsys_add_one_domain(struct scpsys *scpsys, struct device_no
         * late_init time.
         */
        if (MTK_SCPD_CAPS(pd, MTK_SCPD_KEEP_DEFAULT_OFF)) {
-               if (scpsys_domain_is_on(pd))
+               bool domain_is_on;
+
+               if (scpsys->soc_data->type == SCPSYS_MTCMOS_TYPE_HW_VOTER)
+                       domain_is_on = scpsys_hwv_domain_is_enable_done(pd);
+               else
+                       domain_is_on = scpsys_domain_is_on(pd);
+
+               if (domain_is_on)
                        dev_warn(scpsys->dev,
                                 "%pOF: A default off power domain has been ON\n", node);
        } else {
@@ -973,6 +992,7 @@ err_put_node:
 
 static void scpsys_remove_one_domain(struct scpsys_domain *pd)
 {
+       struct scpsys *scpsys = pd->scpsys;
        int ret;
 
        /*
@@ -984,8 +1004,14 @@ static void scpsys_remove_one_domain(struct scpsys_domain *pd)
                dev_err(pd->scpsys->dev,
                        "failed to remove domain '%s' : %d - state may be inconsistent\n",
                        pd->genpd.name, ret);
-       if (scpsys_domain_is_on(pd))
-               scpsys_power_off(&pd->genpd);
+
+       if (scpsys->soc_data->type == SCPSYS_MTCMOS_TYPE_HW_VOTER) {
+               if (scpsys_hwv_domain_is_enable_done(pd))
+                       scpsys_hwv_power_off_internal(pd);
+       } else {
+               if (scpsys_domain_is_on(pd))
+                       scpsys_power_off_internal(pd);
+       }
 
        clk_bulk_put(pd->num_clks, pd->clks);
        clk_bulk_put(pd->num_subsys_clks, pd->subsys_clks);