Skip to content

Commit

Permalink
libata: port and host should be stopped before hardware resources are…
Browse files Browse the repository at this point in the history
… 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: Tejun Heo <htejun@gmail.com>
Cc: Mark Lord <liml@rtr.ca>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
  • Loading branch information
Tejun Heo authored and Jeff Garzik committed Nov 8, 2007
1 parent 1974e20 commit 32ebbc0
Showing 1 changed file with 39 additions and 13 deletions.
52 changes: 39 additions & 13 deletions drivers/ata/libata-core.c
Original file line number Diff line number Diff line change
Expand Up @@ -6821,19 +6821,6 @@ static void ata_host_release(struct device *gendev, void *res)
struct ata_host *host = dev_get_drvdata(gendev);
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++) {
struct ata_port *ap = host->ports[i];

Expand Down Expand Up @@ -6966,6 +6953,24 @@ struct ata_host *ata_host_alloc_pinfo(struct device *dev,
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
* @host: ATA host to start ports for
Expand All @@ -6984,6 +6989,8 @@ struct ata_host *ata_host_alloc_pinfo(struct device *dev,
*/
int ata_host_start(struct ata_host *host)
{
int have_stop = 0;
void *start_dr = NULL;
int i, rc;

if (host->flags & ATA_HOST_STARTED)
Expand All @@ -6995,6 +7002,22 @@ int ata_host_start(struct ata_host *host)
if (!host->ops && !ata_port_is_dummy(ap))
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) {
rc = ap->ops->port_start(ap);
if (rc) {
Expand All @@ -7007,6 +7030,8 @@ int ata_host_start(struct ata_host *host)
ata_eh_freeze_port(ap);
}

if (start_dr)
devres_add(host->dev, start_dr);
host->flags |= ATA_HOST_STARTED;
return 0;

Expand All @@ -7017,6 +7042,7 @@ int ata_host_start(struct ata_host *host)
if (ap->ops->port_stop)
ap->ops->port_stop(ap);
}
devres_free(start_dr);
return rc;
}

Expand Down

0 comments on commit 32ebbc0

Please sign in to comment.