2017-08-30 01:35:03

by Shawn Lin

[permalink] [raw]
Subject: [PATCH v3 0/2] Avoid system abort by move pm domain's detach after devres_release_all


CONFIG_DEBUG_SHIRQ will fire extra irq action to call the registered
irq callback after driver is removed or failed to probe. In general,
the irq callback provided by driver should read its internal registers
to see who fires the irq. So this leads a situation that we access the
registers with a powered-off pm domain that the system abort. This is
a system-wide issue may break lots of drivers, IMHO.

dwmmc driver is one of them suffered from this, please see the commit
message of patch 2 for how that happened.

I haven't find a proper way to freeze the genpd_power_off_work_fn
and fire it again when finish devres_release_all indeed. And it
seems to me that device's pm domain detach is always called when
failing to probe or removing the driver. So I have to cook patch 1
to see if folks think this's the best way to fix that, otherwise we may
need to fix it everywhere for other drivers.


Changes in v3:
- fix the code path for consolidating the attach for both of driver
and bus driver, and then move detach to the error path
- rework the changelog
- include a driver core change to fix the genpd issue.

Shawn Lin (2):
driver core: detach device's pm_domain after devres_release_all
mmc: dw_mmc: fix potential system abort if activating
CONFIG_DEBUG_SHIRQ

drivers/base/dd.c | 16 ++++++++++++++++
drivers/base/platform.c | 18 ++----------------
drivers/mmc/host/dw_mmc.c | 48 +++++++++++++++++++++++------------------------
3 files changed, 41 insertions(+), 41 deletions(-)

--
1.9.1



2017-08-30 01:35:11

by Shawn Lin

[permalink] [raw]
Subject: [PATCH v3 1/2] driver core: detach device's pm_domain after devres_release_all

The intention of this patch is to move dev_pm_domain_detach after
devres_release_all to avoid possible accessing device's registers
with genpd been powered off.

Many common IP drivers use devm_request_irq to request irq for either
shared irq or non-shared irq. So we rely on devres_release_all to
free irq automatically. However we could see a situation that if the
driver use devm_request_irq to register a shared irq and unbind the
driver later, the irq could be triggerd cocurrently just between
finishing dev_pm_domain_detach and calling devm_irq_release, so that
driver's ISR should be called and try to access device's register, which
may hang up the system. The reason is that some SoCs, including Rockchips'
SoCs, couldn't support accessing controllers' registers w/o clk and power
domain enabled.

Also we have CONFIG_DEBUG_SHIRQ to theoretically expose this kind of
problem as devm_free_irq -> __free_irq says: "It's a shared IRQ -- the
driver ought to be prepared for an IRQ event to happen even now it's being
freed". That will simulate the aforementioned situation as it fires
extra irq action to make sure driver/system is robust enough to deal
with this kind of problem.

So now we face a two possible choices to fix this by
(1) either using request_irq and free_irq directly
(2) or moving dev_pm_domain_detach after devres_release_all which
makes sure we free the irq before powering off power domain.

However, choice (1) implies that devm_request_irq shouldn't exist
or at least shouldn't be used for shared irq case. Meanwhile we don't
know how many drivers have this kind of issue and need to fix. So
choice (2) makes more sense to me, and that is the reason for why we
need to fix it like what this patch does.

Signed-off-by: Shawn Lin <[email protected]>

---

Changes in v3:
- fix the code path for consolidating the attach for both of driver
and bus driver, and then move detach to the error path
- rework the changelog

drivers/base/dd.c | 16 ++++++++++++++++
drivers/base/platform.c | 18 ++----------------
2 files changed, 18 insertions(+), 16 deletions(-)

diff --git a/drivers/base/dd.c b/drivers/base/dd.c
index ad44b40..aea0bb1 100644
--- a/drivers/base/dd.c
+++ b/drivers/base/dd.c
@@ -25,7 +25,9 @@
#include <linux/kthread.h>
#include <linux/wait.h>
#include <linux/async.h>
+#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
+#include <linux/pm_domain.h>
#include <linux/pinctrl/devinfo.h>

