Thread (34 messages) 34 messages, 3 authors, 3d ago

Re: [PATCH net v2 06/14] net: pse-pd: check the PI state under the lock when setting a power limit

flat view

From: netdev-bot+sashiko@kernel.org
Date: 2026-10-23 08:13:35
Also in: lkml

Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.

Critical: 0 · High: 1 (1) · Medium: 1 (1) · Low: 1

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] The new check in pse_pi_update_pw_budget() (drivers/net/pse-
  pd/pse_core.c:1849-1851) only tests `pse_pw_d_is_sw_pw_control() &&…
- [Medium] pse_ethtool_set_pw_limit() still races pse_isr() after the
  change.
- [Low] The new eligibility check in pse_pi_update_pw_budget() also
  applies to the rollback call, so the rollback can be skipped while the…

Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but there is an
  AB-BA deadlock between pcdev->lock and the supply regulator's ww_mutex.
- [Medium] This problem wasn't introduced by this patch, but the rollback
  call to pse_pi_update_pw_budget() in pse_ethtool_set_pw_limit() (lines…

--

net: pse-pd: check the PI state under the lock when setting a power limit

This patch moves the "PI is powered" check from pse_ethtool_set_pw_limit()
into pse_pi_update_pw_budget(), where it runs under pcdev->lock. The helper
now reports through a new updated argument whether the budget changed. The
rollback after a failed regulator_set_current_limit() uses that flag
instead of a non-zero previous allocation.
quoted hunk ↗ jump to hunk
diff --git a/drivers/net/pse-pd/pse_core.c b/drivers/net/pse-pd/pse_core.c
index af9eb29792706..36f7dba8190a2 100644
--- a/drivers/net/pse-pd/pse_core.c
+++ b/drivers/net/pse-pd/pse_core.c
@@ -1821,22 +1821,35 @@ EXPORT_SYMBOL_GPL(pse_ethtool_set_config);
[ ... ]
quoted hunk ↗ jump to hunk
 static int pse_pi_update_pw_budget(struct pse_controller_dev *pcdev, int id,
-				   const unsigned int pw_req,
+				   const unsigned int pw_req, bool *updated,
 				   struct netlink_ext_ack *extack)
 {
 	struct pse_pi *pi = &pcdev->pi[id];
 	int previous_pw_allocated;
 	int pw_diff, ret = 0;
 
+	*updated = false;
+
 	/* We don't want pw_allocated_mW value change in the middle of an
 	 * power budget update
 	 */
 	mutex_lock(&pcdev->lock);
+	/* Only a PI under software power control that is enabled and has a
+	 * PD detected gets a budget. Check this here, as pse_isr() changes
+	 * it under the same lock.
+	 */
+	if (!pse_pw_d_is_sw_pw_control(pcdev, pi->pw_d) ||
+	    !pi->admin_state_enabled || !pi->isr_pd_detected)
+		goto out;
[Severity: High]
Do admin_state_enabled and isr_pd_detected really mean the PI is powered?

Both flags can stay set while the port is off in hardware.
pse_set_config_isr() hides -ERANGE from the static budget allocation:

	pcdev->pi[id].isr_pd_detected = true;
	if (pcdev->pi[id].admin_state_enabled) {
		ret = _pse_pi_delivery_power_sw_pw_ctrl(pcdev, id,
							&extack);
		if (ret == -ERANGE)
			ret = 0;
	}

pse_pi_enable() also sets admin_state_enabled on -ERANGE. In both cases
the PI is left pending, with pw_allocated_mW == 0 and pi_enable() never
called.

pse_disable_pi_pol() is another case. It turns off a powered PI through
_pse_pi_disable() to shed budget, but it leaves both flags set.

The core already treats this as a separate state in
pse_pi_is_admin_enable_pending():

	if (!pcdev->pi[id].admin_state_enabled ||
	    !pcdev->pi[id].isr_pd_detected)
		return false;

	ret = pse_pi_is_hw_enabled(pcdev, id);
	/* PSE PI is already enabled at hardware level */
	if (ret == 1)
		return false;

A pending PI passes this check. regulator_request_power_budget() then
reserves pw_limit, and pi->pw_allocated_mW is set to pw_limit, while no
power is delivered.

pse_pw_d_retry_power_delivery() can power the port later. It is called
from _pse_pi_disable() of another PI and from pse_ethtool_set_prio(). The
path is _pse_pi_delivery_power_sw_pw_ctrl() ->
pse_pi_allocate_pw_budget_static_prio():

	while (regulator_request_power_budget(pi->pw_d->supply, pw_req) == -ERANGE) {
	...
	pi->pw_allocated_mW = pw_req;

This requests the full pw_req again and overwrites pw_allocated_mW. The
earlier ethtool reservation is never freed. pse_pi_deallocate_pw_budget()
only frees the overwritten value.

Does the pw_limit reserved through ethtool then stay charged to the supply
for good?

The commit message says the patch stops budget being reserved "for a port
that is no longer powered". That still seems possible, for example right
after pse_disable_pi_pol() turns a port off. The same flag test existed
before the patch, outside the lock.

Would checking pse_pi_is_hw_enabled() == 1 here, under the lock, close
this?

The comment kept in pse_ethtool_set_pw_limit() ("if a Power Device is
powered") also doesn't match what this check tests.

[Severity: Low]
The rollback call in pse_ethtool_set_pw_limit() also goes through this
check. Can the rollback be skipped while the increased reservation is still
held?

pse_ethtool_set_pw_limit()
  pse_pi_update_pw_budget()      raises P -> L, updated = true, unlock

pse_isr()                        DISCONNECTION
  pse_set_config_isr()
    _pse_pi_disable()
      ops->pi_disable() fails (e.g. an I2C error), returns before
      pse_pi_deallocate_pw_budget()
    pcdev->pi[id].isr_pd_detected = false;

pse_ethtool_set_pw_limit()
  regulator_set_current_limit()  fails
  pse_pi_update_pw_budget(previous_pw_allocated)
    !pi->isr_pd_detected -> goto out

pw_allocated_mW then stays at L. The pre-patch rollback would have restored
it to P. Failing this check doesn't always mean the PI's reservation has
been released.
quoted hunk ↗ jump to hunk
+
 	previous_pw_allocated = pi->pw_allocated_mW;
 	pw_diff = pw_req - previous_pw_allocated;
 	if (!pw_diff) {
@@ -1854,6 +1867,7 @@ static int pse_pi_update_pw_budget(struct pse_controller_dev *pcdev, int id,
 		regulator_free_power_budget(pi->pw_d->supply, -pw_diff);
 	}
[Severity: High]
This is a pre-existing issue and was not introduced by this patch. Is
there an AB-BA deadlock between pcdev->lock and the supply regulator's
lock?

Path A, ethtool set_config under rtnl:

pse_ethtool_c33_set_config()
  regulator_enable(psec->ps)
    regulator_lock_dependent()
      regulator_lock_recursive()    locks PI rdev, then its supply rdev
    _regulator_enable()->_regulator_do_enable()
      pse_pi_enable()
        mutex_lock(&pcdev->lock)

regulator_disable() -> pse_pi_disable() takes the locks in the same order.

Path B, the threaded IRQ:

pse_isr()
  mutex_lock(&pcdev->lock)
  pse_set_config_isr()
    _pse_pi_delivery_power_sw_pw_ctrl()
      pse_pi_allocate_pw_budget_static_prio()
        regulator_request_power_budget(pi->pw_d->supply)
          regulator_lock(supply rdev)

On DISCONNECTION, _pse_pi_disable() -> regulator_free_power_budget() takes
the same supply lock.

pw_d->supply comes from regulator_get(&rdev->dev, rdev->supply_name). It
is the same supply rdev that path A already holds.

If A holds the supply rdev and waits for pcdev->lock, while B holds
pcdev->lock and waits for the supply rdev, both tasks hang for good. A
also holds rtnl_lock.

pse_pi_update_pw_budget(), including its new rollback use, keeps the same
pcdev->lock -> supply order.
quoted hunk ↗ jump to hunk
 	pi->pw_allocated_mW = pw_req;
+	*updated = true;
 	ret = previous_pw_allocated;
 
 out:
[ ... ]
quoted hunk ↗ jump to hunk
@@ -1900,21 +1915,17 @@ int pse_ethtool_set_pw_limit(struct pse_control *psec,
 	/* Update power budget only in software power control case and
 	 * if a Power Device is powered.
 	 */
-	if (pse_pw_d_is_sw_pw_control(psec->pcdev,
-				      psec->pcdev->pi[psec->id].pw_d) &&
-	    psec->pcdev->pi[psec->id].admin_state_enabled &&
-	    psec->pcdev->pi[psec->id].isr_pd_detected) {
-		ret = pse_pi_update_pw_budget(psec->pcdev, psec->id,
-					      pw_limit, extack);
-		if (ret < 0)
-			return ret;
-		previous_pw_allocated = ret;
-	}
+	ret = pse_pi_update_pw_budget(psec->pcdev, psec->id, pw_limit,
+				      &updated, extack);
+	if (ret < 0)
+		return ret;
+	previous_pw_allocated = ret;
 
 	ret = regulator_set_current_limit(psec->ps, 0, uA);
-	if (ret < 0 && previous_pw_allocated) {
+	if (ret < 0 && updated) {
 		pse_pi_update_pw_budget(psec->pcdev, psec->id,
-					previous_pw_allocated, extack);
+					previous_pw_allocated, &updated,
+					extack);
 	}
[Severity: Medium]
pcdev->lock is still dropped between three steps: the budget update,
regulator_set_current_limit() (which takes the lock again in
pse_pi_set_current_limit()), and the rollback.

Can updated and previous_pw_allocated be stale by the time the rollback
runs? The rollback also applies an absolute value to whatever PD session
exists at that point.

Two interleavings with pse_isr() look possible.

(a) After the budget update, pse_isr() handles DISCONNECTION, which frees
the budget, and then CLASSIFICATION. _pse_pi_delivery_power_sw_pw_ctrl()
budgets the new PD as min(pi_get_pw_req, pi_get_pw_limit), using the old
hardware limit.

If regulator_set_current_limit() then fails, the rollback forces the new
session's allocation back to the old session's value.

The patch adds a rollback-from-zero case (previous_pw_allocated == 0 and
updated == true, e.g. for a pending PI). In that case the rollback can
free the new PD's whole budget while the PD is powered, so the supply is
oversubscribed.

(b) A PD is classified between the budget update and the hardware limit
write. It is budgeted from the old limit, and then the new, higher limit
is programmed. The hardware then allows more power than is reserved.

These windows existed before the patch. The rollback driven by updated is
new.

The commit message describes the pse_isr() race. Would it make sense to do
the budget update and the hardware limit write in one pcdev->lock section,
so that race is fully closed?

[Severity: Medium]
This isn't a bug introduced by this patch, but the return value of the
rollback pse_pi_update_pw_budget() call is ignored. What happens if the
rollback's re-request fails?

When the limit is lowered from P to L, the first call frees P - L mW and
drops pcdev->lock. A concurrent pse_isr() on another PI in the same domain
can take that freed budget.

regulator_set_current_limit() can then fail.
pse_pi_set_current_limit() returns -ERANGE for max_uA > MAX_PI_CURRENT or a
null voltage, and ops->pi_set_pw_limit() can also fail. The rollback's
regulator_request_power_budget(P - L) then returns -ERANGE.

pw_allocated_mW stays at L, but the hardware keeps the old limit P.

The rollback also overwrites extack with "not enough power budget
available". That hides the real regulator_set_current_limit() error.
 
 	return ret;
-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261020171249.853085-1-github%40szelinsky.de
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help