[SCSI] mptsas: add support for PHY resets

Support PHY resets in mptsas.  Thanks to Eric for various bug fixes
and improvements.

Signed-off-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: James Bottomley <James.Bottomley@SteelEye.com>
diff --git a/drivers/message/fusion/mptsas.c b/drivers/message/fusion/mptsas.c
index dcdf038..1c557a0 100644
--- a/drivers/message/fusion/mptsas.c
+++ b/drivers/message/fusion/mptsas.c
@@ -83,6 +83,7 @@
 static int	mptsasDoneCtx = -1;
 static int	mptsasTaskCtx = -1;
 static int	mptsasInternalCtx = -1; /* Used only for internal commands */
+static int	mptsasMgmtCtx = -1;
 
 
 /*
@@ -359,9 +360,92 @@
 	return error;
 }
 
+static int mptsas_mgmt_done(MPT_ADAPTER *ioc, MPT_FRAME_HDR *req,
+		MPT_FRAME_HDR *reply)
+{
+	ioc->sas_mgmt.status |= MPT_SAS_MGMT_STATUS_COMMAND_GOOD;
+	if (reply != NULL) {
+		ioc->sas_mgmt.status |= MPT_SAS_MGMT_STATUS_RF_VALID;
+		memcpy(ioc->sas_mgmt.reply, reply,
+		    min(ioc->reply_sz, 4 * reply->u.reply.MsgLength));
+	}
+	complete(&ioc->sas_mgmt.done);
+	return 1;
+}
+
+static int mptsas_phy_reset(struct sas_phy *phy, int hard_reset)
+{
+	MPT_ADAPTER *ioc = phy_to_ioc(phy);
+	SasIoUnitControlRequest_t *req;
+	SasIoUnitControlReply_t *reply;
+	MPT_FRAME_HDR *mf;
+	MPIHeader_t *hdr;
+	unsigned long timeleft;
+	int error = -ERESTARTSYS;
+
+	/* not implemented for expanders */
+	if (phy->identify.target_port_protocols & SAS_PROTOCOL_SMP)
+		return -ENXIO;
+
+	if (down_interruptible(&ioc->sas_mgmt.mutex))
+		goto out;
+
+	mf = mpt_get_msg_frame(mptsasMgmtCtx, ioc);
+	if (!mf) {
+		error = -ENOMEM;
+		goto out_unlock;
+	}
+
+	hdr = (MPIHeader_t *) mf;
+	req = (SasIoUnitControlRequest_t *)mf;
+	memset(req, 0, sizeof(SasIoUnitControlRequest_t));
+	req->Function = MPI_FUNCTION_SAS_IO_UNIT_CONTROL;
+	req->MsgContext = hdr->MsgContext;
+	req->Operation = hard_reset ?
+		MPI_SAS_OP_PHY_HARD_RESET : MPI_SAS_OP_PHY_LINK_RESET;
+	req->PhyNum = phy->identify.phy_identifier;
+
+	mpt_put_msg_frame(mptsasMgmtCtx, ioc, mf);
+
+	timeleft = wait_for_completion_timeout(&ioc->sas_mgmt.done,
+			10 * HZ);
+	if (!timeleft) {
+		/* On timeout reset the board */
+		mpt_free_msg_frame(ioc, mf);
+		mpt_HardResetHandler(ioc, CAN_SLEEP);
+		error = -ETIMEDOUT;
+		goto out_unlock;
+	}
+
+	/* a reply frame is expected */
+	if ((ioc->sas_mgmt.status &
+	    MPT_IOCTL_STATUS_RF_VALID) == 0) {
+		error = -ENXIO;
+		goto out_unlock;
+	}
+
+	/* process the completed Reply Message Frame */
+	reply = (SasIoUnitControlReply_t *)ioc->sas_mgmt.reply;
+	if (reply->IOCStatus != MPI_IOCSTATUS_SUCCESS) {
+		printk("%s: IOCStatus=0x%X IOCLogInfo=0x%X\n",
+		    __FUNCTION__,
+		    reply->IOCStatus,
+		    reply->IOCLogInfo);
+		error = -ENXIO;
+		goto out_unlock;
+	}
+
+	error = 0;
+
+ out_unlock:
+	up(&ioc->sas_mgmt.mutex);
+ out:
+	return error;
+}
 
 static struct sas_function_template mptsas_transport_functions = {
 	.get_linkerrors		= mptsas_get_linkerrors,
+	.phy_reset		= mptsas_phy_reset,
 };
 
 static struct scsi_transport_template *mptsas_transport_template;
@@ -1105,6 +1189,8 @@
 	sh->unique_id = ioc->id;
 
 	INIT_LIST_HEAD(&ioc->sas_topology);
+	init_MUTEX(&ioc->sas_mgmt.mutex);
+	init_completion(&ioc->sas_mgmt.done);
 
 	/* Verify that we won't exceed the maximum
 	 * number of chain buffers
@@ -1291,6 +1377,7 @@
 	mptsasTaskCtx = mpt_register(mptscsih_taskmgmt_complete, MPTSAS_DRIVER);
 	mptsasInternalCtx =
 		mpt_register(mptscsih_scandv_complete, MPTSAS_DRIVER);
+	mptsasMgmtCtx = mpt_register(mptsas_mgmt_done, MPTSAS_DRIVER);
 
 	if (mpt_event_register(mptsasDoneCtx, mptscsih_event_process) == 0) {
 		devtprintk((KERN_INFO MYNAM
@@ -1314,6 +1401,7 @@
 	mpt_reset_deregister(mptsasDoneCtx);
 	mpt_event_deregister(mptsasDoneCtx);
 
+	mpt_deregister(mptsasMgmtCtx);
 	mpt_deregister(mptsasInternalCtx);
 	mpt_deregister(mptsasTaskCtx);
 	mpt_deregister(mptsasDoneCtx);