* [PATCH v3 0/3] ata: Do not release the host resources twice on probe() failure
@ 2026-09-10 12:19 Niklas Cassel
2026-09-10 12:19 ` [PATCH v3 1/3] ata: ahci_st: Assert the power down reset in the probe() error path Niklas Cassel
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Niklas Cassel @ 2026-09-10 12:19 UTC (permalink / raw)
To: Patrice Chotard, Damien Le Moal, Niklas Cassel, Hans de Goede,
Philipp Zabel, Tejun Heo, Alexandre Torgue, Lee Jones,
Brian Norris, Jeff Garzik
Cc: linux-arm-kernel, linux-ide
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;
From that point on, releasing the host resources is owned by devres: when
probe() fails, the driver core calls devres_release_all(), which calls
ata_host_stop(), which calls ->port_stop() and ->host_stop().
ata_host_activate() and ahci_host_activate_multi_irqs() can however fail
after ata_host_start() has succeeded - devm_kasprintf(),
devm_request_irq() (a shared IRQ conflict) and ata_host_register()
(scsi_add_host(), ata_tport_add()) can all fail - and they return the
error with the devres action still registered. Since the caller cannot
tell whether ata_host_start() succeeded, and since it has to release the
resources for the failures happening before that, all the ahci-platform
drivers release the host resources in their probe() error path, e.g.
ahci_probe() calls ahci_platform_disable_resources() while
ahci_host_stop() does the same through devres.
The clocks, regulators, resets and PHYs of the host are therefore
released twice, which 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.
Patch 2 adds ata_host_undo_start(), which stops the ports and drops the
devres action without calling ->host_stop(), and calls it from both
activation helpers when they fail, so that "on failure, the caller
releases what it acquired" holds for all of them. sata_qstor and sata_fsl
are the only drivers which implement ->host_stop() while having no error
handling at all for the activate host call, so they get some.
Patch 1 has to come first: ahci_st is the only ahci-platform driver whose
->host_stop() does more than its probe() error path, as it also asserts
the "pwr-dwn" reset. Without patch 1, patch 2 would silently stop that
reset from being asserted when activating the host fails.
Patch 3 is an unrelated kdoc fix that I noticed while documenting the
above.
Changes since v2:
- Do not take the host teardown back in ata_pci_sff_activate_host(), and
drop the sata_nv change which went with it. None of its callers
releases the host resources itself: ata_piix, pata_rdc and
ata_pci_init_one() all rely on ->host_stop() being called through
devres, ata_pci_init_one() by releasing the devres group of the host.
- Document who releases the host resources on failure in the kdoc of
ata_host_activate(), ata_host_register(), ata_pci_sff_activate_host(),
ahci_host_activate() and ahci_platform_init_host().
- New patch 1, so that patch 2 does not stop ahci_st from asserting its
"pwr-dwn" reset on probe() failure.
- New patch 3.
Niklas Cassel (3):
ata: ahci_st: Assert the power down reset in the probe() error path
ata: libata: Do not leave ata_host_stop() registered when activation
fails
ata: libata-core: Fix the ata_host_register() kdoc
drivers/ata/ahci_st.c | 51 ++++++++++++---------
drivers/ata/libahci.c | 17 ++++++-
drivers/ata/libahci_platform.c | 4 ++
drivers/ata/libata-core.c | 81 ++++++++++++++++++++++++++++++----
drivers/ata/libata-sff.c | 5 +++
drivers/ata/sata_fsl.c | 8 +++-
drivers/ata/sata_qstor.c | 8 +++-
include/linux/libata.h | 1 +
8 files changed, 139 insertions(+), 36 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v3 1/3] ata: ahci_st: Assert the power down reset in the probe() error path
2026-09-10 12:19 [PATCH v3 0/3] ata: Do not release the host resources twice on probe() failure Niklas Cassel
@ 2026-09-10 12:19 ` Niklas Cassel
2026-09-10 12:19 ` [PATCH v3 2/3] ata: libata: Do not leave ata_host_stop() registered when activation fails Niklas Cassel
2026-09-10 12:19 ` [PATCH v3 3/3] ata: libata-core: Fix the ata_host_register() kdoc Niklas Cassel
2 siblings, 0 replies; 5+ messages in thread
From: Niklas Cassel @ 2026-09-10 12:19 UTC (permalink / raw)
To: Patrice Chotard, Damien Le Moal, Niklas Cassel, Philipp Zabel,
Lee Jones, Alexandre Torgue, Tejun Heo
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 from both
probe() error paths. 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
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 4336c8a6e208..39ebedf93344 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);
}
@@ -162,38 +171,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] 5+ messages in thread
* [PATCH v3 2/3] ata: libata: Do not leave ata_host_stop() registered when activation fails
2026-09-10 12:19 [PATCH v3 0/3] ata: Do not release the host resources twice on probe() failure Niklas Cassel
2026-09-10 12:19 ` [PATCH v3 1/3] ata: ahci_st: Assert the power down reset in the probe() error path Niklas Cassel
@ 2026-09-10 12:19 ` Niklas Cassel
2026-09-10 12:33 ` sashiko-bot
2026-09-10 12:19 ` [PATCH v3 3/3] ata: libata-core: Fix the ata_host_register() kdoc Niklas Cassel
2 siblings, 1 reply; 5+ messages in thread
From: Niklas Cassel @ 2026-09-10 12:19 UTC (permalink / raw)
To: Damien Le Moal, Niklas Cassel, Hans de Goede, Jeff Garzik,
Brian Norris
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.
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 | 17 +++++++-
drivers/ata/libahci_platform.c | 4 ++
drivers/ata/libata-core.c | 75 +++++++++++++++++++++++++++++++---
drivers/ata/libata-sff.c | 5 +++
drivers/ata/sata_fsl.c | 8 +++-
drivers/ata/sata_qstor.c | 8 +++-
include/linux/libata.h | 1 +
7 files changed, 106 insertions(+), 12 deletions(-)
diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c
index 6d72eb017b49..7260adde8f7d 100644
--- a/drivers/ata/libahci.c
+++ b/drivers/ata/libahci.c
@@ -2723,11 +2723,20 @@ static int ahci_host_activate_multi_irqs(struct ata_host *host,
0, pp->irq_desc, host->ports[i]);
if (rc)
- return rc;
+ goto undo_start;
ata_port_desc_misc(host->ports[i], irq);
}
- return ata_host_register(host, sht);
+ rc = ata_host_register(host, sht);
+ if (rc)
+ goto undo_start;
+
+ return 0;
+
+undo_start:
+ ata_host_undo_start(host);
+
+ return rc;
}
/**
@@ -2735,6 +2744,10 @@ static int ahci_host_activate_multi_irqs(struct ata_host *host,
* @host: target ATA host
* @sht: scsi_host_template to use when registering the host
*
+ * 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).
*
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..5b8dc89ed131 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) {
+ /* if failed, just free the IRQ and leave ports alone */
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 70b210afd291..299b2c886cbe 100644
--- a/drivers/ata/sata_fsl.c
+++ b/drivers/ata/sata_fsl.c
@@ -1490,8 +1490,12 @@ 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) {
+ sata_fsl_host_stop(host);
+ return retval;
+ }
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..b1debcef4947 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);
+extern 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] 5+ messages in thread
* [PATCH v3 3/3] ata: libata-core: Fix the ata_host_register() kdoc
2026-09-10 12:19 [PATCH v3 0/3] ata: Do not release the host resources twice on probe() failure Niklas Cassel
2026-09-10 12:19 ` [PATCH v3 1/3] ata: ahci_st: Assert the power down reset in the probe() error path Niklas Cassel
2026-09-10 12:19 ` [PATCH v3 2/3] ata: libata: Do not leave ata_host_stop() registered when activation fails Niklas Cassel
@ 2026-09-10 12:19 ` Niklas Cassel
2 siblings, 0 replies; 5+ messages in thread
From: Niklas Cassel @ 2026-09-10 12:19 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.
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 5b8dc89ed131..eeb7a33c2c51 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] 5+ messages in thread
* Re: [PATCH v3 2/3] ata: libata: Do not leave ata_host_stop() registered when activation fails
2026-09-10 12:19 ` [PATCH v3 2/3] ata: libata: Do not leave ata_host_stop() registered when activation fails Niklas Cassel
@ 2026-09-10 12:33 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-09-10 12:33 UTC (permalink / raw)
To: Niklas Cassel; +Cc: linux-ide, dlemoal, cassel
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [High] Missing explicit IRQ free in ahci_host_activate_multi_irqs() error path introduces an MMIO Use-After-Disable race condition
- [Low] Outdated comment directly contradicts the newly implemented error handling behavior
Pre-existing issues:
- [High] Double free and use-after-free of host_priv and hcr_base in sata_fsl_probe() during sysfs creation failure
--
commit 768c5e5ea22a8a90eb049d1a94799c1933e49e23
Author: Niklas Cassel <cassel@kernel.org>
ata: libata: Do not leave ata_host_stop() registered when activation fails
This patch addresses devres lifecycle issues by introducing
ata_host_undo_start() to correctly undo host start when activation fails.
It drops the devres action to prevent host_stop from being called during
cleanup when the probe caller manually releases resources.
> diff --git a/drivers/ata/libahci.c b/drivers/ata/libahci.c
> index 6d72eb017b498..7260adde8f7df 100644
> --- a/drivers/ata/libahci.c
> +++ b/drivers/ata/libahci.c
> @@ -2723,11 +2723,20 @@ static int ahci_host_activate_multi_irqs(struct ata_host *host,
> 0, pp->irq_desc, host->ports[i]);
>
> if (rc)
> - return rc;
> + goto undo_start;
> ata_port_desc_misc(host->ports[i], irq);
> }
>
> - return ata_host_register(host, sht);
> + rc = ata_host_register(host, sht);
> + if (rc)
> + goto undo_start;
[Severity: High]
Does this missing explicit IRQ free introduce an mmio use-after-disable
race condition?
If ata_host_register() fails here, we jump to undo_start and return an
error without explicitly freeing the IRQs. Because the IRQs are managed
by devm_request_irq(), they won't be freed until devres cleanup runs
after the probe function returns.
During this window, the caller's error path will manually disable the
hardware resources (clocks, regulators, phys), but the hardware
interrupts remain registered with the OS.
If a spurious interrupt fires during this time,
ahci_multi_irqs_intr_hard() could execute and read from unclocked mmio
registers, causing a synchronous external abort and kernel panic.
[ ... ]
> diff --git a/drivers/ata/libahci_platform.c b/drivers/ata/libahci_platform.c
> index 6e072d681341a..b44c0db4a86e6 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.
[Severity: High]
Does this documented teardown order reversal create a dangerous race? By
requiring the caller to manually disable resources before the devres-managed
IRQs are freed, could this open a window where active IRQs point to
disabled hardware?
[ ... ]
> diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c
> index 59e87b987d73e..30e1c3de2acc8 100644
> --- a/drivers/ata/libata-core.c
> +++ b/drivers/ata/libata-core.c
[ ... ]
> @@ -6245,27 +6295,40 @@ int ata_host_activate(struct ata_host *host, int irq,
[ ... ]
> rc = ata_host_register(host, sht);
> - /* if failed, just free the IRQ and leave ports alone */
> - if (rc)
> + if (rc) {
> + /* if failed, just free the IRQ and leave ports alone */
> devm_free_irq(host->dev, irq, host);
> + goto undo_start;
> + }
[Severity: Low]
Is this comment accurate now? The comment explicitly says "leave ports
alone", but we now jump to undo_start, which calls ata_host_undo_start()
to actively stop the ports.
Should the comment be updated to reflect the newly introduced behavior?
[ ... ]
> diff --git a/drivers/ata/sata_fsl.c b/drivers/ata/sata_fsl.c
> index 70b210afd2911..299b2c886cbed 100644
> --- a/drivers/ata/sata_fsl.c
> +++ b/drivers/ata/sata_fsl.c
> @@ -1490,8 +1490,12 @@ 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) {
> + sata_fsl_host_stop(host);
> + return retval;
> + }
[Severity: High]
This isn't a bug introduced by this patch, but does the later portion of
sata_fsl_probe() suffer from a double-free on sysfs creation failure?
If device_create_file() fails after a successful ata_host_activate(),
the code jumps to error_exit_with_cleanup:
sata_fsl_probe() {
...
error_exit_with_cleanup:
if (host)
ata_host_detach(host);
if (hcr_base)
iounmap(hcr_base);
kfree(host_priv);
return retval;
}
Because ata_host_activate() succeeded, ata_host_stop is still queued as
a devres action. When the probe fails and the driver core executes devres
cleanup, it invokes sata_fsl_host_stop(), which does the exact same cleanup:
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);
}
Couldn't this trigger a double-free of host_priv and iounmap of hcr_base,
leading to slub corruption under memory pressure?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260910121900.97676-5-cassel@kernel.org?part=2
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-10 12:33 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-10 12:19 [PATCH v3 0/3] ata: Do not release the host resources twice on probe() failure Niklas Cassel
2026-09-10 12:19 ` [PATCH v3 1/3] ata: ahci_st: Assert the power down reset in the probe() error path Niklas Cassel
2026-09-10 12:19 ` [PATCH v3 2/3] ata: libata: Do not leave ata_host_stop() registered when activation fails Niklas Cassel
2026-09-10 12:33 ` sashiko-bot
2026-09-10 12:19 ` [PATCH v3 3/3] ata: libata-core: Fix the ata_host_register() kdoc Niklas Cassel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox