isci: Terminate dev requests on FIS err bit rx in NCQ

When the remote device transitions to a not-ready state because of
an NCQ error condition, all outstanding requests to that device
are terminated and completed to libsas on the normal path.  The
device then waits for a READ LOG EXT command to issue on the task
management path.

Signed-off-by: Jeff Skirvin <jeffrey.d.skirvin@intel.com>
Signed-off-by: Dan Williams <dan.j.williams@intel.com>
diff --git a/drivers/scsi/isci/remote_device.c b/drivers/scsi/isci/remote_device.c
index 9f45c2b..c5ce0f0 100644
--- a/drivers/scsi/isci/remote_device.c
+++ b/drivers/scsi/isci/remote_device.c
@@ -68,17 +68,39 @@
  * @isci_host: This parameter specifies the isci host object.
  * @isci_device: This parameter specifies the remote device
  *
+ * scic_lock is held on entrance to this function.
  */
 static void isci_remote_device_not_ready(struct isci_host *ihost,
 				  struct isci_remote_device *idev, u32 reason)
 {
+	struct isci_request * ireq;
+
 	dev_dbg(&ihost->pdev->dev,
 		"%s: isci_device = %p\n", __func__, idev);
 
-	if (reason == SCIC_REMOTE_DEVICE_NOT_READY_STOP_REQUESTED)
+	switch (reason) {
+	case SCIC_REMOTE_DEVICE_NOT_READY_STOP_REQUESTED:
 		set_bit(IDEV_GONE, &idev->flags);
-	else
+		break;
+	case SCIC_REMOTE_DEVICE_NOT_READY_SATA_SDB_ERROR_FIS_RECEIVED:
+		set_bit(IDEV_IO_NCQERROR, &idev->flags);
+
+		/* Kill all outstanding requests for the device. */
+		list_for_each_entry(ireq, &idev->reqs_in_process, dev_node) {
+
+			dev_dbg(&ihost->pdev->dev,
+				"%s: isci_device = %p request = %p\n",
+				__func__, idev, ireq);
+
+			scic_controller_terminate_request(&ihost->sci,
+							  &idev->sci,
+							  &ireq->sci);
+		}
+		/* Fall through into the default case... */
+	default:
 		clear_bit(IDEV_IO_READY, &idev->flags);
+		break;
+	}
 }
 
 /**
@@ -94,6 +116,7 @@
 	dev_dbg(&ihost->pdev->dev,
 		"%s: idev = %p\n", __func__, idev);
 
+	clear_bit(IDEV_IO_NCQERROR, &idev->flags);
 	set_bit(IDEV_IO_READY, &idev->flags);
 	if (test_and_clear_bit(IDEV_START_PENDING, &idev->flags))
 		wake_up(&ihost->eventq);
diff --git a/drivers/scsi/isci/remote_device.h b/drivers/scsi/isci/remote_device.h
index cde5950..0d9e37f 100644
--- a/drivers/scsi/isci/remote_device.h
+++ b/drivers/scsi/isci/remote_device.h
@@ -136,6 +136,7 @@
 	#define IDEV_EH 3
 	#define IDEV_GONE 4
 	#define IDEV_IO_READY 5
+	#define IDEV_IO_NCQERROR 6
 	unsigned long flags;
 	struct kref kref;
 	struct isci_port *isci_port;
diff --git a/drivers/scsi/isci/request.c b/drivers/scsi/isci/request.c
index 1043fed..08a7340b 100644
--- a/drivers/scsi/isci/request.c
+++ b/drivers/scsi/isci/request.c
@@ -3587,9 +3587,30 @@
 
 	spin_lock_irqsave(&ihost->scic_lock, flags);
 
-	/* send the request, let the core assign the IO TAG.	*/
-	status = scic_controller_start_io(&ihost->sci, &idev->sci, &ireq->sci,
-					  SCI_CONTROLLER_INVALID_IO_TAG);
+	if (test_bit(IDEV_IO_NCQERROR, &idev->flags)) {
+
+		if (isci_task_is_ncq_recovery(task)) {
+
+			/* The device is in an NCQ recovery state.  Issue the
+			 * request on the task side.  Note that it will
+			 * complete on the I/O request side because the
+			 * request was built that way (ie.
+			 * ireq->is_task_management_request is false).
+			 */
+			status = scic_controller_start_task(&ihost->sci,
+							    &idev->sci,
+							    &ireq->sci,
+							    SCI_CONTROLLER_INVALID_IO_TAG);
+		} else {
+			status = SCI_FAILURE;
+		}
+	} else {
+
+		/* send the request, let the core assign the IO TAG.	*/
+		status = scic_controller_start_io(&ihost->sci, &idev->sci,
+						  &ireq->sci,
+						  SCI_CONTROLLER_INVALID_IO_TAG);
+	}
 	if (status != SCI_SUCCESS &&
 	    status != SCI_FAILURE_REMOTE_DEVICE_RESET_REQUIRED) {
 		dev_warn(&ihost->pdev->dev,
diff --git a/drivers/scsi/isci/request.h b/drivers/scsi/isci/request.h
index 7c8b59a..9130f22 100644
--- a/drivers/scsi/isci/request.h
+++ b/drivers/scsi/isci/request.h
@@ -687,4 +687,13 @@
 void
 scic_stp_io_request_set_ncq_tag(struct scic_sds_request *sci_req, u16 ncq_tag);
 void scic_sds_smp_request_copy_response(struct scic_sds_request *sci_req);
+
+static inline int isci_task_is_ncq_recovery(struct sas_task *task)
+{
+	return (sas_protocol_ata(task->task_proto) &&
+	        task->ata_task.fis.command == ATA_CMD_READ_LOG_EXT &&
+	        task->ata_task.fis.lbal == ATA_LOG_SATA_NCQ);
+
+}
+
 #endif /* !defined(_ISCI_REQUEST_H_) */
diff --git a/drivers/scsi/isci/task.c b/drivers/scsi/isci/task.c
index 0835a2c..157e997 100644
--- a/drivers/scsi/isci/task.c
+++ b/drivers/scsi/isci/task.c
@@ -133,6 +133,15 @@
 	for (; num > 0; num--,\
 	     task = list_entry(task->list.next, struct sas_task, list))
 
+
+static inline int isci_device_io_ready(struct isci_remote_device *idev,
+				       struct sas_task *task)
+{
+	return idev ? test_bit(IDEV_IO_READY, &idev->flags) ||
+		      (test_bit(IDEV_IO_NCQERROR, &idev->flags) &&
+		       isci_task_is_ncq_recovery(task))
+		    : 0;
+}
 /**
  * isci_task_execute_task() - This function is one of the SAS Domain Template
  *    functions. This function is called by libsas to send a task down to
@@ -165,7 +174,7 @@
 	for_each_sas_task(num, task) {
 		spin_lock_irqsave(&ihost->scic_lock, flags);
 		idev = isci_lookup_device(task->dev);
-		io_ready = idev ? test_bit(IDEV_IO_READY, &idev->flags) : 0;
+		io_ready = isci_device_io_ready(idev, task);
 		spin_unlock_irqrestore(&ihost->scic_lock, flags);
 
 		dev_dbg(&ihost->pdev->dev,
@@ -178,6 +187,7 @@
 					 SAS_DEVICE_UNKNOWN);
 			isci_host_can_dequeue(ihost, 1);
 		} else if (!io_ready) {
+
 			/* Indicate QUEUE_FULL so that the scsi midlayer
 			 * retries.
 			  */
@@ -299,7 +309,9 @@
 	/* sanity check, return TMF_RESP_FUNC_FAILED
 	 * if the device is not there and ready.
 	 */
-	if (!isci_device || !test_bit(IDEV_IO_READY, &isci_device->flags)) {
+	if (!isci_device ||
+	    (!test_bit(IDEV_IO_READY, &isci_device->flags) &&
+	     !test_bit(IDEV_IO_NCQERROR, &isci_device->flags))) {
 		dev_dbg(&ihost->pdev->dev,
 			"%s: isci_device = %p not ready (%#lx)\n",
 			__func__,