Commit 32ebbc0c authored by Tejun Heo's avatar Tejun Heo Committed by Jeff Garzik

libata: port and host should be stopped before hardware resources are released

Port / host stop calls used to be made from ata_host_release() which
is called after all hardware resources acquired after host allocation
are released.  This is wrong as port and host stop routines often
access the hardware.

Add separate devres for port / host stop which is invoked right after
IRQ is released but with all other hardware resources intact.  The
devres is added iff ->host_stop and/or ->port_stop exist.

This problem has been spotted by Mark Lord.
Signed-off-by: default avatarTejun Heo <htejun@gmail.com>
Cc: Mark Lord <liml@rtr.ca>
Signed-off-by: default avatarJeff Garzik <jeff@garzik.org>
parent 1974e201
...@@ -6821,19 +6821,6 @@ static void ata_host_release(struct device *gendev, void *res) ...@@ -6821,19 +6821,6 @@ static void ata_host_release(struct device *gendev, void *res)
struct ata_host *host = dev_get_drvdata(gendev); struct ata_host *host = dev_get_drvdata(gendev);
int i; int i;
for (i = 0; i < host->n_ports; i++) {
struct ata_port *ap = host->ports[i];
if (!ap)
continue;
if ((host->flags & ATA_HOST_STARTED) && ap->ops->port_stop)
ap->ops->port_stop(ap);
}
if ((host->flags & ATA_HOST_STARTED) && host->ops->host_stop)
host->ops->host_stop(host);
for (i = 0; i < host->n_ports; i++) { for (i = 0; i < host->n_ports; i++) {
struct ata_port *ap = host->ports[i]; struct ata_port *ap = host->ports[i];
...@@ -6966,6 +6953,24 @@ struct ata_host *ata_host_alloc_pinfo(struct device *dev, ...@@ -6966,6 +6953,24 @@ struct ata_host *ata_host_alloc_pinfo(struct device *dev,
return host; return host;
} }
static void ata_host_stop(struct device *gendev, void *res)
{
struct ata_host *host = dev_get_drvdata(gendev);
int i;
WARN_ON(!(host->flags & ATA_HOST_STARTED));
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);
}
if (host->ops->host_stop)
host->ops->host_stop(host);
}
/** /**
* ata_host_start - start and freeze ports of an ATA host * ata_host_start - start and freeze ports of an ATA host
* @host: ATA host to start ports for * @host: ATA host to start ports for
...@@ -6984,6 +6989,8 @@ struct ata_host *ata_host_alloc_pinfo(struct device *dev, ...@@ -6984,6 +6989,8 @@ struct ata_host *ata_host_alloc_pinfo(struct device *dev,
*/ */
int ata_host_start(struct ata_host *host) int ata_host_start(struct ata_host *host)
{ {
int have_stop = 0;
void *start_dr = NULL;
int i, rc; int i, rc;
if (host->flags & ATA_HOST_STARTED) if (host->flags & ATA_HOST_STARTED)
...@@ -6995,6 +7002,22 @@ int ata_host_start(struct ata_host *host) ...@@ -6995,6 +7002,22 @@ int ata_host_start(struct ata_host *host)
if (!host->ops && !ata_port_is_dummy(ap)) if (!host->ops && !ata_port_is_dummy(ap))
host->ops = ap->ops; host->ops = ap->ops;
if (ap->ops->port_stop)
have_stop = 1;
}
if (host->ops->host_stop)
have_stop = 1;
if (have_stop) {
start_dr = devres_alloc(ata_host_stop, 0, GFP_KERNEL);
if (!start_dr)
return -ENOMEM;
}
for (i = 0; i < host->n_ports; i++) {
struct ata_port *ap = host->ports[i];
if (ap->ops->port_start) { if (ap->ops->port_start) {
rc = ap->ops->port_start(ap); rc = ap->ops->port_start(ap);
if (rc) { if (rc) {
...@@ -7007,6 +7030,8 @@ int ata_host_start(struct ata_host *host) ...@@ -7007,6 +7030,8 @@ int ata_host_start(struct ata_host *host)
ata_eh_freeze_port(ap); ata_eh_freeze_port(ap);
} }
if (start_dr)
devres_add(host->dev, start_dr);
host->flags |= ATA_HOST_STARTED; host->flags |= ATA_HOST_STARTED;
return 0; return 0;
...@@ -7017,6 +7042,7 @@ int ata_host_start(struct ata_host *host) ...@@ -7017,6 +7042,7 @@ int ata_host_start(struct ata_host *host)
if (ap->ops->port_stop) if (ap->ops->port_stop)
ap->ops->port_stop(ap); ap->ops->port_stop(ap);
} }
devres_free(start_dr);
return rc; return rc;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment