Hi Samuel On 16/04/25 06:44, Samuel Holland wrote: > Hi Miquel, > > On 2025-04-03 2:39 AM, Miquel Raynal wrote: >> It is very surprising that such an uclass, specifically designed to >> handle resources that may be shared by different devices, is not keeping >> the count of the number of times a power domain has been >> enabled/disabled to avoid shutting it down unexpectedly or disabling it >> several times. >> >> Doing this causes troubles on eg. i.MX8MP because disabling power >> domains can be done in recursive loops were the same power domain >> disabled up to 4 times in a row. PGCs seem to have tight FSM internal >> timings to respect and it is easy to produce a race condition that puts >> the power domains in an unstable state, leading to ADB400 errors and >> later crashes in Linux. >> >> CI tests using power domains are slightly updated to make sure the count >> of on/off calls is even and the results match what we *now* expect. >> >> As we do not want to break existing users while stile getting >> interesting error codes, the implementation is split between: >> - a low-level helper reporting error codes if the requested transition >> could not be operated, >> - a higher-level helper ignoring the "non error" codes, like EALREADY and >> EBUSY. >> >> Signed-off-by: Miquel Raynal <miquel.ray...@bootlin.com> >> --- >> drivers/firmware/scmi/sandbox-scmi_devices.c | 1 + >> drivers/power/domain/power-domain-uclass.c | 40 ++++++++++++++-- >> drivers/power/domain/sandbox-power-domain-test.c | 1 + >> include/power-domain.h | 60 >> ++++++++++++++++++++---- >> test/dm/power-domain.c | 2 +- >> 5 files changed, 91 insertions(+), 13 deletions(-) >> >> diff --git a/drivers/firmware/scmi/sandbox-scmi_devices.c >> b/drivers/firmware/scmi/sandbox-scmi_devices.c >> index >> 96c2922b067e2886b3fa963bcd7e396f4569a569..9f253b0fd40f703a5ec11d34c197423d27ad8b01 >> 100644 >> --- a/drivers/firmware/scmi/sandbox-scmi_devices.c >> +++ b/drivers/firmware/scmi/sandbox-scmi_devices.c >> @@ -163,4 +163,5 @@ U_BOOT_DRIVER(sandbox_scmi_devices) = { >> .priv_auto = sizeof(struct sandbox_scmi_device_priv), >> .remove = sandbox_scmi_devices_remove, >> .probe = sandbox_scmi_devices_probe, >> + .flags = DM_FLAG_DEFAULT_PD_CTRL_OFF, >> }; >> diff --git a/drivers/power/domain/power-domain-uclass.c >> b/drivers/power/domain/power-domain-uclass.c >> index >> 938bd8cbc9ffd1ba2109d702f886b6a99288d063..a6e5f9ed0369eb9d2dfa66edc9e938bac6720dab >> 100644 >> --- a/drivers/power/domain/power-domain-uclass.c >> +++ b/drivers/power/domain/power-domain-uclass.c >> @@ -12,6 +12,10 @@ >> #include <power-domain-uclass.h> >> #include <dm/device-internal.h> >> >> +struct power_domain_priv { >> + int on_count; >> +}; >> + >> static inline struct power_domain_ops *power_domain_dev_ops(struct udevice >> *dev) >> { >> return (struct power_domain_ops *)dev->driver->ops; >> @@ -107,22 +111,49 @@ int power_domain_free(struct power_domain >> *power_domain) >> return ops->rfree ? ops->rfree(power_domain) : 0; >> } >> >> -int power_domain_on(struct power_domain *power_domain) >> +int power_domain_on_lowlevel(struct power_domain *power_domain) >> { >> + struct power_domain_priv *priv = dev_get_uclass_priv(power_domain->dev); >> struct power_domain_ops *ops = power_domain_dev_ops(power_domain->dev); >> + int ret; >> >> debug("%s(power_domain=%p)\n", __func__, power_domain); >> >> - return ops->on ? ops->on(power_domain) : 0; >> + if (priv->on_count++ > 0) >> + return -EALREADY; > > This change is broken for power domain providers with #power-domain-cells = > <1>, > which can have multiple domains per provider device. There would need to be a > separate reference count per domain, and currently the uclass doesn't know the > range of valid domain IDs.
I didn't see this reply earlier, would've saved some time debugging to come to the same conclusion :) but yes this is the reason for breaking. > > Regards, > Samuel > >> + >> + ret = ops->on ? ops->on(power_domain) : 0; >> + if (ret) { >> + priv->on_count--; >> + return ret; >> + } >> + >> + return 0; >> } >> >> -int power_domain_off(struct power_domain *power_domain) >> +int power_domain_off_lowlevel(struct power_domain *power_domain) >> { >> + struct power_domain_priv *priv = dev_get_uclass_priv(power_domain->dev); >> struct power_domain_ops *ops = power_domain_dev_ops(power_domain->dev); >> + int ret; >> >> debug("%s(power_domain=%p)\n", __func__, power_domain); >> >> - return ops->off ? ops->off(power_domain) : 0; >> + if (priv->on_count <= 0) { >> + debug("Power domain %s already off.\n", >> power_domain->dev->name); >> + return -EALREADY; >> + } >> + >> + if (priv->on_count-- > 1) >> + return -EBUSY; >> + >> + ret = ops->off ? ops->off(power_domain) : 0; >> + if (ret) { >> + priv->on_count++; >> + return ret; >> + } >> + >> + return 0; >> } >> >> #if CONFIG_IS_ENABLED(OF_REAL) >> @@ -180,4 +211,5 @@ int dev_power_domain_off(struct udevice *dev) >> UCLASS_DRIVER(power_domain) = { >> .id = UCLASS_POWER_DOMAIN, >> .name = "power_domain", >> + .per_device_auto = sizeof(struct power_domain_priv), >> }; >> diff --git a/drivers/power/domain/sandbox-power-domain-test.c >> b/drivers/power/domain/sandbox-power-domain-test.c >> index >> 08c15ef342b3dd3ce01807ee59b7e97337f7dde5..5b530974e942ffcba453e53be330baaf3a113a13 >> 100644 >> --- a/drivers/power/domain/sandbox-power-domain-test.c >> +++ b/drivers/power/domain/sandbox-power-domain-test.c >> @@ -51,4 +51,5 @@ U_BOOT_DRIVER(sandbox_power_domain_test) = { >> .id = UCLASS_MISC, >> .of_match = sandbox_power_domain_test_ids, >> .priv_auto = sizeof(struct sandbox_power_domain_test), >> + .flags = DM_FLAG_DEFAULT_PD_CTRL_OFF, >> }; >> diff --git a/include/power-domain.h b/include/power-domain.h >> index >> 18525073e5e3534fcbac6fae4e18462f29a4dc49..ad33dea76ce5808beaa4fbf4388438a504e36027 >> 100644 >> --- a/include/power-domain.h >> +++ b/include/power-domain.h >> @@ -147,37 +147,81 @@ static inline int power_domain_free(struct >> power_domain *power_domain) >> #endif >> >> /** >> - * power_domain_on - Enable power to a power domain. >> + * power_domain_on_lowlevel - Enable power to a power domain (with >> refcounting) >> * >> * @power_domain: A power domain struct that was previously successfully >> * requested by power_domain_get(). >> - * Return: 0 if OK, or a negative error code. >> + * Return: 0 if the transition has been performed correctly, >> + * -EALREADY if the domain is already on, >> + * a negative error code otherwise. >> */ >> #if CONFIG_IS_ENABLED(POWER_DOMAIN) >> -int power_domain_on(struct power_domain *power_domain); >> +int power_domain_on_lowlevel(struct power_domain *power_domain); >> #else >> -static inline int power_domain_on(struct power_domain *power_domain) >> +static inline int power_domain_on_lowlevel(struct power_domain >> *power_domain) >> { >> return -ENOSYS; >> } >> #endif >> >> /** >> - * power_domain_off - Disable power to a power domain. >> + * power_domain_on - Enable power to a power domain (ignores the actual >> state >> + * of the power domain) >> * >> * @power_domain: A power domain struct that was previously successfully >> * requested by power_domain_get(). >> - * Return: 0 if OK, or a negative error code. >> + * Return: a negative error code upon error during the transition, 0 >> otherwise. >> + */ >> +static inline int power_domain_on(struct power_domain *power_domain) >> +{ >> + int ret; >> + >> + ret = power_domain_on_lowlevel(power_domain); >> + if (ret == -EALREADY) >> + ret = 0; >> + >> + return ret; >> +} >> + >> +/** >> + * power_domain_off_lowlevel - Disable power to a power domain (with >> refcounting) >> + * >> + * @power_domain: A power domain struct that was previously successfully >> + * requested by power_domain_get(). >> + * Return: 0 if the transition has been performed correctly, >> + * -EALREADY if the domain is already off, >> + * -EBUSY if another device is keeping the domain on (but the >> refcounter >> + * is decremented), >> + * a negative error code otherwise. >> */ >> #if CONFIG_IS_ENABLED(POWER_DOMAIN) >> -int power_domain_off(struct power_domain *power_domain); >> +int power_domain_off_lowlevel(struct power_domain *power_domain); >> #else >> -static inline int power_domain_off(struct power_domain *power_domain) >> +static inline int power_domain_off_lowlevel(struct power_domain >> *power_domain) >> { >> return -ENOSYS; >> } >> #endif >> >> +/** >> + * power_domain_off - Disable power to a power domain (ignores the actual >> state >> + * of the power domain) >> + * >> + * @power_domain: A power domain struct that was previously successfully >> + * requested by power_domain_get(). >> + * Return: a negative error code upon error during the transition, 0 >> otherwise. >> + */ >> +static inline int power_domain_off(struct power_domain *power_domain) >> +{ >> + int ret; >> + >> + ret = power_domain_off_lowlevel(power_domain); >> + if (ret == -EALREADY || ret == -EBUSY) >> + ret = 0; >> + >> + return ret; >> +} >> + >> /** >> * dev_power_domain_on - Enable power domains for a device . >> * >> diff --git a/test/dm/power-domain.c b/test/dm/power-domain.c >> index >> 896cf5b2ae9d26701150fad70e888f8b135a22b0..8a95f6bdb903be9d1993528d87d5cae0075a83e4 >> 100644 >> --- a/test/dm/power-domain.c >> +++ b/test/dm/power-domain.c >> @@ -27,7 +27,7 @@ static int dm_test_power_domain(struct unit_test_state >> *uts) >> >> ut_assertok(uclass_get_device_by_name(UCLASS_MISC, "power-domain-test", >> &dev_test)); >> - ut_asserteq(1, sandbox_power_domain_query(dev_power_domain, >> + ut_asserteq(0, sandbox_power_domain_query(dev_power_domain, >> TEST_POWER_DOMAIN)); >> ut_assertok(sandbox_power_domain_test_get(dev_test)); >> >> > -- Thanking You Neha Malcom Francis