All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Konrad Dybcio" <konradybcio@kernel.org>
Cc: linux-clk@vger.kernel.org, devicetree@vger.kernel.org,
	robh@kernel.org, conor+dt@kernel.org
Subject: Re: [PATCH RFC 07/16] clk: qcom: Add clock-owned GenPD DVFS aggregation
Date: Wed, 14 Oct 2026 12:35:42 +0000	[thread overview]
Message-ID: <sashiko-outbox-171287@kernel.org> (raw)
In-Reply-To: <20261014-topic-qc_clk_dvfs-v1-7-3041b788e9f7@oss.qualcomm.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] clk: qcom: Hardware undervolted on actual_rate lookup failure
- [High] clk: qcom: TOCTOU race in qcom_clk_dvfs_cleanup() leads to UAF
- [High] clk: qcom: permanent DVFS domain bricking on failed performance state
- [Low] cleanup: mixed goto-based error handling and __free() in qcom_clk_dvfs_resource_acquire()
--

commit f24ba5feffbb1fedbb7c842c732865ac4c8e3e69
Author: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>

    clk: qcom: Add clock-owned GenPD DVFS aggregation
    
    Clocks on Qualcomm SoCs need a minimum power rail performance state to
    operate at a given frequency. We currently leave this to clock
    consumers, so a driver has to know which power domains supply both the
    clock and its parents. This ties knowledge of the clock tree to
    individual consumers and doesn't protect other users that prepare or
    change the same clock without making those power requests.