#include "base.h"
@@ -356,6 +358,7 @@ static int really_probe(struct device *dev, struct device_driver *drv)
int local_trigger_count = atomic_read(&deferred_trigger_count);
bool test_remove = IS_ENABLED(CONFIG_DEBUG_TEST_DRIVER_REMOVE) &&
!drv->suppress_bind_attrs;
+ struct platform_driver *pdrv;

if (defer_all_probes) {
/*
@@ -409,6 +412,16 @@ static int really_probe(struct device *dev, struct device_driver *drv)
*/
devices_kset_move_last(dev);

+ ret = dev_pm_domain_attach(dev, true);
+ pdrv = to_platform_driver(dev->driver);
+ /* don't fail if just dev_pm_domain_attach failed */
+ if (pdrv && pdrv->prevent_deferred_probe &&
+ ret == -EPROBE_DEFER) {
+ dev_warn(dev, "probe deferral not supported\n");
+ ret = -ENXIO;
+ goto probe_failed;
+ }
+
if (dev->bus->probe) {
ret = dev->bus->probe(dev);
if (ret)
@@ -428,6 +441,7 @@ static int really_probe(struct device *dev, struct device_driver *drv)
drv->remove(dev);

devres_release_all(dev);
+ dev_pm_domain_detach(dev, true);
driver_sysfs_remove(dev);
dev->driver = NULL;
dev_set_drvdata(dev, NULL);
@@ -458,6 +472,7 @@ static int really_probe(struct device *dev, struct device_driver *drv)
pinctrl_bind_failed:
device_links_no_driver(dev);
devres_release_all(dev);
+ dev_pm_domain_detach(dev, true);
driver_sysfs_remove(dev);
dev->driver = NULL;
dev_set_drvdata(dev, NULL);
@@ -864,6 +879,7 @@ static void __device_release_driver(struct device *dev, struct device *parent)
dma_deconfigure(dev);

devres_release_all(dev);
+ dev_pm_domain_detach(dev, true);
dev->driver = NULL;
dev_set_drvdata(dev, NULL);
if (dev->pm_domain && dev->pm_domain->dismiss)
diff --git a/drivers/base/platform.c b/drivers/base/platform.c
index d1bd992..8fa688d 100644
--- a/drivers/base/platform.c
+++ b/drivers/base/platform.c
@@ -572,22 +572,8 @@ static int platform_drv_probe(struct device *_dev)
if (ret < 0)
return ret;

- ret = dev_pm_domain_attach(_dev, true);
- if (ret != -EPROBE_DEFER) {
- if (drv->probe) {
- ret = drv->probe(dev);
- if (ret)
- dev_pm_domain_detach(_dev, true);
- } else {
- /* don't fail if just dev_pm_domain_attach failed */
- ret = 0;
- }
- }
-
- if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {
- dev_warn(_dev, "probe deferral not supported\n");
- ret = -ENXIO;
- }
+ if (drv->probe)
+ ret = drv->probe(dev);

return ret;
}
--
1.9.1


2017-08-30 01:35:25

by Shawn Lin

[permalink] [raw]
Subject: [PATCH v3 2/2] mmc: dw_mmc: fix potential system abort if activating CONFIG_DEBUG_SHIRQ

With CONFIG_DEBUG_SHIRQ enabled, the irq tear down routine
would still access the irq handler registed as a shard irq.
Per the comment within the function of __free_irq, it says
"It's a shared IRQ -- the driver ought to be prepared for
an IRQ event to happen even now it's being freed". However
when failing to probe the driver, dw_mmc disables the clock
and asserts the reset pin, even power off its genpd for accessing
the registers and the following check for shared irq state would call
the irq handler which accesses the register w/o all necessary resource
prepared. That will hang the system forever.

With adding some dump_stack we could see how that happened.

Synopsys Designware Multimedia Card Interface Driver
dwmmc_rockchip fe320000.dwmmc: IDMAC supports 32-bit address mode.
dwmmc_rockchip fe320000.dwmmc: Using internal DMA controller.
dwmmc_rockchip fe320000.dwmmc: Version ID is 270a
CPU: 0 PID: 1 Comm: swapper/0 Not tainted
4.13.0-rc3-next-20170807-00004-g93d3644-dirty #5
Hardware name: Firefly-RK3399 Board (DT)
Call trace:
[<ffff20000808b5a0>] dump_backtrace+0x0/0x300
[<ffff20000808ba1c>] show_stack+0x14/0x20
[<ffff200008dc480c>] dump_stack+0xa4/0xc8
[<ffff200008b9691c>] dw_mci_interrupt+0x3c/0x6a8
[<ffff200008157450>] __free_irq+0x308/0x410
[<ffff20000815760c>] free_irq+0x54/0xb0
[<ffff20000815d630>] devm_irq_release+0x30/0x40
[<ffff2000087f0174>] release_nodes+0x1e4/0x390
[<ffff2000087f04c0>] devres_release_all+0x50/0x78
[<ffff2000087e9bc0>] driver_probe_device+0x128/0x3b8
[<ffff2000087e9f34>] __driver_attach+0xe4/0xe8
[<ffff2000087e7048>] bus_for_each_dev+0xe0/0x138
[<ffff2000087e93b8>] driver_attach+0x30/0x40
[<ffff2000087e8c00>] bus_add_driver+0x1d0/0x328
[<ffff2000087ead0c>] driver_register+0xb4/0x198
[<ffff2000087ec98c>] __platform_driver_register+0x7c/0x88
[<ffff2000095bc564>] dw_mci_rockchip_pltfm_driver_init+0x18/0x20
[<ffff200008083a8c>] do_one_initcall+0x14c/0x1b8
[<ffff200009560ff8>] kernel_init_freeable+0x238/0x2d8
[<ffff200008dde500>] kernel_init+0x10/0x110
[<ffff2000080836c0>] ret_from_fork+0x10/0x50
Synchronous External Abort: synchronous external abort (0x96000010) at
0xffff20000aaa4040
Internal error: : 96000010 [#1] PREEMPT SMP
Modules linked in:
CPU: 0 PID: 1 Comm: swapper/0 Not tainted
task: ffff80006ba28080 task.stack: ffff80006ba24000
PC is at dw_mci_interrupt+0x4c/0x6a8
LR is at dw_mci_interrupt+0x44/0x6a8
pc : [<ffff200008b9692c>] lr : [<ffff200008b96924>] pstate: 600001c5

...

Kernel panic - not syncing: Attempted to kill init! exitcode=0x0000000b

SMP: stopping secondary CPUs
Kernel Offset: disabled
CPU features: 0x00200c
Memory Limit: none
---[ end Kernel panic - not syncing: Attempted to kill init!
exitcode=0x0000000b

In order to fix this, we remove all the clock-disabling from
the error handle path and driver's remove function. And replying
on the devm_add_action_or_reset to fire the clock-disabling and reset
signal at the appropriate time.

Signed-off-by: Shawn Lin <[email protected]>

---

Changes in v3:
- include a driver core change to fix the genpd issue.

drivers/mmc/host/dw_mmc.c | 48 +++++++++++++++++++++++------------------------
1 file changed, 23 insertions(+), 25 deletions(-)

diff --git a/drivers/mmc/host/dw_mmc.c b/drivers/mmc/host/dw_mmc.c
index 860313b..610613a 100644
--- a/drivers/mmc/host/dw_mmc.c
+++ b/drivers/mmc/host/dw_mmc.c
@@ -3057,6 +3057,18 @@ static void dw_mci_enable_cd(struct dw_mci *host)
}
}

+static void dw_mci_post_cleanup(void *data)
+{
+ struct dw_mci *host = data;
+
+ clk_disable_unprepare(host->ciu_clk);
+ clk_disable_unprepare(host->biu_clk);
+
+ if (!IS_ERR(host->pdata->rstc))
+ reset_control_assert(host->pdata->rstc);
+
+}
+
int dw_mci_probe(struct dw_mci *host)
{
const struct dw_mci_drv_data *drv_data = host->drv_data;
@@ -3092,7 +3104,7 @@ int dw_mci_probe(struct dw_mci *host)
ret = clk_prepare_enable(host->ciu_clk);
if (ret) {
dev_err(host->dev, "failed to enable ciu clock\n");
- goto err_clk_biu;
+ return ret;
}

if (host->pdata->bus_hz) {
@@ -3105,11 +3117,16 @@ int dw_mci_probe(struct dw_mci *host)
host->bus_hz = clk_get_rate(host->ciu_clk);
}

+ ret = devm_add_action_or_reset(host->dev, dw_mci_post_cleanup, host);
+ if (ret) {
+ dev_err(host->dev, "unable to add action or reset\n");
+ return ret;
+ }
+
if (!host->bus_hz) {
dev_err(host->dev,
"Platform data must supply bus speed\n");
- ret = -ENODEV;
- goto err_clk_ciu;
+ return -ENODEV;
}

if (!IS_ERR(host->pdata->rstc)) {
@@ -3123,7 +3140,7 @@ int dw_mci_probe(struct dw_mci *host)
if (ret) {
dev_err(host->dev,
"implementation specific init failed\n");
- goto err_clk_ciu;
+ return ret;
}
}

@@ -3167,10 +3184,8 @@ int dw_mci_probe(struct dw_mci *host)
}

/* Reset all blocks */
- if (!dw_mci_ctrl_reset(host, SDMMC_CTRL_ALL_RESET_FLAGS)) {
- ret = -ENODEV;
- goto err_clk_ciu;
- }
+ if (!dw_mci_ctrl_reset(host, SDMMC_CTRL_ALL_RESET_FLAGS))
+ return -ENODEV;

host->dma_ops = host->pdata->dma_ops;
dw_mci_init_dma(host);
@@ -3257,15 +3272,6 @@ int dw_mci_probe(struct dw_mci *host)
if (host->use_dma && host->dma_ops->exit)
host->dma_ops->exit(host);

- if (!IS_ERR(host->pdata->rstc))
- reset_control_assert(host->pdata->rstc);
-
-err_clk_ciu:
- clk_disable_unprepare(host->ciu_clk);
-
-err_clk_biu:
- clk_disable_unprepare(host->biu_clk);
-
return ret;
}
EXPORT_SYMBOL(dw_mci_probe);
@@ -3285,17 +3291,9 @@ void dw_mci_remove(struct dw_mci *host)

