[PATCH net-next 2/4] net: pse-pd: unwind allocations when controller registration fails
From: Carlo Szelinsky
Date: Sun Oct 18 2026 - 04:19:49 EST
When pse_controller_register() fails, it leaks the notification kfifo,
the PI array and the OF node references. Worse, if pse_register_pw_ds()
fails part way, the power domains it created stay in the global map
while devres frees them, so the next controller that registers reads
freed memory. A later patch makes -EPROBE_DEFER a normal result here,
so this will happen more often.
Free what was allocated on each error path. The PI array is still
leaked once the PI regulators are registered, because their ops use it
until devres unregisters them.
pse_register_pw_ds() now undoes its own power domains while it still
holds pse_pw_d_mutex, so no other controller can pick one up just
before it is freed. pi[].pw_d is cleared under pcdev->lock before its
reference is dropped, and pse_ethtool_set_prio() and
pse_pi_update_pw_budget() now check it under that lock.
A power domain shared by two controllers is still devm memory of the
controller that created it; that is fixed separately on net.
Signed-off-by: Carlo Szelinsky <github@xxxxxxxxxxxx>
---
Notes:
Merging with the net fixes:
- "net: pse-pd: do not tie a shared power domain to its creator's
devres": move its kfree(pw_d) to the end of
pse_pw_d_release_locked(), and name that function in its
pse_alloc_pw_d() comment. In pse_flush_pw_ds(), keep this patch's
pse_pi_take_pw_d() call.
- "net: pse-pd: check the PI state under the lock when setting a power
limit": in pse_pi_update_pw_budget(), keep the net fix's check.
drivers/net/pse-pd/pse_core.c | 82 ++++++++++++++++++++++++++++-------
1 file changed, 66 insertions(+), 16 deletions(-)
diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c
index 364acc025097..05b02d736931 100644
--- a/drivers/net/pse-pd/pse_core.c
+++ b/drivers/net/pse-pd/pse_core.c
@@ -878,7 +878,8 @@ devm_pse_pi_regulator_register(struct pse_controller_dev *pcdev,
return 0;
}
-static void __pse_pw_d_release(struct kref *kref)
+/* Release for a put made with pse_pw_d_mutex already held. */
+static void pse_pw_d_release_locked(struct kref *kref)
{
struct pse_power_domain *pw_d = container_of(kref,
struct pse_power_domain,
@@ -886,9 +887,30 @@ static void __pse_pw_d_release(struct kref *kref)
regulator_put(pw_d->supply);
xa_erase(&pse_pw_d_map, pw_d->id);
+}
+
+static void __pse_pw_d_release(struct kref *kref)
+{
+ pse_pw_d_release_locked(kref);
mutex_unlock(&pse_pw_d_mutex);
}
+/* Clear pi[id].pw_d before its reference is dropped. The PI regulator
+ * ops hold pcdev->lock while they use it.
+ */
+static struct pse_power_domain *
+pse_pi_take_pw_d(struct pse_controller_dev *pcdev, int id)
+{
+ struct pse_power_domain *pw_d;
+
+ mutex_lock(&pcdev->lock);
+ pw_d = pcdev->pi[id].pw_d;
+ pcdev->pi[id].pw_d = NULL;
+ mutex_unlock(&pcdev->lock);
+
+ return pw_d;
+}
+
/**
* pse_flush_pw_ds - flush all PSE power domains of a PSE
* @pcdev: a pointer to the initialized PSE controller device
@@ -899,10 +921,11 @@ static void pse_flush_pw_ds(struct pse_controller_dev *pcdev)
int i;
for (i = 0; i < pcdev->nr_lines; i++) {
- if (!pcdev->pi[i].pw_d)
+ pw_d = pse_pi_take_pw_d(pcdev, i);
+ if (!pw_d)
continue;
- pw_d = xa_load(&pse_pw_d_map, pcdev->pi[i].pw_d->id);
+ pw_d = xa_load(&pse_pw_d_map, pw_d->id);
if (!pw_d)
continue;
@@ -998,6 +1021,13 @@ static int pse_register_pw_ds(struct pse_controller_dev *pcdev)
}
out:
+ /* Undo this call's domains before another controller can find them. */
+ while (ret && i--) {
+ struct pse_power_domain *pw_d = pse_pi_take_pw_d(pcdev, i);
+
+ if (pw_d)
+ kref_put(&pw_d->refcnt, pse_pw_d_release_locked);
+ }
mutex_unlock(&pse_pw_d_mutex);
return ret;
}
@@ -1058,17 +1088,18 @@ int pse_controller_register(struct pse_controller_dev *pcdev)
!pcdev->ops->pi_get_pw_status) {
dev_err(pcdev->dev,
"Mandatory status report callbacks are missing");
- return -EINVAL;
+ ret = -EINVAL;
+ goto free_kfifo;
}
ret = of_load_pse_pis(pcdev);
if (ret)
- return ret;
+ goto free_kfifo;
if (pcdev->ops->setup_pi_matrix) {
ret = pcdev->ops->setup_pi_matrix(pcdev);
if (ret)
- return ret;
+ goto release_pis;
}
/* Each regulator name len is pcdev dev name + 7 char +
@@ -1076,7 +1107,9 @@ int pse_controller_register(struct pse_controller_dev *pcdev)
*/
reg_name_len = strlen(dev_name(pcdev->dev)) + 18;
- /* Register PI regulators */
+ /* Register PI regulators. Their ops index pcdev->pi[] until devres
+ * unregisters them, so failures from here on must not free it.
+ */
for (i = 0; i < pcdev->nr_lines; i++) {
char *reg_name;
@@ -1085,26 +1118,38 @@ int pse_controller_register(struct pse_controller_dev *pcdev)
continue;
reg_name = devm_kzalloc(pcdev->dev, reg_name_len, GFP_KERNEL);
- if (!reg_name)
- return -ENOMEM;
+ if (!reg_name) {
+ ret = -ENOMEM;
+ goto free_kfifo;
+ }
snprintf(reg_name, reg_name_len, "pse-%s_pi%d",
dev_name(pcdev->dev), i);
ret = devm_pse_pi_regulator_register(pcdev, reg_name, i);
if (ret)
- return ret;
+ goto free_kfifo;
}
ret = pse_register_pw_ds(pcdev);
if (ret)
- return ret;
+ goto free_kfifo;
mutex_lock(&pse_list_mutex);
list_add(&pcdev->list, &pse_controller_list);
mutex_unlock(&pse_list_mutex);
return 0;
+
+release_pis:
+ pse_release_pis(pcdev);
+ pcdev->pi = NULL;
+
+free_kfifo:
+ cancel_work_sync(&pcdev->ntf_work);
+ kfifo_free(&pcdev->ntf_fifo);
+
+ return ret;
}
EXPORT_SYMBOL_GPL(pse_controller_register);
@@ -1820,6 +1865,10 @@ static int pse_pi_update_pw_budget(struct pse_controller_dev *pcdev, int id,
* power budget update
*/
mutex_lock(&pcdev->lock);
+ /* pi->pw_d may have been cleared since the caller looked. */
+ if (!pi->pw_d)
+ goto out;
+
previous_pw_allocated = pi->pw_allocated_mW;
pw_diff = pw_req - previous_pw_allocated;
if (!pw_diff) {
@@ -1921,15 +1970,16 @@ int pse_ethtool_set_prio(struct pse_control *psec,
const struct pse_controller_ops *ops;
int ret = 0;
- if (!pcdev->pi[psec->id].pw_d) {
- NL_SET_ERR_MSG(extack, "no power domain attached");
- return -EOPNOTSUPP;
- }
-
/* We don't want priority change in the middle of an
* enable/disable call or a priority mode change
*/
mutex_lock(&pcdev->lock);
+ if (!pcdev->pi[psec->id].pw_d) {
+ NL_SET_ERR_MSG(extack, "no power domain attached");
+ ret = -EOPNOTSUPP;
+ goto out;
+ }
+
switch (pcdev->pi[psec->id].pw_d->budget_eval_strategy) {
case PSE_BUDGET_EVAL_STRAT_STATIC:
if (prio >= pcdev->nr_lines) {
--
2.43.0