summaryrefslogtreecommitdiff
path: root/drivers/message/fusion/mptscsih.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/message/fusion/mptscsih.c')
-rw-r--r--drivers/message/fusion/mptscsih.c6021
1 files changed, 6021 insertions, 0 deletions
diff --git a/drivers/message/fusion/mptscsih.c b/drivers/message/fusion/mptscsih.c
new file mode 100644
index 000000000000..c98d6257ec02
--- /dev/null
+++ b/drivers/message/fusion/mptscsih.c
@@ -0,0 +1,6021 @@
+/*
+ * linux/drivers/message/fusion/mptscsih.c
+ * High performance SCSI / Fibre Channel SCSI Host device driver.
+ * For use with PCI chip/adapter(s):
+ * LSIFC9xx/LSI409xx Fibre Channel
+ * running LSI Logic Fusion MPT (Message Passing Technology) firmware.
+ *
+ * Credits:
+ * This driver would not exist if not for Alan Cox's development
+ * of the linux i2o driver.
+ *
+ * A special thanks to Pamela Delaney (LSI Logic) for tons of work
+ * and countless enhancements while adding support for the 1030
+ * chip family. Pam has been instrumental in the development of
+ * of the 2.xx.xx series fusion drivers, and her contributions are
+ * far too numerous to hope to list in one place.
+ *
+ * A huge debt of gratitude is owed to David S. Miller (DaveM)
+ * for fixing much of the stupid and broken stuff in the early
+ * driver while porting to sparc64 platform. THANK YOU!
+ *
+ * (see mptbase.c)
+ *
+ * Copyright (c) 1999-2004 LSI Logic Corporation
+ * Original author: Steven J. Ralston
+ * (mailto:sjralston1@netscape.net)
+ * (mailto:mpt_linux_developer@lsil.com)
+ *
+ * $Id: mptscsih.c,v 1.104 2002/12/03 21:26:34 pdelaney Exp $
+ */
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+/*
+ 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; version 2 of the License.
+
+ 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.
+
+ NO WARRANTY
+ THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR
+ CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT
+ LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT,
+ MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is
+ solely responsible for determining the appropriateness of using and
+ distributing the Program and assumes all risks associated with its
+ exercise of rights under this Agreement, including but not limited to
+ the risks and costs of program errors, damage to or loss of data,
+ programs or equipment, and unavailability or interruption of operations.
+
+ DISCLAIMER OF LIABILITY
+ NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
+ HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*/
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+
+#include "linux_compat.h" /* linux-2.6 tweaks */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/kdev_t.h>
+#include <linux/blkdev.h>
+#include <linux/delay.h> /* for mdelay */
+#include <linux/interrupt.h> /* needed for in_interrupt() proto */
+#include <linux/reboot.h> /* notifier code */
+#include <linux/sched.h>
+#include <linux/workqueue.h>
+
+#include <scsi/scsi.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_tcq.h>
+
+#include "mptbase.h"
+#include "mptscsih.h"
+
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+#define my_NAME "Fusion MPT SCSI Host driver"
+#define my_VERSION MPT_LINUX_VERSION_COMMON
+#define MYNAM "mptscsih"
+
+MODULE_AUTHOR(MODULEAUTHOR);
+MODULE_DESCRIPTION(my_NAME);
+MODULE_LICENSE("GPL");
+
+/* Command line args */
+static int mpt_dv = MPTSCSIH_DOMAIN_VALIDATION;
+MODULE_PARM(mpt_dv, "i");
+MODULE_PARM_DESC(mpt_dv, " DV Algorithm: enhanced=1, basic=0 (default=MPTSCSIH_DOMAIN_VALIDATION=1)");
+
+static int mpt_width = MPTSCSIH_MAX_WIDTH;
+MODULE_PARM(mpt_width, "i");
+MODULE_PARM_DESC(mpt_width, " Max Bus Width: wide=1, narrow=0 (default=MPTSCSIH_MAX_WIDTH=1)");
+
+static int mpt_factor = MPTSCSIH_MIN_SYNC;
+MODULE_PARM(mpt_factor, "h");
+MODULE_PARM_DESC(mpt_factor, " Min Sync Factor (default=MPTSCSIH_MIN_SYNC=0x08)");
+
+static int mpt_saf_te = MPTSCSIH_SAF_TE;
+MODULE_PARM(mpt_saf_te, "i");
+MODULE_PARM_DESC(mpt_saf_te, " Force enabling SEP Processor: enable=1 (default=MPTSCSIH_SAF_TE=0)");
+
+static int mpt_pq_filter = 0;
+MODULE_PARM(mpt_pq_filter, "i");
+MODULE_PARM_DESC(mpt_pq_filter, " Enable peripheral qualifier filter: enable=1 (default=0)");
+
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+
+typedef struct _BIG_SENSE_BUF {
+ u8 data[MPT_SENSE_BUFFER_ALLOC];
+} BIG_SENSE_BUF;
+
+#define MPT_SCANDV_GOOD (0x00000000) /* must be 0 */
+#define MPT_SCANDV_DID_RESET (0x00000001)
+#define MPT_SCANDV_SENSE (0x00000002)
+#define MPT_SCANDV_SOME_ERROR (0x00000004)
+#define MPT_SCANDV_SELECTION_TIMEOUT (0x00000008)
+#define MPT_SCANDV_ISSUE_SENSE (0x00000010)
+#define MPT_SCANDV_FALLBACK (0x00000020)
+
+#define MPT_SCANDV_MAX_RETRIES (10)
+
+#define MPT_ICFLAG_BUF_CAP 0x01 /* ReadBuffer Read Capacity format */
+#define MPT_ICFLAG_ECHO 0x02 /* ReadBuffer Echo buffer format */
+#define MPT_ICFLAG_PHYS_DISK 0x04 /* Any SCSI IO but do Phys Disk Format */
+#define MPT_ICFLAG_TAGGED_CMD 0x08 /* Do tagged IO */
+#define MPT_ICFLAG_DID_RESET 0x20 /* Bus Reset occurred with this command */
+#define MPT_ICFLAG_RESERVED 0x40 /* Reserved has been issued */
+
+typedef struct _internal_cmd {
+ char *data; /* data pointer */
+ dma_addr_t data_dma; /* data dma address */
+ int size; /* transfer size */
+ u8 cmd; /* SCSI Op Code */
+ u8 bus; /* bus number */
+ u8 id; /* SCSI ID (virtual) */
+ u8 lun;
+ u8 flags; /* Bit Field - See above */
+ u8 physDiskNum; /* Phys disk number, -1 else */
+ u8 rsvd2;
+ u8 rsvd;
+} INTERNAL_CMD;
+
+typedef struct _negoparms {
+ u8 width;
+ u8 offset;
+ u8 factor;
+ u8 flags;
+} NEGOPARMS;
+
+typedef struct _dv_parameters {
+ NEGOPARMS max;
+ NEGOPARMS now;
+ u8 cmd;
+ u8 id;
+ u16 pad1;
+} DVPARAMETERS;
+
+
+/*
+ * Other private/forward protos...
+ */
+static int mptscsih_io_done(MPT_ADAPTER *ioc, MPT_FRAME_HDR *mf, MPT_FRAME_HDR *r);
+static void mptscsih_report_queue_full(struct scsi_cmnd *sc, SCSIIOReply_t *pScsiReply, SCSIIORequest_t *pScsiReq);
+static int mptscsih_taskmgmt_complete(MPT_ADAPTER *ioc, MPT_FRAME_HDR *mf, MPT_FRAME_HDR *r);
+
+static int mptscsih_AddSGE(MPT_ADAPTER *ioc, struct scsi_cmnd *SCpnt,
+ SCSIIORequest_t *pReq, int req_idx);
+static void mptscsih_freeChainBuffers(MPT_ADAPTER *ioc, int req_idx);
+static void copy_sense_data(struct scsi_cmnd *sc, MPT_SCSI_HOST *hd, MPT_FRAME_HDR *mf, SCSIIOReply_t *pScsiReply);
+static int mptscsih_tm_pending_wait(MPT_SCSI_HOST * hd);
+static int mptscsih_tm_wait_for_completion(MPT_SCSI_HOST * hd, ulong timeout );
+static u32 SCPNT_TO_LOOKUP_IDX(struct scsi_cmnd *sc);
+
+static int mptscsih_TMHandler(MPT_SCSI_HOST *hd, u8 type, u8 channel, u8 target, u8 lun, int ctx2abort, ulong timeout);
+static int mptscsih_IssueTaskMgmt(MPT_SCSI_HOST *hd, u8 type, u8 channel, u8 target, u8 lun, int ctx2abort, ulong timeout);
+
+static int mptscsih_ioc_reset(MPT_ADAPTER *ioc, int post_reset);
+static int mptscsih_event_process(MPT_ADAPTER *ioc, EventNotificationReply_t *pEvReply);
+
+static void mptscsih_initTarget(MPT_SCSI_HOST *hd, int bus_id, int target_id, u8 lun, char *data, int dlen);
+static void mptscsih_setTargetNegoParms(MPT_SCSI_HOST *hd, VirtDevice *target, char byte56);
+static void mptscsih_set_dvflags(MPT_SCSI_HOST *hd, SCSIIORequest_t *pReq);
+static void mptscsih_setDevicePage1Flags (u8 width, u8 factor, u8 offset, int *requestedPtr, int *configurationPtr, u8 flags);
+static void mptscsih_no_negotiate(MPT_SCSI_HOST *hd, int target_id);
+static int mptscsih_writeSDP1(MPT_SCSI_HOST *hd, int portnum, int target, int flags);
+static int mptscsih_writeIOCPage4(MPT_SCSI_HOST *hd, int target_id, int bus);
+static int mptscsih_scandv_complete(MPT_ADAPTER *ioc, MPT_FRAME_HDR *mf, MPT_FRAME_HDR *r);
+static void mptscsih_timer_expired(unsigned long data);
+static int mptscsih_do_cmd(MPT_SCSI_HOST *hd, INTERNAL_CMD *iocmd);
+static int mptscsih_synchronize_cache(MPT_SCSI_HOST *hd, int portnum);
+
+#ifdef MPTSCSIH_ENABLE_DOMAIN_VALIDATION
+static int mptscsih_do_raid(MPT_SCSI_HOST *hd, u8 action, INTERNAL_CMD *io);
+static void mptscsih_domainValidation(void *hd);
+static int mptscsih_is_phys_disk(MPT_ADAPTER *ioc, int id);
+static void mptscsih_qas_check(MPT_SCSI_HOST *hd, int id);
+static int mptscsih_doDv(MPT_SCSI_HOST *hd, int channel, int target);
+static void mptscsih_dv_parms(MPT_SCSI_HOST *hd, DVPARAMETERS *dv,void *pPage);
+static void mptscsih_fillbuf(char *buffer, int size, int index, int width);
+#endif
+/* module entry point */
+static int __init mptscsih_init (void);
+static void __exit mptscsih_exit (void);
+
+static int mptscsih_probe (struct pci_dev *, const struct pci_device_id *);
+static void mptscsih_remove(struct pci_dev *);
+static void mptscsih_shutdown(struct device *);
+#ifdef CONFIG_PM
+static int mptscsih_suspend(struct pci_dev *pdev, u32 state);
+static int mptscsih_resume(struct pci_dev *pdev);
+#endif
+
+
+/*
+ * Private data...
+ */
+
+static int mpt_scsi_hosts = 0;
+
+static int ScsiDoneCtx = -1;
+static int ScsiTaskCtx = -1;
+static int ScsiScanDvCtx = -1; /* Used only for bus scan and dv */
+
+#define SNS_LEN(scp) sizeof((scp)->sense_buffer)
+
+#ifdef MPTSCSIH_ENABLE_DOMAIN_VALIDATION
+/*
+ * Domain Validation task structure
+ */
+static DEFINE_SPINLOCK(dvtaskQ_lock);
+static int dvtaskQ_active = 0;
+static int dvtaskQ_release = 0;
+static struct work_struct mptscsih_dvTask;
+#endif
+
+/*
+ * Wait Queue setup
+ */
+static DECLARE_WAIT_QUEUE_HEAD (scandv_waitq);
+static int scandv_wait_done = 1;
+
+
+/* Driver command line structure
+ */
+static struct scsi_host_template driver_template;
+
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+/**
+ * mptscsih_add_sge - Place a simple SGE at address pAddr.
+ * @pAddr: virtual address for SGE
+ * @flagslength: SGE flags and data transfer length
+ * @dma_addr: Physical address
+ *
+ * This routine places a MPT request frame back on the MPT adapter's
+ * FreeQ.
+ */
+static inline void
+mptscsih_add_sge(char *pAddr, u32 flagslength, dma_addr_t dma_addr)
+{
+ if (sizeof(dma_addr_t) == sizeof(u64)) {
+ SGESimple64_t *pSge = (SGESimple64_t *) pAddr;
+ u32 tmp = dma_addr & 0xFFFFFFFF;
+
+ pSge->FlagsLength = cpu_to_le32(flagslength);
+ pSge->Address.Low = cpu_to_le32(tmp);
+ tmp = (u32) ((u64)dma_addr >> 32);
+ pSge->Address.High = cpu_to_le32(tmp);
+
+ } else {
+ SGESimple32_t *pSge = (SGESimple32_t *) pAddr;
+ pSge->FlagsLength = cpu_to_le32(flagslength);
+ pSge->Address = cpu_to_le32(dma_addr);
+ }
+} /* mptscsih_add_sge() */
+
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+/**
+ * mptscsih_add_chain - Place a chain SGE at address pAddr.
+ * @pAddr: virtual address for SGE
+ * @next: nextChainOffset value (u32's)
+ * @length: length of next SGL segment
+ * @dma_addr: Physical address
+ *
+ * This routine places a MPT request frame back on the MPT adapter's
+ * FreeQ.
+ */
+static inline void
+mptscsih_add_chain(char *pAddr, u8 next, u16 length, dma_addr_t dma_addr)
+{
+ if (sizeof(dma_addr_t) == sizeof(u64)) {
+ SGEChain64_t *pChain = (SGEChain64_t *) pAddr;
+ u32 tmp = dma_addr & 0xFFFFFFFF;
+
+ pChain->Length = cpu_to_le16(length);
+ pChain->Flags = MPI_SGE_FLAGS_CHAIN_ELEMENT | mpt_addr_size();
+
+ pChain->NextChainOffset = next;
+
+ pChain->Address.Low = cpu_to_le32(tmp);
+ tmp = (u32) ((u64)dma_addr >> 32);
+ pChain->Address.High = cpu_to_le32(tmp);
+ } else {
+ SGEChain32_t *pChain = (SGEChain32_t *) pAddr;
+ pChain->Length = cpu_to_le16(length);
+ pChain->Flags = MPI_SGE_FLAGS_CHAIN_ELEMENT | mpt_addr_size();
+ pChain->NextChainOffset = next;
+ pChain->Address = cpu_to_le32(dma_addr);
+ }
+} /* mptscsih_add_chain() */
+
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+/*
+ * mptscsih_getFreeChainBuffer - Function to get a free chain
+ * from the MPT_SCSI_HOST FreeChainQ.
+ * @ioc: Pointer to MPT_ADAPTER structure
+ * @req_idx: Index of the SCSI IO request frame. (output)
+ *
+ * return SUCCESS or FAILED
+ */
+static inline int
+mptscsih_getFreeChainBuffer(MPT_ADAPTER *ioc, int *retIndex)
+{
+ MPT_FRAME_HDR *chainBuf;
+ unsigned long flags;
+ int rc;
+ int chain_idx;
+
+ dsgprintk((MYIOC_s_INFO_FMT "getFreeChainBuffer called\n",
+ ioc->name));
+ spin_lock_irqsave(&ioc->FreeQlock, flags);
+ if (!list_empty(&ioc->FreeChainQ)) {
+ int offset;
+
+ chainBuf = list_entry(ioc->FreeChainQ.next, MPT_FRAME_HDR,
+ u.frame.linkage.list);
+ list_del(&chainBuf->u.frame.linkage.list);
+ offset = (u8 *)chainBuf - (u8 *)ioc->ChainBuffer;
+ chain_idx = offset / ioc->req_sz;
+ rc = SUCCESS;
+ dsgprintk((MYIOC_s_INFO_FMT "getFreeChainBuffer (index %d), got buf=%p\n",
+ ioc->name, *retIndex, chainBuf));
+ } else {
+ rc = FAILED;
+ chain_idx = MPT_HOST_NO_CHAIN;
+ dfailprintk((MYIOC_s_ERR_FMT "getFreeChainBuffer failed\n",
+ ioc->name));
+ }
+ spin_unlock_irqrestore(&ioc->FreeQlock, flags);
+
+ *retIndex = chain_idx;
+ return rc;
+} /* mptscsih_getFreeChainBuffer() */
+
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+/*
+ * mptscsih_AddSGE - Add a SGE (plus chain buffers) to the
+ * SCSIIORequest_t Message Frame.
+ * @ioc: Pointer to MPT_ADAPTER structure
+ * @SCpnt: Pointer to scsi_cmnd structure
+ * @pReq: Pointer to SCSIIORequest_t structure
+ *
+ * Returns ...
+ */
+static int
+mptscsih_AddSGE(MPT_ADAPTER *ioc, struct scsi_cmnd *SCpnt,
+ SCSIIORequest_t *pReq, int req_idx)
+{
+ char *psge;
+ char *chainSge;
+ struct scatterlist *sg;
+ int frm_sz;
+ int sges_left, sg_done;
+ int chain_idx = MPT_HOST_NO_CHAIN;
+ int sgeOffset;
+ int numSgeSlots, numSgeThisFrame;
+ u32 sgflags, sgdir, thisxfer = 0;
+ int chain_dma_off = 0;
+ int newIndex;
+ int ii;
+ dma_addr_t v2;
+ u32 RequestNB;
+
+ sgdir = le32_to_cpu(pReq->Control) & MPI_SCSIIO_CONTROL_DATADIRECTION_MASK;
+ if (sgdir == MPI_SCSIIO_CONTROL_WRITE) {
+ sgdir = MPT_TRANSFER_HOST_TO_IOC;
+ } else {
+ sgdir = MPT_TRANSFER_IOC_TO_HOST;
+ }
+
+ psge = (char *) &pReq->SGL;
+ frm_sz = ioc->req_sz;
+
+ /* Map the data portion, if any.
+ * sges_left = 0 if no data transfer.
+ */
+ if ( (sges_left = SCpnt->use_sg) ) {
+ sges_left = pci_map_sg(ioc->pcidev,
+ (struct scatterlist *) SCpnt->request_buffer,
+ SCpnt->use_sg,
+ SCpnt->sc_data_direction);
+ if (sges_left == 0)
+ return FAILED;
+ } else if (SCpnt->request_bufflen) {
+ SCpnt->SCp.dma_handle = pci_map_single(ioc->pcidev,
+ SCpnt->request_buffer,
+ SCpnt->request_bufflen,
+ SCpnt->sc_data_direction);
+ dsgprintk((MYIOC_s_INFO_FMT "SG: non-SG for %p, len=%d\n",
+ ioc->name, SCpnt, SCpnt->request_bufflen));
+ mptscsih_add_sge((char *) &pReq->SGL,
+ 0xD1000000|MPT_SGE_FLAGS_ADDRESSING|sgdir|SCpnt->request_bufflen,
+ SCpnt->SCp.dma_handle);
+
+ return SUCCESS;
+ }
+
+ /* Handle the SG case.
+ */
+ sg = (struct scatterlist *) SCpnt->request_buffer;
+ sg_done = 0;
+ sgeOffset = sizeof(SCSIIORequest_t) - sizeof(SGE_IO_UNION);
+ chainSge = NULL;
+
+ /* Prior to entering this loop - the following must be set
+ * current MF: sgeOffset (bytes)
+ * chainSge (Null if original MF is not a chain buffer)
+ * sg_done (num SGE done for this MF)
+ */
+
+nextSGEset:
+ numSgeSlots = ((frm_sz - sgeOffset) / (sizeof(u32) + sizeof(dma_addr_t)) );
+ numSgeThisFrame = (sges_left < numSgeSlots) ? sges_left : numSgeSlots;
+
+ sgflags = MPT_SGE_FLAGS_SIMPLE_ELEMENT | MPT_SGE_FLAGS_ADDRESSING | sgdir;
+
+ /* Get first (num - 1) SG elements
+ * Skip any SG entries with a length of 0
+ * NOTE: at finish, sg and psge pointed to NEXT data/location positions
+ */
+ for (ii=0; ii < (numSgeThisFrame-1); ii++) {
+ thisxfer = sg_dma_len(sg);
+ if (thisxfer == 0) {
+ sg ++; /* Get next SG element from the OS */
+ sg_done++;
+ continue;
+ }
+
+ v2 = sg_dma_address(sg);
+ mptscsih_add_sge(psge, sgflags | thisxfer, v2);
+
+ sg++; /* Get next SG element from the OS */
+ psge += (sizeof(u32) + sizeof(dma_addr_t));
+ sgeOffset += (sizeof(u32) + sizeof(dma_addr_t));
+ sg_done++;
+ }
+
+ if (numSgeThisFrame == sges_left) {
+ /* Add last element, end of buffer and end of list flags.
+ */
+ sgflags |= MPT_SGE_FLAGS_LAST_ELEMENT |
+ MPT_SGE_FLAGS_END_OF_BUFFER |
+ MPT_SGE_FLAGS_END_OF_LIST;
+
+ /* Add last SGE and set termination flags.
+ * Note: Last SGE may have a length of 0 - which should be ok.
+ */
+ thisxfer = sg_dma_len(sg);
+
+ v2 = sg_dma_address(sg);
+ mptscsih_add_sge(psge, sgflags | thisxfer, v2);
+ /*
+ sg++;
+ psge += (sizeof(u32) + sizeof(dma_addr_t));
+ */
+ sgeOffset += (sizeof(u32) + sizeof(dma_addr_t));
+ sg_done++;
+
+ if (chainSge) {
+ /* The current buffer is a chain buffer,
+ * but there is not another one.
+ * Update the chain element
+ * Offset and Length fields.
+ */
+ mptscsih_add_chain((char *)chainSge, 0, sgeOffset, ioc->ChainBufferDMA + chain_dma_off);
+ } else {
+ /* The current buffer is the original MF
+ * and there is no Chain buffer.
+ */
+ pReq->ChainOffset = 0;
+ RequestNB = (((sgeOffset - 1) >> ioc->NBShiftFactor) + 1) & 0x03;
+ dsgprintk((MYIOC_s_ERR_FMT
+ "Single Buffer RequestNB=%x, sgeOffset=%d\n", ioc->name, RequestNB, sgeOffset));
+ ioc->RequestNB[req_idx] = RequestNB;
+ }
+ } else {
+ /* At least one chain buffer is needed.
+ * Complete the first MF
+ * - last SGE element, set the LastElement bit
+ * - set ChainOffset (words) for orig MF
+ * (OR finish previous MF chain buffer)
+ * - update MFStructPtr ChainIndex
+ * - Populate chain element
+ * Also
+ * Loop until done.
+ */
+
+ dsgprintk((MYIOC_s_INFO_FMT "SG: Chain Required! sg done %d\n",
+ ioc->name, sg_done));
+
+ /* Set LAST_ELEMENT flag for last non-chain element
+ * in the buffer. Since psge points at the NEXT
+ * SGE element, go back one SGE element, update the flags
+ * and reset the pointer. (Note: sgflags & thisxfer are already
+ * set properly).
+ */
+ if (sg_done) {
+ u32 *ptmp = (u32 *) (psge - (sizeof(u32) + sizeof(dma_addr_t)));
+ sgflags = le32_to_cpu(*ptmp);
+ sgflags |= MPT_SGE_FLAGS_LAST_ELEMENT;
+ *ptmp = cpu_to_le32(sgflags);
+ }
+
+ if (chainSge) {
+ /* The current buffer is a chain buffer.
+ * chainSge points to the previous Chain Element.
+ * Update its chain element Offset and Length (must
+ * include chain element size) fields.
+ * Old chain element is now complete.
+ */
+ u8 nextChain = (u8) (sgeOffset >> 2);
+ sgeOffset += (sizeof(u32) + sizeof(dma_addr_t));
+ mptscsih_add_chain((char *)chainSge, nextChain, sgeOffset, ioc->ChainBufferDMA + chain_dma_off);
+ } else {
+ /* The original MF buffer requires a chain buffer -
+ * set the offset.
+ * Last element in this MF is a chain element.
+ */
+ pReq->ChainOffset = (u8) (sgeOffset >> 2);
+ RequestNB = (((sgeOffset - 1) >> ioc->NBShiftFactor) + 1) & 0x03;
+ dsgprintk((MYIOC_s_ERR_FMT "Chain Buffer Needed, RequestNB=%x sgeOffset=%d\n", ioc->name, RequestNB, sgeOffset));
+ ioc->RequestNB[req_idx] = RequestNB;
+ }
+
+ sges_left -= sg_done;
+
+
+ /* NOTE: psge points to the beginning of the chain element
+ * in current buffer. Get a chain buffer.
+ */
+ dsgprintk((MYIOC_s_INFO_FMT
+ "calling getFreeChainBuffer SCSI cmd=%02x (%p)\n",
+ ioc->name, pReq->CDB[0], SCpnt));
+ if ((mptscsih_getFreeChainBuffer(ioc, &newIndex)) == FAILED)
+ return FAILED;
+
+ /* Update the tracking arrays.
+ * If chainSge == NULL, update ReqToChain, else ChainToChain
+ */
+ if (chainSge) {
+ ioc->ChainToChain[chain_idx] = newIndex;
+ } else {
+ ioc->ReqToChain[req_idx] = newIndex;
+ }
+ chain_idx = newIndex;
+ chain_dma_off = ioc->req_sz * chain_idx;
+
+ /* Populate the chainSGE for the current buffer.
+ * - Set chain buffer pointer to psge and fill
+ * out the Address and Flags fields.
+ */
+ chainSge = (char *) psge;
+ dsgprintk((KERN_INFO " Current buff @ %p (index 0x%x)",
+ psge, req_idx));
+
+ /* Start the SGE for the next buffer
+ */
+ psge = (char *) (ioc->ChainBuffer + chain_dma_off);
+ sgeOffset = 0;
+ sg_done = 0;
+
+ dsgprintk((KERN_INFO " Chain buff @ %p (index 0x%x)\n",
+ psge, chain_idx));
+
+ /* Start the SGE for the next buffer
+ */
+
+ goto nextSGEset;
+ }
+
+ return SUCCESS;
+} /* mptscsih_AddSGE() */
+
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+/*
+ * mptscsih_io_done - Main SCSI IO callback routine registered to
+ * Fusion MPT (base) driver
+ * @ioc: Pointer to MPT_ADAPTER structure
+ * @mf: Pointer to original MPT request frame
+ * @r: Pointer to MPT reply frame (NULL if TurboReply)
+ *
+ * This routine is called from mpt.c::mpt_interrupt() at the completion
+ * of any SCSI IO request.
+ * This routine is registered with the Fusion MPT (base) driver at driver
+ * load/init time via the mpt_register() API call.
+ *
+ * Returns 1 indicating alloc'd request frame ptr should be freed.
+ */
+static int
+mptscsih_io_done(MPT_ADAPTER *ioc, MPT_FRAME_HDR *mf, MPT_FRAME_HDR *mr)
+{
+ struct scsi_cmnd *sc;
+ MPT_SCSI_HOST *hd;
+ SCSIIORequest_t *pScsiReq;
+ SCSIIOReply_t *pScsiReply;
+ u16 req_idx;
+
+ hd = (MPT_SCSI_HOST *) ioc->sh->hostdata;
+
+ req_idx = le16_to_cpu(mf->u.frame.hwhdr.msgctxu.fld.req_idx);
+ sc = hd->ScsiLookup[req_idx];
+ if (sc == NULL) {
+ MPIHeader_t *hdr = (MPIHeader_t *)mf;
+
+ /* Remark: writeSDP1 will use the ScsiDoneCtx
+ * If a SCSI I/O cmd, device disabled by OS and
+ * completion done. Cannot touch sc struct. Just free mem.
+ */
+ if (hdr->Function == MPI_FUNCTION_SCSI_IO_REQUEST)
+ printk(MYIOC_s_ERR_FMT "NULL ScsiCmd ptr!\n",
+ ioc->name);
+
+ mptscsih_freeChainBuffers(ioc, req_idx);
+ return 1;
+ }
+
+ dmfprintk((MYIOC_s_INFO_FMT
+ "ScsiDone (mf=%p,mr=%p,sc=%p,idx=%d)\n",
+ ioc->name, mf, mr, sc, req_idx));
+
+ sc->result = DID_OK << 16; /* Set default reply as OK */
+ pScsiReq = (SCSIIORequest_t *) mf;
+ pScsiReply = (SCSIIOReply_t *) mr;
+
+ if (pScsiReply == NULL) {
+ /* special context reply handling */
+ ;
+ } else {
+ u32 xfer_cnt;
+ u16 status;
+ u8 scsi_state, scsi_status;
+
+ status = le16_to_cpu(pScsiReply->IOCStatus) & MPI_IOCSTATUS_MASK;
+ scsi_state = pScsiReply->SCSIState;
+ scsi_status = pScsiReply->SCSIStatus;
+ xfer_cnt = le32_to_cpu(pScsiReply->TransferCount);
+ sc->resid = sc->request_bufflen - xfer_cnt;
+
+ dreplyprintk((KERN_NOTICE "Reply ha=%d id=%d lun=%d:\n"
+ "IOCStatus=%04xh SCSIState=%02xh SCSIStatus=%02xh\n"
+ "resid=%d bufflen=%d xfer_cnt=%d\n",
+ ioc->id, pScsiReq->TargetID, pScsiReq->LUN[1],
+ status, scsi_state, scsi_status, sc->resid,
+ sc->request_bufflen, xfer_cnt));
+
+ if (scsi_state & MPI_SCSI_STATE_AUTOSENSE_VALID)
+ copy_sense_data(sc, hd, mf, pScsiReply);
+
+ /*
+ * Look for + dump FCP ResponseInfo[]!
+ */
+ if (scsi_state & MPI_SCSI_STATE_RESPONSE_INFO_VALID) {
+ printk(KERN_NOTICE " FCP_ResponseInfo=%08xh\n",
+ le32_to_cpu(pScsiReply->ResponseInfo));
+ }
+
+ switch(status) {
+ case MPI_IOCSTATUS_BUSY: /* 0x0002 */
+ /* CHECKME!
+ * Maybe: DRIVER_BUSY | SUGGEST_RETRY | DID_SOFT_ERROR (retry)
+ * But not: DID_BUS_BUSY lest one risk
+ * killing interrupt handler:-(
+ */
+ sc->result = SAM_STAT_BUSY;
+ break;
+
+ case MPI_IOCSTATUS_SCSI_INVALID_BUS: /* 0x0041 */
+ case MPI_IOCSTATUS_SCSI_INVALID_TARGETID: /* 0x0042 */
+ sc->result = DID_BAD_TARGET << 16;
+ break;
+
+ case MPI_IOCSTATUS_SCSI_DEVICE_NOT_THERE: /* 0x0043 */
+ /* Spoof to SCSI Selection Timeout! */
+ sc->result = DID_NO_CONNECT << 16;
+
+ if (hd->sel_timeout[pScsiReq->TargetID] < 0xFFFF)
+ hd->sel_timeout[pScsiReq->TargetID]++;
+ break;
+
+ case MPI_IOCSTATUS_SCSI_TASK_TERMINATED: /* 0x0048 */
+ case MPI_IOCSTATUS_SCSI_IOC_TERMINATED: /* 0x004B */
+ case MPI_IOCSTATUS_SCSI_EXT_TERMINATED: /* 0x004C */
+ /* Linux handles an unsolicited DID_RESET better
+ * than an unsolicited DID_ABORT.
+ */
+ sc->result = DID_RESET << 16;
+
+ /* GEM Workaround. */
+ if (ioc->bus_type == SCSI)
+ mptscsih_no_negotiate(hd, sc->device->id);
+ break;
+
+ case MPI_IOCSTATUS_SCSI_RESIDUAL_MISMATCH: /* 0x0049 */
+ if ( xfer_cnt >= sc->underflow ) {
+ /* Sufficient data transfer occurred */
+ sc->result = (DID_OK << 16) | scsi_status;
+ } else if ( xfer_cnt == 0 ) {
+ /* A CRC Error causes this condition; retry */
+ sc->result = (DRIVER_SENSE << 24) | (DID_OK << 16) |
+ (CHECK_CONDITION << 1);
+ sc->sense_buffer[0] = 0x70;
+ sc->sense_buffer[2] = NO_SENSE;
+ sc->sense_buffer[12] = 0;
+ sc->sense_buffer[13] = 0;
+ } else {
+ sc->result = DID_SOFT_ERROR << 16;
+ }
+ dreplyprintk((KERN_NOTICE "RESIDUAL_MISMATCH: result=%x on id=%d\n", sc->result, sc->target));
+ break;
+
+ case MPI_IOCSTATUS_SCSI_DATA_UNDERRUN: /* 0x0045 */
+ /*
+ * Do upfront check for valid SenseData and give it
+ * precedence!
+ */
+ sc->result = (DID_OK << 16) | scsi_status;
+ if (scsi_state & MPI_SCSI_STATE_AUTOSENSE_VALID) {
+ /* Have already saved the status and sense data
+ */
+ ;
+ } else {
+ if (xfer_cnt < sc->underflow) {
+ sc->result = DID_SOFT_ERROR << 16;
+ }
+ if (scsi_state & (MPI_SCSI_STATE_AUTOSENSE_FAILED | MPI_SCSI_STATE_NO_SCSI_STATUS)) {
+ /* What to do?
+ */
+ sc->result = DID_SOFT_ERROR << 16;
+ }
+ else if (scsi_state & MPI_SCSI_STATE_TERMINATED) {
+ /* Not real sure here either... */
+ sc->result = DID_RESET << 16;
+ }
+ }
+
+ dreplyprintk((KERN_NOTICE " sc->underflow={report ERR if < %02xh bytes xfer'd}\n",
+ sc->underflow));
+ dreplyprintk((KERN_NOTICE " ActBytesXferd=%02xh\n", xfer_cnt));
+ /* Report Queue Full
+ */
+ if (scsi_status == MPI_SCSI_STATUS_TASK_SET_FULL)
+ mptscsih_report_queue_full(sc, pScsiReply, pScsiReq);
+
+ break;
+
+ case MPI_IOCSTATUS_SCSI_RECOVERED_ERROR: /* 0x0040 */
+ case MPI_IOCSTATUS_SUCCESS: /* 0x0000 */
+ scsi_status = pScsiReply->SCSIStatus;
+ sc->result = (DID_OK << 16) | scsi_status;
+ if (scsi_state == 0) {
+ ;
+ } else if (scsi_state & MPI_SCSI_STATE_AUTOSENSE_VALID) {
+ /*
+ * If running against circa 200003dd 909 MPT f/w,
+ * may get this (AUTOSENSE_VALID) for actual TASK_SET_FULL
+ * (QUEUE_FULL) returned from device! --> get 0x0000?128
+ * and with SenseBytes set to 0.
+ */
+ if (pScsiReply->SCSIStatus == MPI_SCSI_STATUS_TASK_SET_FULL)
+ mptscsih_report_queue_full(sc, pScsiReply, pScsiReq);
+
+ }
+ else if (scsi_state &
+ (MPI_SCSI_STATE_AUTOSENSE_FAILED | MPI_SCSI_STATE_NO_SCSI_STATUS)
+ ) {
+ /*
+ * What to do?
+ */
+ sc->result = DID_SOFT_ERROR << 16;
+ }
+ else if (scsi_state & MPI_SCSI_STATE_TERMINATED) {
+ /* Not real sure here either... */
+ sc->result = DID_RESET << 16;
+ }
+ else if (scsi_state & MPI_SCSI_STATE_QUEUE_TAG_REJECTED) {
+ /* Device Inq. data indicates that it supports
+ * QTags, but rejects QTag messages.
+ * This command completed OK.
+ *
+ * Not real sure here either so do nothing... */
+ }
+
+ if (sc->result == MPI_SCSI_STATUS_TASK_SET_FULL)
+ mptscsih_report_queue_full(sc, pScsiReply, pScsiReq);
+
+ /* Add handling of:
+ * Reservation Conflict, Busy,
+ * Command Terminated, CHECK
+ */
+ break;
+
+ case MPI_IOCSTATUS_SCSI_PROTOCOL_ERROR: /* 0x0047 */
+ sc->result = DID_SOFT_ERROR << 16;
+ break;
+
+ case MPI_IOCSTATUS_INVALID_FUNCTION: /* 0x0001 */
+ case MPI_IOCSTATUS_INVALID_SGL: /* 0x0003 */
+ case MPI_IOCSTATUS_INTERNAL_ERROR: /* 0x0004 */
+ case MPI_IOCSTATUS_RESERVED: /* 0x0005 */
+ case MPI_IOCSTATUS_INSUFFICIENT_RESOURCES: /* 0x0006 */
+ case MPI_IOCSTATUS_INVALID_FIELD: /* 0x0007 */
+ case MPI_IOCSTATUS_INVALID_STATE: /* 0x0008 */
+ case MPI_IOCSTATUS_SCSI_DATA_OVERRUN: /* 0x0044 */
+ case MPI_IOCSTATUS_SCSI_IO_DATA_ERROR: /* 0x0046 */
+ case MPI_IOCSTATUS_SCSI_TASK_MGMT_FAILED: /* 0x004A */
+ default:
+ /*
+ * What to do?
+ */
+ sc->result = DID_SOFT_ERROR << 16;
+ break;
+
+ } /* switch(status) */
+
+ dreplyprintk((KERN_NOTICE " sc->result is %08xh\n", sc->result));
+ } /* end of address reply case */
+
+ /* Unmap the DMA buffers, if any. */
+ if (sc->use_sg) {
+ pci_unmap_sg(ioc->pcidev, (struct scatterlist *) sc->request_buffer,
+ sc->use_sg, sc->sc_data_direction);
+ } else if (sc->request_bufflen) {
+ pci_unmap_single(ioc->pcidev, sc->SCp.dma_handle,
+ sc->request_bufflen, sc->sc_data_direction);
+ }
+
+ hd->ScsiLookup[req_idx] = NULL;
+
+ sc->scsi_done(sc); /* Issue the command callback */
+
+ /* Free Chain buffers */
+ mptscsih_freeChainBuffers(ioc, req_idx);
+ return 1;
+}
+
+
+/*
+ * mptscsih_flush_running_cmds - For each command found, search
+ * Scsi_Host instance taskQ and reply to OS.
+ * Called only if recovering from a FW reload.
+ * @hd: Pointer to a SCSI HOST structure
+ *
+ * Returns: None.
+ *
+ * Must be called while new I/Os are being queued.
+ */
+static void
+mptscsih_flush_running_cmds(MPT_SCSI_HOST *hd)
+{
+ MPT_ADAPTER *ioc = hd->ioc;
+ struct scsi_cmnd *SCpnt;
+ MPT_FRAME_HDR *mf;
+ int ii;
+ int max = ioc->req_depth;
+
+ dprintk((KERN_INFO MYNAM ": flush_ScsiLookup called\n"));
+ for (ii= 0; ii < max; ii++) {
+ if ((SCpnt = hd->ScsiLookup[ii]) != NULL) {
+
+ /* Command found.
+ */
+
+ /* Null ScsiLookup index
+ */
+ hd->ScsiLookup[ii] = NULL;
+
+ mf = MPT_INDEX_2_MFPTR(ioc, ii);
+ dmfprintk(( "flush: ScsiDone (mf=%p,sc=%p)\n",
+ mf, SCpnt));
+
+ /* Set status, free OS resources (SG DMA buffers)
+ * Do OS callback
+ * Free driver resources (chain, msg buffers)
+ */
+ if (scsi_device_online(SCpnt->device)) {
+ if (SCpnt->use_sg) {
+ pci_unmap_sg(ioc->pcidev,
+ (struct scatterlist *) SCpnt->request_buffer,
+ SCpnt->use_sg,
+ SCpnt->sc_data_direction);
+ } else if (SCpnt->request_bufflen) {
+ pci_unmap_single(ioc->pcidev,
+ SCpnt->SCp.dma_handle,
+ SCpnt->request_bufflen,
+ SCpnt->sc_data_direction);
+ }
+ }
+ SCpnt->result = DID_RESET << 16;
+ SCpnt->host_scribble = NULL;
+
+ /* Free Chain buffers */
+ mptscsih_freeChainBuffers(ioc, ii);
+
+ /* Free Message frames */
+ mpt_free_msg_frame(ioc, mf);
+
+ SCpnt->scsi_done(SCpnt); /* Issue the command callback */
+ }
+ }
+
+ return;
+}
+
+/*
+ * mptscsih_search_running_cmds - Delete any commands associated
+ * with the specified target and lun. Function called only
+ * when a lun is disable by mid-layer.
+ * Do NOT access the referenced scsi_cmnd structure or
+ * members. Will cause either a paging or NULL ptr error.
+ * @hd: Pointer to a SCSI HOST structure
+ * @target: target id
+ * @lun: lun
+ *
+ * Returns: None.
+ *
+ * Called from slave_destroy.
+ */
+static void
+mptscsih_search_running_cmds(MPT_SCSI_HOST *hd, uint target, uint lun)
+{
+ SCSIIORequest_t *mf = NULL;
+ int ii;
+ int max = hd->ioc->req_depth;
+
+ dsprintk((KERN_INFO MYNAM ": search_running target %d lun %d max %d\n",
+ target, lun, max));
+
+ for (ii=0; ii < max; ii++) {
+ if (hd->ScsiLookup[ii] != NULL) {
+
+ mf = (SCSIIORequest_t *)MPT_INDEX_2_MFPTR(hd->ioc, ii);
+
+ dsprintk(( "search_running: found (sc=%p, mf = %p) target %d, lun %d \n",
+ hd->ScsiLookup[ii], mf, mf->TargetID, mf->LUN[1]));
+
+ if ((mf->TargetID != ((u8)target)) || (mf->LUN[1] != ((u8) lun)))
+ continue;
+
+ /* Cleanup
+ */
+ hd->ScsiLookup[ii] = NULL;
+ mptscsih_freeChainBuffers(hd->ioc, ii);
+ mpt_free_msg_frame(hd->ioc, (MPT_FRAME_HDR *)mf);
+ }
+ }
+
+ return;
+}
+
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+/*
+ * Hack! It might be nice to report if a device is returning QUEUE_FULL
+ * but maybe not each and every time...
+ */
+static long last_queue_full = 0;
+
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+/*
+ * mptscsih_report_queue_full - Report QUEUE_FULL status returned
+ * from a SCSI target device.
+ * @sc: Pointer to scsi_cmnd structure
+ * @pScsiReply: Pointer to SCSIIOReply_t
+ * @pScsiReq: Pointer to original SCSI request
+ *
+ * This routine periodically reports QUEUE_FULL status returned from a
+ * SCSI target device. It reports this to the console via kernel
+ * printk() API call, not more than once every 10 seconds.
+ */
+static void
+mptscsih_report_queue_full(struct scsi_cmnd *sc, SCSIIOReply_t *pScsiReply, SCSIIORequest_t *pScsiReq)
+{
+ long time = jiffies;
+
+ if (time - last_queue_full > 10 * HZ) {
+ char *ioc_str = "ioc?";
+
+ if (sc->device && sc->device->host != NULL && sc->device->host->hostdata != NULL)
+ ioc_str = ((MPT_SCSI_HOST *)sc->device->host->hostdata)->ioc->name;
+ dprintk((MYIOC_s_WARN_FMT "Device (%d:%d:%d) reported QUEUE_FULL!\n",
+ ioc_str, 0, sc->device->id, sc->device->lun));
+ last_queue_full = time;
+ }
+}
+
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+static char *info_kbuf = NULL;
+
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
+/*
+ * mptscsih_probe - Installs scsi devices per bus.
+ * @pdev: Pointer to pci_dev structure
+ *
+ * Returns 0 for success, non-zero for failure.
+ *
+ */
+
+static int
+mptscsih_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+ struct Scsi_Host *sh;
+ MPT