* [PATCH v5 1/6] ata: ahci_st: Do not ignore errors when getting the reset controls
2026-09-15 8:41 [PATCH v5 0/6] ata: Do not release the host resources twice on probe() failure Niklas Cassel
@ 2026-09-15 8:41 ` Niklas Cassel
2026-09-15 8:48 ` Damien Le Moal
2026-09-15 8:41 ` [PATCH v5 2/6] ata: ahci_st: Assert the power down reset in the probe() error path Niklas Cassel
` (5 subsequent siblings)
6 siblings, 1 reply; 12+ messages in thread
From: Niklas Cassel @ 2026-09-15 8:41 UTC (permalink / raw)
To: Patrice Chotard, Damien Le Moal, Niklas Cassel, Philipp Zabel,
Alexandre Torgue, Lee Jones, Tejun Heo
Cc: Brian Norris, stable, linux-arm-kernel, linux-ide
st_ahci_probe_resets() treats any error from devm_reset_control_get() as
"reset control not defined" and continues with a NULL reset control:
drv_data->pwr = devm_reset_control_get(dev, "pwr-dwn");
if (IS_ERR(drv_data->pwr)) {
dev_info(dev, "power reset control not defined\n");
drv_data->pwr = NULL;
}
This also swallows -EPROBE_DEFER, which is returned when the reset
controller providing the reset has not been probed yet. probe() then
continues as if the device tree did not specify any reset, so the resets
of the SATA IP are never deasserted, and st_ahci_configure_oob() and
ahci_platform_init_host() access the MMIO of an IP which is still held in
reset and powered down, which can result in an external abort.
The resets are optional in the binding, as they are not part of its
required properties, so use devm_reset_control_get_optional(), which
returns NULL if the reset is not specified in the device tree, and
propagate all other errors.
Note that an absent reset is now indicated by a NULL reset control
instead of an error, so the "reset control not defined" messages are
dropped.
Fixes: 76884cb2f7da ("ahci: st: Add support for ST's SATA IP")
Cc: stable@vger.kernel.org
Signed-off-by: Niklas Cassel <cassel@kernel.org>
---
drivers/ata/ahci_st.c | 31 ++++++++++++++-----------------
1 file changed, 14 insertions(+), 17 deletions(-)
diff --git a/drivers/ata/ahci_st.c b/drivers/ata/ahci_st.c
index 4336c8a6e208..819269a022ef 100644
--- a/drivers/ata/ahci_st.c
+++ b/drivers/ata/ahci_st.c
@@ -104,23 +104,20 @@ static int st_ahci_probe_resets(struct ahci_host_priv *hpriv,
{
struct st_ahci_drv_data *drv_data = hpriv->plat_data;
- drv_data->pwr = devm_reset_control_get(dev, "pwr-dwn");
- if (IS_ERR(drv_data->pwr)) {
- dev_info(dev, "power reset control not defined\n");
- drv_data->pwr = NULL;
- }
-
- drv_data->sw_rst = devm_reset_control_get(dev, "sw-rst");
- if (IS_ERR(drv_data->sw_rst)) {
- dev_info(dev, "soft reset control not defined\n");
- drv_data->sw_rst = NULL;
- }
-
- drv_data->pwr_rst = devm_reset_control_get(dev, "pwr-rst");
- if (IS_ERR(drv_data->pwr_rst)) {
- dev_dbg(dev, "power soft reset control not defined\n");
- drv_data->pwr_rst = NULL;
- }
+ drv_data->pwr = devm_reset_control_get_optional(dev, "pwr-dwn");
+ if (IS_ERR(drv_data->pwr))
+ return dev_err_probe(dev, PTR_ERR(drv_data->pwr),
+ "failed to get pwr-dwn reset\n");
+
+ drv_data->sw_rst = devm_reset_control_get_optional(dev, "sw-rst");
+ if (IS_ERR(drv_data->sw_rst))
+ return dev_err_probe(dev, PTR_ERR(drv_data->sw_rst),
+ "failed to get sw-rst reset\n");
+
+ drv_data->pwr_rst = devm_reset_control_get_optional(dev, "pwr-rst");
+ if (IS_ERR(drv_data->pwr_rst))
+ return dev_err_probe(dev, PTR_ERR(drv_data->pwr_rst),
+ "failed to get pwr-rst reset\n");
return st_ahci_deassert_resets(hpriv, dev);
}
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v5 1/6] ata: ahci_st: Do not ignore errors when getting the reset controls
2026-09-15 8:41 ` [PATCH v5 1/6] ata: ahci_st: Do not ignore errors when getting the reset controls Niklas Cassel
@ 2026-09-15 8:48 ` Damien Le Moal
0 siblings, 0 replies; 12+ messages in thread
From: Damien Le Moal @ 2026-09-15 8:48 UTC (permalink / raw)
To: Niklas Cassel, Patrice Chotard, Philipp Zabel, Alexandre Torgue,
Lee Jones, Tejun Heo
Cc: Brian Norris, stable, linux-arm-kernel, linux-ide
On 2026/09/15 15:41, Niklas Cassel wrote:
> st_ahci_probe_resets() treats any error from devm_reset_control_get() as
> "reset control not defined" and continues with a NULL reset control:
>
> drv_data->pwr = devm_reset_control_get(dev, "pwr-dwn");
> if (IS_ERR(drv_data->pwr)) {
> dev_info(dev, "power reset control not defined\n");
> drv_data->pwr = NULL;
> }
>
> This also swallows -EPROBE_DEFER, which is returned when the reset
> controller providing the reset has not been probed yet. probe() then
> continues as if the device tree did not specify any reset, so the resets
> of the SATA IP are never deasserted, and st_ahci_configure_oob() and
> ahci_platform_init_host() access the MMIO of an IP which is still held in
> reset and powered down, which can result in an external abort.
>
> The resets are optional in the binding, as they are not part of its
> required properties, so use devm_reset_control_get_optional(), which
> returns NULL if the reset is not specified in the device tree, and
> propagate all other errors.
>
> Note that an absent reset is now indicated by a NULL reset control
> instead of an error, so the "reset control not defined" messages are
> dropped.
>
> Fixes: 76884cb2f7da ("ahci: st: Add support for ST's SATA IP")
> Cc: stable@vger.kernel.org
> Signed-off-by: Niklas Cassel <cassel@kernel.org>
Looks good.
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
--
Damien Le Moal
Western Digital Research
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v5 2/6] ata: ahci_st: Assert the power down reset in the probe() error path
2026-09-15 8:41 [PATCH v5 0/6] ata: Do not release the host resources twice on probe() failure Niklas Cassel
2026-09-15 8:41 ` [PATCH v5 1/6] ata: ahci_st: Do not ignore errors when getting the reset controls Niklas Cassel
@ 2026-09-15 8:41 ` Niklas Cassel
2026-09-15 8:41 ` [PATCH v5 3/6] ata: sata_fsl: Fix use-after-free of host_priv on probe() failure Niklas Cassel
` (4 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Niklas Cassel @ 2026-09-15 8:41 UTC (permalink / raw)
To: Patrice Chotard, Damien Le Moal, Niklas Cassel, Philipp Zabel,
Alexandre Torgue, Tejun Heo, Lee Jones
Cc: Brian Norris, stable, linux-arm-kernel, linux-ide
st_ahci_probe_resets() deasserts the "pwr-dwn" reset, but the probe()
error path of st_ahci_probe() only releases the host resources, so the
SATA IP is left powered up when probe() fails after
st_ahci_probe_resets() has succeeded, e.g. when
ahci_platform_enable_resources() fails.
The reset is asserted by st_ahci_host_stop(), however ->host_stop() is
only called through the ata_host_stop() devres action registered by
ata_host_start(), so it does not cover any failure happening before the
host has been started.
Factor the assert out into st_ahci_assert_pwrdwn() and call it when
either ahci_platform_enable_resources() or ahci_platform_init_host()
fails. The "pwr-dwn" reset control is an exclusive one, so asserting it
once more from st_ahci_host_stop() is harmless.
No functional change intended for ->host_stop() and st_ahci_suspend().
Fixes: 76884cb2f7da ("ahci: st: Add support for ST's SATA IP")
Cc: stable@vger.kernel.org
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Signed-off-by: Niklas Cassel <cassel@kernel.org>
---
drivers/ata/ahci_st.c | 51 +++++++++++++++++++++++++------------------
1 file changed, 30 insertions(+), 21 deletions(-)
diff --git a/drivers/ata/ahci_st.c b/drivers/ata/ahci_st.c
index 819269a022ef..eea75961038c 100644
--- a/drivers/ata/ahci_st.c
+++ b/drivers/ata/ahci_st.c
@@ -83,18 +83,27 @@ static int st_ahci_deassert_resets(struct ahci_host_priv *hpriv,
return 0;
}
-static void st_ahci_host_stop(struct ata_host *host)
+static int st_ahci_assert_pwrdwn(struct ahci_host_priv *hpriv,
+ struct device *dev)
{
- struct ahci_host_priv *hpriv = host->private_data;
struct st_ahci_drv_data *drv_data = hpriv->plat_data;
- struct device *dev = host->dev;
int err;
- if (drv_data->pwr) {
- err = reset_control_assert(drv_data->pwr);
- if (err)
- dev_err(dev, "unable to pwrdwn\n");
- }
+ if (!drv_data->pwr)
+ return 0;
+
+ err = reset_control_assert(drv_data->pwr);
+ if (err)
+ dev_err(dev, "unable to pwrdwn\n");
+
+ return err;
+}
+
+static void st_ahci_host_stop(struct ata_host *host)
+{
+ struct ahci_host_priv *hpriv = host->private_data;
+
+ st_ahci_assert_pwrdwn(hpriv, host->dev);
ahci_platform_disable_resources(hpriv);
}
@@ -159,38 +168,38 @@ static int st_ahci_probe(struct platform_device *pdev)
err = ahci_platform_enable_resources(hpriv);
if (err)
- return err;
+ goto assert_pwrdwn;
st_ahci_configure_oob(hpriv->mmio);
err = ahci_platform_init_host(pdev, hpriv, &st_ahci_port_info,
&ahci_platform_sht);
- if (err) {
- ahci_platform_disable_resources(hpriv);
- return err;
- }
+ if (err)
+ goto disable_resources;
return 0;
+
+disable_resources:
+ ahci_platform_disable_resources(hpriv);
+assert_pwrdwn:
+ st_ahci_assert_pwrdwn(hpriv, &pdev->dev);
+
+ return err;
}
static int st_ahci_suspend(struct device *dev)
{
struct ata_host *host = dev_get_drvdata(dev);
struct ahci_host_priv *hpriv = host->private_data;
- struct st_ahci_drv_data *drv_data = hpriv->plat_data;
int err;
err = ahci_platform_suspend_host(dev);
if (err)
return err;
- if (drv_data->pwr) {
- err = reset_control_assert(drv_data->pwr);
- if (err) {
- dev_err(dev, "unable to pwrdwn");
- return err;
- }
- }
+ err = st_ahci_assert_pwrdwn(hpriv, dev);
+ if (err)
+ return err;
ahci_platform_disable_resources(hpriv);
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v5 3/6] ata: sata_fsl: Fix use-after-free of host_priv on probe() failure
2026-09-15 8:41 [PATCH v5 0/6] ata: Do not release the host resources twice on probe() failure Niklas Cassel
2026-09-15 8:41 ` [PATCH v5 1/6] ata: ahci_st: Do not ignore errors when getting the reset controls Niklas Cassel
2026-09-15 8:41 ` [PATCH v5 2/6] ata: ahci_st: Assert the power down reset in the probe() error path Niklas Cassel
@ 2026-09-15 8:41 ` Niklas Cassel
2026-09-15 8:50 ` Damien Le Moal
2026-09-15 8:41 ` [PATCH v5 4/6] ata: libahci: Free the IRQs when activating a multi-IRQ host fails Niklas Cassel
` (3 subsequent siblings)
6 siblings, 1 reply; 12+ messages in thread
From: Niklas Cassel @ 2026-09-15 8:41 UTC (permalink / raw)
To: Damien Le Moal, Niklas Cassel, Baokun Li, Sergei Shtylyov
Cc: Brian Norris, stable, Damien Le Moal, linux-ide
sata_fsl_host_stop() releases hcr_base and host_priv:
static void sata_fsl_host_stop(struct ata_host *host)
{
struct sata_fsl_host_priv *host_priv = host->private_data;
iounmap(host_priv->hcr_base);
kfree(host_priv);
}
->host_stop() is called by the driver core through the ata_host_stop()
devres action which ata_host_start() registers, so it is called both when
the device is unbound and when probe() fails after the host has been
started.
The error_exit_with_cleanup label of sata_fsl_probe() releases hcr_base
and host_priv as well, and it is reachable from the two
device_create_file() calls which are done after the host has been
activated. In that case sata_fsl_host_stop() runs on an already freed
host_priv, resulting in a use-after-free and a double iounmap()/kfree().
Add a separate error label for the failures happening after the host has
been activated, which only detaches the host and leaves hcr_base and
host_priv to sata_fsl_host_stop().
Note that ata_host_detach() is dropped from error_exit_with_cleanup, as
that label is now only reachable while host is still NULL.
Fixes: 6c8ad7e8cf29 ("sata_fsl: fix UAF in sata_fsl_port_stop when rmmod sata_fsl")
Cc: stable@vger.kernel.org
Signed-off-by: Niklas Cassel <cassel@kernel.org>
---
drivers/ata/sata_fsl.c | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)
diff --git a/drivers/ata/sata_fsl.c b/drivers/ata/sata_fsl.c
index 70b210afd291..4037dd758f87 100644
--- a/drivers/ata/sata_fsl.c
+++ b/drivers/ata/sata_fsl.c
@@ -1500,7 +1500,7 @@ static int sata_fsl_probe(struct platform_device *ofdev)
host_priv->intr_coalescing.attr.mode = S_IRUGO | S_IWUSR;
retval = device_create_file(host->dev, &host_priv->intr_coalescing);
if (retval)
- goto error_exit_with_cleanup;
+ goto error_exit_detach;
host_priv->rx_watermark.show = fsl_sata_rx_watermark_show;
host_priv->rx_watermark.store = fsl_sata_rx_watermark_store;
@@ -1510,16 +1510,24 @@ static int sata_fsl_probe(struct platform_device *ofdev)
retval = device_create_file(host->dev, &host_priv->rx_watermark);
if (retval) {
device_remove_file(&ofdev->dev, &host_priv->intr_coalescing);
- goto error_exit_with_cleanup;
+ goto error_exit_detach;
}
return 0;
-error_exit_with_cleanup:
+ /*
+ * Once the host has been activated, hcr_base and host_priv are
+ * released by sata_fsl_host_stop(), which is called by the driver core
+ * through the ata_host_stop() devres action registered by
+ * ata_host_start(). Releasing them here as well would result in a
+ * double iounmap() and a use-after-free.
+ */
+error_exit_detach:
+ ata_host_detach(host);
- if (host)
- ata_host_detach(host);
+ return retval;
+error_exit_with_cleanup:
if (hcr_base)
iounmap(hcr_base);
kfree(host_priv);
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v5 3/6] ata: sata_fsl: Fix use-after-free of host_priv on probe() failure
2026-09-15 8:41 ` [PATCH v5 3/6] ata: sata_fsl: Fix use-after-free of host_priv on probe() failure Niklas Cassel
@ 2026-09-15 8:50 ` Damien Le Moal
2026-09-15 9:49 ` Niklas Cassel
0 siblings, 1 reply; 12+ messages in thread
From: Damien Le Moal @ 2026-09-15 8:50 UTC (permalink / raw)
To: Niklas Cassel, Baokun Li, Sergei Shtylyov
Cc: Brian Norris, stable, Damien Le Moal, linux-ide
On 2026/09/15 15:41, Niklas Cassel wrote:
> sata_fsl_host_stop() releases hcr_base and host_priv:
>
> static void sata_fsl_host_stop(struct ata_host *host)
> {
> struct sata_fsl_host_priv *host_priv = host->private_data;
>
> iounmap(host_priv->hcr_base);
> kfree(host_priv);
> }
>
> ->host_stop() is called by the driver core through the ata_host_stop()
> devres action which ata_host_start() registers, so it is called both when
> the device is unbound and when probe() fails after the host has been
> started.
>
> The error_exit_with_cleanup label of sata_fsl_probe() releases hcr_base
> and host_priv as well, and it is reachable from the two
> device_create_file() calls which are done after the host has been
> activated. In that case sata_fsl_host_stop() runs on an already freed
> host_priv, resulting in a use-after-free and a double iounmap()/kfree().
>
> Add a separate error label for the failures happening after the host has
> been activated, which only detaches the host and leaves hcr_base and
> host_priv to sata_fsl_host_stop().
>
> Note that ata_host_detach() is dropped from error_exit_with_cleanup, as
> that label is now only reachable while host is still NULL.
>
> Fixes: 6c8ad7e8cf29 ("sata_fsl: fix UAF in sata_fsl_port_stop when rmmod sata_fsl")
> Cc: stable@vger.kernel.org
> Signed-off-by: Niklas Cassel <cassel@kernel.org>
Looks good.
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
One nit below that is worth fixing I think.
> + /*
> + * Once the host has been activated, hcr_base and host_priv are
> + * released by sata_fsl_host_stop(), which is called by the driver core
> + * through the ata_host_stop() devres action registered by
> + * ata_host_start(). Releasing them here as well would result in a
> + * double iounmap() and a use-after-free.
> + */
> +error_exit_detach:
The label should really go above the comment block to clearly show this is the
explanation for what is done in this label block.
> + ata_host_detach(host);
>
> - if (host)
> - ata_host_detach(host);
> + return retval;
>
> +error_exit_with_cleanup:
> if (hcr_base)
> iounmap(hcr_base);
> kfree(host_priv);
--
Damien Le Moal
Western Digital Research
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH v5 3/6] ata: sata_fsl: Fix use-after-free of host_priv on probe() failure
2026-09-15 8:50 ` Damien Le Moal
@ 2026-09-15 9:49 ` Niklas Cassel
0 siblings, 0 replies; 12+ messages in thread
From: Niklas Cassel @ 2026-09-15 9:49 UTC (permalink / raw)
To: Damien Le Moal
Cc: Baokun Li, Sergei Shtylyov, Brian Norris, stable, Damien Le Moal,
linux-ide
On Tue, Sep 15, 2026 at 03:50:50PM +0700, Damien Le Moal wrote:
> On 2026/09/15 15:41, Niklas Cassel wrote:
> > sata_fsl_host_stop() releases hcr_base and host_priv:
> >
> > static void sata_fsl_host_stop(struct ata_host *host)
> > {
> > struct sata_fsl_host_priv *host_priv = host->private_data;
> >
> > iounmap(host_priv->hcr_base);
> > kfree(host_priv);
> > }
> >
> > ->host_stop() is called by the driver core through the ata_host_stop()
> > devres action which ata_host_start() registers, so it is called both when
> > the device is unbound and when probe() fails after the host has been
> > started.
> >
> > The error_exit_with_cleanup label of sata_fsl_probe() releases hcr_base
> > and host_priv as well, and it is reachable from the two
> > device_create_file() calls which are done after the host has been
> > activated. In that case sata_fsl_host_stop() runs on an already freed
> > host_priv, resulting in a use-after-free and a double iounmap()/kfree().
> >
> > Add a separate error label for the failures happening after the host has
> > been activated, which only detaches the host and leaves hcr_base and
> > host_priv to sata_fsl_host_stop().
> >
> > Note that ata_host_detach() is dropped from error_exit_with_cleanup, as
> > that label is now only reachable while host is still NULL.
> >
> > Fixes: 6c8ad7e8cf29 ("sata_fsl: fix UAF in sata_fsl_port_stop when rmmod sata_fsl")
> > Cc: stable@vger.kernel.org
> > Signed-off-by: Niklas Cassel <cassel@kernel.org>
>
> Looks good.
>
> Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
>
> One nit below that is worth fixing I think.
>
> > + /*
> > + * Once the host has been activated, hcr_base and host_priv are
> > + * released by sata_fsl_host_stop(), which is called by the driver core
> > + * through the ata_host_stop() devres action registered by
> > + * ata_host_start(). Releasing them here as well would result in a
> > + * double iounmap() and a use-after-free.
> > + */
> > +error_exit_detach:
>
> The label should really go above the comment block to clearly show this is the
> explanation for what is done in this label block.
You are right, that seems to be the convention.
Will fixup when applying.
Kind regards,
Niklas
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v5 4/6] ata: libahci: Free the IRQs when activating a multi-IRQ host fails
2026-09-15 8:41 [PATCH v5 0/6] ata: Do not release the host resources twice on probe() failure Niklas Cassel
` (2 preceding siblings ...)
2026-09-15 8:41 ` [PATCH v5 3/6] ata: sata_fsl: Fix use-after-free of host_priv on probe() failure Niklas Cassel
@ 2026-09-15 8:41 ` Niklas Cassel
2026-09-15 8:41 ` [PATCH v5 5/6] ata: libata: Do not leave ata_host_stop() registered when activation fails Niklas Cassel
` (2 subsequent siblings)
6 siblings, 0 replies; 12+ messages in thread
From: Niklas Cassel @ 2026-09-15 8:41 UTC (permalink / raw)
To: Damien Le Moal, Niklas Cassel, Tejun Heo, Dan Williams
Cc: Brian Norris, stable, linux-ide
ahci_host_activate_multi_irqs() requests one IRQ per port, but it does
not free them when requesting one of them, or when registering the host,
fails. The IRQs are only freed by the driver core, when it releases the
devres of the device after probe() has returned, while the caller of
ahci_host_activate() releases the resources of the host in its probe()
error path, e.g. ahci_probe() disables the clocks, regulators, resets and
PHYs of the host. An IRQ handler running in that window would access the
MMIO of a host which is no longer clocked.
ahci_host_activate_multi_irqs() did free the IRQs until commit
0a142b26921c ("ahci: cleanup ahci_host_activate_multi_irqs"), which
removed the explicit free because devm makes it unnecessary. That is true
for freeing the IRQs as such, but not for the window described above:
devres is only released after probe() has returned, i.e. after the error
path of the caller has released the resources of the host.
Note that this window cannot be hit with the current users: the only user
of AHCI_HFLAG_MULTI_MSI is the AHCI PCI driver, which does not release
any resource in its probe() error path, and the ports of the host are
still frozen, i.e. their interrupts are masked, when ata_host_register()
fails.
Free the IRQs explicitly again, like ata_host_activate() does, so that
the IRQ handlers cannot run once ahci_host_activate() has failed.
Fixes: 0a142b26921c ("ahci: cleanup ahci_host_activate_multi_irqs")
Cc: stable@vger.kernel.org
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Signed-off-by: Niklas Cassel <cassel@kernel.org>
---
drivers/ata/libahci.c | 26 ++++++++++++++++++++++++--
1 file changed, 24 insertions(+), 2 deletions(-)
diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c
index 6d72eb017b49..9f479daa89b0 100644
--- a/drivers/ata/libahci.c
+++ b/drivers/ata/libahci.c
@@ -2723,11 +2723,33 @@ static int ahci_host_activate_multi_irqs(struct ata_host *host,
0, pp->irq_desc, host->ports[i]);
if (rc)
- return rc;
+ goto free_irqs;
ata_port_desc_misc(host->ports[i], irq);
}
- return ata_host_register(host, sht);
+ rc = ata_host_register(host, sht);
+ if (rc)
+ goto free_irqs;
+
+ return 0;
+
+free_irqs:
+ /*
+ * Free the IRQs which have been requested, so that the handlers can no
+ * longer access the MMIO of the host once we return, e.g. after the
+ * caller has disabled the clocks of the host.
+ */
+ while (--i >= 0) {
+ struct ahci_port_priv *pp = host->ports[i]->private_data;
+
+ if (!pp)
+ continue;
+
+ devm_free_irq(host->dev, hpriv->get_irq_vector(host, i),
+ host->ports[i]);
+ }
+
+ return rc;
}
/**
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH v5 5/6] ata: libata: Do not leave ata_host_stop() registered when activation fails
2026-09-15 8:41 [PATCH v5 0/6] ata: Do not release the host resources twice on probe() failure Niklas Cassel
` (3 preceding siblings ...)
2026-09-15 8:41 ` [PATCH v5 4/6] ata: libahci: Free the IRQs when activating a multi-IRQ host fails Niklas Cassel
@ 2026-09-15 8:41 ` Niklas Cassel
2026-09-15 8:52 ` Damien Le Moal
2026-09-15 8:41 ` [PATCH v5 6/6] ata: libata-core: Fix the ata_host_register() kdoc Niklas Cassel
2026-09-15 9:58 ` [PATCH v5 0/6] ata: Do not release the host resources twice on probe() failure Niklas Cassel
6 siblings, 1 reply; 12+ messages in thread
From: Niklas Cassel @ 2026-09-15 8:41 UTC (permalink / raw)
To: Damien Le Moal, Niklas Cassel, Hans de Goede, Brian Norris,
Jeff Garzik
Cc: stable, linux-ide
ata_host_start() registers ata_host_stop() as a devres action as soon as
it has succeeded, which hands the release of the host resources over to
devres: ->port_stop() and ->host_stop() are then called by the driver
core when probe() fails.
ata_host_activate() and ahci_host_activate_multi_irqs() can both fail
after ata_host_start() has succeeded, e.g. if devm_request_irq() or
ata_host_register() fails, and they return the error with the devres
action still registered. A driver which releases the host resources in
its probe() error path therefore releases them twice: once itself and
once through ->host_stop().
All ahci-platform drivers are in that situation, e.g. ahci_probe() calls
ahci_platform_disable_resources() while ahci_host_stop() does the same
through devres. This gives refcount underflow warnings from the clk,
regulator and phy cores and, for shared resources, can disable resources
which are still in use by other devices.
Add ata_host_undo_start(), which stops the ports and drops the devres
action without calling ->host_stop(), and call it from both activation
helpers when they fail. Releasing the host resources on failure is then
always left to the caller, which is what all callers having a probe()
error path already assume.
This changes the semantics for the drivers which implement ->host_stop()
while also completely lacking error handling for the activate host call.
Add activate host error handling for sata_qstor and sata_fsl. For
sata_fsl this also means that hcr_base and host_priv are now released
when activating the host fails, which ->host_stop() did not do when
ata_host_start() itself failed.
Note that ata_pci_sff_activate_host() is deliberately left as is: none of
its callers releases the host resources in its probe() error path, they
all rely on ->host_stop() being called by devres, including
ata_pci_init_one(), which releases the devres group of the host itself.
Fixes: 1896b15eddb4 ("ahci_platform: perform platform exit in host_stop() hook")
Cc: stable@vger.kernel.org
Signed-off-by: Niklas Cassel <cassel@kernel.org>
---
drivers/ata/libahci.c | 2 +
drivers/ata/libahci_platform.c | 4 ++
drivers/ata/libata-core.c | 75 +++++++++++++++++++++++++++++++---
drivers/ata/libata-sff.c | 5 +++
drivers/ata/sata_fsl.c | 6 ++-
drivers/ata/sata_qstor.c | 8 +++-
include/linux/libata.h | 1 +
7 files changed, 91 insertions(+), 10 deletions(-)
diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c
index 9f479daa89b0..27d1b2197868 100644
--- a/drivers/ata/libahci.c
+++ b/drivers/ata/libahci.c
@@ -2749,6 +2749,8 @@ static int ahci_host_activate_multi_irqs(struct ata_host *host,
host->ports[i]);
}
+ ata_host_undo_start(host);
+
return rc;
}
diff --git a/drivers/ata/libahci_platform.c b/drivers/ata/libahci_platform.c
index 6e072d681341..b44c0db4a86e 100644
--- a/drivers/ata/libahci_platform.c
+++ b/drivers/ata/libahci_platform.c
@@ -689,6 +689,10 @@ EXPORT_SYMBOL_GPL(ahci_platform_get_resources);
* ahci-platform host, note any necessary resources (ie clks, phys, etc.)
* must be initialized / enabled before calling this.
*
+ * On failure, ->host_stop() is not called, so the caller has to release the
+ * resources it enabled (clocks, regulators, resets, PHYs) in its probe()
+ * error path.
+ *
* RETURNS:
* 0 on success otherwise a negative error code
*/
diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c
index f482c0a6d7e9..197f5d6e93ce 100644
--- a/drivers/ata/libata-core.c
+++ b/drivers/ata/libata-core.c
@@ -6127,6 +6127,46 @@ int ata_host_start(struct ata_host *host)
}
EXPORT_SYMBOL_GPL(ata_host_start);
+/**
+ * ata_host_undo_start - undo ata_host_start()
+ * @host: ATA host to undo_start
+ *
+ * Stop the ports of @host and drop the devres action registered by
+ * ata_host_start(), without calling ->host_stop(). Nothing is done if
+ * @host has not been started.
+ *
+ * This gives the release of the host resources back to the caller, which
+ * is what a driver whose probe() error path releases those resources
+ * itself needs when starting or activating the host fails.
+ *
+ * LOCKING:
+ * Inherited from calling layer (may sleep).
+ */
+void ata_host_undo_start(struct ata_host *host)
+{
+ int i;
+
+ if (!(host->flags & ATA_HOST_STARTED))
+ return;
+
+ for (i = 0; i < host->n_ports; i++) {
+ struct ata_port *ap = host->ports[i];
+
+ if (ap->ops->port_stop)
+ ap->ops->port_stop(ap);
+ }
+
+ /*
+ * Drop the action added by ata_host_start() without calling it.
+ * It does not exist if neither ->port_stop() nor ->host_stop() is
+ * implemented, in which case there is nothing to drop.
+ */
+ devres_destroy(host->dev, ata_host_stop, NULL, NULL);
+
+ host->flags &= ~ATA_HOST_STARTED;
+}
+EXPORT_SYMBOL_GPL(ata_host_undo_start);
+
/**
* ata_host_init - Initialize a host struct for sas (ipr, libsas)
* @host: host to initialize
@@ -6201,6 +6241,12 @@ static void async_port_probe(void *data, async_cookie_t cookie)
* starts ports, registers @host with ATA and SCSI layers and
* probe registered devices.
*
+ * On failure, the host remains started, i.e. the devres action
+ * registered by ata_host_start() is kept, so ->host_stop() is called by
+ * the driver core when probe() fails. A caller which releases the host
+ * resources in its own probe() error path must therefore call
+ * ata_host_undo_start().
+ *
* LOCKING:
* Inherited from calling layer (may sleep).
*
@@ -6294,6 +6340,10 @@ EXPORT_SYMBOL_GPL(ata_host_register);
* have set polling mode on the port. In this case, @irq_handler
* should be NULL.
*
+ * On failure, the ports are stopped again and the devres action
+ * registered by ata_host_start() is dropped without calling
+ * ->host_stop(), so releasing the host resources is left to the caller.
+ *
* LOCKING:
* Inherited from calling layer (may sleep).
*
@@ -6314,27 +6364,40 @@ int ata_host_activate(struct ata_host *host, int irq,
/* Special case for polling mode */
if (!irq) {
WARN_ON(irq_handler);
- return ata_host_register(host, sht);
+ rc = ata_host_register(host, sht);
+ if (rc)
+ goto undo_start;
+
+ return 0;
}
irq_desc = devm_kasprintf(host->dev, GFP_KERNEL, "%s[%s]",
dev_driver_string(host->dev),
dev_name(host->dev));
- if (!irq_desc)
- return -ENOMEM;
+ if (!irq_desc) {
+ rc = -ENOMEM;
+ goto undo_start;
+ }
rc = devm_request_irq(host->dev, irq, irq_handler, irq_flags,
irq_desc, host);
if (rc)
- return rc;
+ goto undo_start;
for (i = 0; i < host->n_ports; i++)
ata_port_desc_misc(host->ports[i], irq);
rc = ata_host_register(host, sht);
- /* if failed, just free the IRQ and leave ports alone */
- if (rc)
+ if (rc) {
+ /* Free the IRQ, so that the handler can no longer run */
devm_free_irq(host->dev, irq, host);
+ goto undo_start;
+ }
+
+ return 0;
+
+undo_start:
+ ata_host_undo_start(host);
return rc;
}
diff --git a/drivers/ata/libata-sff.c b/drivers/ata/libata-sff.c
index 976e4e160494..d5ef0338735c 100644
--- a/drivers/ata/libata-sff.c
+++ b/drivers/ata/libata-sff.c
@@ -2267,6 +2267,11 @@ EXPORT_SYMBOL_GPL(ata_pci_sff_prepare_host);
* hosts. This separate helper is necessary because SFF hosts
* use two separate interrupts in legacy mode.
*
+ * Note that, unlike ata_host_activate(), the devres action registered
+ * by ata_host_start() is kept on failure, i.e. ->host_stop() is called
+ * by the driver core when probe() fails. All callers of this function
+ * rely on that, none of them releases the host resources itself.
+ *
* LOCKING:
* Inherited from calling layer (may sleep).
*
diff --git a/drivers/ata/sata_fsl.c b/drivers/ata/sata_fsl.c
index 4037dd758f87..2141a2189b5e 100644
--- a/drivers/ata/sata_fsl.c
+++ b/drivers/ata/sata_fsl.c
@@ -1490,8 +1490,10 @@ static int sata_fsl_probe(struct platform_device *ofdev)
* device discovery process, invoking our port_start() handler &
* error_handler() to execute a dummy Softreset EH session
*/
- ata_host_activate(host, irq, sata_fsl_interrupt, SATA_FSL_IRQ_FLAG,
- &sata_fsl_sht);
+ retval = ata_host_activate(host, irq, sata_fsl_interrupt,
+ SATA_FSL_IRQ_FLAG, &sata_fsl_sht);
+ if (retval)
+ goto error_exit_with_cleanup;
host_priv->intr_coalescing.show = fsl_sata_intr_coalescing_show;
host_priv->intr_coalescing.store = fsl_sata_intr_coalescing_store;
diff --git a/drivers/ata/sata_qstor.c b/drivers/ata/sata_qstor.c
index 4e7f5b2ff3f6..f38310b84f51 100644
--- a/drivers/ata/sata_qstor.c
+++ b/drivers/ata/sata_qstor.c
@@ -584,8 +584,12 @@ static int qs_ata_init_one(struct pci_dev *pdev,
qs_host_init(host, board_idx);
pci_set_master(pdev);
- return ata_host_activate(host, pdev->irq, qs_intr, IRQF_SHARED,
- &qs_ata_sht);
+ rc = ata_host_activate(host, pdev->irq, qs_intr, IRQF_SHARED,
+ &qs_ata_sht);
+ if (rc)
+ qs_host_stop(host);
+
+ return rc;
}
module_pci_driver(qs_ata_pci_driver);
diff --git a/include/linux/libata.h b/include/linux/libata.h
index 313e96173b19..4625b2eea294 100644
--- a/include/linux/libata.h
+++ b/include/linux/libata.h
@@ -1147,6 +1147,7 @@ extern struct ata_host *ata_host_alloc_pinfo(struct device *dev,
extern void ata_host_get(struct ata_host *host);
extern void ata_host_put(struct ata_host *host);
extern int ata_host_start(struct ata_host *host);
+void ata_host_undo_start(struct ata_host *host);
extern int ata_host_register(struct ata_host *host,
const struct scsi_host_template *sht);
extern int ata_host_activate(struct ata_host *host, int irq,
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v5 5/6] ata: libata: Do not leave ata_host_stop() registered when activation fails
2026-09-15 8:41 ` [PATCH v5 5/6] ata: libata: Do not leave ata_host_stop() registered when activation fails Niklas Cassel
@ 2026-09-15 8:52 ` Damien Le Moal
0 siblings, 0 replies; 12+ messages in thread
From: Damien Le Moal @ 2026-09-15 8:52 UTC (permalink / raw)
To: Niklas Cassel, Hans de Goede, Brian Norris, Jeff Garzik; +Cc: stable, linux-ide
On 2026/09/15 15:41, Niklas Cassel wrote:
> ata_host_start() registers ata_host_stop() as a devres action as soon as
> it has succeeded, which hands the release of the host resources over to
> devres: ->port_stop() and ->host_stop() are then called by the driver
> core when probe() fails.
>
> ata_host_activate() and ahci_host_activate_multi_irqs() can both fail
> after ata_host_start() has succeeded, e.g. if devm_request_irq() or
> ata_host_register() fails, and they return the error with the devres
> action still registered. A driver which releases the host resources in
> its probe() error path therefore releases them twice: once itself and
> once through ->host_stop().
>
> All ahci-platform drivers are in that situation, e.g. ahci_probe() calls
> ahci_platform_disable_resources() while ahci_host_stop() does the same
> through devres. This gives refcount underflow warnings from the clk,
> regulator and phy cores and, for shared resources, can disable resources
> which are still in use by other devices.
>
> Add ata_host_undo_start(), which stops the ports and drops the devres
> action without calling ->host_stop(), and call it from both activation
> helpers when they fail. Releasing the host resources on failure is then
> always left to the caller, which is what all callers having a probe()
> error path already assume.
>
> This changes the semantics for the drivers which implement ->host_stop()
> while also completely lacking error handling for the activate host call.
> Add activate host error handling for sata_qstor and sata_fsl. For
> sata_fsl this also means that hcr_base and host_priv are now released
> when activating the host fails, which ->host_stop() did not do when
> ata_host_start() itself failed.
>
> Note that ata_pci_sff_activate_host() is deliberately left as is: none of
> its callers releases the host resources in its probe() error path, they
> all rely on ->host_stop() being called by devres, including
> ata_pci_init_one(), which releases the devres group of the host itself.
>
> Fixes: 1896b15eddb4 ("ahci_platform: perform platform exit in host_stop() hook")
> Cc: stable@vger.kernel.org
> Signed-off-by: Niklas Cassel <cassel@kernel.org>
Looks good.
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
--
Damien Le Moal
Western Digital Research
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v5 6/6] ata: libata-core: Fix the ata_host_register() kdoc
2026-09-15 8:41 [PATCH v5 0/6] ata: Do not release the host resources twice on probe() failure Niklas Cassel
` (4 preceding siblings ...)
2026-09-15 8:41 ` [PATCH v5 5/6] ata: libata: Do not leave ata_host_stop() registered when activation fails Niklas Cassel
@ 2026-09-15 8:41 ` Niklas Cassel
2026-09-15 9:58 ` [PATCH v5 0/6] ata: Do not release the host resources twice on probe() failure Niklas Cassel
6 siblings, 0 replies; 12+ messages in thread
From: Niklas Cassel @ 2026-09-15 8:41 UTC (permalink / raw)
To: Damien Le Moal, Niklas Cassel; +Cc: Brian Norris, linux-ide
ata_host_register() does not start the ports of the host, it requires
them to have been started already:
/* host must have been started */
if (!(host->flags & ATA_HOST_STARTED)) {
dev_err(host->dev, "BUG: trying to register unstarted host\n");
WARN_ON(1);
return -EINVAL;
}
Fix the kdoc accordingly, and fix the grammar of the last sentence while
at it.
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Signed-off-by: Niklas Cassel <cassel@kernel.org>
---
drivers/ata/libata-core.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c
index 197f5d6e93ce..682c58889715 100644
--- a/drivers/ata/libata-core.c
+++ b/drivers/ata/libata-core.c
@@ -6237,9 +6237,9 @@ static void async_port_probe(void *data, async_cookie_t cookie)
* @sht: template for SCSI host
*
* Register initialized ATA host. @host is allocated using
- * ata_host_alloc() and fully initialized by LLD. This function
- * starts ports, registers @host with ATA and SCSI layers and
- * probe registered devices.
+ * ata_host_alloc(), fully initialized by the LLD and started using
+ * ata_host_start(). This function registers @host with the ATA and
+ * SCSI layers and probes the registered devices.
*
* On failure, the host remains started, i.e. the devres action
* registered by ata_host_start() is kept, so ->host_stop() is called by
--
2.55.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* Re: [PATCH v5 0/6] ata: Do not release the host resources twice on probe() failure
2026-09-15 8:41 [PATCH v5 0/6] ata: Do not release the host resources twice on probe() failure Niklas Cassel
` (5 preceding siblings ...)
2026-09-15 8:41 ` [PATCH v5 6/6] ata: libata-core: Fix the ata_host_register() kdoc Niklas Cassel
@ 2026-09-15 9:58 ` Niklas Cassel
6 siblings, 0 replies; 12+ messages in thread
From: Niklas Cassel @ 2026-09-15 9:58 UTC (permalink / raw)
To: Patrice Chotard, Damien Le Moal, Hans de Goede, Philipp Zabel,
Alexandre Torgue, Lee Jones, Tejun Heo, Sergei Shtylyov,
Baokun Li, Dan Williams, Brian Norris, Niklas Cassel
Cc: Damien Le Moal, linux-arm-kernel, linux-ide
On Tue, 15 Sep 2026 10:41:26 +0200, Niklas Cassel wrote:
> ata_host_start() registers ata_host_stop() as a devres action as soon as
> it has succeeded:
>
> if (have_stop) {
> start_dr = devres_alloc(ata_host_stop, 0, GFP_KERNEL);
> ...
> if (start_dr)
> devres_add(host->dev, start_dr);
> host->flags |= ATA_HOST_STARTED;
>
> [...]
Applied to libata/linux.git (for-7.4), thanks!
[1/6] ata: ahci_st: Do not ignore errors when getting the reset controls
https://git.kernel.org/libata/linux/c/c74ee923
[2/6] ata: ahci_st: Assert the power down reset in the probe() error path
https://git.kernel.org/libata/linux/c/91e0a745
[3/6] ata: sata_fsl: Fix use-after-free of host_priv on probe() failure
https://git.kernel.org/libata/linux/c/70b11967
[4/6] ata: libahci: Free the IRQs when activating a multi-IRQ host fails
https://git.kernel.org/libata/linux/c/34a4a344
[5/6] ata: libata: Do not leave ata_host_stop() registered when activation fails
https://git.kernel.org/libata/linux/c/d2f97700
[6/6] ata: libata-core: Fix the ata_host_register() kdoc
https://git.kernel.org/libata/linux/c/52d19459
Kind regards,
Niklas
^ permalink raw reply [flat|nested] 12+ messages in thread