diff options
author | Tejun Heo <htejun@gmail.com> | 2010-09-06 17:57:14 +0200 |
---|---|---|
committer | Jeff Garzik <jgarzik@redhat.com> | 2010-10-21 20:21:05 -0400 |
commit | c0c362b60e259e3480a36ef70280d545818844f0 (patch) | |
tree | d9871b719cd76f9f683278f938662e080a6ad9d7 /drivers/ata/libata-core.c | |
parent | 97750cebb3000a9cc08f8ce8dc8c7143be7d7201 (diff) | |
download | linux-stable-c0c362b60e259e3480a36ef70280d545818844f0.tar.gz linux-stable-c0c362b60e259e3480a36ef70280d545818844f0.tar.bz2 linux-stable-c0c362b60e259e3480a36ef70280d545818844f0.zip |
libata: implement cross-port EH exclusion
In libata, the non-EH code paths should always take and release
ap->lock explicitly when accessing hardware or shared data structures.
However, once EH is active, it's assumed that the port is owned by EH
and EH methods don't explicitly take ap->lock unless race from irq
handler or other code paths are expected. However, libata EH didn't
guarantee exclusion among EHs for ports of the same host. IOW,
multiple EHs may execute in parallel on multiple ports of the same
controller.
In many cases, especially in SATA, the ports are completely
independent of each other and this doesn't cause problems; however,
there are cases where different ports share the same resource, which
lead to obscure timing related bugs such as the one fixed by commit
213373cf (ata_piix: fix locking around SIDPR access).
This patch implements exclusion among EHs of the same host. When EH
begins, it acquires per-host EH ownership by calling ata_eh_acquire().
When EH finishes, the ownership is released by calling
ata_eh_release(). EH ownership is also released whenever the EH
thread goes to sleep from ata_msleep() or explicitly and reacquired
after waking up.
This ensures that while EH is actively accessing the hardware, it has
exclusive access to it while allowing EHs to interleave and progress
in parallel as they hit waiting stages, which dominate the time spent
in EH. This achieves cross-port EH exclusion without pervasive and
fragile changes while still allowing parallel EH for the most part.
This was first reported by yuanding02@gmail.com more than three years
ago in the following bugzilla. :-)
https://bugzilla.kernel.org/show_bug.cgi?id=8223
Signed-off-by: Tejun Heo <tj@kernel.org>
Cc: Alan Cox <alan@lxorguk.ukuu.org.uk>
Reported-by: yuanding02@gmail.com
Signed-off-by: Jeff Garzik <jgarzik@redhat.com>
Diffstat (limited to 'drivers/ata/libata-core.c')
-rw-r--r-- | drivers/ata/libata-core.c | 30 |
1 files changed, 30 insertions, 0 deletions
diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index 42d9ce29f50d..7f77c67d267c 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -1628,8 +1628,14 @@ unsigned ata_exec_internal_sg(struct ata_device *dev, } } + if (ap->ops->error_handler) + ata_eh_release(ap); + rc = wait_for_completion_timeout(&wait, msecs_to_jiffies(timeout)); + if (ap->ops->error_handler) + ata_eh_acquire(ap); + ata_sff_flush_pio_task(ap); if (!rc) { @@ -5570,6 +5576,7 @@ struct ata_host *ata_host_alloc(struct device *dev, int max_ports) dev_set_drvdata(dev, host); spin_lock_init(&host->lock); + mutex_init(&host->eh_mutex); host->dev = dev; host->n_ports = max_ports; @@ -5867,6 +5874,7 @@ void ata_host_init(struct ata_host *host, struct device *dev, unsigned long flags, struct ata_port_operations *ops) { spin_lock_init(&host->lock); + mutex_init(&host->eh_mutex); host->dev = dev; host->flags = flags; host->ops = ops; @@ -6483,9 +6491,31 @@ int ata_ratelimit(void) return __ratelimit(&ratelimit); } +/** + * ata_msleep - ATA EH owner aware msleep + * @ap: ATA port to attribute the sleep to + * @msecs: duration to sleep in milliseconds + * + * Sleeps @msecs. If the current task is owner of @ap's EH, the + * ownership is released before going to sleep and reacquired + * after the sleep is complete. IOW, other ports sharing the + * @ap->host will be allowed to own the EH while this task is + * sleeping. + * + * LOCKING: + * Might sleep. + */ void ata_msleep(struct ata_port *ap, unsigned int msecs) { + bool owns_eh = ap && ap->host->eh_owner == current; + + if (owns_eh) + ata_eh_release(ap); + msleep(msecs); + + if (owns_eh) + ata_eh_acquire(ap); } /** |