* [PATCH 02/10] libata-pm: update ata_eh_reset() for PM
2006-05-11 16:43 [PATCHSET 11/11] implement PM support Tejun Heo
2006-05-11 16:43 ` [PATCH 01/10] libata-pm: add PM related constants, fields, ops and update helpers Tejun Heo
@ 2006-05-11 16:43 ` Tejun Heo
2006-05-11 16:43 ` [PATCH 04/10] libata-pm: hook PM support and enable it Tejun Heo
` (7 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Tejun Heo @ 2006-05-11 16:43 UTC (permalink / raw)
To: jgarzik, alan, axboe, albertcc, forrest.zhao, efalk, linux-ide; +Cc: Tejun Heo
PM always requires SRST to be enabled. Also, hardreset reports
classification code from the first device when PM is attached. Update
ata_eh_reset() such that followup softreset is performed if the
controller is PM capable.
---
drivers/scsi/libata-eh.c | 10 +++++++---
1 files changed, 7 insertions(+), 3 deletions(-)
dc71d3ff3a4334c93936e0a1320afd973c6f1312
diff --git a/drivers/scsi/libata-eh.c b/drivers/scsi/libata-eh.c
index d62a529..3ad8c01 100644
--- a/drivers/scsi/libata-eh.c
+++ b/drivers/scsi/libata-eh.c
@@ -1420,13 +1420,16 @@ static int ata_do_reset(struct ata_link
return 0;
}
-static int ata_eh_followup_srst_needed(int rc, int classify,
+static int ata_eh_followup_srst_needed(struct ata_port *ap,
+ int rc, int classify,
const unsigned int *classes)
{
if (rc == -EAGAIN)
return 1;
if (rc != 0)
return 0;
+ if (ap->flags & ATA_FLAG_PM)
+ return 1;
if (classify && classes[0] == ATA_DEV_UNKNOWN)
return 1;
return 0;
@@ -1436,10 +1439,11 @@ int ata_eh_reset(struct ata_link *link,
ata_prereset_fn_t prereset, ata_reset_fn_t softreset,
ata_reset_fn_t hardreset, ata_postreset_fn_t postreset)
{
+ struct ata_port *ap = link->ap;
struct ata_eh_context *ehc = &link->eh_context;
unsigned int *classes = ehc->classes;
int tries = link->reset_tries;
- int verbose = !(link->ap->flags & ATA_FLAG_LOADING);
+ int verbose = !(ap->flags & ATA_FLAG_LOADING);
unsigned int action;
ata_reset_fn_t reset;
int did_followup_srst, rc;
@@ -1493,7 +1497,7 @@ int ata_eh_reset(struct ata_link *link,
did_followup_srst = 0;
if (reset == hardreset &&
- ata_eh_followup_srst_needed(rc, classify, classes)) {
+ ata_eh_followup_srst_needed(ap, rc, classify, classes)) {
/* okay, let's do follow-up softreset */
did_followup_srst = 1;
reset = softreset;
--
1.2.4
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 06/10] sata_sil24: add PM related constants
2006-05-11 16:43 [PATCHSET 11/11] implement PM support Tejun Heo
` (3 preceding siblings ...)
2006-05-11 16:43 ` [PATCH 07/10] sata_sil24: separate out sil24_exec_polled_cmd() Tejun Heo
@ 2006-05-11 16:43 ` Tejun Heo
2006-05-11 16:43 ` [PATCH 05/10] sata_sil24: rename PORT_CS_RESUME to PORT_CS_PM_RESUME Tejun Heo
` (4 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Tejun Heo @ 2006-05-11 16:43 UTC (permalink / raw)
To: jgarzik, alan, axboe, albertcc, forrest.zhao, efalk, linux-ide; +Cc: Tejun Heo
Add PM related constants.
---
drivers/scsi/sata_sil24.c | 5 +++++
1 files changed, 5 insertions(+), 0 deletions(-)
f7d8e76d5f80de240710b14018ea6372764bc8eb
diff --git a/drivers/scsi/sata_sil24.c b/drivers/scsi/sata_sil24.c
index e38e1fc..dfbe750 100644
--- a/drivers/scsi/sata_sil24.c
+++ b/drivers/scsi/sata_sil24.c
@@ -103,6 +103,10 @@ enum {
PORT_LRAM_SLOT_SZ = 0x0080, /* 32 bytes PRB + 2 SGE, ACT... */
PORT_PM = 0x0f80, /* 8 bytes PM * 16 (128 bytes) */
+ PORT_PM_STATUS = 0x0000, /* port device status offset */
+ PORT_PM_QACTIVE = 0x0004, /* port device QActive offset */
+ PORT_PM_SIZE = 0x0008, /* 8 bytes per PM */
+
/* 32 bit regs */
PORT_CTRL_STAT = 0x1000, /* write: ctrl-set, read: stat */
PORT_CTRL_CLR = 0x1004, /* write: ctrl-clear */
@@ -125,6 +129,7 @@ enum {
PORT_PHY_CFG = 0x1050,
PORT_SLOT_STAT = 0x1800,
PORT_CMD_ACTIVATE = 0x1c00, /* 64 bit cmd activate * 31 (248 bytes) */
+ PORT_CONTEXT = 0x1e04,
PORT_EXEC_DIAG = 0x1e00, /* 32bit exec diag * 16 (64 bytes, 0-10 used on 3124) */
PORT_PSD_DIAG = 0x1e40, /* 32bit psd diag * 16 (64 bytes, 0-8 used on 3124) */
PORT_SCONTROL = 0x1f00,
--
1.2.4
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 04/10] libata-pm: hook PM support and enable it
2006-05-11 16:43 [PATCHSET 11/11] implement PM support Tejun Heo
2006-05-11 16:43 ` [PATCH 01/10] libata-pm: add PM related constants, fields, ops and update helpers Tejun Heo
2006-05-11 16:43 ` [PATCH 02/10] libata-pm: update ata_eh_reset() for PM Tejun Heo
@ 2006-05-11 16:43 ` Tejun Heo
2006-05-11 16:43 ` [PATCH 07/10] sata_sil24: separate out sil24_exec_polled_cmd() Tejun Heo
` (6 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Tejun Heo @ 2006-05-11 16:43 UTC (permalink / raw)
To: jgarzik, alan, axboe, albertcc, forrest.zhao, efalk, linux-ide; +Cc: Tejun Heo
Hook PM support into libata and enable it. Connect SCR and probing
functions, and update ata_dev_classify() to detect PM.
---
drivers/scsi/libata-core.c | 92 ++++++++++++++++++++++++++++++--------------
drivers/scsi/libata-eh.c | 15 +++++++
2 files changed, 78 insertions(+), 29 deletions(-)
281a766bd690af3ec5d29fcdba68c59ce6e0186b
diff --git a/drivers/scsi/libata-core.c b/drivers/scsi/libata-core.c
index 133a6e6..2cea289 100644
--- a/drivers/scsi/libata-core.c
+++ b/drivers/scsi/libata-core.c
@@ -542,29 +542,46 @@ static unsigned int ata_devchk(struct at
* None.
*
* RETURNS:
- * Device type, %ATA_DEV_ATA, %ATA_DEV_ATAPI, or %ATA_DEV_UNKNOWN
- * the event of failure.
+ * Device type, %ATA_DEV_ATA, %ATA_DEV_ATAPI, %ATA_DEV_PM or
+ * %ATA_DEV_UNKNOWN the event of failure.
*/
-
unsigned int ata_dev_classify(const struct ata_taskfile *tf)
{
/* Apple's open source Darwin code hints that some devices only
* put a proper signature into the LBA mid/high registers,
* So, we only check those. It's sufficient for uniqueness.
+ *
+ * ATA/ATAPI-7 (d1532v1r1: Feb. 19, 2003) specified separate
+ * signatures for ATA and ATAPI devices attached on SerialATA,
+ * 0x3c/0xc3 and 0x69/0x96 respectively. However, SerialATA
+ * spec has never mentioned about using different signatures
+ * for ATA/ATAPI devices. Then, Serial ATA II: Port
+ * Multiplier specification began to use 0x69/0x96 to identify
+ * port multpliers. ATA/ATAPI-7 dropped descriptions about
+ * 0x3c/0xc3 and 0x69/0x96 shortly and described them as
+ * reserved for SerialATA.
+ *
+ * We follow the current spec and consider that 0x69/0x96
+ * identifies a port multiplier. If there is an ATAPI device
+ * which returns 0x69/0x96 as its signature, we'll have to
+ * implement 'try PM, then try ATAPI' logic.
*/
-
if (((tf->lbam == 0) && (tf->lbah == 0)) ||
((tf->lbam == 0x3c) && (tf->lbah == 0xc3))) {
DPRINTK("found ATA device by sig\n");
return ATA_DEV_ATA;
}
- if (((tf->lbam == 0x14) && (tf->lbah == 0xeb)) ||
- ((tf->lbam == 0x69) && (tf->lbah == 0x96))) {
+ if ((tf->lbam == 0x14) && (tf->lbah == 0xeb)) {
DPRINTK("found ATAPI device by sig\n");
return ATA_DEV_ATAPI;
}
+ if ((tf->lbam == 0x69) && (tf->lbah == 0x96)) {
+ DPRINTK("found PM device by sig\n");
+ return ATA_DEV_PM;
+ }
+
DPRINTK("unknown device\n");
return ATA_DEV_UNKNOWN;
}
@@ -4851,24 +4868,29 @@ int ata_scr_valid(struct ata_link *link)
* @val: Place to store read value
*
* Read SCR register @reg of @link into *@val. This function is
- * guaranteed to succeed if the cable type of the port is SATA
- * and the port implements ->scr_read.
+ * guaranteed to succeed if @link is ap->link, the cable type of
+ * the port is SATA and the port implements ->scr_read.
*
* LOCKING:
- * None.
+ * None if @link is ap->link. Kernel thread context otherwise.
*
* RETURNS:
* 0 on success, negative errno on failure.
*/
int ata_scr_read(struct ata_link *link, int reg, u32 *val)
{
- struct ata_port *ap = link->ap;
+ if (ata_is_host_link(link)) {
+ struct ata_port *ap = link->ap;
- if (ata_scr_valid(link)) {
- *val = ap->ops->scr_read(ap, reg);
- return 0;
+ if (ata_scr_valid(link)) {
+ *val = ap->ops->scr_read(ap, reg);
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
}
- return -EOPNOTSUPP;
+
+ return ata_pm_scr_read(link, reg, val);
}
/**
@@ -4878,24 +4900,29 @@ int ata_scr_read(struct ata_link *link,
* @val: value to write
*
* Write @val to SCR register @reg of @link. This function is
- * guaranteed to succeed if the cable type of the port is SATA
- * and the port implements ->scr_read.
+ * guaranteed to succeed if @link is ap->link, the cable type of
+ * the port is SATA and the port implements ->scr_read.
*
* LOCKING:
- * None.
+ * None if @link is ap->link. Kernel thread context otherwise.
*
* RETURNS:
* 0 on success, negative errno on failure.
*/
int ata_scr_write(struct ata_link *link, int reg, u32 val)
{
- struct ata_port *ap = link->ap;
+ if (ata_is_host_link(link)) {
+ struct ata_port *ap = link->ap;
- if (ata_scr_valid(link)) {
- ap->ops->scr_write(ap, reg, val);
- return 0;
+ if (ata_scr_valid(link)) {
+ ap->ops->scr_write(ap, reg, val);
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
}
- return -EOPNOTSUPP;
+
+ return ata_pm_scr_write(link, reg, val);
}
/**
@@ -4908,21 +4935,26 @@ int ata_scr_write(struct ata_link *link,
* function performs flush after writing to the register.
*
* LOCKING:
- * None.
+ * None if @link is ap->link. Kernel thread context otherwise.
*
* RETURNS:
* 0 on success, negative errno on failure.
*/
int ata_scr_write_flush(struct ata_link *link, int reg, u32 val)
{
- struct ata_port *ap = link->ap;
+ if (ata_is_host_link(link)) {
+ struct ata_port *ap = link->ap;
- if (ata_scr_valid(link)) {
- ap->ops->scr_write(ap, reg, val);
- ap->ops->scr_read(ap, reg);
- return 0;
+ if (ata_scr_valid(link)) {
+ ap->ops->scr_write(ap, reg, val);
+ ap->ops->scr_read(ap, reg);
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
}
- return -EOPNOTSUPP;
+
+ return ata_pm_scr_write(link, reg, val);
}
/**
@@ -5637,6 +5669,8 @@ int ata_scsi_release(struct Scsi_Host *h
ap->ops->port_disable(ap);
ata_host_remove(ap, 0);
+ kfree(ap->pm_link);
+
DPRINTK("EXIT\n");
return 1;
}
diff --git a/drivers/scsi/libata-eh.c b/drivers/scsi/libata-eh.c
index 3ad8c01..0fd35ed 100644
--- a/drivers/scsi/libata-eh.c
+++ b/drivers/scsi/libata-eh.c
@@ -1569,6 +1569,8 @@ static int ata_eh_revalidate_and_attach(
ata_link_for_each_dev(dev, link) {
if (ehc->i.action & ATA_EH_REVALIDATE && ata_dev_enabled(dev) &&
(!ehc->i.dev || ehc->i.dev == dev)) {
+ WARN_ON(dev->class == ATA_DEV_PM);
+
if (ata_link_offline(dev->link)) {
rc = -EIO;
break;
@@ -1586,6 +1588,13 @@ static int ata_eh_revalidate_and_attach(
ata_class_enabled(ehc->classes[dev->devno])) {
dev->class = ehc->classes[dev->devno];
+ if (dev->class == ATA_DEV_PM) {
+ rc = ata_pm_attach(dev);
+ if (rc)
+ dev->class = ATA_DEV_UNKNOWN;
+ break;
+ }
+
rc = ata_dev_read_id(dev, &dev->class, 1, dev->id);
if (rc == 0)
rc = ata_dev_configure(dev, 1);
@@ -1760,6 +1769,12 @@ int ata_eh_recover(struct ata_port *ap,
if (rc)
goto dev_fail;
+ /* if PM got attached, return, we don't know what to do */
+ if (link->device->class == ATA_DEV_PM) {
+ ehc->i.action = 0;
+ return 0;
+ }
+
/* configure transfer mode if the port has been reset */
if (ehc->flags & ATA_EHC_DID_RESET) {
rc = ata_set_mode(link, &dev);
--
1.2.4
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 05/10] sata_sil24: rename PORT_CS_RESUME to PORT_CS_PM_RESUME
2006-05-11 16:43 [PATCHSET 11/11] implement PM support Tejun Heo
` (4 preceding siblings ...)
2006-05-11 16:43 ` [PATCH 06/10] sata_sil24: add PM related constants Tejun Heo
@ 2006-05-11 16:43 ` Tejun Heo
2006-05-11 16:43 ` [PATCH 03/10] libata-pm: implement Port Multiplier support Tejun Heo
` (3 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Tejun Heo @ 2006-05-11 16:43 UTC (permalink / raw)
To: jgarzik, alan, axboe, albertcc, forrest.zhao, efalk, linux-ide; +Cc: Tejun Heo
Rename PORT_CS_RESUME to PORT_CS_PM_RESUME for clarification.
---
drivers/scsi/sata_sil24.c | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
9509aa53e70d4bb8f4d20c54458637970ec15b62
diff --git a/drivers/scsi/sata_sil24.c b/drivers/scsi/sata_sil24.c
index e3d13d6..e38e1fc 100644
--- a/drivers/scsi/sata_sil24.c
+++ b/drivers/scsi/sata_sil24.c
@@ -138,7 +138,7 @@ enum {
PORT_CS_INIT = (1 << 2), /* port initialize */
PORT_CS_IRQ_WOC = (1 << 3), /* interrupt write one to clear */
PORT_CS_CDB16 = (1 << 5), /* 0=12b cdb, 1=16b cdb */
- PORT_CS_RESUME = (1 << 6), /* port resume */
+ PORT_CS_PM_RESUME = (1 << 6), /* PM resume */
PORT_CS_32BIT_ACTV = (1 << 10), /* 32-bit activation */
PORT_CS_PM_EN = (1 << 13), /* port multiplier enable */
PORT_CS_RDY = (1 << 31), /* port ready to accept commands */
@@ -1128,7 +1128,7 @@ static int sil24_init_one(struct pci_dev
writel(PORT_CS_32BIT_ACTV, port + PORT_CTRL_CLR);
/* Clear port multiplier enable and resume bits */
- writel(PORT_CS_PM_EN | PORT_CS_RESUME, port + PORT_CTRL_CLR);
+ writel(PORT_CS_PM_EN | PORT_CS_PM_RESUME, port + PORT_CTRL_CLR);
}
/* Turn on interrupts */
--
1.2.4
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCHSET 11/11] implement PM support
@ 2006-05-11 16:43 Tejun Heo
2006-05-11 16:43 ` [PATCH 01/10] libata-pm: add PM related constants, fields, ops and update helpers Tejun Heo
` (9 more replies)
0 siblings, 10 replies; 11+ messages in thread
From: Tejun Heo @ 2006-05-11 16:43 UTC (permalink / raw)
To: jgarzik, alan, axboe, albertcc, forrest.zhao, efalk, linux-ide,
htejun
Hello, all. This is the last one, finally.
This is part of patchset series described in [T].
This is the first take of implement-PM-support patchset. This
patchset contains 10 patches and implements PM support.
#01-04: implement libata PM support
#05-10: implement sata_sil24 PM support
At the moment, only sata_sil24 supports PM and sil4726 is the only
tested PM. But expanding the supported controllers and PMs shouldn't
be difficult once more hardwares become available. More info can be
found in the git tree posting [T].
This patchset is against
upstream (acc696d93dcf993dec123d69d599979e1456ffec)
+ [1] prep-for-new-EH patchset
+ [2] new-EH-framework patchset, take 3
+ [3] new-EH-implementation patchset, take 3
+ [4] merge-irq-pio patchset
+ [5] add-NCQ-support patchset, take 3
+ [6] prep-for-hotplug support, take 2
+ [7] prep-LLDDs-for-hotplug-support, take 1
+ [8] add-hotplug-support, take 2
+ [9] implement-ata_link, take 1
+ [10] prep-for-PM-support, take 1
Thanks. Good night.
--
tejun
[T] http://article.gmane.org/gmane.linux.ide/9957
[1] http://article.gmane.org/gmane.linux.ide/9959
[2] http://article.gmane.org/gmane.linux.ide/9984
[3] http://article.gmane.org/gmane.linux.ide/9995
[4] http://article.gmane.org/gmane.linux.ide/10005
[5] http://article.gmane.org/gmane.linux.ide/10011
[6] http://article.gmane.org/gmane.linux.ide/10028
[7] http://article.gmane.org/gmane.linux.ide/10063
[8] http://article.gmane.org/gmane.linux.ide/10073
[9] http://article.gmane.org/gmane.linux.ide/10106
[10] http://article.gmane.org/gmane.linux.ide/10112
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 03/10] libata-pm: implement Port Multiplier support
2006-05-11 16:43 [PATCHSET 11/11] implement PM support Tejun Heo
` (5 preceding siblings ...)
2006-05-11 16:43 ` [PATCH 05/10] sata_sil24: rename PORT_CS_RESUME to PORT_CS_PM_RESUME Tejun Heo
@ 2006-05-11 16:43 ` Tejun Heo
2006-05-11 16:43 ` [PATCH 10/10] sata_sil24: implement PORT_RST Tejun Heo
` (2 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Tejun Heo @ 2006-05-11 16:43 UTC (permalink / raw)
To: jgarzik, alan, axboe, albertcc, forrest.zhao, efalk, linux-ide; +Cc: Tejun Heo
Implement Port Multiplier support. To support PM, a LLDD has to
supply ops->pm_read() and pm_write(). If non-null, pm_attach() and
pm_detach() are called on PM attach and detach, respectively.
pm_read/write() can be called while the port is frozen, so they must
be implemented by polling. This patch supplies several helpers to
ease pm_read/write() implementation.
Also, irq_handler() and error_handler() must be PM aware. Most part
of PM EH can be done by calling ata_pm_do_eh() with appropriate
methods. PM EH uses separate set of reset methods and this patch
implements standard prereset, hardreset and postreset methods.
This patch only implements PM support. The next patch will integrate
PM into the rest of libata and enable it.
---
drivers/scsi/Makefile | 3
drivers/scsi/libata-core.c | 8
drivers/scsi/libata-pm.c | 994 ++++++++++++++++++++++++++++++++++++++++++++
drivers/scsi/libata.h | 6
include/linux/libata.h | 18 +
5 files changed, 1028 insertions(+), 1 deletions(-)
create mode 100644 drivers/scsi/libata-pm.c
4b9ab70e0d3c56ce962ade99cd94b2a82f4b1e25
diff --git a/drivers/scsi/Makefile b/drivers/scsi/Makefile
index 669ff6b..0f5592f 100644
--- a/drivers/scsi/Makefile
+++ b/drivers/scsi/Makefile
@@ -164,7 +164,8 @@ ncr53c8xx-flags-$(CONFIG_SCSI_ZALON) \
CFLAGS_ncr53c8xx.o := $(ncr53c8xx-flags-y) $(ncr53c8xx-flags-m)
zalon7xx-objs := zalon.o ncr53c8xx.o
NCR_Q720_mod-objs := NCR_Q720.o ncr53c8xx.o
-libata-objs := libata-core.o libata-scsi.o libata-bmdma.o libata-eh.o
+libata-objs := libata-core.o libata-scsi.o libata-bmdma.o libata-eh.o \
+ libata-pm.o
oktagon_esp_mod-objs := oktagon_esp.o oktagon_io.o
# Files generated that shall be removed upon make clean
diff --git a/drivers/scsi/libata-core.c b/drivers/scsi/libata-core.c
index 148d84d..133a6e6 100644
--- a/drivers/scsi/libata-core.c
+++ b/drivers/scsi/libata-core.c
@@ -5949,6 +5949,14 @@ EXPORT_SYMBOL_GPL(ata_device_resume);
EXPORT_SYMBOL_GPL(ata_scsi_device_suspend);
EXPORT_SYMBOL_GPL(ata_scsi_device_resume);
+EXPORT_SYMBOL_GPL(ata_pm_read_init_tf);
+EXPORT_SYMBOL_GPL(ata_pm_read_val);
+EXPORT_SYMBOL_GPL(ata_pm_write_init_tf);
+EXPORT_SYMBOL_GPL(ata_pm_std_prereset);
+EXPORT_SYMBOL_GPL(ata_pm_std_hardreset);
+EXPORT_SYMBOL_GPL(ata_pm_std_postreset);
+EXPORT_SYMBOL_GPL(ata_pm_do_eh);
+
EXPORT_SYMBOL_GPL(ata_eng_timeout);
EXPORT_SYMBOL_GPL(ata_port_schedule_eh);
EXPORT_SYMBOL_GPL(ata_link_abort);
diff --git a/drivers/scsi/libata-pm.c b/drivers/scsi/libata-pm.c
new file mode 100644
index 0000000..002d8b2
--- /dev/null
+++ b/drivers/scsi/libata-pm.c
@@ -0,0 +1,994 @@
+/*
+ * libata-pm.c - libata port multiplier support
+ *
+ * Maintained by: Jeff Garzik <jgarzik@pobox.com>
+ * Please ALWAYS copy linux-ide@vger.kernel.org
+ * on emails.
+ *
+ * Copyright 2006 Tejun Heo <htejun@gmail.com>
+ *
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
+ * USA.
+ *
+ *
+ * libata documentation is available via 'make {ps|pdf}docs',
+ * as Documentation/DocBook/libata.*
+ *
+ * Hardware documentation available from http://www.t13.org/ and
+ * http://www.sata-io.org/
+ *
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+
+#include <linux/libata.h>
+
+#include "libata.h"
+
+/**
+ * ata_pm_read_init_tf - initialize TF for PM read
+ * @tf: taskfile to initialize
+ * @dev: PM dev
+ * @pmp: port multiplier port number
+ * @reg: register to read
+ *
+ * Initialize @tf for PM read command.
+ *
+ * LOCKING:
+ * None.
+ */
+void ata_pm_read_init_tf(struct ata_taskfile *tf,
+ struct ata_device *dev, int pmp, int reg)
+{
+ ata_tf_init(dev, tf);
+ tf->command = ATA_CMD_PM_READ;
+ tf->protocol = ATA_PROT_NODATA;
+ tf->flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE;
+ tf->feature = reg;
+ tf->device = pmp;
+}
+
+/**
+ * ata_pm_read_val - extract PM read result from TF
+ * @tf: target TF
+ *
+ * Determine PM read result from @tf.
+ *
+ * LOCKING:
+ * None.
+ */
+u32 ata_pm_read_val(const struct ata_taskfile *tf)
+{
+ return tf->nsect | tf->lbal << 8 | tf->lbam << 16 | tf->lbah << 24;
+}
+
+/**
+ * ata_pm_read_init_tf - initialize TF for PM write
+ * @tf: taskfile to initialize
+ * @dev: PM dev
+ * @pmp: port multiplier port number
+ * @reg: register to read
+ * @val: value to write
+ *
+ * Initialize @tf for PM write command.
+ *
+ * LOCKING:
+ * None.
+ */
+void ata_pm_write_init_tf(struct ata_taskfile *tf,
+ struct ata_device *dev, int pmp, int reg, u32 val)
+{
+ ata_tf_init(dev, tf);
+ tf->command = ATA_CMD_PM_WRITE;
+ tf->protocol = ATA_PROT_NODATA;
+ tf->flags |= ATA_TFLAG_ISADDR | ATA_TFLAG_DEVICE;
+ tf->feature = reg;
+ tf->device = pmp;
+ tf->nsect = val & 0xff;
+ tf->lbal = (val >> 8) & 0xff;
+ tf->lbam = (val >> 16) & 0xff;
+ tf->lbah = (val >> 24) & 0xff;
+}
+
+/**
+ * ata_pm_scr_read - read PSCR
+ * @link: ATA link to read PSCR for
+ * @reg: PSCR to read
+ * @r_val: resulting value
+ *
+ * Read PSCR @reg into @r_val for @link, to be called from
+ * ata_scr_read().
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ *
+ * RETURNS:
+ * 0 on success, -errno on failure.
+ */
+int ata_pm_scr_read(struct ata_link *link, int reg, u32 *r_val)
+{
+ struct ata_port *ap = link->ap;
+ struct ata_device *pm_dev = ap->link.device;
+
+ might_sleep();
+
+ if (reg > ATA_PM_PSCR_CONTROL)
+ return -EINVAL;
+
+ return ap->ops->pm_read(pm_dev, link->pmp, reg, r_val);
+}
+
+/**
+ * ata_pm_scr_write - write PSCR
+ * @link: ATA link to write PSCR for
+ * @reg: PSCR to write
+ * @val: value to be written
+ *
+ * Write @val to PSCR @reg for @link, to be called from
+ * ata_scr_write() and ata_scr_write_flush().
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ *
+ * RETURNS:
+ * 0 on success, -errno on failure.
+ */
+int ata_pm_scr_write(struct ata_link *link, int reg, u32 val)
+{
+ struct ata_port *ap = link->ap;
+ struct ata_device *pm_dev = ap->link.device;
+
+ might_sleep();
+
+ if (reg > ATA_PM_PSCR_CONTROL)
+ return -EINVAL;
+
+ return ap->ops->pm_write(pm_dev, link->pmp, reg, val);
+}
+
+/**
+ * ata_pm_std_prereset - prepare PM link for reset
+ * @link: link to be reset
+ *
+ * @link is about to be reset. Initialize it.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep)
+ *
+ * RETURNS:
+ * 0 on success, -errno otherwise.
+ */
+int ata_pm_std_prereset(struct ata_link *link)
+{
+ int rc;
+
+ /* if we're about to do hardreset, don't bother */
+ if (link->eh_context.i.action & ATA_EH_HARDRESET)
+ return 0;
+
+ /* resume link */
+ rc = sata_link_resume(link, 1);
+ if (rc) {
+ /* phy resume failed */
+ ata_link_printk(link, KERN_WARNING, "failed to resume link "
+ "for reset (errno=%d)\n", rc);
+ return rc;
+ }
+
+ /* clear SError bits including .X which blocks the port when set */
+ rc = ata_scr_write(link, SCR_ERROR, 0xffffffff);
+ if (rc) {
+ ata_link_printk(link, KERN_ERR,
+ "failed to clear SError (errno=%d)\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+/**
+ * ata_pm_std_hardreset - standard hardreset method for PM link
+ * @link: link to be reset
+ *
+ * Hardreset PM port @link. Note that this function doesn't wait
+ * for BSY clearance. There simply isn't a generic way to wait
+ * the event. Instead, this function return -EAGAIN thus telling
+ * libata-EH to followup with softreset.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep)
+ *
+ * RETURNS:
+ * 0 on success, -errno otherwise.
+ */
+int ata_pm_std_hardreset(struct ata_link *link, unsigned int *class)
+{
+ int rc;
+
+ DPRINTK("ENTER\n");
+
+ /* reset */
+ rc = sata_link_hardreset(link);
+ if (rc)
+ goto out;
+
+ /* clear SError bits including .X which blocks the port when set */
+ rc = ata_scr_write(link, SCR_ERROR, 0xffffffff);
+ if (rc) {
+ ata_link_printk(link, KERN_ERR, "failed to clear SError "
+ "during hardreset (errno=%d)\n", rc);
+ goto out;
+ }
+
+ /* follow up with softreset so that we can wait for !BSY */
+ rc = -EAGAIN;
+ out:
+ DPRINTK("EXIT, rc=%d\n", rc);
+ return rc;
+}
+
+/**
+ * ata_std_postreset - standard postreset method for PM link
+ * @link: the target ata_link
+ * @classes: classes of attached devices
+ *
+ * This function is invoked after a successful reset. Note that
+ * the device might have been reset more than once using
+ * different reset methods before postreset is invoked.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep)
+ */
+void ata_pm_std_postreset(struct ata_link *link, unsigned int *class)
+{
+ u32 serror;
+
+ DPRINTK("ENTER\n");
+
+ /* clear SError */
+ if (ata_scr_read(link, SCR_ERROR, &serror) == 0)
+ ata_scr_write(link, SCR_ERROR, serror);
+
+ /* print link status */
+ sata_print_link_status(link);
+
+ DPRINTK("EXIT\n");
+}
+
+/**
+ * ata_pm_read_gscr - read GSCR block of SATA PM
+ * @dev: PM device
+ * @gscr: buffer to read GSCR block into
+ *
+ * Read selected PM GSCRs from the PM at @dev. This will serve
+ * as configuration and identification info for the PM.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ *
+ * RETURNS:
+ * 0 on success, -errno on failure.
+ */
+static int ata_pm_read_gscr(struct ata_device *dev, u32 *gscr)
+{
+ static const int gscr_to_read[] = { 0, 1, 2, 32, 33, 64, 96 };
+ struct ata_port *ap = dev->link->ap;
+ int i, rc;
+
+ for (i = 0; i < ARRAY_SIZE(gscr_to_read); i++) {
+ int reg = gscr_to_read[i];
+
+ rc = ap->ops->pm_read(dev, ATA_PM_CTRL_PORT, reg, &gscr[reg]);
+ if (rc) {
+ ata_dev_printk(dev, KERN_ERR, "failed to read "
+ "PM GSCR[%d] (errno=%d)\n", reg, rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static const char *ata_pm_spec_rev_str(const u32 *gscr)
+{
+ u32 rev = gscr[ATA_PM_GSCR_REV];
+
+ if (rev & (1 << 2))
+ return "1.1";
+ if (rev & (1 << 1))
+ return "1.0";
+ return "<unknown>";
+}
+
+static void ata_pm_quirks(u32 *gscr, int *nr_ports, unsigned int *link_flags)
+{
+ u16 vendor = ata_pm_gscr_vendor(gscr);
+ u16 devid = ata_pm_gscr_devid(gscr);
+
+ /* Sil4726 reports two extra ports for configuration and
+ * enclosure processor, both of which are ignored ATM. Also,
+ * it requires hardreset to resume PM links.
+ */
+ if (vendor == 0x1095 && devid == 0x4726) {
+ *nr_ports -= 2;
+ *link_flags |= ATA_LFLAG_HRST_TO_RESUME;
+ }
+}
+
+static int ata_pm_configure(struct ata_device *dev, int *r_nr_ports,
+ unsigned int *r_link_flags, int print_info)
+{
+ struct ata_link *link = dev->link;
+ struct ata_port *ap = link->ap;
+ u32 *gscr = dev->gscr;
+ unsigned int link_flags;
+ const char *reason;
+ int nr_ports, rc;
+
+ nr_ports = ata_pm_gscr_ports(gscr);
+ link_flags = 0;
+
+ ata_pm_quirks(gscr, &nr_ports, &link_flags);
+
+ if (nr_ports <= 0 || nr_ports > ATA_PM_MAX_PORTS) {
+ rc = -EINVAL;
+ reason = "invalid nr_ports";
+ goto fail;
+ }
+
+ rc = ap->ops->pm_write(dev, link->pmp, ATA_PM_GSCR_ERROR_EN,
+ SERR_PHYRDY_CHG | SERR_DEV_XCHG);
+ if (rc) {
+ reason = "failed to write GSCR_ERROR_EN";
+ goto fail;
+ }
+
+ if (ap->flags & ATA_FLAG_SDB_NOTIFY &&
+ gscr[ATA_PM_GSCR_FEAT] & ATA_PM_FEAT_NOTIFY) {
+ gscr[ATA_PM_GSCR_FEAT_EN] |= ATA_PM_FEAT_NOTIFY;
+
+ rc = ap->ops->pm_write(dev, link->pmp, ATA_PM_GSCR_FEAT_EN,
+ gscr[ATA_PM_GSCR_FEAT_EN]);
+ if (rc) {
+ reason = "failed to write GSCR_FEAT_EN";
+ goto fail;
+ }
+ }
+
+ if (print_info)
+ ata_dev_printk(dev, KERN_INFO, "Port Multiplier %s, "
+ "0x%04x:0x%04x r%d, %d ports, feat 0x%x/0x%x\n",
+ ata_pm_spec_rev_str(gscr),
+ ata_pm_gscr_vendor(gscr),
+ ata_pm_gscr_devid(gscr), ata_pm_gscr_rev(gscr),
+ nr_ports, gscr[ATA_PM_GSCR_FEAT_EN],
+ gscr[ATA_PM_GSCR_FEAT]);
+
+ *r_nr_ports = nr_ports;
+ *r_link_flags = link_flags;
+ return 0;
+
+ fail:
+ ata_dev_printk(dev, KERN_ERR,
+ "failed to configure Port Multiplier (%s)\n", reason);
+ return rc;
+}
+
+static int ata_pm_init_links(struct ata_port *ap, int nr_ports,
+ unsigned int link_flags)
+{
+ struct ata_link *pm_link = ap->pm_link;
+ int i;
+
+ if (!pm_link) {
+ pm_link = kzalloc(sizeof(pm_link[0]) * ATA_PM_MAX_PORTS,
+ GFP_NOIO);
+ if (!pm_link)
+ return -ENOMEM;
+
+ for (i = 0; i < ATA_PM_MAX_PORTS; i++)
+ ata_link_init(ap, &pm_link[i], i);
+
+ ap->pm_link = pm_link;
+ }
+
+ for (i = 0; i < nr_ports; i++) {
+ struct ata_link *link = &pm_link[i];
+ link->flags |= link_flags;
+ link->reset_tries = ATA_EH_PM_RESET_TRIES;
+ ata_link_init_probe(link);
+ }
+
+ return 0;
+}
+
+/**
+ * ata_pm_attach - attach a SATA PM device
+ * @dev: SATA PM device to attach
+ *
+ * Configure and attach SATA PM device @dev. This function is
+ * also responsible for allocating and initializing PM links.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ *
+ * RETURNS:
+ * 0 on success, -errno on failure.
+ */
+int ata_pm_attach(struct ata_device *dev)
+{
+ struct ata_link *link = dev->link;
+ struct ata_port *ap = link->ap;
+ unsigned int link_flags;
+ unsigned long flags;
+ struct ata_link *tlink;
+ int nr_ports, rc;
+
+ /* is it haging off the right place? */
+ if (!(ap->flags & ATA_FLAG_PM)) {
+ ata_dev_printk(dev, KERN_ERR,
+ "host does not support Port Multiplier\n");
+ return -EINVAL;
+ }
+
+ if (!ata_is_host_link(link)) {
+ ata_dev_printk(dev, KERN_ERR,
+ "Port Multipliers cannot be nested\n");
+ return -EINVAL;
+ }
+
+ if (dev->devno) {
+ ata_dev_printk(dev, KERN_ERR,
+ "Port Multiplier must be the first device\n");
+ return -EINVAL;
+ }
+
+ WARN_ON(link->pmp != 0);
+ link->pmp = ATA_PM_CTRL_PORT;
+
+ /* read GSCR block */
+ rc = ata_pm_read_gscr(dev, dev->gscr);
+ if (rc)
+ goto fail;
+
+ /* config PM */
+ rc = ata_pm_configure(dev, &nr_ports, &link_flags, 1);
+ if (rc)
+ goto fail;
+
+ rc = ata_pm_init_links(ap, nr_ports, link_flags);
+ if (rc) {
+ ata_dev_printk(dev, KERN_INFO,
+ "failed to initialize PM links\n");
+ goto fail;
+ }
+
+ /* attach it */
+ spin_lock_irqsave(&ap->host_set->lock, flags);
+ WARN_ON(ap->nr_pm_links);
+ ap->nr_pm_links = nr_ports;
+ spin_unlock_irqrestore(&ap->host_set->lock, flags);
+
+ if (ap->ops->pm_attach)
+ ap->ops->pm_attach(ap);
+
+ ata_port_for_each_link(tlink, ap)
+ ata_link_init_sata_spd_limit(tlink);
+
+ return 0;
+
+ fail:
+ link->pmp = 0;
+ return rc;
+}
+
+/**
+ * ata_pm_detach - detach a SATA PM device
+ * @dev: SATA PM device to detach
+ *
+ * Detach SATA PM device @dev. This function is also responsible
+ * for deconfiguring PM links.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ */
+void ata_pm_detach(struct ata_device *dev)
+{
+ struct ata_link *link = dev->link;
+ struct ata_port *ap = link->ap;
+ struct ata_link *tlink;
+ unsigned long flags;
+
+ ata_dev_printk(dev, KERN_INFO, "Port Multiplier detaching\n");
+
+ WARN_ON(!ata_is_host_link(link) || dev->devno ||
+ link->pmp != ATA_PM_CTRL_PORT);
+
+ if (ap->ops->pm_detach)
+ ap->ops->pm_detach(ap);
+
+ ata_port_for_each_link(tlink, ap)
+ ata_eh_detach_dev(tlink->device);
+
+ spin_lock_irqsave(&ap->host_set->lock, flags);
+ ap->nr_pm_links = 0;
+ link->pmp = 0;
+ spin_unlock_irqrestore(&ap->host_set->lock, flags);
+}
+
+/**
+ * ata_pm_same_pm - determine whether new GSCR matches the configured PM
+ * @dev: PM device to compare against
+ * @new_gscr: GSCR block of the new device
+ *
+ * Compare @new_gscr against @dev and determine whether @dev is
+ * the PM described by @new_gscr.
+ *
+ * LOCKING:
+ * None.
+ *
+ * RETURNS:
+ * 1 if @dev matches @new_gscr, 0 otherwise.
+ */
+static int ata_pm_same_pm(struct ata_device *dev, const u32 *new_gscr)
+{
+ const u32 *old_gscr = dev->gscr;
+ u16 old_vendor, new_vendor, old_devid, new_devid;
+ int old_nr_ports, new_nr_ports;
+
+ old_vendor = ata_pm_gscr_vendor(old_gscr);
+ new_vendor = ata_pm_gscr_vendor(new_gscr);
+ old_devid = ata_pm_gscr_devid(old_gscr);
+ new_devid = ata_pm_gscr_devid(new_gscr);
+ old_nr_ports = ata_pm_gscr_ports(old_gscr);
+ new_nr_ports = ata_pm_gscr_ports(new_gscr);
+
+ if (old_vendor != new_vendor) {
+ ata_dev_printk(dev, KERN_INFO, "Port Multiplier "
+ "vendor mismatch '0x%x' != '0x%x'\n",
+ old_vendor, new_vendor);
+ return 0;
+ }
+
+ if (old_devid != new_devid) {
+ ata_dev_printk(dev, KERN_INFO, "Port Multiplier "
+ "device ID mismatch '0x%x' != '0x%x'\n",
+ old_devid, new_devid);
+ return 0;
+ }
+
+ if (old_nr_ports != new_nr_ports) {
+ ata_dev_printk(dev, KERN_INFO, "Port Multiplier "
+ "nr_ports mismatch '0x%x' != '0x%x'\n",
+ old_nr_ports, new_nr_ports);
+ return 0;
+ }
+
+ return 1;
+}
+
+/**
+ * ata_pm_revalidate - revalidate SATA PM
+ * @dev: PM device to revalidate
+ * @new_class: new class code
+ *
+ * Re-read GSCR block and make sure @dev is still attached to the
+ * port and properly configured.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ *
+ * RETURNS:
+ * 0 on success, -errno otherwise.
+ */
+static int ata_pm_revalidate(struct ata_device *dev, unsigned int new_class)
+{
+ struct ata_link *link = dev->link;
+ struct ata_port *ap = link->ap;
+ u32 *gscr = (void *)ap->sector_buf;
+ unsigned int link_flags;
+ int nr_ports, rc;
+
+ DPRINTK("ENTER\n");
+
+ ata_eh_about_to_do(link, ATA_EH_REVALIDATE);
+
+ if (!ata_dev_enabled(dev)) {
+ rc = -ENODEV;
+ goto fail;
+ }
+
+ /* sometimes wrong class is reported, let it retry */
+ if (ata_class_enabled(new_class) && new_class != ATA_DEV_PM) {
+ rc = -EIO;
+ goto fail;
+ }
+
+ /* read GSCR */
+ rc = ata_pm_read_gscr(dev, gscr);
+ if (rc)
+ goto fail;
+
+ /* is the pm still there? */
+ if (!ata_pm_same_pm(dev, gscr)) {
+ rc = -ENODEV;
+ goto fail;
+ }
+
+ memcpy(dev->gscr, gscr, sizeof(gscr[0]) * ATA_PM_GSCR_DWORDS);
+
+ rc = ata_pm_configure(dev, &nr_ports, &link_flags, 0);
+ if (rc)
+ goto fail;
+
+ if (nr_ports != ap->nr_pm_links) {
+ rc = -ENODEV;
+ goto fail;
+ }
+
+ link->eh_context.i.action &= ~ATA_EH_REVALIDATE;
+
+ DPRINTK("EXIT, rc=0\n");
+ return 0;
+
+ fail:
+ ata_dev_printk(dev, KERN_ERR,
+ "PM revalidation failed (errno=%d)\n", rc);
+ DPRINTK("EXIT, rc=%d\n", rc);
+ return rc;
+}
+
+/**
+ * ata_pm_revalidate_quick - revalidate SATA PM quickly
+ * @dev: PM device to revalidate
+ *
+ * Make sure the attached PM is accessible.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ *
+ * RETURNS:
+ * 0 on success, -errno otherwise.
+ */
+static int ata_pm_revalidate_quick(struct ata_device *dev)
+{
+ struct ata_port *ap = dev->link->ap;
+ u32 prod_id;
+ int rc;
+
+ rc = ap->ops->pm_read(dev, ATA_PM_CTRL_PORT, ATA_PM_GSCR_PROD_ID,
+ &prod_id);
+ if (rc) {
+ ata_dev_printk(dev, KERN_ERR, "failed to read PM product ID\n");
+ return rc;
+ }
+
+ if (prod_id != dev->gscr[ATA_PM_GSCR_PROD_ID]) {
+ ata_dev_printk(dev, KERN_ERR, "PM product ID mismatch\n");
+ /* something weird is going on, request full PM recovery */
+ return -EIO;
+ }
+
+ return 0;
+}
+
+/**
+ * ata_pm_eh_recover_pm - recover PM
+ * @ap: ATA port PM is attached to
+ * @prereset: prereset method (can be NULL)
+ * @softreset: softreset method
+ * @hardreset: hardreset method
+ * @postreset: postreset method (can be NULL)
+ * @link_tries: link tries left
+ *
+ * Recover PM attached to @ap. Recovery procedure is somewhat
+ * similar to that of ata_eh_recover() except that reset should
+ * always be performed in hard->soft sequence and recovery
+ * failure results in PM detachment.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ *
+ * RETURNS:
+ * 0 on success, -errno on failure.
+ */
+static int ata_pm_eh_recover_pm(struct ata_port *ap, ata_prereset_fn_t prereset,
+ ata_reset_fn_t softreset,
+ ata_reset_fn_t hardreset,
+ ata_postreset_fn_t postreset, int *link_tries)
+{
+ struct ata_link *link = &ap->link;
+ struct ata_eh_context *ehc = &link->eh_context;
+ struct ata_device *dev = link->device;
+ int tries = link->reset_tries;
+ int detach = 0, probe = 0, rc = 0;
+
+ DPRINTK("ENTER\n");
+
+ if (dev->flags & ATA_DFLAG_DETACH) {
+ detach = 1;
+ goto fail;
+ }
+
+ retry:
+ ehc->classes[0] = ATA_DEV_UNKNOWN;
+
+ if (ehc->i.action & ATA_EH_RESET_MASK) {
+ struct ata_link *tlink;
+
+ ata_eh_freeze_port(ap);
+
+ /* reset */
+ ehc->i.action = ATA_EH_HARDRESET;
+ rc = ata_eh_reset(link, 0, prereset, softreset, hardreset,
+ postreset);
+ if (rc) {
+ ata_link_printk(link, KERN_ERR,
+ "failed to reset PM, giving up\n");
+ goto fail;
+ }
+
+ ata_eh_thaw_port(ap);
+
+ ata_port_for_each_link(tlink, ap) {
+ struct ata_eh_context *tehc = &tlink->eh_context;
+
+ if (link_tries[tlink->pmp])
+ tehc->i.action = ata_link_resume_action(tlink);
+ else
+ tehc->i.action = 0;
+ }
+ }
+
+ /* If revalidation is requested, revalidate and reconfigure;
+ * otherwise, do quick revalidation.
+ */
+ if (ehc->i.action & ATA_EH_REVALIDATE)
+ rc = ata_pm_revalidate(dev, ehc->classes[0]);
+ else
+ rc = ata_pm_revalidate_quick(dev);
+
+ if (rc == -ENODEV) {
+ probe = 1;
+ goto fail;
+ } else if (rc) {
+ if (--tries) {
+ int sleep = ehc->flags & ATA_EHC_DID_RESET;
+
+ ata_dev_printk(dev, KERN_WARNING,
+ "retrying hardreset%s\n",
+ sleep ? " in 5 secs" : "");
+ if (sleep)
+ ssleep(5);
+ ehc->i.action |= ATA_EH_HARDRESET;
+ goto retry;
+ } else {
+ ata_dev_printk(dev, KERN_ERR, "failed to recover PM "
+ "after %d resets, giving up\n",
+ link->reset_tries);
+ goto fail;
+ }
+ }
+
+ /* okay, PM resurrected */
+ ehc->flags &= ~ATA_EHC_DID_RESET;
+ ehc->i.action = 0;
+
+ DPRINTK("EXIT, rc=0\n");
+ return 0;
+
+ fail:
+ ata_pm_detach(dev);
+ if (probe || detach) {
+ ata_eh_detach_dev(dev);
+ if (probe)
+ ehc->i.probe_mask |= 1;
+ } else
+ ata_dev_disable(dev);
+
+ DPRINTK("EXIT, rc=%d\n", rc);
+ return rc;
+}
+
+/**
+ * ata_pm_eh_recover - recover PM-enabled port
+ * @ap: ATA port to recover
+ * @prereset: prereset method (can be NULL)
+ * @softreset: softreset method
+ * @hardreset: hardreset method
+ * @postreset: postreset method (can be NULL)
+ * @pm_prereset: PM prereset method (can be NULL)
+ * @pm_softreset: PM softreset method (can be NULL)
+ * @pm_hardreset: PM hardreset method (can be NULL)
+ * @pm_postreset: PM postreset method (can be NULL)
+ *
+ * Drive EH recovery operation for PM enable port @ap. This
+ * function recovers host and PM ports with proper retrials and
+ * fallbacks. Actual recovery operations are performed using
+ * ata_eh_recover() and ata_pm_eh_recover_pm().
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ *
+ * RETURNS:
+ * 0 on success, -errno on failure.
+ */
+static int ata_pm_eh_recover(struct ata_port *ap,
+ ata_prereset_fn_t prereset, ata_reset_fn_t softreset,
+ ata_reset_fn_t hardreset, ata_postreset_fn_t postreset,
+ ata_prereset_fn_t pm_prereset, ata_reset_fn_t pm_softreset,
+ ata_reset_fn_t pm_hardreset, ata_postreset_fn_t pm_postreset)
+{
+ int pm_tries, link_tries[ATA_PM_MAX_PORTS];
+ struct ata_link *pm_link = &ap->link;
+ struct ata_device *pm_dev = pm_link->device;
+ struct ata_eh_info *pm_ehi = &pm_link->eh_context.i;
+ struct ata_link *link;
+ struct ata_device *dev;
+ u32 gscr_error;
+ int cnt, rc;
+
+ pm_tries = ATA_EH_PM_TRIES;
+ ata_port_for_each_link(link, ap)
+ link_tries[link->pmp] = ATA_EH_PM_LINK_TRIES;
+
+ retry:
+ /* PM attached? */
+ if (!ap->nr_pm_links) {
+ rc = ata_eh_recover(ap, prereset, softreset, hardreset,
+ postreset, NULL);
+ if (rc) {
+ ata_link_for_each_dev(dev, &ap->link)
+ ata_dev_disable(dev);
+ return rc;
+ }
+
+ if (pm_dev->class != ATA_DEV_PM)
+ return 0;
+
+ /* new PM online */
+ ata_port_for_each_link(link, ap)
+ link_tries[link->pmp] = ATA_EH_PM_LINK_TRIES;
+
+ /* fall through */
+ }
+
+ /* recover pm */
+ rc = ata_pm_eh_recover_pm(ap, prereset, softreset, hardreset, postreset,
+ link_tries);
+ if (rc)
+ goto pm_retry;
+
+ /* recover links */
+ rc = ata_eh_recover(ap, pm_prereset, pm_softreset, pm_hardreset,
+ pm_postreset, &link);
+ if (rc)
+ goto link_retry;
+
+ /* Connection status might have changed while resetting other
+ * links, check ATA_PM_GSCR_ERROR before returning.
+ */
+
+ /* clear snotification */
+ ata_scr_write(pm_link, SCR_NOTIFICATION, (1 << ap->nr_pm_links) - 1);
+
+ /* check GSCR_ERROR */
+ rc = ap->ops->pm_read(pm_dev, pm_link->pmp, ATA_PM_GSCR_ERROR,
+ &gscr_error);
+ if (rc) {
+ ata_dev_printk(pm_dev, KERN_ERR,
+ "failed to read PM_GSCR_ERROR\n");
+ goto pm_retry;
+ }
+
+ cnt = 0;
+ ata_port_for_each_link(link, ap) {
+ struct ata_eh_info *ehi = &link->eh_context.i;
+
+ if (gscr_error & (1 << link->pmp) &&
+ link_tries[link->pmp] && --link_tries[link->pmp]) {
+ ehi->probe_mask |= 1;
+ ehi->action = ata_link_resume_action(link);
+ cnt++;
+ }
+ }
+
+ if (cnt) {
+ ata_port_printk(ap, KERN_INFO, "PM SError.N/X set for some "
+ "ports, repeating recovery\n");
+ goto retry;
+ }
+
+ return 0;
+
+ link_retry:
+ if (link_tries[link->pmp] && --link_tries[link->pmp]) {
+ ata_link_printk(link, KERN_WARNING,
+ "failed to recover link, resetting PM\n");
+ pm_ehi->action |= ATA_EH_HARDRESET;
+ goto retry;
+ }
+
+ /* give up this link */
+ ata_link_printk(link, KERN_ERR,
+ "failed to recover link after %d tries, giving up\n",
+ ATA_EH_PM_LINK_TRIES);
+ ata_dev_disable(link->device);
+ link->eh_context.i.action = 0;
+
+ /* fall through */
+ pm_retry:
+ /* Control always ends up here after detaching PM. Shut up
+ * and return if we're unloading.
+ */
+ if (ap->flags & ATA_FLAG_UNLOADING)
+ return rc;
+
+ if (!ap->nr_pm_links)
+ goto retry;
+
+ if (--pm_tries) {
+ ata_port_printk(ap, KERN_WARNING,
+ "failed to recover PM, retrying in 5 secs\n");
+ pm_ehi->action |= ATA_EH_HARDRESET;
+ ssleep(5);
+ goto retry;
+ }
+
+ ata_port_printk(ap, KERN_ERR,
+ "failed to recover PM after %d tries, giving up\n",
+ ATA_EH_PM_TRIES);
+ ata_pm_detach(pm_dev);
+
+ return rc;
+}
+
+/**
+ * ata_pm_do_eh - do standard error handling for PM-enabled host
+ * @ap: host port to handle error for
+ * @prereset: prereset method (can be NULL)
+ * @softreset: softreset method
+ * @hardreset: hardreset method
+ * @postreset: postreset method (can be NULL)
+ * @pm_prereset: PM prereset method (can be NULL)
+ * @pm_softreset: PM softreset method (can be NULL)
+ * @pm_hardreset: PM hardreset method (can be NULL)
+ * @pm_postreset: PM postreset method (can be NULL)
+ *
+ * Perform standard error handling sequence for PM-enabled host
+ * @ap.
+ *
+ * LOCKING:
+ * Kernel thread context (may sleep).
+ */
+void ata_pm_do_eh(struct ata_port *ap,
+ ata_prereset_fn_t prereset, ata_reset_fn_t softreset,
+ ata_reset_fn_t hardreset, ata_postreset_fn_t postreset,
+ ata_prereset_fn_t pm_prereset, ata_reset_fn_t pm_softreset,
+ ata_reset_fn_t pm_hardreset, ata_postreset_fn_t pm_postreset)
+{
+ if (!(ap->flags & (ATA_FLAG_LOADING | ATA_FLAG_UNLOADING))) {
+ ata_eh_autopsy(ap);
+ ata_eh_report(ap);
+ }
+
+ ata_pm_eh_recover(ap, prereset, softreset, hardreset, postreset,
+ pm_prereset, pm_softreset, pm_hardreset,
+ pm_postreset);
+
+ ata_eh_finish(ap);
+}
diff --git a/drivers/scsi/libata.h b/drivers/scsi/libata.h
index 0f3cefe..7210c87 100644
--- a/drivers/scsi/libata.h
+++ b/drivers/scsi/libata.h
@@ -110,6 +110,12 @@ extern void ata_scsi_rbuf_fill(struct at
u8 *rbuf, unsigned int buflen));
extern void ata_schedule_scsi_eh(struct Scsi_Host *shost);
+/* libata-pm.c */
+extern int ata_pm_scr_read(struct ata_link *link, int reg, u32 *val);
+extern int ata_pm_scr_write(struct ata_link *link, int reg, u32 val);
+extern int ata_pm_attach(struct ata_device *dev);
+extern void ata_pm_detach(struct ata_device *dev);
+
/* libata-eh.c */
extern void ata_ering_init(struct ata_ering *ering, int size);
extern enum scsi_eh_timer_return ata_scsi_timed_out(struct scsi_cmnd *cmd);
diff --git a/include/linux/libata.h b/include/linux/libata.h
index c25d8d2..5772797 100644
--- a/include/linux/libata.h
+++ b/include/linux/libata.h
@@ -809,6 +809,24 @@ extern unsigned long ata_pci_default_fil
#endif /* CONFIG_PCI */
/*
+ * PM
+ */
+extern void ata_pm_read_init_tf(struct ata_taskfile *tf,
+ struct ata_device *dev, int pmp, int reg);
+extern u32 ata_pm_read_val(const struct ata_taskfile *tf);
+extern void ata_pm_write_init_tf(struct ata_taskfile *tf,
+ struct ata_device *dev,
+ int pmp, int reg, u32 val);
+extern int ata_pm_std_prereset(struct ata_link *link);
+extern int ata_pm_std_hardreset(struct ata_link *link, unsigned int *class);
+extern void ata_pm_std_postreset(struct ata_link *link, unsigned int *class);
+extern void ata_pm_do_eh(struct ata_port *ap,
+ ata_prereset_fn_t prereset, ata_reset_fn_t softreset,
+ ata_reset_fn_t hardreset, ata_postreset_fn_t postreset,
+ ata_prereset_fn_t pm_prereset, ata_reset_fn_t pm_softreset,
+ ata_reset_fn_t pm_hardreset, ata_postreset_fn_t pm_postreset);
+
+/*
* EH
*/
extern void ata_eng_timeout(struct ata_port *ap);
--
1.2.4
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 01/10] libata-pm: add PM related constants, fields, ops and update helpers
2006-05-11 16:43 [PATCHSET 11/11] implement PM support Tejun Heo
@ 2006-05-11 16:43 ` Tejun Heo
2006-05-11 16:43 ` [PATCH 02/10] libata-pm: update ata_eh_reset() for PM Tejun Heo
` (8 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Tejun Heo @ 2006-05-11 16:43 UTC (permalink / raw)
To: jgarzik, alan, axboe, albertcc, forrest.zhao, efalk, linux-ide; +Cc: Tejun Heo
Add PM related constants, fields and ops. Also, update
ata_class_enabled/disabled() such that PM classes are considered.
---
include/linux/libata.h | 30 ++++++++++++++++++++++++++----
1 files changed, 26 insertions(+), 4 deletions(-)
0b01a8f6e5988eac0df74062857194288714042a
diff --git a/include/linux/libata.h b/include/linux/libata.h
index bbfd176..c25d8d2 100644
--- a/include/linux/libata.h
+++ b/include/linux/libata.h
@@ -139,7 +139,9 @@ enum {
ATA_DEV_ATA_UNSUP = 2, /* ATA device (unsupported) */
ATA_DEV_ATAPI = 3, /* ATAPI device */
ATA_DEV_ATAPI_UNSUP = 4, /* ATAPI device (unsupported) */
- ATA_DEV_NONE = 5, /* no device */
+ ATA_DEV_PM = 5, /* SATA port multiplier */
+ ATA_DEV_PM_UNSUP = 6, /* SATA port multiplier (unsupported) */
+ ATA_DEV_NONE = 7, /* no device */
ATA_DEV_ERING_SIZE = 32, /* record 32 recent errors */
@@ -161,6 +163,8 @@ enum {
ATA_FLAG_PIO_POLLING = (1 << 10), /* use polling PIO if LLD
* doesn't handle PIO interrupts */
ATA_FLAG_NCQ = (1 << 11), /* host supports NCQ */
+ ATA_FLAG_PM = (1 << 12), /* host supports port multiplier */
+ ATA_FLAG_SDB_NOTIFY = (1 << 13), /* host support SDB notify */
ATA_FLAG_DEBUGMSG = (1 << 14),
ATA_FLAG_FLUSH_PORT_TASK = (1 << 15), /* flush port task */
@@ -263,6 +267,9 @@ enum {
ATA_PROBE_MAX_TRIES = 3,
ATA_EH_RESET_TRIES = 3,
ATA_EH_DEV_TRIES = 3,
+ ATA_EH_PM_TRIES = 5,
+ ATA_EH_PM_RESET_TRIES = 2,
+ ATA_EH_PM_LINK_TRIES = 2,
/* timing constants in millisecs */
ATA_DEBOUNCE_QUICK_INTERVAL = 5,
@@ -272,6 +279,8 @@ enum {
ATA_DEBOUNCE_INTERVAL = 10,
ATA_DEBOUNCE_DURATION = 500,
ATA_DEBOUNCE_TIMEOUT = 10000,
+
+ ATA_PM_SCR_TIMEOUT = 500,
};
enum hsm_task_states {
@@ -433,7 +442,12 @@ struct ata_device {
/* fields above n_sectors are not cleared across device init */
u64 n_sectors; /* size of device, if ATA */
unsigned int class; /* ATA_DEV_xxx */
- u16 id[ATA_ID_WORDS]; /* IDENTIFY xxx DEVICE data */
+
+ union {
+ u16 id[ATA_ID_WORDS]; /* IDENTIFY xxx DEVICE data */
+ u32 gscr[ATA_PM_GSCR_DWORDS]; /* PM GSCR block */
+ };
+
u8 pio_mode;
u8 dma_mode;
u8 xfer_mode;
@@ -580,6 +594,12 @@ struct ata_port_operations {
void (*qc_prep) (struct ata_queued_cmd *qc);
unsigned int (*qc_issue) (struct ata_queued_cmd *qc);
+ /* port multiplier */
+ void (*pm_attach) (struct ata_port *ap);
+ void (*pm_detach) (struct ata_port *ap);
+ int (*pm_read) (struct ata_device *dev, int pmp, int reg, u32 *r_val);
+ int (*pm_write) (struct ata_device *dev, int pmp, int reg, u32 val);
+
/* Error handlers. ->error_handler overrides ->eng_timeout and
* indicates that new-style EH is in place.
*/
@@ -894,12 +914,14 @@ static inline unsigned int ata_tag_inter
*/
static inline unsigned int ata_class_enabled(unsigned int class)
{
- return class == ATA_DEV_ATA || class == ATA_DEV_ATAPI;
+ return class == ATA_DEV_ATA || class == ATA_DEV_ATAPI ||
+ class == ATA_DEV_PM;
}
static inline unsigned int ata_class_disabled(unsigned int class)
{
- return class == ATA_DEV_ATA_UNSUP || class == ATA_DEV_ATAPI_UNSUP;
+ return class == ATA_DEV_ATA_UNSUP || class == ATA_DEV_ATAPI_UNSUP ||
+ class == ATA_DEV_PM_UNSUP;
}
static inline unsigned int ata_class_absent(unsigned int class)
--
1.2.4
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 07/10] sata_sil24: separate out sil24_exec_polled_cmd()
2006-05-11 16:43 [PATCHSET 11/11] implement PM support Tejun Heo
` (2 preceding siblings ...)
2006-05-11 16:43 ` [PATCH 04/10] libata-pm: hook PM support and enable it Tejun Heo
@ 2006-05-11 16:43 ` Tejun Heo
2006-05-11 16:43 ` [PATCH 06/10] sata_sil24: add PM related constants Tejun Heo
` (5 subsequent siblings)
9 siblings, 0 replies; 11+ messages in thread
From: Tejun Heo @ 2006-05-11 16:43 UTC (permalink / raw)
To: jgarzik, alan, axboe, albertcc, forrest.zhao, efalk, linux-ide; +Cc: Tejun Heo
Separate out sil24_exec_polled_cmd() from sil24_softreset(). This
will be used to implement sil24_pm_read/write().
---
drivers/scsi/sata_sil24.c | 75 ++++++++++++++++++++++++++++++++-------------
1 files changed, 54 insertions(+), 21 deletions(-)
4530c25627ea84e1907b94166c8b74a3ade942f4
diff --git a/drivers/scsi/sata_sil24.c b/drivers/scsi/sata_sil24.c
index dfbe750..34f2e67 100644
--- a/drivers/scsi/sata_sil24.c
+++ b/drivers/scsi/sata_sil24.c
@@ -515,16 +515,59 @@ static int sil24_init_port(struct ata_po
return 0;
}
-static int sil24_softreset(struct ata_link *link, unsigned int *class)
+static int sil24_exec_polled_cmd(struct ata_port *ap, u32 ctrl,
+ const struct ata_taskfile *tf,
+ int pmp, int is_cmd,
+ unsigned long timeout_msec)
{
- struct ata_port *ap = link->ap;
void __iomem *port = (void __iomem *)ap->ioaddr.cmd_addr;
struct sil24_port_priv *pp = ap->private_data;
struct sil24_prb *prb = &pp->cmd_block[0].ata.prb;
dma_addr_t paddr = pp->cmd_block_dma;
+ u32 irq_enabled, irq_mask, irq_stat;
+ int rc;
+
+ prb->ctrl = cpu_to_le16(ctrl);
+ ata_tf_to_fis(tf, pmp, is_cmd, prb->fis);
+
+ /* temporarily plug completion and error interrupts */
+ irq_enabled = readl(port + PORT_IRQ_ENABLE_SET);
+ writel(PORT_IRQ_COMPLETE | PORT_IRQ_ERROR, port + PORT_IRQ_ENABLE_CLR);
+
+ writel((u32)paddr, port + PORT_CMD_ACTIVATE);
+ writel((u64)paddr >> 32, port + PORT_CMD_ACTIVATE + 4);
+
+ irq_mask = (PORT_IRQ_COMPLETE | PORT_IRQ_ERROR) << PORT_IRQ_RAW_SHIFT;
+ irq_stat = ata_wait_register(port + PORT_IRQ_STAT, irq_mask, 0x0,
+ 10, timeout_msec);
+
+ writel(irq_mask, port + PORT_IRQ_STAT); /* clear IRQs */
+ irq_stat >>= PORT_IRQ_RAW_SHIFT;
+
+ if (irq_stat & PORT_IRQ_COMPLETE)
+ rc = 0;
+ else {
+ /* force port into known state */
+ sil24_init_port(ap);
+
+ if (irq_stat & PORT_IRQ_ERROR)
+ rc = -EIO;
+ else
+ rc = -EBUSY;
+ }
+
+ /* restore IRQ enabled */
+ writel(irq_enabled, port + PORT_IRQ_ENABLE_SET);
+
+ return rc;
+}
+
+static int sil24_softreset(struct ata_link *link, unsigned int *class)
+{
+ struct ata_port *ap = link->ap;
struct ata_taskfile tf;
- u32 mask, irq_stat;
const char *reason;
+ int rc;
DPRINTK("ENTER\n");
@@ -541,24 +584,14 @@ static int sil24_softreset(struct ata_li
}
/* do SRST */
- prb->ctrl = cpu_to_le16(PRB_CTRL_SRST);
- prb->fis[1] = 0; /* no PM yet */
-
- writel((u32)paddr, port + PORT_CMD_ACTIVATE);
- writel((u64)paddr >> 32, port + PORT_CMD_ACTIVATE + 4);
-
- mask = (PORT_IRQ_COMPLETE | PORT_IRQ_ERROR) << PORT_IRQ_RAW_SHIFT;
- irq_stat = ata_wait_register(port + PORT_IRQ_STAT, mask, 0x0,
- 100, ATA_TMOUT_BOOT / HZ * 1000);
-
- writel(irq_stat, port + PORT_IRQ_STAT); /* clear IRQs */
- irq_stat >>= PORT_IRQ_RAW_SHIFT;
-
- if (!(irq_stat & PORT_IRQ_COMPLETE)) {
- if (irq_stat & PORT_IRQ_ERROR)
- reason = "SRST command error";
- else
- reason = "timeout";
+ ata_tf_init(link->device, &tf); /* doesn't really matter */
+ rc = sil24_exec_polled_cmd(ap, PRB_CTRL_SRST, &tf, 0, 0,
+ ATA_TMOUT_BOOT / HZ * 1000);
+ if (rc == -EBUSY) {
+ reason = "timeout";
+ goto err;
+ } else if (rc) {
+ reason = "SRST command error";
goto err;
}
--
1.2.4
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 08/10] sata_sil24: separate out sil24_do_softreset()
2006-05-11 16:43 [PATCHSET 11/11] implement PM support Tejun Heo
` (8 preceding siblings ...)
2006-05-11 16:43 ` [PATCH 09/10] sata_sil24: implement PM support Tejun Heo
@ 2006-05-11 16:43 ` Tejun Heo
9 siblings, 0 replies; 11+ messages in thread
From: Tejun Heo @ 2006-05-11 16:43 UTC (permalink / raw)
To: jgarzik, alan, axboe, albertcc, forrest.zhao, efalk, linux-ide; +Cc: Tejun Heo
Separate out sil24_do_softreset() which takes @pmp as its last
argument. This will be used to implement sil24_pm_softreset().
---
drivers/scsi/sata_sil24.c | 10 ++++++++--
1 files changed, 8 insertions(+), 2 deletions(-)
201f4cac006ca715aafa794a2f7020cbc40086ca
diff --git a/drivers/scsi/sata_sil24.c b/drivers/scsi/sata_sil24.c
index 34f2e67..75b04e2 100644
--- a/drivers/scsi/sata_sil24.c
+++ b/drivers/scsi/sata_sil24.c
@@ -562,7 +562,8 @@ static int sil24_exec_polled_cmd(struct
return rc;
}
-static int sil24_softreset(struct ata_link *link, unsigned int *class)
+static int sil24_do_softreset(struct ata_link *link, unsigned int *class,
+ int pmp)
{
struct ata_port *ap = link->ap;
struct ata_taskfile tf;
@@ -585,7 +586,7 @@ static int sil24_softreset(struct ata_li
/* do SRST */
ata_tf_init(link->device, &tf); /* doesn't really matter */
- rc = sil24_exec_polled_cmd(ap, PRB_CTRL_SRST, &tf, 0, 0,
+ rc = sil24_exec_polled_cmd(ap, PRB_CTRL_SRST, &tf, pmp, 0,
ATA_TMOUT_BOOT / HZ * 1000);
if (rc == -EBUSY) {
reason = "timeout";
@@ -610,6 +611,11 @@ static int sil24_softreset(struct ata_li
return -EIO;
}
+static int sil24_softreset(struct ata_link *link, unsigned int *class)
+{
+ return sil24_do_softreset(link, class, 0);
+}
+
static int sil24_hardreset(struct ata_link *link, unsigned int *class)
{
struct ata_port *ap = link->ap;
--
1.2.4
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 09/10] sata_sil24: implement PM support
2006-05-11 16:43 [PATCHSET 11/11] implement PM support Tejun Heo
` (7 preceding siblings ...)
2006-05-11 16:43 ` [PATCH 10/10] sata_sil24: implement PORT_RST Tejun Heo
@ 2006-05-11 16:43 ` Tejun Heo
2006-05-11 16:43 ` [PATCH 08/10] sata_sil24: separate out sil24_do_softreset() Tejun Heo
9 siblings, 0 replies; 11+ messages in thread
From: Tejun Heo @ 2006-05-11 16:43 UTC (permalink / raw)
To: jgarzik, alan, axboe, albertcc, forrest.zhao, efalk, linux-ide; +Cc: Tejun Heo
Implement PM support. sil24 supports full FIS-switching. However, it
has a PM DMA CS errata which requires port-wide resetting if commands
are outstanding to three or more devices when an error occurs on one
of them.
ATAPI commands often result in CHECK SENSE and it's crucial to not
reset them before fetching sense data. Unfortunately, ATAPI CHECK
SENSE causes a lot of problem if command is outstanding to any other
device usually resulting in port-wide reset. So, sata_sil24
implements sil24_qc_defer() which guarantees ATAPI command is run by
itself.
---
drivers/scsi/sata_sil24.c | 206 ++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 193 insertions(+), 13 deletions(-)
8bab6c564499e24143045c65d524b2a48becaf9c
diff --git a/drivers/scsi/sata_sil24.c b/drivers/scsi/sata_sil24.c
index 75b04e2..1979cfc 100644
--- a/drivers/scsi/sata_sil24.c
+++ b/drivers/scsi/sata_sil24.c
@@ -165,7 +165,7 @@ enum {
DEF_PORT_IRQ = PORT_IRQ_COMPLETE | PORT_IRQ_ERROR |
PORT_IRQ_PHYRDY_CHG | PORT_IRQ_DEV_XCHG |
- PORT_IRQ_UNK_FIS,
+ PORT_IRQ_UNK_FIS | PORT_IRQ_SDB_NOTIFY,
/* bits[27:16] are unmasked (raw) */
PORT_IRQ_RAW_SHIFT = 16,
@@ -234,7 +234,8 @@ enum {
/* host flags */
SIL24_COMMON_FLAGS = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY |
ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA |
- ATA_FLAG_NCQ,
+ ATA_FLAG_NCQ | ATA_FLAG_PM |
+ ATA_FLAG_SDB_NOTIFY,
SIL24_FLAG_PCIX_IRQ_WOC = (1 << 24), /* IRQ loss errata on PCI-X */
IRQ_STAT_4PORTS = 0xf,
@@ -328,10 +329,15 @@ struct sil24_host_priv {
static void sil24_dev_config(struct ata_port *ap, struct ata_device *dev);
static u32 sil24_scr_read(struct ata_port *ap, unsigned sc_reg);
static void sil24_scr_write(struct ata_port *ap, unsigned sc_reg, u32 val);
+static int sil24_qc_defer(struct ata_queued_cmd *qc);
static void sil24_qc_prep(struct ata_queued_cmd *qc);
static unsigned int sil24_qc_issue(struct ata_queued_cmd *qc);
static void sil24_irq_clear(struct ata_port *ap);
static irqreturn_t sil24_interrupt(int irq, void *dev_instance, struct pt_regs *regs);
+static void sil24_pm_attach(struct ata_port *ap);
+static void sil24_pm_detach(struct ata_port *ap);
+static int sil24_pm_read(struct ata_device *dev, int pmp, int reg, u32 *r_val);
+static int sil24_pm_write(struct ata_device *dev, int pmp, int reg, u32 val);
static void sil24_freeze(struct ata_port *ap);
static void sil24_thaw(struct ata_port *ap);
static void sil24_error_handler(struct ata_port *ap);
@@ -385,7 +391,7 @@ static const struct ata_port_operations
.check_altstatus = ata_noop_check_status,
.dev_select = ata_noop_dev_select,
- .qc_defer = ata_std_qc_defer,
+ .qc_defer = sil24_qc_defer,
.qc_prep = sil24_qc_prep,
.qc_issue = sil24_qc_issue,
@@ -395,6 +401,11 @@ static const struct ata_port_operations
.scr_read = sil24_scr_read,
.scr_write = sil24_scr_write,
+ .pm_attach = sil24_pm_attach,
+ .pm_detach = sil24_pm_detach,
+ .pm_read = sil24_pm_read,
+ .pm_write = sil24_pm_write,
+
.freeze = sil24_freeze,
.thaw = sil24_thaw,
.error_handler = sil24_error_handler,
@@ -499,6 +510,31 @@ static void sil24_scr_write(struct ata_p
}
}
+static void sil24_config_pm(struct ata_port *ap, int attached)
+{
+ void __iomem *port = (void __iomem *)ap->ioaddr.cmd_addr;
+
+ if (attached)
+ writel(PORT_CS_PM_EN, port + PORT_CTRL_STAT);
+ else
+ writel(PORT_CS_PM_EN, port + PORT_CTRL_CLR);
+}
+
+static void sil24_clear_pm(struct ata_port *ap)
+{
+ void __iomem *port = (void __iomem *)ap->ioaddr.cmd_addr;
+ int i;
+
+ writel(PORT_CS_PM_RESUME, port + PORT_CTRL_CLR);
+
+ for (i = 0; i < ATA_PM_MAX_PORTS; i++) {
+ void __iomem *pm_base = port + PORT_PM + i * PORT_PM_SIZE;
+
+ writel(0, pm_base + PORT_PM_STATUS);
+ writel(0, pm_base + PORT_PM_QACTIVE);
+ }
+}
+
static int sil24_init_port(struct ata_port *ap)
{
void __iomem *port = (void __iomem *)ap->ioaddr.cmd_addr;
@@ -510,6 +546,9 @@ static int sil24_init_port(struct ata_po
tmp = ata_wait_register(port + PORT_CTRL_STAT,
PORT_CS_RDY, 0, 10, 100);
+ /* clear PM error status */
+ sil24_clear_pm(ap);
+
if ((tmp & (PORT_CS_INIT | PORT_CS_RDY)) != PORT_CS_RDY)
return -EIO;
return 0;
@@ -613,7 +652,7 @@ static int sil24_do_softreset(struct ata
static int sil24_softreset(struct ata_link *link, unsigned int *class)
{
- return sil24_do_softreset(link, class, 0);
+ return sil24_do_softreset(link, class, ATA_PM_CTRL_PORT);
}
static int sil24_hardreset(struct ata_link *link, unsigned int *class)
@@ -684,6 +723,38 @@ static inline void sil24_fill_sg(struct
}
}
+static int sil24_qc_defer(struct ata_queued_cmd *qc)
+{
+ struct ata_link *link = qc->dev->link;
+ struct ata_port *ap = link->ap;
+ u8 prot = qc->tf.protocol;
+ int is_atapi = (prot == ATA_PROT_ATAPI ||
+ prot == ATA_PROT_ATAPI_NODATA ||
+ prot == ATA_PROT_ATAPI_DMA);
+
+ /* ATAPI commands completing with CHECK_SENSE cause various
+ * weird problems if other commands are active. PM DMA CS
+ * errata doesn't cover all and HSM violation occurs even with
+ * only one other device active. Always run an ATAPI command
+ * by itself.
+ */
+ if (unlikely(ap->excl_link)) {
+ if (link == ap->excl_link) {
+ if (ap->nr_active_links)
+ return ATA_DEFER_PORT;
+ qc->flags |= ATA_QCFLAG_CLEAR_EXCL;
+ } else
+ return ATA_DEFER_LINK;
+ } else if (unlikely(is_atapi)) {
+ ap->excl_link = link;
+ if (ap->nr_active_links)
+ return ATA_DEFER_PORT;
+ qc->flags |= ATA_QCFLAG_CLEAR_EXCL;
+ }
+
+ return ata_std_qc_defer(qc);
+}
+
static void sil24_qc_prep(struct ata_queued_cmd *qc)
{
struct ata_port *ap = qc->ap;
@@ -756,6 +827,63 @@ static void sil24_irq_clear(struct ata_p
/* unused */
}
+static void sil24_pm_attach(struct ata_port *ap)
+{
+ sil24_config_pm(ap, 1);
+ sil24_clear_pm(ap);
+}
+
+static void sil24_pm_detach(struct ata_port *ap)
+{
+ sil24_clear_pm(ap);
+ sil24_config_pm(ap, 0);
+}
+
+static int sil24_pm_read(struct ata_device *dev, int pmp, int reg, u32 *r_val)
+{
+ struct ata_port *ap = dev->link->ap;
+ struct ata_taskfile tf;
+ int rc;
+
+ ata_pm_read_init_tf(&tf, dev, pmp, reg);
+ rc = sil24_exec_polled_cmd(ap, 0, &tf, ATA_PM_CTRL_PORT, 1,
+ ATA_PM_SCR_TIMEOUT);
+ if (rc == 0) {
+ sil24_read_tf(ap, 0, &tf);
+ *r_val = ata_pm_read_val(&tf);
+ }
+ return rc;
+}
+
+static int sil24_pm_write(struct ata_device *dev, int pmp, int reg, u32 val)
+{
+ struct ata_port *ap = dev->link->ap;
+ struct ata_taskfile tf;
+
+ ata_pm_write_init_tf(&tf, dev, pmp, reg, val);
+ return sil24_exec_polled_cmd(ap, 0, &tf, ATA_PM_CTRL_PORT, 1,
+ ATA_PM_SCR_TIMEOUT);
+}
+
+static int sil24_pm_softreset(struct ata_link *link, unsigned int *class)
+{
+ return sil24_do_softreset(link, class, link->pmp);
+}
+
+static int sil24_pm_hardreset(struct ata_link *link, unsigned int *class)
+{
+ int rc;
+
+ rc = sil24_init_port(link->ap);
+ if (rc) {
+ ata_link_printk(link, KERN_ERR,
+ "hardreset failed (port not ready)\n");
+ return rc;
+ }
+
+ return ata_pm_std_hardreset(link, class);
+}
+
static void sil24_freeze(struct ata_port *ap)
{
void __iomem *port = (void __iomem *)ap->ioaddr.cmd_addr;
@@ -782,8 +910,9 @@ static void sil24_thaw(struct ata_port *
static void sil24_error_intr(struct ata_port *ap)
{
void __iomem *port = (void __iomem *)ap->ioaddr.cmd_addr;
- struct ata_eh_info *ehi = &ap->link.eh_info;
int freeze = 0;
+ struct ata_link *link;
+ struct ata_eh_info *ehi;
u32 irq_stat;
/* on error, we need to clear IRQ explicitly */
@@ -791,6 +920,8 @@ static void sil24_error_intr(struct ata_
writel(irq_stat, port + PORT_IRQ_STAT);
/* first, analyze and record host port events */
+ link = &ap->link;
+ ehi = &link->eh_info;
ata_ehi_clear_desc(ehi);
ata_ehi_push_desc(ehi, "irq_stat 0x%08x", irq_stat);
@@ -817,12 +948,59 @@ static void sil24_error_intr(struct ata_
freeze = 1;
}
+ if (irq_stat & PORT_IRQ_SDB_NOTIFY) {
+ /* No SNotification register. We don't know which
+ * ports should be aborted. Just let them timeout.
+ */
+ ehi->err_mask |= AC_ERR_OTHER;
+ ata_ehi_push_desc(ehi, ", SDB notify");
+ ata_port_schedule_eh(ap);
+ }
+
/* deal with command error */
if (irq_stat & PORT_IRQ_ERROR) {
struct sil24_cerr_info *ci = NULL;
+ struct ata_queued_cmd *qc = NULL;
unsigned int err_mask = 0, action = 0;
- struct ata_queued_cmd *qc;
- u32 cerr;
+ u32 context, cerr;
+ int pmp;
+
+ /* DMA Context Switch Failure in Port Multiplier Mode
+ * errata. If we have active commands to 3 or more
+ * devices, any error condition on active devices can
+ * corrupt DMA context switching.
+ */
+ if (ap->nr_active_links >= 3) {
+ ehi->err_mask |= AC_ERR_OTHER;
+ ehi->action |= ATA_EH_HARDRESET;
+ ata_ehi_push_desc(ehi, ", PM DMA CS errata");
+ freeze = 1;
+ }
+
+ /* find out the offending link and qc */
+ if (ap->nr_pm_links) {
+ context = readl(port + PORT_CONTEXT);
+ pmp = (context >> 5) & 0xf;
+
+ if (pmp < ap->nr_pm_links) {
+ link = &ap->pm_link[pmp];
+ ehi = &link->eh_info;
+ qc = ata_qc_from_tag(ap, link->active_tag);
+
+ ata_ehi_clear_desc(ehi);
+ ata_ehi_push_desc(ehi, "irq_stat 0x%08x",
+ irq_stat);
+
+ ata_link_abort(link);
+ } else {
+ err_mask |= AC_ERR_HSM;
+ action |= ATA_EH_HARDRESET;
+ freeze = 1;
+ }
+ } else {
+ qc = ata_qc_from_tag(ap, link->active_tag);
+ ata_link_abort(link);
+ }
/* analyze CMD_ERR */
cerr = readl(port + PORT_CMD_ERR);
@@ -841,7 +1019,6 @@ static void sil24_error_intr(struct ata_
}
/* record error info */
- qc = ata_qc_from_tag(ap, ap->link.active_tag);
if (qc) {
sil24_read_tf(ap, sil24_tag(qc->tag), &qc->result_tf);
qc->err_mask |= err_mask;
@@ -849,13 +1026,15 @@ static void sil24_error_intr(struct ata_
ehi->err_mask |= err_mask;
ehi->action |= action;
+
+ /* if PM, resume */
+ if (ap->nr_pm_links)
+ writel(PORT_CS_PM_RESUME, port + PORT_CTRL_STAT);
}
- /* freeze or abort */
+ /* freeze? */
if (freeze)
ata_port_freeze(ap);
- else
- ata_port_abort(ap);
}
static void sil24_finish_qc(struct ata_queued_cmd *qc)
@@ -945,8 +1124,9 @@ static void sil24_error_handler(struct a
}
/* perform recovery */
- ata_do_eh(ap, ata_std_prereset, sil24_softreset, sil24_hardreset,
- ata_std_postreset);
+ ata_pm_do_eh(ap, ata_std_prereset, sil24_softreset, sil24_hardreset,
+ ata_std_postreset, ata_pm_std_prereset, sil24_pm_softreset,
+ sil24_pm_hardreset, ata_pm_std_postreset);
}
static void sil24_post_internal_cmd(struct ata_queued_cmd *qc)
--
1.2.4
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 10/10] sata_sil24: implement PORT_RST
2006-05-11 16:43 [PATCHSET 11/11] implement PM support Tejun Heo
` (6 preceding siblings ...)
2006-05-11 16:43 ` [PATCH 03/10] libata-pm: implement Port Multiplier support Tejun Heo
@ 2006-05-11 16:43 ` Tejun Heo
2006-05-11 16:43 ` [PATCH 09/10] sata_sil24: implement PM support Tejun Heo
2006-05-11 16:43 ` [PATCH 08/10] sata_sil24: separate out sil24_do_softreset() Tejun Heo
9 siblings, 0 replies; 11+ messages in thread
From: Tejun Heo @ 2006-05-11 16:43 UTC (permalink / raw)
To: jgarzik, alan, axboe, albertcc, forrest.zhao, efalk, linux-ide; +Cc: Tejun Heo
DEV_RST (hardreset) sometimes fail to recover the controller
(especially after PM CS DMA errata). In such cases, perform PORT_RST
prior to DEV_RST.
---
drivers/scsi/sata_sil24.c | 98 +++++++++++++++++++++++++++++++++------------
1 files changed, 72 insertions(+), 26 deletions(-)
246bc1468a20be1c93d294eb0b75a9df71e2a8ef
diff --git a/drivers/scsi/sata_sil24.c b/drivers/scsi/sata_sil24.c
index 1979cfc..9b7f46f 100644
--- a/drivers/scsi/sata_sil24.c
+++ b/drivers/scsi/sata_sil24.c
@@ -318,6 +318,7 @@ static struct sil24_cerr_info {
struct sil24_port_priv {
union sil24_cmd_block *cmd_block; /* 32 cmd blocks */
dma_addr_t cmd_block_dma; /* DMA base addr for them */
+ int do_port_rst;
};
/* ap->host_set->private_data */
@@ -510,6 +511,30 @@ static void sil24_scr_write(struct ata_p
}
}
+static void sil24_config_controller(void __iomem *port,
+ unsigned long host_flags)
+{
+ /* configure IRQ WoC */
+ if (host_flags & SIL24_FLAG_PCIX_IRQ_WOC)
+ writel(PORT_CS_IRQ_WOC, port + PORT_CTRL_STAT);
+ else
+ writel(PORT_CS_IRQ_WOC, port + PORT_CTRL_CLR);
+
+ /* zero error counters. */
+ writel(0x8000, port + PORT_DECODE_ERR_THRESH);
+ writel(0x8000, port + PORT_CRC_ERR_THRESH);
+ writel(0x8000, port + PORT_HSHK_ERR_THRESH);
+ writel(0x0000, port + PORT_DECODE_ERR_CNT);
+ writel(0x0000, port + PORT_CRC_ERR_CNT);
+ writel(0x0000, port + PORT_HSHK_ERR_CNT);
+
+ /* always use 64bit activation */
+ writel(PORT_CS_32BIT_ACTV, port + PORT_CTRL_CLR);
+
+ /* clear port multiplier enable and resume bits */
+ writel(PORT_CS_PM_EN | PORT_CS_PM_RESUME, port + PORT_CTRL_CLR);
+}
+
static void sil24_config_pm(struct ata_port *ap, int attached)
{
void __iomem *port = (void __iomem *)ap->ioaddr.cmd_addr;
@@ -538,6 +563,7 @@ static void sil24_clear_pm(struct ata_po
static int sil24_init_port(struct ata_port *ap)
{
void __iomem *port = (void __iomem *)ap->ioaddr.cmd_addr;
+ struct sil24_port_priv *pp = ap->private_data;
u32 tmp;
writel(PORT_CS_INIT, port + PORT_CTRL_STAT);
@@ -549,8 +575,12 @@ static int sil24_init_port(struct ata_po
/* clear PM error status */
sil24_clear_pm(ap);
- if ((tmp & (PORT_CS_INIT | PORT_CS_RDY)) != PORT_CS_RDY)
+ if ((tmp & (PORT_CS_INIT | PORT_CS_RDY)) != PORT_CS_RDY) {
+ pp->do_port_rst = 1;
+ ap->link.eh_context.i.action |= ATA_EH_HARDRESET;
return -EIO;
+ }
+
return 0;
}
@@ -659,10 +689,34 @@ static int sil24_hardreset(struct ata_li
{
struct ata_port *ap = link->ap;
void __iomem *port = (void __iomem *)ap->ioaddr.cmd_addr;
+ struct sil24_port_priv *pp = ap->private_data;
+ int did_port_rst = 0;
const char *reason;
int tout_msec, rc;
u32 tmp;
+ retry:
+ /* Sometimes, DEV_RST is not enough to recover the controller.
+ * This happens a lot after PM DMA CS errata.
+ */
+ if (pp->do_port_rst) {
+ ata_port_printk(ap, KERN_WARNING, "controller in dubious "
+ "state, performing PORT_RST\n");
+
+ writel(PORT_CS_PORT_RST, port + PORT_CTRL_STAT);
+ msleep(10);
+ writel(PORT_CS_PORT_RST, port + PORT_CTRL_CLR);
+ ata_wait_register(port + PORT_CTRL_STAT, PORT_CS_RDY, 0,
+ 10, 5000);
+
+ /* restore port configuration */
+ sil24_config_controller(port, ap->flags);
+ sil24_config_pm(ap, ap->nr_pm_links);
+
+ pp->do_port_rst = 0;
+ did_port_rst = 1;
+ }
+
/* sil24 does the right thing(tm) without any protection */
ata_set_sata_spd(link);
@@ -700,6 +754,11 @@ static int sil24_hardreset(struct ata_li
return -EAGAIN;
err:
+ if (!did_port_rst) {
+ pp->do_port_rst = 1;
+ goto retry;
+ }
+
ata_link_printk(link, KERN_ERR, "hardreset failed (%s)\n", reason);
return -EIO;
}
@@ -910,6 +969,7 @@ static void sil24_thaw(struct ata_port *
static void sil24_error_intr(struct ata_port *ap)
{
void __iomem *port = (void __iomem *)ap->ioaddr.cmd_addr;
+ struct sil24_port_priv *pp = ap->private_data;
int freeze = 0;
struct ata_link *link;
struct ata_eh_info *ehi;
@@ -974,6 +1034,7 @@ static void sil24_error_intr(struct ata_
ehi->err_mask |= AC_ERR_OTHER;
ehi->action |= ATA_EH_HARDRESET;
ata_ehi_push_desc(ehi, ", PM DMA CS errata");
+ pp->do_port_rst = 1;
freeze = 1;
}
@@ -1116,17 +1177,17 @@ static irqreturn_t sil24_interrupt(int i
static void sil24_error_handler(struct ata_port *ap)
{
- struct ata_eh_context *ehc = &ap->link.eh_context;
+ struct sil24_port_priv *pp = ap->private_data;
- if (sil24_init_port(ap)) {
+ if (sil24_init_port(ap))
ata_eh_freeze_port(ap);
- ehc->i.action |= ATA_EH_HARDRESET;
- }
/* perform recovery */
ata_pm_do_eh(ap, ata_std_prereset, sil24_softreset, sil24_hardreset,
ata_std_postreset, ata_pm_std_prereset, sil24_pm_softreset,
sil24_pm_hardreset, ata_pm_std_postreset);
+
+ pp->do_port_rst = 0;
}
static void sil24_post_internal_cmd(struct ata_queued_cmd *qc)
@@ -1137,8 +1198,10 @@ static void sil24_post_internal_cmd(stru
qc->err_mask |= AC_ERR_OTHER;
/* make DMA engine forget about the failed command */
- if (qc->err_mask)
- sil24_init_port(ap);
+ if (qc->err_mask) {
+ if (sil24_init_port(ap))
+ ata_eh_freeze_port(ap);
+ }
}
static inline void sil24_cblk_free(struct sil24_port_priv *pp, struct device *dev)
@@ -1334,25 +1397,8 @@ static int sil24_init_one(struct pci_dev
"failed to clear port RST\n");
}
- /* Configure IRQ WoC */
- if (probe_ent->host_flags & SIL24_FLAG_PCIX_IRQ_WOC)
- writel(PORT_CS_IRQ_WOC, port + PORT_CTRL_STAT);
- else
- writel(PORT_CS_IRQ_WOC, port + PORT_CTRL_CLR);
-
- /* Zero error counters. */
- writel(0x8000, port + PORT_DECODE_ERR_THRESH);
- writel(0x8000, port + PORT_CRC_ERR_THRESH);
- writel(0x8000, port + PORT_HSHK_ERR_THRESH);
- writel(0x0000, port + PORT_DECODE_ERR_CNT);
- writel(0x0000, port + PORT_CRC_ERR_CNT);
- writel(0x0000, port + PORT_HSHK_ERR_CNT);
-
- /* Always use 64bit activation */
- writel(PORT_CS_32BIT_ACTV, port + PORT_CTRL_CLR);
-
- /* Clear port multiplier enable and resume bits */
- writel(PORT_CS_PM_EN | PORT_CS_PM_RESUME, port + PORT_CTRL_CLR);
+ /* configure controller */
+ sil24_config_controller(port, probe_ent->host_flags);
}
/* Turn on interrupts */
--
1.2.4
^ permalink raw reply related [flat|nested] 11+ messages in thread
end of thread, other threads:[~2006-05-11 16:43 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2006-05-11 16:43 [PATCHSET 11/11] implement PM support Tejun Heo
2006-05-11 16:43 ` [PATCH 01/10] libata-pm: add PM related constants, fields, ops and update helpers Tejun Heo
2006-05-11 16:43 ` [PATCH 02/10] libata-pm: update ata_eh_reset() for PM Tejun Heo
2006-05-11 16:43 ` [PATCH 04/10] libata-pm: hook PM support and enable it Tejun Heo
2006-05-11 16:43 ` [PATCH 07/10] sata_sil24: separate out sil24_exec_polled_cmd() Tejun Heo
2006-05-11 16:43 ` [PATCH 06/10] sata_sil24: add PM related constants Tejun Heo
2006-05-11 16:43 ` [PATCH 05/10] sata_sil24: rename PORT_CS_RESUME to PORT_CS_PM_RESUME Tejun Heo
2006-05-11 16:43 ` [PATCH 03/10] libata-pm: implement Port Multiplier support Tejun Heo
2006-05-11 16:43 ` [PATCH 10/10] sata_sil24: implement PORT_RST Tejun Heo
2006-05-11 16:43 ` [PATCH 09/10] sata_sil24: implement PM support Tejun Heo
2006-05-11 16:43 ` [PATCH 08/10] sata_sil24: separate out sil24_do_softreset() Tejun Heo
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).