Skip to contentSkip to conversation

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

PATCH Applied Tejun Heo 2 participants 2 messages Nov 8, 2007 libata

lore
Share & tools
lore

Tejun Heo

author
#0 lore raw

PATCH 1/1 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.

---
Jeff, this really needs to get into 2.6.24. Although it hasn't caused any real problem (yet), ahci is one of the affected drivers and it's poking released iomems in its port_ops.

 drivers/ata/libata-core.c |   52 ++++++++++++++++++++++++++++++++++------------
 1 file changed, 39 insertions(+), 13 deletions(-)
drivers/ata/libata-core.c +39 -13
index ec3ce12..07c3444 100644
@@ -6821,19 +6821,6 @@ static void ata_host_release(struct device *gendev, void *res)
 		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];
-
-		if (!ap)
-			continue;
-
 		if (ap->scsi_host)
 			scsi_host_put(ap->scsi_host);
 
@@ -6960,6 +6947,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
@@ -6978,6 +6983,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)
@@ -6989,6 +6996,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) {
@@ -7001,6 +7024,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;
 
@@ -7011,6 +7036,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;
 }
 
Applied
#1 lore raw
Quoted 9 lines · Tejun Heo

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.

Quoted 8 lines

This problem has been spotted by Mark Lord.

---
Jeff, this really needs to get into 2.6.24. Although it hasn't caused any real problem (yet), ahci is one of the affected drivers and it's poking released iomems in its port_ops.

applied