Skip to content

Commit 62d4490

Browse files
committed
PM / Domains: Allow device callbacks to be added at any time
Make it possible to modify device callbacks used by the generic PM domains core code at any time, not only after the device has been added to a domain. This will allow device drivers to provide their own device PM domain callbacks even if they are registered before adding the devices to PM domains. For this purpose, use the observation that the struct generic_pm_domain_data object containing the relevant callback pointers may be allocated by pm_genpd_add_callbacks() and the callbacks may be set before __pm_genpd_add_device() is run for the given device. This object will then be used by __pm_genpd_add_device(), but it has to be protected from premature removal by reference counting. Signed-off-by: Rafael J. Wysocki <[email protected]>
1 parent 1d5fcfe commit 62d4490

File tree

1 file changed

+50
-14
lines changed

1 file changed

+50
-14
lines changed

drivers/base/power/domain.c

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,33 +1603,52 @@ int pm_genpd_remove_subdomain(struct generic_pm_domain *genpd,
16031603
* @dev: Device to add the callbacks to.
16041604
* @ops: Set of callbacks to add.
16051605
* @td: Timing data to add to the device along with the callbacks (optional).
1606+
*
1607+
* Every call to this routine should be balanced with a call to
1608+
* __pm_genpd_remove_callbacks() and they must not be nested.
16061609
*/
16071610
int pm_genpd_add_callbacks(struct device *dev, struct gpd_dev_ops *ops,
16081611
struct gpd_timing_data *td)
16091612
{
1610-
struct pm_domain_data *pdd;
1613+
struct generic_pm_domain_data *gpd_data_new, *gpd_data = NULL;
16111614
int ret = 0;
16121615

1613-
if (!(dev && dev->power.subsys_data && ops))
1616+
if (!(dev && ops))
16141617
return -EINVAL;
16151618

1619+
gpd_data_new = __pm_genpd_alloc_dev_data(dev);
1620+
if (!gpd_data_new)
1621+
return -ENOMEM;
1622+
16161623
pm_runtime_disable(dev);
16171624
device_pm_lock();
16181625

1619-
pdd = dev->power.subsys_data->domain_data;
1620-
if (pdd) {
1621-
struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd);
1626+
ret = dev_pm_get_subsys_data(dev);
1627+
if (ret)
1628+
goto out;
1629+
1630+
spin_lock_irq(&dev->power.lock);
16221631

1623-
gpd_data->ops = *ops;
1624-
if (td)
1625-
gpd_data->td = *td;
1632+
if (dev->power.subsys_data->domain_data) {
1633+
gpd_data = to_gpd_data(dev->power.subsys_data->domain_data);
16261634
} else {
1627-
ret = -EINVAL;
1635+
gpd_data = gpd_data_new;
1636+
dev->power.subsys_data->domain_data = &gpd_data->base;
16281637
}
1638+
gpd_data->refcount++;
1639+
gpd_data->ops = *ops;
1640+
if (td)
1641+
gpd_data->td = *td;
16291642

1643+
spin_unlock_irq(&dev->power.lock);
1644+
1645+
out:
16301646
device_pm_unlock();
16311647
pm_runtime_enable(dev);
16321648

1649+
if (gpd_data != gpd_data_new)
1650+
__pm_genpd_free_dev_data(dev, gpd_data_new);
1651+
16331652
return ret;
16341653
}
16351654
EXPORT_SYMBOL_GPL(pm_genpd_add_callbacks);
@@ -1638,10 +1657,13 @@ EXPORT_SYMBOL_GPL(pm_genpd_add_callbacks);
16381657
* __pm_genpd_remove_callbacks - Remove PM domain callbacks from a given device.
16391658
* @dev: Device to remove the callbacks from.
16401659
* @clear_td: If set, clear the device's timing data too.
1660+
*
1661+
* This routine can only be called after pm_genpd_add_callbacks().
16411662
*/
16421663
int __pm_genpd_remove_callbacks(struct device *dev, bool clear_td)
16431664
{
1644-
struct pm_domain_data *pdd;
1665+
struct generic_pm_domain_data *gpd_data = NULL;
1666+
bool remove = false;
16451667
int ret = 0;
16461668

16471669
if (!(dev && dev->power.subsys_data))
@@ -1650,21 +1672,35 @@ int __pm_genpd_remove_callbacks(struct device *dev, bool clear_td)
16501672
pm_runtime_disable(dev);
16511673
device_pm_lock();
16521674

1653-
pdd = dev->power.subsys_data->domain_data;
1654-
if (pdd) {
1655-
struct generic_pm_domain_data *gpd_data = to_gpd_data(pdd);
1675+
spin_lock_irq(&dev->power.lock);
16561676

1677+
if (dev->power.subsys_data->domain_data) {
1678+
gpd_data = to_gpd_data(dev->power.subsys_data->domain_data);
16571679
gpd_data->ops = (struct gpd_dev_ops){ 0 };
16581680
if (clear_td)
16591681
gpd_data->td = (struct gpd_timing_data){ 0 };
1682+
1683+
if (--gpd_data->refcount == 0) {
1684+
dev->power.subsys_data->domain_data = NULL;
1685+
remove = true;
1686+
}
16601687
} else {
16611688
ret = -EINVAL;
16621689
}
16631690

1691+
spin_unlock_irq(&dev->power.lock);
1692+
16641693
device_pm_unlock();
16651694
pm_runtime_enable(dev);
16661695

1667-
return ret;
1696+
if (ret)
1697+
return ret;
1698+
1699+
dev_pm_put_subsys_data(dev);
1700+
if (remove)
1701+
__pm_genpd_free_dev_data(dev, gpd_data);
1702+
1703+
return 0;
16681704
}
16691705
EXPORT_SYMBOL_GPL(__pm_genpd_remove_callbacks);
16701706

0 commit comments

Comments
 (0)