> diff --git a/drivers/clk/qcom/clk-dvfs.c b/drivers/clk/qcom/clk-dvfs.c
> new file mode 100644
> index 0000000000000..e6490c4dab949
> --- /dev/null
> +++ b/drivers/clk/qcom/clk-dvfs.c
> @@ -0,0 +1,574 @@
[ ... ]
> +static unsigned int
> +qcom_clk_dvfs_aggregate(struct qcom_clk_dvfs_rail_voter *rail_voter)
> +{
> +	struct qcom_clk_dvfs_vote *vote;
> +	unsigned int level = 0;
> +
> +	list_for_each_entry(vote, &rail_voter->votes, node)
> +		level = max(level, vote->level);
> +
> +	return max(level, rail_voter->safety_floor_level);
> +}
[ ... ]
> +static int qcom_clk_dvfs_apply(struct qcom_clk_dvfs_rail_voter *rail_voter,
> +			       unsigned int level)
> +{
> +	int ret;
> +
> +	ret = qcom_clk_dvfs_activate(rail_voter);
> +	if (ret)
> +		return ret;
> +	ret = dev_pm_genpd_set_performance_state(rail_voter->dev, level);
> +	if (ret) {
> +		rail_voter->safety_floor_level =
> +			max(rail_voter->safety_floor_level,
> +			    max(rail_voter->last_successful_vote_level, level));
> +		return ret;

[Severity: High]
Does this code permanently limit the DVFS domain after a failed update?

If dev_pm_genpd_set_performance_state() fails in qcom_clk_dvfs_apply(),
the failed level is incorporated into safety_floor_level. 

qcom_clk_dvfs_aggregate() will then always return at least this level for any
future updates, forcing the domain to retry a known-bad state. This could
permanently break frequency scaling for the domain and leak a PM runtime
reference because the domain can never reach level 0 to suspend.

> +	}
> +
> +	rail_voter->last_successful_vote_level = level;
> +	rail_voter->safety_floor_level = 0;
> +
> +	return 0;
> +}
[ ... ]
> +int qcom_clk_dvfs_resource_acquire(struct clk_hw *hw,
> +				 const struct clk_resource_transition *transition,
> +				 void **cookie)
> +{
> +	struct qcom_clk_dvfs_clock *clock = to_clk_regmap(hw)->dvfs;
> +	struct qcom_clk_dvfs_transition *dvfs_transition __free(kfree) = NULL;
> +	struct qcom_clk_dvfs_vote *votes __free(kfree) = NULL;
> +	unsigned int level;
> +	size_t i;
> +	int ret;
> +
> +	if (!clock) {
> +		*cookie = NULL;
> +		return 0;
> +	}
> +
> +	dvfs_transition = kzalloc_obj(*dvfs_transition);
> +	if (!dvfs_transition)
> +		return -ENOMEM;
> +
> +	votes = kcalloc(clock->data->num_pd_indices, sizeof(*votes),
> +			 GFP_KERNEL);
> +	if (!votes)
> +		return -ENOMEM;
> +
> +	dvfs_transition->votes = votes;
> +	dvfs_transition->num_votes = clock->data->num_pd_indices;
> +	ret = qcom_clk_dvfs_find_level(clock->data, transition->new_rate, &level);
> +	if (ret)
> +		return ret;
> +
> +	for (i = 0; i < dvfs_transition->num_votes; i++) {
> +		unsigned int pd_index = clock->data->pd_indices[i];
> +
> +		ret = qcom_clk_dvfs_add_vote(&clock->rail_voters[pd_index],
> +						  &dvfs_transition->votes[i], level);
> +		if (ret)
> +			goto unwind;
> +	}
> +
> +	dvfs_transition->votes = no_free_ptr(votes);
> +	*cookie = no_free_ptr(dvfs_transition);
> +
> +	return 0;
> +
> +unwind:
> +	while (i--) {

[Severity: Low]
This isn't a bug, but does this function mix goto error handling with __free()?

It appears qcom_clk_dvfs_resource_acquire() uses __free(kfree) for
dvfs_transition and votes while also using a "goto unwind;" path for error
cleanup. The cleanup subsystem guide recommends converting either all resources
to scope-based cleanup or none, rather than mixing both paradigms in the same
function.

> +		unsigned int pd_index = clock->data->pd_indices[i];
> +
> +		qcom_clk_dvfs_remove_vote(&clock->rail_voters[pd_index],
> +					    &dvfs_transition->votes[i]);
> +	}
> +
> +	return ret;
> +}
> +
> +int qcom_clk_dvfs_resource_release(struct clk_hw *hw,
> +				 const struct clk_resource_transition *transition,
> +				 void *cookie,
> +				 enum clk_resource_change_result result)
> +{
> +	struct qcom_clk_dvfs_transition *dvfs_transition = cookie;
> +	struct qcom_clk_dvfs_clock *clock = to_clk_regmap(hw)->dvfs;
> +	int first_error = 0;
> +	unsigned int level;
> +	int ret;
> +
> +	if (!clock)
> +		return 0;
> +	if (result == CLK_RESOURCE_COMPLETE) {
> +		ret = qcom_clk_dvfs_find_level(clock->data, transition->actual_rate,
> +					   &level);
> +		if (ret)
> +			first_error = ret;
> +	}
> +
> +	for (size_t i = 0; i < dvfs_transition->num_votes; i++) {
> +		unsigned int pd_index = clock->data->pd_indices[i];
> +
> +		if (!clock->votes[i].level)
> +			continue;
> +
> +		if (result == CLK_RESOURCE_COMPLETE) {
> +			ret = first_error;

[Severity: High]
Can this undervolt the hardware on an actual_rate lookup failure?

If qcom_clk_dvfs_find_level() fails to look up the level for the new rate
(setting first_error to a non-zero value), this loop sets ret = first_error.
Because ret is non-zero, it skips qcom_clk_dvfs_replace_vote(), leaving the
permanent vote at the old, lower level. 

Then, the temporary high transition vote is removed. Because the hardware
is already running at the higher actual_rate, the resulting lower aggregate
voltage vote could cause an immediate undervolt.

> +		} else if (result == CLK_RESOURCE_HW_CHANGED) {
> +			level = max(clock->votes[i].level,
> +				    dvfs_transition->votes[i].level);
> +			ret = 0;
> +		} else {
> +			continue;
> +		}
> +
> +		if (!ret) {
> +			ret = qcom_clk_dvfs_replace_vote(&clock->rail_voters[pd_index],
> +							 &clock->votes[i], level);
> +		}
> +		if (ret && !first_error)
> +			first_error = ret;
> +	}
[ ... ]
> +static void qcom_clk_dvfs_cleanup(void *data)
> +{
> +	struct qcom_clk_dvfs *dvfs = data;
> +
> +	for (size_t i = 0; i < dvfs->num_clocks; i++)
> +		dvfs->clocks[i].rclk->dvfs = NULL;

[Severity: High]
Can this race with consumer unprepare operations?

If a consumer holds a reference and unbinds, devres executes
qcom_clk_dvfs_cleanup(), which nullifies rclk->dvfs without taking the CCF
prepare_lock. 

A concurrent clk_unprepare() could read the non-NULL pointer, and if
qcom_clk_dvfs_cleanup() then proceeds to destroy rail_voter->lock and free
memory, qcom_clk_dvfs_unprepare() would cause a use-after-free when it tries
to lock the destroyed mutex in qcom_clk_dvfs_replace_vote().

> +
> +	for (size_t i = dvfs->num_rail_voters; i-- > 0;) {
> +		struct qcom_clk_dvfs_rail_voter *rail_voter = &dvfs->rail_voters[i];
> +
> +		if (rail_voter->active) {
> +			dev_pm_genpd_set_performance_state(rail_voter->dev, 0);
> +			WRITE_ONCE(rail_voter->must_contribute, false);
> +			pm_runtime_put_sync_suspend(rail_voter->dev);
> +		}
> +		dev_pm_genpd_remove_notifier(rail_voter->dev);
> +		device_link_del(rail_voter->link);
> +		dev_pm_domain_detach(rail_voter->dev, true);
> +		mutex_destroy(&rail_voter->lock);
> +	}
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261014-topic-qc_clk_dvfs-v1-0-3041b788e9f7@oss.qualcomm.com?part=7

  reply	other threads:[~2026-10-14 12:35 UTC|newest]

Thread overview: 57+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-14 12:19 [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 01/16] clk: Propagate rate programming failures Konrad Dybcio
2026-10-14 12:35   ` sashiko-bot
2026-10-14 13:25   ` Dmitry Baryshkov
2026-10-14 12:19 ` [PATCH RFC 02/16] clk: Acquire resources for clock transitions Konrad Dybcio
2026-10-14 12:40   ` sashiko-bot
2026-10-14 12:19 ` [PATCH RFC 03/16] OPP: Retry failed default single-clock transitions Konrad Dybcio
2026-10-14 12:34   ` sashiko-bot
2026-10-14 13:35   ` Dmitry Baryshkov
2026-10-14 16:14     ` Konrad Dybcio
2026-10-14 16:32       ` Dmitry Baryshkov
2026-10-14 12:19 ` [PATCH RFC 04/16] pmdomain: core: Support named indexed virtual attachments Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 05/16] pmdomain: core: Support subdomains without pstate propagation Konrad Dybcio
2026-10-14 12:41   ` sashiko-bot
2026-10-14 12:19 ` [PATCH RFC 06/16] pmdomain: core: Support indexed power-domain attachment lists Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 07/16] clk: qcom: Add clock-owned GenPD DVFS aggregation Konrad Dybcio
2026-10-14 12:35   ` sashiko-bot [this message]
2026-10-14 14:36   ` Dmitry Baryshkov
2026-10-14 15:26     ` Konrad Dybcio
2026-10-14 16:30       ` Dmitry Baryshkov
2026-10-14 16:45         ` Konrad Dybcio
2026-10-14 17:31           ` Dmitry Baryshkov
2026-10-14 12:19 ` [PATCH RFC 08/16] clk: qcom: dvfs: Add debugfs Konrad Dybcio
2026-10-14 12:37   ` sashiko-bot
2026-10-14 12:19 ` [PATCH RFC 09/16] clk: qcom: clk-alpha-pll: Add DVFS (un)prepare Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 10/16] clk: qcom: clk-alpha-pll: Vote for the PLL calibration frequency Konrad Dybcio
2026-10-14 12:34   ` sashiko-bot
2026-10-14 12:19 ` [PATCH RFC 11/16] clk: qcom: gdsc: Make GDSC parenting optional Konrad Dybcio
2026-10-14 12:30   ` sashiko-bot
2026-10-14 14:17   ` Dmitry Baryshkov
2026-10-14 14:20     ` Konrad Dybcio
2026-10-14 14:34       ` Dmitry Baryshkov
2026-10-14 14:36         ` Konrad Dybcio
2026-10-14 12:19 ` [PATCH RFC 12/16] clk: qcom: gdsc: Link required power domains Konrad Dybcio
2026-10-14 12:32   ` sashiko-bot
2026-10-14 12:19 ` [PATCH DNM RFC 13/16] arm64: dts: qcom: sm8650: Describe clock controller power rails Konrad Dybcio
2026-10-14 12:41   ` sashiko-bot
2026-10-14 14:19   ` Dmitry Baryshkov
2026-10-14 14:20     ` Konrad Dybcio
2026-10-14 14:33       ` Dmitry Baryshkov
2026-10-14 14:36         ` Konrad Dybcio
2026-10-14 15:01           ` Dmitry Baryshkov
2026-10-14 15:23             ` Konrad Dybcio
2026-10-14 12:19 ` [PATCH DNM RFC 14/16] clk: qcom: Enable clk-dvfs on SM8650 Konrad Dybcio
2026-10-14 12:36   ` sashiko-bot
2026-10-14 14:40   ` Dmitry Baryshkov
2026-10-14 15:22     ` Konrad Dybcio
2026-10-14 16:35       ` Dmitry Baryshkov
2026-10-14 16:42         ` Konrad Dybcio
2026-10-14 12:19 ` [PATCH DNM RFC 15/16] arm64: dts: qcom: sm8650: Drop required-opps Konrad Dybcio
2026-10-14 12:39   ` sashiko-bot
2026-10-14 14:23   ` Dmitry Baryshkov
2026-10-14 14:33     ` Konrad Dybcio
2026-10-14 12:19 ` [PATCH DNM RFC 16/16] pmdomain: qcom: rpmhpd: Stop forwarding MMCX pstate to CX Konrad Dybcio
2026-10-14 12:37   ` sashiko-bot
2026-10-14 14:37 ` [RFC PATCH 00/16] Centralize the ownership of power rail votes for Qualcomm clocks Dmitry Baryshkov
2026-10-14 16:19   ` Konrad Dybcio

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=sashiko-outbox-171287@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=konradybcio@kernel.org \
    --cc=linux-clk@vger.kernel.org \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.