if (host->use_dma && host->dma_ops->exit)
host->dma_ops->exit(host);
-
- if (!IS_ERR(host->pdata->rstc))
- reset_control_assert(host->pdata->rstc);
-
- clk_disable_unprepare(host->ciu_clk);
- clk_disable_unprepare(host->biu_clk);
}
EXPORT_SYMBOL(dw_mci_remove);

-
-
#ifdef CONFIG_PM
int dw_mci_runtime_suspend(struct device *dev)
{
--
1.9.1


2017-08-30 12:12:59

by Rafael J. Wysocki

[permalink] [raw]
Subject: Re: [PATCH v3 1/2] driver core: detach device's pm_domain after devres_release_all

On Wednesday, August 30, 2017 3:34:26 AM CEST Shawn Lin wrote:
> The intention of this patch is to move dev_pm_domain_detach after
> devres_release_all to avoid possible accessing device's registers
> with genpd been powered off.
>
> Many common IP drivers use devm_request_irq to request irq for either
> shared irq or non-shared irq. So we rely on devres_release_all to
> free irq automatically. However we could see a situation that if the
> driver use devm_request_irq to register a shared irq and unbind the
> driver later, the irq could be triggerd cocurrently just between
> finishing dev_pm_domain_detach and calling devm_irq_release, so that
> driver's ISR should be called and try to access device's register, which
> may hang up the system. The reason is that some SoCs, including Rockchips'
> SoCs, couldn't support accessing controllers' registers w/o clk and power
> domain enabled.
>
> Also we have CONFIG_DEBUG_SHIRQ to theoretically expose this kind of
> problem as devm_free_irq -> __free_irq says: "It's a shared IRQ -- the
> driver ought to be prepared for an IRQ event to happen even now it's being
> freed". That will simulate the aforementioned situation as it fires
> extra irq action to make sure driver/system is robust enough to deal
> with this kind of problem.
>
> So now we face a two possible choices to fix this by
> (1) either using request_irq and free_irq directly
> (2) or moving dev_pm_domain_detach after devres_release_all which
> makes sure we free the irq before powering off power domain.
>
> However, choice (1) implies that devm_request_irq shouldn't exist
> or at least shouldn't be used for shared irq case. Meanwhile we don't
> know how many drivers have this kind of issue and need to fix. So
> choice (2) makes more sense to me, and that is the reason for why we
> need to fix it like what this patch does.
>
> Signed-off-by: Shawn Lin <[email protected]>
>
> ---
>
> Changes in v3:
> - fix the code path for consolidating the attach for both of driver
> and bus driver, and then move detach to the error path
> - rework the changelog
>
> drivers/base/dd.c | 16 ++++++++++++++++
> drivers/base/platform.c | 18 ++----------------
> 2 files changed, 18 insertions(+), 16 deletions(-)
>
> diff --git a/drivers/base/dd.c b/drivers/base/dd.c
> index ad44b40..aea0bb1 100644
> --- a/drivers/base/dd.c
> +++ b/drivers/base/dd.c
> @@ -25,7 +25,9 @@
> #include <linux/kthread.h>
> #include <linux/wait.h>
> #include <linux/async.h>
> +#include <linux/platform_device.h>
> #include <linux/pm_runtime.h>
> +#include <linux/pm_domain.h>
> #include <linux/pinctrl/devinfo.h>
>
> #include "base.h"
> @@ -356,6 +358,7 @@ static int really_probe(struct device *dev, struct device_driver *drv)
> int local_trigger_count = atomic_read(&deferred_trigger_count);
> bool test_remove = IS_ENABLED(CONFIG_DEBUG_TEST_DRIVER_REMOVE) &&
> !drv->suppress_bind_attrs;
> + struct platform_driver *pdrv;

Oh.

>
> if (defer_all_probes) {
> /*
> @@ -409,6 +412,16 @@ static int really_probe(struct device *dev, struct device_driver *drv)
> */
> devices_kset_move_last(dev);
>
> + ret = dev_pm_domain_attach(dev, true);

Are you sure this can go here?

> + pdrv = to_platform_driver(dev->driver);

Well, no. There has to be a better way.

> + /* don't fail if just dev_pm_domain_attach failed */
> + if (pdrv && pdrv->prevent_deferred_probe &&
> + ret == -EPROBE_DEFER) {
> + dev_warn(dev, "probe deferral not supported\n");
> + ret = -ENXIO;
> + goto probe_failed;
> + }
> +
> if (dev->bus->probe) {
> ret = dev->bus->probe(dev);
> if (ret)
> @@ -428,6 +441,7 @@ static int really_probe(struct device *dev, struct device_driver *drv)
> drv->remove(dev);
>
> devres_release_all(dev);
> + dev_pm_domain_detach(dev, true);
> driver_sysfs_remove(dev);
> dev->driver = NULL;
> dev_set_drvdata(dev, NULL);
> @@ -458,6 +472,7 @@ static int really_probe(struct device *dev, struct device_driver *drv)
> pinctrl_bind_failed:
> device_links_no_driver(dev);
> devres_release_all(dev);
> + dev_pm_domain_detach(dev, true);
> driver_sysfs_remove(dev);
> dev->driver = NULL;
> dev_set_drvdata(dev, NULL);
> @@ -864,6 +879,7 @@ static void __device_release_driver(struct device *dev, struct device *parent)
> dma_deconfigure(dev);
>
> devres_release_all(dev);
> + dev_pm_domain_detach(dev, true);
> dev->driver = NULL;
> dev_set_drvdata(dev, NULL);
> if (dev->pm_domain && dev->pm_domain->dismiss)
> diff --git a/drivers/base/platform.c b/drivers/base/platform.c
> index d1bd992..8fa688d 100644
> --- a/drivers/base/platform.c
> +++ b/drivers/base/platform.c
> @@ -572,22 +572,8 @@ static int platform_drv_probe(struct device *_dev)
> if (ret < 0)
> return ret;
>
> - ret = dev_pm_domain_attach(_dev, true);

There are other bus types using dev_pm_domain_attach() IIRC, so why is
platform special?

> - if (ret != -EPROBE_DEFER) {
> - if (drv->probe) {
> - ret = drv->probe(dev);
> - if (ret)
> - dev_pm_domain_detach(_dev, true);
> - } else {
> - /* don't fail if just dev_pm_domain_attach failed */
> - ret = 0;
> - }
> - }
> -
> - if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {
> - dev_warn(_dev, "probe deferral not supported\n");
> - ret = -ENXIO;
> - }
> + if (drv->probe)
> + ret = drv->probe(dev);
>
> return ret;
> }
>

The last part is not related to PM domains. Why does it need to be changed?

Thanks,
Rafael

2017-08-30 13:35:04

by Ulf Hansson

[permalink] [raw]
Subject: Re: [PATCH v3 1/2] driver core: detach device's pm_domain after devres_release_all

On 30 August 2017 at 03:34, Shawn Lin <[email protected]> wrote:
> The intention of this patch is to move dev_pm_domain_detach after
> devres_release_all to avoid possible accessing device's registers
> with genpd been powered off.
>
> Many common IP drivers use devm_request_irq to request irq for either
> shared irq or non-shared irq. So we rely on devres_release_all to
> free irq automatically. However we could see a situation that if the
> driver use devm_request_irq to register a shared irq and unbind the
> driver later, the irq could be triggerd cocurrently just between
> finishing dev_pm_domain_detach and calling devm_irq_release, so that
> driver's ISR should be called and try to access device's register, which
> may hang up the system. The reason is that some SoCs, including Rockchips'
> SoCs, couldn't support accessing controllers' registers w/o clk and power
> domain enabled.
>
> Also we have CONFIG_DEBUG_SHIRQ to theoretically expose this kind of
> problem as devm_free_irq -> __free_irq says: "It's a shared IRQ -- the
> driver ought to be prepared for an IRQ event to happen even now it's being
> freed". That will simulate the aforementioned situation as it fires
> extra irq action to make sure driver/system is robust enough to deal
> with this kind of problem.

Hmm, I think there are a related problem when the device becomes
suspended. We simply need a way to avoid having the irq handler being
called, when the device is suspended.

Please have a look at the following discussion, it concerns an issue
for sdhci during suspend:
https://lkml.org/lkml/2016/1/28/213

I don't think we ended up fixing the problem, but a couple of solution
was discussed. Likely some of them should be able to solve also this
issue, I think.

Kind regards
Uffe

>
> So now we face a two possible choices to fix this by
> (1) either using request_irq and free_irq directly
> (2) or moving dev_pm_domain_detach after devres_release_all which
> makes sure we free the irq before powering off power domain.
>
> However, choice (1) implies that devm_request_irq shouldn't exist
> or at least shouldn't be used for shared irq case. Meanwhile we don't
> know how many drivers have this kind of issue and need to fix. So
> choice (2) makes more sense to me, and that is the reason for why we
> need to fix it like what this patch does.
>
> Signed-off-by: Shawn Lin <[email protected]>
>
> ---
>
> Changes in v3:
> - fix the code path for consolidating the attach for both of driver
> and bus driver, and then move detach to the error path
> - rework the changelog
>
> drivers/base/dd.c | 16 ++++++++++++++++
> drivers/base/platform.c | 18 ++----------------
> 2 files changed, 18 insertions(+), 16 deletions(-)
>
> diff --git a/drivers/base/dd.c b/drivers/base/dd.c
> index ad44b40..aea0bb1 100644
> --- a/drivers/base/dd.c
> +++ b/drivers/base/dd.c
> @@ -25,7 +25,9 @@
> #include <linux/kthread.h>
> #include <linux/wait.h>
> #include <linux/async.h>
> +#include <linux/platform_device.h>
> #include <linux/pm_runtime.h>
> +#include <linux/pm_domain.h>
> #include <linux/pinctrl/devinfo.h>
>
> #include "base.h"
> @@ -356,6 +358,7 @@ static int really_probe(struct device *dev, struct device_driver *drv)
> int local_trigger_count = atomic_read(&deferred_trigger_count);
> bool test_remove = IS_ENABLED(CONFIG_DEBUG_TEST_DRIVER_REMOVE) &&
> !drv->suppress_bind_attrs;
> + struct platform_driver *pdrv;
>
> if (defer_all_probes) {
> /*
> @@ -409,6 +412,16 @@ static int really_probe(struct device *dev, struct device_driver *drv)
> */
> devices_kset_move_last(dev);
>
> + ret = dev_pm_domain_attach(dev, true);
> + pdrv = to_platform_driver(dev->driver);
> + /* don't fail if just dev_pm_domain_attach failed */
> + if (pdrv && pdrv->prevent_deferred_probe &&
> + ret == -EPROBE_DEFER) {
> + dev_warn(dev, "probe deferral not supported\n");
> + ret = -ENXIO;
> + goto probe_failed;
> + }
> +
> if (dev->bus->probe) {
> ret = dev->bus->probe(dev);
> if (ret)
> @@ -428,6 +441,7 @@ static int really_probe(struct device *dev, struct device_driver *drv)
> drv->remove(dev);
>
> devres_release_all(dev);
> + dev_pm_domain_detach(dev, true);
> driver_sysfs_remove(dev);
> dev->driver = NULL;
> dev_set_drvdata(dev, NULL);
> @@ -458,6 +472,7 @@ static int really_probe(struct device *dev, struct device_driver *drv)
> pinctrl_bind_failed:
> device_links_no_driver(dev);
> devres_release_all(dev);
> + dev_pm_domain_detach(dev, true);
> driver_sysfs_remove(dev);
> dev->driver = NULL;
> dev_set_drvdata(dev, NULL);
> @@ -864,6 +879,7 @@ static void __device_release_driver(struct device *dev, struct device *parent)
> dma_deconfigure(dev);
>
> devres_release_all(dev);
> + dev_pm_domain_detach(dev, true);
> dev->driver = NULL;
> dev_set_drvdata(dev, NULL);
> if (dev->pm_domain && dev->pm_domain->dismiss)
> diff --git a/drivers/base/platform.c b/drivers/base/platform.c
> index d1bd992..8fa688d 100644
> --- a/drivers/base/platform.c
> +++ b/drivers/base/platform.c
> @@ -572,22 +572,8 @@ static int platform_drv_probe(struct device *_dev)
> if (ret < 0)
> return ret;
>
> - ret = dev_pm_domain_attach(_dev, true);
> - if (ret != -EPROBE_DEFER) {
> - if (drv->probe) {
> - ret = drv->probe(dev);
> - if (ret)
> - dev_pm_domain_detach(_dev, true);
> - } else {
> - /* don't fail if just dev_pm_domain_attach failed */
> - ret = 0;
> - }
> - }
> -
> - if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {
> - dev_warn(_dev, "probe deferral not supported\n");
> - ret = -ENXIO;
> - }
> + if (drv->probe)
> + ret = drv->probe(dev);
>
> return ret;
> }
> --
> 1.9.1
>
>