diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 15:20:36 -0700 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/message/fusion/mptscsih.c |
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/message/fusion/mptscsih.c')
-rw-r--r-- | drivers/message/fusion/mptscsih.c | 6021 |
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 00000000000..c98d6257ec0 --- /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_SCSI_HOST *hd; + MPT_ADAPTER *ioc = pci_get_drvdata(pdev); + unsigned long flags; + int sz, ii; + int numSGE = 0; + int scale; + int ioc_cap; + u8 *mem; + int error=0; + + + /* 20010202 -sralston + * Added sanity check on readiness of the MPT adapter. + */ + if (ioc->last_state != MPI_IOC_STATE_OPERATIONAL) { + printk(MYIOC_s_WARN_FMT + "Skipping because it's not operational!\n", + ioc->name); + return -ENODEV; + } + + if (!ioc->active) { + printk(MYIOC_s_WARN_FMT "Skipping because it's disabled!\n", + ioc->name); + return -ENODEV; + } + + /* Sanity check - ensure at least 1 port is INITIATOR capable + */ + ioc_cap = 0; + for (ii=0; ii < ioc->facts.NumberOfPorts; ii++) { + if (ioc->pfacts[ii].ProtocolFlags & + MPI_PORTFACTS_PROTOCOL_INITIATOR) + ioc_cap ++; + } + + if (!ioc_cap) { + printk(MYIOC_s_WARN_FMT + "Skipping ioc=%p because SCSI Initiator mode is NOT enabled!\n", + ioc->name, ioc); + return -ENODEV; + } + + sh = scsi_host_alloc(&driver_template, sizeof(MPT_SCSI_HOST)); + + if (!sh) { + printk(MYIOC_s_WARN_FMT + "Unable to register controller with SCSI subsystem\n", + ioc->name); + return -1; + } + + spin_lock_irqsave(&ioc->FreeQlock, flags); + + /* Attach the SCSI Host to the IOC structure + */ + ioc->sh = sh; + + sh->io_port = 0; + sh->n_io_port = 0; + sh->irq = 0; + + /* set 16 byte cdb's */ + sh->max_cmd_len = 16; + + /* Yikes! This is important! + * Otherwise, by default, linux + * only scans target IDs 0-7! + * pfactsN->MaxDevices unreliable + * (not supported in early + * versions of the FW). + * max_id = 1 + actual max id, + * max_lun = 1 + actual last lun, + * see hosts.h :o( + */ + if (ioc->bus_type == SCSI) { + sh->max_id = MPT_MAX_SCSI_DEVICES; + } else { + /* For FC, increase the queue depth + * from MPT_SCSI_CAN_QUEUE (31) + * to MPT_FC_CAN_QUEUE (63). + */ + sh->can_queue = MPT_FC_CAN_QUEUE; + sh->max_id = + MPT_MAX_FC_DEVICES<256 ? MPT_MAX_FC_DEVICES : 255; + } + + sh->max_lun = MPT_LAST_LUN + 1; + sh->max_channel = 0; + sh->this_id = ioc->pfacts[0].PortSCSIID; + + /* Required entry. + */ + sh->unique_id = ioc->id; + + /* Verify that we won't exceed the maximum + * number of chain buffers + * We can optimize: ZZ = req_sz/sizeof(SGE) + * For 32bit SGE's: + * numSGE = 1 + (ZZ-1)*(maxChain -1) + ZZ + * + (req_sz - 64)/sizeof(SGE) + * A slightly different algorithm is required for + * 64bit SGEs. + */ + scale = ioc->req_sz/(sizeof(dma_addr_t) + sizeof(u32)); + if (sizeof(dma_addr_t) == sizeof(u64)) { + numSGE = (scale - 1) * + (ioc->facts.MaxChainDepth-1) + scale + + (ioc->req_sz - 60) / (sizeof(dma_addr_t) + + sizeof(u32)); + } else { + numSGE = 1 + (scale - 1) * + (ioc->facts.MaxChainDepth-1) + scale + + (ioc->req_sz - 64) / (sizeof(dma_addr_t) + + sizeof(u32)); + } + + if (numSGE < sh->sg_tablesize) { + /* Reset this value */ + dprintk((MYIOC_s_INFO_FMT + "Resetting sg_tablesize to %d from %d\n", + ioc->name, numSGE, sh->sg_tablesize)); + sh->sg_tablesize = numSGE; + } + + /* Set the pci device pointer in Scsi_Host structure. + */ + scsi_set_device(sh, &ioc->pcidev->dev); + + spin_unlock_irqrestore(&ioc->FreeQlock, flags); + + hd = (MPT_SCSI_HOST *) sh->hostdata; + hd->ioc = ioc; + + /* SCSI needs scsi_cmnd lookup table! + * (with size equal to req_depth*PtrSz!) + */ + sz = ioc->req_depth * sizeof(void *); + mem = kmalloc(sz, GFP_ATOMIC); + if (mem == NULL) { + error = -ENOMEM; + goto mptscsih_probe_failed; + } + + memset(mem, 0, sz); + hd->ScsiLookup = (struct scsi_cmnd **) mem; + + dprintk((MYIOC_s_INFO_FMT "ScsiLookup @ %p, sz=%d\n", + ioc->name, hd->ScsiLookup, sz)); + + /* Allocate memory for the device structures. + * A non-Null pointer at an offset + * indicates a device exists. + * max_id = 1 + maximum id (hosts.h) + */ + sz = sh->max_id * sizeof(void *); + mem = kmalloc(sz, GFP_ATOMIC); + if (mem == NULL) { + error = -ENOMEM; + goto mptscsih_probe_failed; + } + + memset(mem, 0, sz); + hd->Targets = (VirtDevice **) mem; + + dprintk((KERN_INFO + " Targets @ %p, sz=%d\n", hd->Targets, sz)); + + /* Clear the TM flags + */ + hd->tmPending = 0; + hd->tmState = TM_STATE_NONE; + hd->resetPending = 0; + hd->abortSCpnt = NULL; + + /* Clear the pointer used to store + * single-threaded commands, i.e., those + * issued during a bus scan, dv and + * configuration pages. + */ + hd->cmdPtr = NULL; + + /* Initialize this SCSI Hosts' timers + * To use, set the timer expires field + * and add_timer + */ + init_timer(&hd->timer); + hd->timer.data = (unsigned long) hd; + hd->timer.function = mptscsih_timer_expired; + + if (ioc->bus_type == SCSI) { + /* Update with the driver setup + * values. + */ + if (ioc->spi_data.maxBusWidth > mpt_width) + ioc->spi_data.maxBusWidth = mpt_width; + if (ioc->spi_data.minSyncFactor < mpt_factor) + ioc->spi_data.minSyncFactor = mpt_factor; + + if (ioc->spi_data.minSyncFactor == MPT_ASYNC) { + ioc->spi_data.maxSyncOffset = 0; + } + + ioc->spi_data.Saf_Te = mpt_saf_te; + + hd->negoNvram = 0; +#ifndef MPTSCSIH_ENABLE_DOMAIN_VALIDATION + hd->negoNvram = MPT_SCSICFG_USE_NVRAM; +#endif + ioc->spi_data.forceDv = 0; + ioc->spi_data.noQas = 0; + for (ii=0; ii < MPT_MAX_SCSI_DEVICES; ii++) { + ioc->spi_data.dvStatus[ii] = + MPT_SCSICFG_NEGOTIATE; + } + + for (ii=0; ii < MPT_MAX_SCSI_DEVICES; ii++) + ioc->spi_data.dvStatus[ii] |= + MPT_SCSICFG_DV_NOT_DONE; + + dinitprintk((MYIOC_s_INFO_FMT + "dv %x width %x factor %x saf_te %x\n", + ioc->name, mpt_dv, + mpt_width, + mpt_factor, + mpt_saf_te)); + } + + mpt_scsi_hosts++; + + error = scsi_add_host (sh, &ioc->pcidev->dev); + if(error) { + dprintk((KERN_ERR MYNAM + "scsi_add_host failed\n")); + goto mptscsih_probe_failed; + } + + scsi_scan_host(sh); + return 0; + +mptscsih_probe_failed: + + mptscsih_remove(pdev); + return error; + +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * mptscsih_remove - Removed scsi devices + * @pdev: Pointer to pci_dev structure + * + * + */ +static void +mptscsih_remove(struct pci_dev *pdev) +{ + MPT_ADAPTER *ioc = pci_get_drvdata(pdev); + struct Scsi_Host *host = ioc->sh; + MPT_SCSI_HOST *hd; + int count; + unsigned long flags; + + if(!host) + return; + + scsi_remove_host(host); + +#ifdef MPTSCSIH_ENABLE_DOMAIN_VALIDATION + /* Check DV thread active */ + count = 10 * HZ; + spin_lock_irqsave(&dvtaskQ_lock, flags); + if (dvtaskQ_active) { + spin_unlock_irqrestore(&dvtaskQ_lock, flags); + while(dvtaskQ_active && --count) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(1); + } + } else { + spin_unlock_irqrestore(&dvtaskQ_lock, flags); + } + if (!count) + printk(KERN_ERR MYNAM ": ERROR - DV thread still active!\n"); +#if defined(MPT_DEBUG_DV) || defined(MPT_DEBUG_DV_TINY) + else + printk(KERN_ERR MYNAM ": DV thread orig %d, count %d\n", 10 * HZ, count); +#endif +#endif + + hd = (MPT_SCSI_HOST *)host->hostdata; + if (hd != NULL) { + int sz1; + + mptscsih_shutdown(&pdev->dev); + + sz1=0; + + if (hd->ScsiLookup != NULL) { + sz1 = hd->ioc->req_depth * sizeof(void *); + kfree(hd->ScsiLookup); + hd->ScsiLookup = NULL; + } + + if (hd->Targets != NULL) { + /* + * Free pointer array. + */ + kfree(hd->Targets); + hd->Targets = NULL; + } + + dprintk((MYIOC_s_INFO_FMT + "Free'd ScsiLookup (%d) memory\n", + hd->ioc->name, sz1)); + + /* NULL the Scsi_Host pointer + */ + hd->ioc->sh = NULL; + } + + scsi_host_put(host); + mpt_scsi_hosts--; + +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * mptscsih_shutdown - reboot notifier + * + */ +static void +mptscsih_shutdown(struct device * dev) +{ + MPT_ADAPTER *ioc = pci_get_drvdata(to_pci_dev(dev)); + struct Scsi_Host *host = ioc->sh; + MPT_SCSI_HOST *hd; + + if(!host) + return; + + hd = (MPT_SCSI_HOST *)host->hostdata; + + /* Flush the cache of this adapter + */ + if(hd != NULL) + mptscsih_synchronize_cache(hd, 0); + +} + +#ifdef CONFIG_PM +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * mptscsih_suspend - Fusion MPT scsie driver suspend routine. + * + * + */ +static int +mptscsih_suspend(struct pci_dev *pdev, u32 state) +{ + mptscsih_shutdown(&pdev->dev); + return 0; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * mptscsih_resume - Fusion MPT scsi driver resume routine. + * + * + */ +static int +mptscsih_resume(struct pci_dev *pdev) +{ + MPT_ADAPTER *ioc = pci_get_drvdata(pdev); + struct Scsi_Host *host = ioc->sh; + MPT_SCSI_HOST *hd; + + if(!host) + return 0; + + hd = (MPT_SCSI_HOST *)host->hostdata; + if(!hd) + return 0; + +#ifdef MPTSCSIH_ENABLE_DOMAIN_VALIDATION + { + unsigned long lflags; + spin_lock_irqsave(&dvtaskQ_lock, lflags); + if (!dvtaskQ_active) { + dvtaskQ_active = 1; + spin_unlock_irqrestore(&dvtaskQ_lock, lflags); + INIT_WORK(&mptscsih_dvTask, + mptscsih_domainValidation, (void *) hd); + schedule_work(&mptscsih_dvTask); + } else { + spin_unlock_irqrestore(&dvtaskQ_lock, lflags); + } + } +#endif + return 0; +} + +#endif + +static struct mpt_pci_driver mptscsih_driver = { + .probe = mptscsih_probe, + .remove = mptscsih_remove, + .shutdown = mptscsih_shutdown, +#ifdef CONFIG_PM + .suspend = mptscsih_suspend, + .resume = mptscsih_resume, +#endif +}; + +/* SCSI host fops start here... */ +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_init - Register MPT adapter(s) as SCSI host(s) with + * linux scsi mid-layer. + * + * Returns 0 for success, non-zero for failure. + */ +static int __init +mptscsih_init(void) +{ + + show_mptmod_ver(my_NAME, my_VERSION); + + ScsiDoneCtx = mpt_register(mptscsih_io_done, MPTSCSIH_DRIVER); + ScsiTaskCtx = mpt_register(mptscsih_taskmgmt_complete, MPTSCSIH_DRIVER); + ScsiScanDvCtx = mpt_register(mptscsih_scandv_complete, MPTSCSIH_DRIVER); + + if (mpt_event_register(ScsiDoneCtx, mptscsih_event_process) == 0) { + devtprintk((KERN_INFO MYNAM + ": Registered for IOC event notifications\n")); + } + + if (mpt_reset_register(ScsiDoneCtx, mptscsih_ioc_reset) == 0) { + dprintk((KERN_INFO MYNAM + ": Registered for IOC reset notifications\n")); + } + + if(mpt_device_driver_register(&mptscsih_driver, + MPTSCSIH_DRIVER) != 0 ) { + dprintk((KERN_INFO MYNAM + ": failed to register dd callbacks\n")); + } + + return 0; + +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_exit - Unregisters MPT adapter(s) + * + */ +static void __exit +mptscsih_exit(void) +{ + mpt_device_driver_deregister(MPTSCSIH_DRIVER); + + mpt_reset_deregister(ScsiDoneCtx); + dprintk((KERN_INFO MYNAM + ": Deregistered for IOC reset notifications\n")); + + mpt_event_deregister(ScsiDoneCtx); + dprintk((KERN_INFO MYNAM + ": Deregistered for IOC event notifications\n")); + + mpt_deregister(ScsiScanDvCtx); + mpt_deregister(ScsiTaskCtx); + mpt_deregister(ScsiDoneCtx); + + if (info_kbuf != NULL) + kfree(info_kbuf); + +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_info - Return information about MPT adapter + * @SChost: Pointer to Scsi_Host structure + * + * (linux scsi_host_template.info routine) + * + * Returns pointer to buffer where information was written. + */ +static const char * +mptscsih_info(struct Scsi_Host *SChost) +{ + MPT_SCSI_HOST *h; + int size = 0; + + if (info_kbuf == NULL) + if ((info_kbuf = kmalloc(0x1000 /* 4Kb */, GFP_KERNEL)) == NULL) + return info_kbuf; + + h = (MPT_SCSI_HOST *)SChost->hostdata; + info_kbuf[0] = '\0'; + if (h) { + mpt_print_ioc_summary(h->ioc, info_kbuf, &size, 0, 0); + info_kbuf[size-1] = '\0'; + } + + return info_kbuf; +} + +struct info_str { + char *buffer; + int length; + int offset; + int pos; +}; + +static void copy_mem_info(struct info_str *info, char *data, int len) +{ + if (info->pos + len > info->length) + len = info->length - info->pos; + + if (info->pos + len < info->offset) { + info->pos += len; + return; + } + + if (info->pos < info->offset) { + data += (info->offset - info->pos); + len -= (info->offset - info->pos); + } + + if (len > 0) { + memcpy(info->buffer + info->pos, data, len); + info->pos += len; + } +} + +static int copy_info(struct info_str *info, char *fmt, ...) +{ + va_list args; + char buf[81]; + int len; + + va_start(args, fmt); + len = vsprintf(buf, fmt, args); + va_end(args); + + copy_mem_info(info, buf, len); + return len; +} + +static int mptscsih_host_info(MPT_ADAPTER *ioc, char *pbuf, off_t offset, int len) +{ + struct info_str info; + + info.buffer = pbuf; + info.length = len; + info.offset = offset; + info.pos = 0; + + copy_info(&info, "%s: %s, ", ioc->name, ioc->prod_name); + copy_info(&info, "%s%08xh, ", MPT_FW_REV_MAGIC_ID_STRING, ioc->facts.FWVersion.Word); + copy_info(&info, "Ports=%d, ", ioc->facts.NumberOfPorts); + copy_info(&info, "MaxQ=%d\n", ioc->req_depth); + + return ((info.pos > info.offset) ? info.pos - info.offset : 0); +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_proc_info - Return information about MPT adapter + * + * (linux scsi_host_template.info routine) + * + * buffer: if write, user data; if read, buffer for user + * length: if write, return length; + * offset: if write, 0; if read, the current offset into the buffer from + * the previous read. + * hostno: scsi host number + * func: if write = 1; if read = 0 + */ +static int +mptscsih_proc_info(struct Scsi_Host *host, char *buffer, char **start, off_t offset, + int length, int func) +{ + MPT_SCSI_HOST *hd = (MPT_SCSI_HOST *)host->hostdata; + MPT_ADAPTER *ioc = hd->ioc; + int size = 0; + + if (func) { + /* + * write is not supported + */ + } else { + if (start) + *start = buffer; + + size = mptscsih_host_info(ioc, buffer, offset, length); + } + + return size; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +#define ADD_INDEX_LOG(req_ent) do { } while(0) + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_qcmd - Primary Fusion MPT SCSI initiator IO start routine. + * @SCpnt: Pointer to scsi_cmnd structure + * @done: Pointer SCSI mid-layer IO completion function + * + * (linux scsi_host_template.queuecommand routine) + * This is the primary SCSI IO start routine. Create a MPI SCSIIORequest + * from a linux scsi_cmnd request and send it to the IOC. + * + * Returns 0. (rtn value discarded by linux scsi mid-layer) + */ +static int +mptscsih_qcmd(struct scsi_cmnd *SCpnt, void (*done)(struct scsi_cmnd *)) +{ + MPT_SCSI_HOST *hd; + MPT_FRAME_HDR *mf; + SCSIIORequest_t *pScsiReq; + VirtDevice *pTarget; + int target; + int lun; + u32 datalen; + u32 scsictl; + u32 scsidir; + u32 cmd_len; + int my_idx; + int ii; + + hd = (MPT_SCSI_HOST *) SCpnt->device->host->hostdata; + target = SCpnt->device->id; + lun = SCpnt->device->lun; + SCpnt->scsi_done = done; + + pTarget = hd->Targets[target]; + + dmfprintk((MYIOC_s_INFO_FMT "qcmd: SCpnt=%p, done()=%p\n", + (hd && hd->ioc) ? hd->ioc->name : "ioc?", SCpnt, done)); + + if (hd->resetPending) { + dtmprintk((MYIOC_s_WARN_FMT "qcmd: SCpnt=%p timeout + 60HZ\n", + (hd && hd->ioc) ? hd->ioc->name : "ioc?", SCpnt)); + return SCSI_MLQUEUE_HOST_BUSY; + } + + /* + * Put together a MPT SCSI request... + */ + if ((mf = mpt_get_msg_frame(ScsiDoneCtx, hd->ioc)) == NULL) { + dprintk((MYIOC_s_WARN_FMT "QueueCmd, no msg frames!!\n", + hd->ioc->name)); + return SCSI_MLQUEUE_HOST_BUSY; + } + + pScsiReq = (SCSIIORequest_t *) mf; + + my_idx = le16_to_cpu(mf->u.frame.hwhdr.msgctxu.fld.req_idx); + + ADD_INDEX_LOG(my_idx); + + /* BUG FIX! 19991030 -sralston + * TUR's being issued with scsictl=0x02000000 (DATA_IN)! + * Seems we may receive a buffer (datalen>0) even when there + * will be no data transfer! GRRRRR... + */ + if (SCpnt->sc_data_direction == DMA_FROM_DEVICE) { + datalen = SCpnt->request_bufflen; + scsidir = MPI_SCSIIO_CONTROL_READ; /* DATA IN (host<--ioc<--dev) */ + } else if (SCpnt->sc_data_direction == DMA_TO_DEVICE) { + datalen = SCpnt->request_bufflen; + scsidir = MPI_SCSIIO_CONTROL_WRITE; /* DATA OUT (host-->ioc-->dev) */ + } else { + datalen = 0; + scsidir = MPI_SCSIIO_CONTROL_NODATATRANSFER; + } + + /* Default to untagged. Once a target structure has been allocated, + * use the Inquiry data to determine if device supports tagged. + */ + if ( pTarget + && (pTarget->tflags & MPT_TARGET_FLAGS_Q_YES) + && (SCpnt->device->tagged_supported)) { + scsictl = scsidir | MPI_SCSIIO_CONTROL_SIMPLEQ; + } else { + scsictl = scsidir | MPI_SCSIIO_CONTROL_UNTAGGED; + } + + /* Use the above information to set up the message frame + */ + pScsiReq->TargetID = (u8) target; + pScsiReq->Bus = (u8) SCpnt->device->channel; + pScsiReq->ChainOffset = 0; + pScsiReq->Function = MPI_FUNCTION_SCSI_IO_REQUEST; + pScsiReq->CDBLength = SCpnt->cmd_len; + pScsiReq->SenseBufferLength = MPT_SENSE_BUFFER_SIZE; + pScsiReq->Reserved = 0; + pScsiReq->MsgFlags = mpt_msg_flags(); + pScsiReq->LUN[0] = 0; + pScsiReq->LUN[1] = lun; + pScsiReq->LUN[2] = 0; + pScsiReq->LUN[3] = 0; + pScsiReq->LUN[4] = 0; + pScsiReq->LUN[5] = 0; + pScsiReq->LUN[6] = 0; + pScsiReq->LUN[7] = 0; + pScsiReq->Control = cpu_to_le32(scsictl); + + /* + * Write SCSI CDB into the message + */ + cmd_len = SCpnt->cmd_len; + for (ii=0; ii < cmd_len; ii++) + pScsiReq->CDB[ii] = SCpnt->cmnd[ii]; + + for (ii=cmd_len; ii < 16; ii++) + pScsiReq->CDB[ii] = 0; + + /* DataLength */ + pScsiReq->DataLength = cpu_to_le32(datalen); + + /* SenseBuffer low address */ + pScsiReq->SenseBufferLowAddr = cpu_to_le32(hd->ioc->sense_buf_low_dma + + (my_idx * MPT_SENSE_BUFFER_ALLOC)); + + /* Now add the SG list + * Always have a SGE even if null length. + */ + if (datalen == 0) { + /* Add a NULL SGE */ + mptscsih_add_sge((char *)&pScsiReq->SGL, MPT_SGE_FLAGS_SSIMPLE_READ | 0, + (dma_addr_t) -1); + } else { + /* Add a 32 or 64 bit SGE */ + if (mptscsih_AddSGE(hd->ioc, SCpnt, pScsiReq, my_idx) != SUCCESS) + goto fail; + } + + hd->ScsiLookup[my_idx] = SCpnt; + SCpnt->host_scribble = NULL; + +#ifdef MPTSCSIH_ENABLE_DOMAIN_VALIDATION + if (hd->ioc->bus_type == SCSI) { + int dvStatus = hd->ioc->spi_data.dvStatus[target]; + int issueCmd = 1; + + if (dvStatus || hd->ioc->spi_data.forceDv) { + + if ((dvStatus & MPT_SCSICFG_NEED_DV) || + (hd->ioc->spi_data.forceDv & MPT_SCSICFG_NEED_DV)) { + unsigned long lflags; + /* Schedule DV if necessary */ + spin_lock_irqsave(&dvtaskQ_lock, lflags); + if (!dvtaskQ_active) { + dvtaskQ_active = 1; + spin_unlock_irqrestore(&dvtaskQ_lock, lflags); + INIT_WORK(&mptscsih_dvTask, mptscsih_domainValidation, (void *) hd); + + schedule_work(&mptscsih_dvTask); + } else { + spin_unlock_irqrestore(&dvtaskQ_lock, lflags); + } + hd->ioc->spi_data.forceDv &= ~MPT_SCSICFG_NEED_DV; + } + + /* Trying to do DV to this target, extend timeout. + * Wait to issue until flag is clear + */ + if (dvStatus & MPT_SCSICFG_DV_PENDING) { + mod_timer(&SCpnt->eh_timeout, jiffies + 40 * HZ); + issueCmd = 0; + } + + /* Set the DV flags. + */ + if (dvStatus & MPT_SCSICFG_DV_NOT_DONE) + mptscsih_set_dvflags(hd, pScsiReq); + + if (!issueCmd) + goto fail; + } + } +#endif + + mpt_put_msg_frame(ScsiDoneCtx, hd->ioc, mf); + dmfprintk((MYIOC_s_INFO_FMT "Issued SCSI cmd (%p) mf=%p idx=%d\n", + hd->ioc->name, SCpnt, mf, my_idx)); + DBG_DUMP_REQUEST_FRAME(mf) + return 0; + + fail: + mptscsih_freeChainBuffers(hd->ioc, my_idx); + mpt_free_msg_frame(hd->ioc, mf); + return SCSI_MLQUEUE_HOST_BUSY; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * mptscsih_freeChainBuffers - Function to free chain buffers associated + * with a SCSI IO request + * @hd: Pointer to the MPT_SCSI_HOST instance + * @req_idx: Index of the SCSI IO request frame. + * + * Called if SG chain buffer allocation fails and mptscsih callbacks. + * No return. + */ +static void +mptscsih_freeChainBuffers(MPT_ADAPTER *ioc, int req_idx) +{ + MPT_FRAME_HDR *chain; + unsigned long flags; + int chain_idx; + int next; + + /* Get the first chain index and reset + * tracker state. + */ + chain_idx = ioc->ReqToChain[req_idx]; + ioc->ReqToChain[req_idx] = MPT_HOST_NO_CHAIN; + + while (chain_idx != MPT_HOST_NO_CHAIN) { + + /* Save the next chain buffer index */ + next = ioc->ChainToChain[chain_idx]; + + /* Free this chain buffer and reset + * tracker + */ + ioc->ChainToChain[chain_idx] = MPT_HOST_NO_CHAIN; + + chain = (MPT_FRAME_HDR *) (ioc->ChainBuffer + + (chain_idx * ioc->req_sz)); + + spin_lock_irqsave(&ioc->FreeQlock, flags); + list_add_tail(&chain->u.frame.linkage.list, &ioc->FreeChainQ); + spin_unlock_irqrestore(&ioc->FreeQlock, flags); + + dmfprintk((MYIOC_s_INFO_FMT "FreeChainBuffers (index %d)\n", + ioc->name, chain_idx)); + + /* handle next */ + chain_idx = next; + } + return; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * Reset Handling + */ + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * mptscsih_TMHandler - Generic handler for SCSI Task Management. + * Fall through to mpt_HardResetHandler if: not operational, too many + * failed TM requests or handshake failure. + * + * @ioc: Pointer to MPT_ADAPTER structure + * @type: Task Management type + * @target: Logical Target ID for reset (if appropriate) + * @lun: Logical Unit for reset (if appropriate) + * @ctx2abort: Context for the task to be aborted (if appropriate) + * + * Remark: Currently invoked from a non-interrupt thread (_bh). + * + * Remark: With old EH code, at most 1 SCSI TaskMgmt function per IOC + * will be active. + * + * Returns 0 for SUCCESS or -1 if FAILED. + */ +static int +mptscsih_TMHandler(MPT_SCSI_HOST *hd, u8 type, u8 channel, u8 target, u8 lun, int ctx2abort, ulong timeout) +{ + MPT_ADAPTER *ioc; + int rc = -1; + int doTask = 1; + u32 ioc_raw_state; + unsigned long flags; + + /* If FW is being reloaded currently, return success to + * the calling function. + */ + if (hd == NULL) + return 0; + + ioc = hd->ioc; + if (ioc == NULL) { + printk(KERN_ERR MYNAM " TMHandler" " NULL ioc!\n"); + return FAILED; + } + dtmprintk((MYIOC_s_INFO_FMT "TMHandler Entered!\n", ioc->name)); + + // SJR - CHECKME - Can we avoid this here? + // (mpt_HardResetHandler has this check...) + spin_lock_irqsave(&ioc->diagLock, flags); + if ((ioc->diagPending) || (ioc->alt_ioc && ioc->alt_ioc->diagPending)) { + spin_unlock_irqrestore(&ioc->diagLock, flags); + return FAILED; + } + spin_unlock_irqrestore(&ioc->diagLock, flags); + + /* Wait a fixed amount of time for the TM pending flag to be cleared. + * If we time out and not bus reset, then we return a FAILED status to the caller. + * The call to mptscsih_tm_pending_wait() will set the pending flag if we are + * successful. Otherwise, reload the FW. + */ + if (mptscsih_tm_pending_wait(hd) == FAILED) { + if (type == MPI_SCSITASKMGMT_TASKTYPE_ABORT_TASK) { + dtmprintk((KERN_WARNING MYNAM ": %s: TMHandler abort: " + "Timed out waiting for last TM (%d) to complete! \n", + hd->ioc->name, hd->tmPending)); + return FAILED; + } else if (type == MPI_SCSITASKMGMT_TASKTYPE_TARGET_RESET) { + dtmprintk((KERN_WARNING MYNAM ": %s: TMHandler target reset: " + "Timed out waiting for last TM (%d) to complete! \n", + hd->ioc->name, hd->tmPending)); + return FAILED; + } else if (type == MPI_SCSITASKMGMT_TASKTYPE_RESET_BUS) { + dtmprintk((KERN_WARNING MYNAM ": %s: TMHandler bus reset: " + "Timed out waiting for last TM (%d) to complete! \n", + hd->ioc->name, hd->tmPending)); + if (hd->tmPending & (1 << MPI_SCSITASKMGMT_TASKTYPE_RESET_BUS)) + return FAILED; + + doTask = 0; + } + } else { + spin_lock_irqsave(&hd->ioc->FreeQlock, flags); + hd->tmPending |= (1 << type); + spin_unlock_irqrestore(&hd->ioc->FreeQlock, flags); + } + + /* Is operational? + */ + ioc_raw_state = mpt_GetIocState(hd->ioc, 0); + +#ifdef MPT_DEBUG_RESET + if ((ioc_raw_state & MPI_IOC_STATE_MASK) != MPI_IOC_STATE_OPERATIONAL) { + printk(MYIOC_s_WARN_FMT + "TM Handler: IOC Not operational(0x%x)!\n", + hd->ioc->name, ioc_raw_state); + } +#endif + + if (doTask && ((ioc_raw_state & MPI_IOC_STATE_MASK) == MPI_IOC_STATE_OPERATIONAL) + && !(ioc_raw_state & MPI_DOORBELL_ACTIVE)) { + + /* Isse the Task Mgmt request. + */ + if (hd->hard_resets < -1) + hd->hard_resets++; + rc = mptscsih_IssueTaskMgmt(hd, type, channel, target, lun, ctx2abort, timeout); + if (rc) { + printk(MYIOC_s_INFO_FMT "Issue of TaskMgmt failed!\n", hd->ioc->name); + } else { + dtmprintk((MYIOC_s_INFO_FMT "Issue of TaskMgmt Successful!\n", hd->ioc->name)); + } + } + + /* Only fall through to the HRH if this is a bus reset + */ + if ((type == MPI_SCSITASKMGMT_TASKTYPE_RESET_BUS) && (rc || + ioc->reload_fw || (ioc->alt_ioc && ioc->alt_ioc->reload_fw))) { + dtmprintk((MYIOC_s_INFO_FMT "Calling HardReset! \n", + hd->ioc->name)); + rc = mpt_HardResetHandler(hd->ioc, CAN_SLEEP); + } + + dtmprintk((MYIOC_s_INFO_FMT "TMHandler rc = %d!\n", hd->ioc->name, rc)); + + return rc; +} + + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * mptscsih_IssueTaskMgmt - Generic send Task Management function. + * @hd: Pointer to MPT_SCSI_HOST structure + * @type: Task Management type + * @target: Logical Target ID for reset (if appropriate) + * @lun: Logical Unit for reset (if appropriate) + * @ctx2abort: Context for the task to be aborted (if appropriate) + * + * Remark: _HardResetHandler can be invoked from an interrupt thread (timer) + * or a non-interrupt thread. In the former, must not call schedule(). + * + * Not all fields are meaningfull for all task types. + * + * Returns 0 for SUCCESS, -999 for "no msg frames", + * else other non-zero value returned. + */ +static int +mptscsih_IssueTaskMgmt(MPT_SCSI_HOST *hd, u8 type, u8 channel, u8 target, u8 lun, int ctx2abort, ulong timeout) +{ + MPT_FRAME_HDR *mf; + SCSITaskMgmt_t *pScsiTm; + int ii; + int retval; + + /* Return Fail to calling function if no message frames available. + */ + if ((mf = mpt_get_msg_frame(ScsiTaskCtx, hd->ioc)) == NULL) { + dfailprintk((MYIOC_s_ERR_FMT "IssueTaskMgmt, no msg frames!!\n", + hd->ioc->name)); + //return FAILED; + return -999; + } + dtmprintk((MYIOC_s_INFO_FMT "IssueTaskMgmt request @ %p\n", + hd->ioc->name, mf)); + + /* Format the Request + */ + pScsiTm = (SCSITaskMgmt_t *) mf; + pScsiTm->TargetID = target; + pScsiTm->Bus = channel; + pScsiTm->ChainOffset = 0; + pScsiTm->Function = MPI_FUNCTION_SCSI_TASK_MGMT; + + pScsiTm->Reserved = 0; + pScsiTm->TaskType = type; + pScsiTm->Reserved1 = 0; + pScsiTm->MsgFlags = (type == MPI_SCSITASKMGMT_TASKTYPE_RESET_BUS) + ? MPI_SCSITASKMGMT_MSGFLAGS_LIPRESET_RESET_OPTION : 0; + + for (ii= 0; ii < 8; ii++) { + pScsiTm->LUN[ii] = 0; + } + pScsiTm->LUN[1] = lun; + + for (ii=0; ii < 7; ii++) + pScsiTm->Reserved2[ii] = 0; + + pScsiTm->TaskMsgContext = ctx2abort; + + dtmprintk((MYIOC_s_INFO_FMT + "IssueTaskMgmt: ctx2abort (0x%08x) type=%d\n", + hd->ioc->name, ctx2abort, type)); + + DBG_DUMP_TM_REQUEST_FRAME((u32 *)pScsiTm); + + if ((retval = mpt_send_handshake_request(ScsiTaskCtx, hd->ioc, + sizeof(SCSITaskMgmt_t), (u32*)pScsiTm, + CAN_SLEEP)) != 0) { + dfailprintk((MYIOC_s_ERR_FMT "_send_handshake FAILED!" + " (hd %p, ioc %p, mf %p) \n", hd->ioc->name, hd, + hd->ioc, mf)); + mpt_free_msg_frame(hd->ioc, mf); + return retval; + } + + if(mptscsih_tm_wait_for_completion(hd, timeout) == FAILED) { + dfailprintk((MYIOC_s_ERR_FMT "_wait_for_completion FAILED!" + " (hd %p, ioc %p, mf %p) \n", hd->ioc->name, hd, + hd->ioc, mf)); + mpt_free_msg_frame(hd->ioc, mf); + dtmprintk((MYIOC_s_INFO_FMT "Calling HardReset! \n", + hd->ioc->name)); + retval = mpt_HardResetHandler(hd->ioc, CAN_SLEEP); + } + + return retval; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_abort - Abort linux scsi_cmnd routine, new_eh variant + * @SCpnt: Pointer to scsi_cmnd structure, IO to be aborted + * + * (linux scsi_host_template.eh_abort_handler routine) + * + * Returns SUCCESS or FAILED. + */ +static int +mptscsih_abort(struct scsi_cmnd * SCpnt) +{ + MPT_SCSI_HOST *hd; + MPT_ADAPTER *ioc; + MPT_FRAME_HDR *mf; + u32 ctx2abort; + int scpnt_idx; + spinlock_t *host_lock = SCpnt->device->host->host_lock; + + /* If we can't locate our host adapter structure, return FAILED status. + */ + if ((hd = (MPT_SCSI_HOST *) SCpnt->device->host->hostdata) == NULL) { + SCpnt->result = DID_RESET << 16; + SCpnt->scsi_done(SCpnt); + dfailprintk((KERN_WARNING MYNAM ": mptscsih_abort: " + "Can't locate host! (sc=%p)\n", + SCpnt)); + return FAILED; + } + + ioc = hd->ioc; + if (hd->resetPending) + return FAILED; + + printk(KERN_WARNING MYNAM ": %s: >> Attempting task abort! (sc=%p)\n", + hd->ioc->name, SCpnt); + + if (hd->timeouts < -1) + hd->timeouts++; + + /* Find this command + */ + if ((scpnt_idx = SCPNT_TO_LOOKUP_IDX(SCpnt)) < 0) { + /* Cmd not found in ScsiLookup. + * Do OS callback. + */ + SCpnt->result = DID_RESET << 16; + dtmprintk((KERN_WARNING MYNAM ": %s: mptscsih_abort: " + "Command not in the active list! (sc=%p)\n", + hd->ioc->name, SCpnt)); + return SUCCESS; + } + + /* Most important! Set TaskMsgContext to SCpnt's MsgContext! + * (the IO to be ABORT'd) + * + * NOTE: Since we do not byteswap MsgContext, we do not + * swap it here either. It is an opaque cookie to + * the controller, so it does not matter. -DaveM + */ + mf = MPT_INDEX_2_MFPTR(hd->ioc, scpnt_idx); + ctx2abort = mf->u.frame.hwhdr.msgctxu.MsgContext; + + hd->abortSCpnt = SCpnt; + + spin_unlock_irq(host_lock); + if (mptscsih_TMHandler(hd, MPI_SCSITASKMGMT_TASKTYPE_ABORT_TASK, + SCpnt->device->channel, SCpnt->device->id, SCpnt->device->lun, + ctx2abort, 2 /* 2 second timeout */) + < 0) { + + /* The TM request failed and the subsequent FW-reload failed! + * Fatal error case. + */ + printk(MYIOC_s_WARN_FMT "Error issuing abort task! (sc=%p)\n", + hd->ioc->name, SCpnt); + + /* We must clear our pending flag before clearing our state. + */ + hd->tmPending = 0; + hd->tmState = TM_STATE_NONE; + + spin_lock_irq(host_lock); + + /* Unmap the DMA buffers, if any. */ + 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); + } + hd->ScsiLookup[scpnt_idx] = NULL; + SCpnt->result = DID_RESET << 16; + SCpnt->scsi_done(SCpnt); /* Issue the command callback */ + mptscsih_freeChainBuffers(ioc, scpnt_idx); + mpt_free_msg_frame(ioc, mf); + return FAILED; + } + spin_lock_irq(host_lock); + return SUCCESS; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_dev_reset - Perform a SCSI TARGET_RESET! new_eh variant + * @SCpnt: Pointer to scsi_cmnd structure, IO which reset is due to + * + * (linux scsi_host_template.eh_dev_reset_handler routine) + * + * Returns SUCCESS or FAILED. + */ +static int +mptscsih_dev_reset(struct scsi_cmnd * SCpnt) +{ + MPT_SCSI_HOST *hd; + spinlock_t *host_lock = SCpnt->device->host->host_lock; + + /* If we can't locate our host adapter structure, return FAILED status. + */ + if ((hd = (MPT_SCSI_HOST *) SCpnt->device->host->hostdata) == NULL){ + dtmprintk((KERN_WARNING MYNAM ": mptscsih_dev_reset: " + "Can't locate host! (sc=%p)\n", + SCpnt)); + return FAILED; + } + + if (hd->resetPending) + return FAILED; + + printk(KERN_WARNING MYNAM ": %s: >> Attempting target reset! (sc=%p)\n", + hd->ioc->name, SCpnt); + + spin_unlock_irq(host_lock); + if (mptscsih_TMHandler(hd, MPI_SCSITASKMGMT_TASKTYPE_TARGET_RESET, + SCpnt->device->channel, SCpnt->device->id, + 0, 0, 5 /* 5 second timeout */) + < 0){ + /* The TM request failed and the subsequent FW-reload failed! + * Fatal error case. + */ + printk(MYIOC_s_WARN_FMT "Error processing TaskMgmt request (sc=%p)\n", + hd->ioc->name, SCpnt); + hd->tmPending = 0; + hd->tmState = TM_STATE_NONE; + spin_lock_irq(host_lock); + return FAILED; + } + spin_lock_irq(host_lock); + return SUCCESS; + +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_bus_reset - Perform a SCSI BUS_RESET! new_eh variant + * @SCpnt: Pointer to scsi_cmnd structure, IO which reset is due to + * + * (linux scsi_host_template.eh_bus_reset_handler routine) + * + * Returns SUCCESS or FAILED. + */ +static int +mptscsih_bus_reset(struct scsi_cmnd * SCpnt) +{ + MPT_SCSI_HOST *hd; + spinlock_t *host_lock = SCpnt->device->host->host_lock; + + /* If we can't locate our host adapter structure, return FAILED status. + */ + if ((hd = (MPT_SCSI_HOST *) SCpnt->device->host->hostdata) == NULL){ + dtmprintk((KERN_WARNING MYNAM ": mptscsih_bus_reset: " + "Can't locate host! (sc=%p)\n", + SCpnt ) ); + return FAILED; + } + + printk(KERN_WARNING MYNAM ": %s: >> Attempting bus reset! (sc=%p)\n", + hd->ioc->name, SCpnt); + + if (hd->timeouts < -1) + hd->timeouts++; + + /* We are now ready to execute the task management request. */ + spin_unlock_irq(host_lock); + if (mptscsih_TMHandler(hd, MPI_SCSITASKMGMT_TASKTYPE_RESET_BUS, + SCpnt->device->channel, 0, 0, 0, 5 /* 5 second timeout */) + < 0){ + + /* The TM request failed and the subsequent FW-reload failed! + * Fatal error case. + */ + printk(MYIOC_s_WARN_FMT + "Error processing TaskMgmt request (sc=%p)\n", + hd->ioc->name, SCpnt); + hd->tmPending = 0; + hd->tmState = TM_STATE_NONE; + spin_lock_irq(host_lock); + return FAILED; + } + spin_lock_irq(host_lock); + return SUCCESS; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_host_reset - Perform a SCSI host adapter RESET! + * new_eh variant + * @SCpnt: Pointer to scsi_cmnd structure, IO which reset is due to + * + * (linux scsi_host_template.eh_host_reset_handler routine) + * + * Returns SUCCESS or FAILED. + */ +static int +mptscsih_host_reset(struct scsi_cmnd *SCpnt) +{ + MPT_SCSI_HOST * hd; + int status = SUCCESS; + spinlock_t *host_lock = SCpnt->device->host->host_lock; + + /* If we can't locate the host to reset, then we failed. */ + if ((hd = (MPT_SCSI_HOST *) SCpnt->device->host->hostdata) == NULL){ + dtmprintk( ( KERN_WARNING MYNAM ": mptscsih_host_reset: " + "Can't locate host! (sc=%p)\n", + SCpnt ) ); + return FAILED; + } + + printk(KERN_WARNING MYNAM ": %s: >> Attempting host reset! (sc=%p)\n", + hd->ioc->name, SCpnt); + + /* If our attempts to reset the host failed, then return a failed + * status. The host will be taken off line by the SCSI mid-layer. + */ + spin_unlock_irq(host_lock); + if (mpt_HardResetHandler(hd->ioc, CAN_SLEEP) < 0){ + status = FAILED; + } else { + /* Make sure TM pending is cleared and TM state is set to + * NONE. + */ + hd->tmPending = 0; + hd->tmState = TM_STATE_NONE; + } + spin_lock_irq(host_lock); + + + dtmprintk( ( KERN_WARNING MYNAM ": mptscsih_host_reset: " + "Status = %s\n", + (status == SUCCESS) ? "SUCCESS" : "FAILED" ) ); + + return status; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_tm_pending_wait - wait for pending task management request to + * complete. + * @hd: Pointer to MPT host structure. + * + * Returns {SUCCESS,FAILED}. + */ +static int +mptscsih_tm_pending_wait(MPT_SCSI_HOST * hd) +{ + unsigned long flags; + int loop_count = 4 * 10; /* Wait 10 seconds */ + int status = FAILED; + + do { + spin_lock_irqsave(&hd->ioc->FreeQlock, flags); + if (hd->tmState == TM_STATE_NONE) { + hd->tmState = TM_STATE_IN_PROGRESS; + hd->tmPending = 1; + status = SUCCESS; + spin_unlock_irqrestore(&hd->ioc->FreeQlock, flags); + break; + } + spin_unlock_irqrestore(&hd->ioc->FreeQlock, flags); + msleep(250); + } while (--loop_count); + + return status; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_tm_wait_for_completion - wait for completion of TM task + * @hd: Pointer to MPT host structure. + * + * Returns {SUCCESS,FAILED}. + */ +static int +mptscsih_tm_wait_for_completion(MPT_SCSI_HOST * hd, ulong timeout ) +{ + unsigned long flags; + int loop_count = 4 * timeout; + int status = FAILED; + + do { + spin_lock_irqsave(&hd->ioc->FreeQlock, flags); + if(hd->tmPending == 0) { + status = SUCCESS; + spin_unlock_irqrestore(&hd->ioc->FreeQlock, flags); + break; + } + spin_unlock_irqrestore(&hd->ioc->FreeQlock, flags); + msleep_interruptible(250); + } while (--loop_count); + + return status; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_taskmgmt_complete - Registered with Fusion MPT base driver + * @ioc: Pointer to MPT_ADAPTER structure + * @mf: Pointer to SCSI task mgmt request frame + * @mr: Pointer to SCSI task mgmt reply frame + * + * This routine is called from mptbase.c::mpt_interrupt() at the completion + * of any SCSI task management request. + * This routine is registered with the 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_taskmgmt_complete(MPT_ADAPTER *ioc, MPT_FRAME_HDR *mf, MPT_FRAME_HDR *mr) +{ + SCSITaskMgmtReply_t *pScsiTmReply; + SCSITaskMgmt_t *pScsiTmReq; + MPT_SCSI_HOST *hd; + unsigned long flags; + u16 iocstatus; + u8 tmType; + + dtmprintk((MYIOC_s_WARN_FMT "TaskMgmt completed (mf=%p,mr=%p)\n", + ioc->name, mf, mr)); + if (ioc->sh) { + /* Depending on the thread, a timer is activated for + * the TM request. Delete this timer on completion of TM. + * Decrement count of outstanding TM requests. + */ + hd = (MPT_SCSI_HOST *)ioc->sh->hostdata; + } else { + dtmprintk((MYIOC_s_WARN_FMT "TaskMgmt Complete: NULL Scsi Host Ptr\n", + ioc->name)); + return 1; + } + + if (mr == NULL) { + dtmprintk((MYIOC_s_WARN_FMT "ERROR! TaskMgmt Reply: NULL Request %p\n", + ioc->name, mf)); + return 1; + } else { + pScsiTmReply = (SCSITaskMgmtReply_t*)mr; + pScsiTmReq = (SCSITaskMgmt_t*)mf; + + /* Figure out if this was ABORT_TASK, TARGET_RESET, or BUS_RESET! */ + tmType = pScsiTmReq->TaskType; + + dtmprintk((MYIOC_s_WARN_FMT " TaskType = %d, TerminationCount=%d\n", + ioc->name, tmType, le32_to_cpu(pScsiTmReply->TerminationCount))); + DBG_DUMP_TM_REPLY_FRAME((u32 *)pScsiTmReply); + + iocstatus = le16_to_cpu(pScsiTmReply->IOCStatus) & MPI_IOCSTATUS_MASK; + dtmprintk((MYIOC_s_WARN_FMT " SCSI TaskMgmt (%d) IOCStatus=%04x IOCLogInfo=%08x\n", + ioc->name, tmType, iocstatus, le32_to_cpu(pScsiTmReply->IOCLogInfo))); + /* Error? (anything non-zero?) */ + if (iocstatus) { + + /* clear flags and continue. + */ + if (tmType == MPI_SCSITASKMGMT_TASKTYPE_ABORT_TASK) + hd->abortSCpnt = NULL; + + /* If an internal command is present + * or the TM failed - reload the FW. + * FC FW may respond FAILED to an ABORT + */ + if (tmType == MPI_SCSITASKMGMT_TASKTYPE_RESET_BUS) { + if ((hd->cmdPtr) || + (iocstatus == MPI_IOCSTATUS_SCSI_TASK_MGMT_FAILED)) { + if (mpt_HardResetHandler(ioc, NO_SLEEP) < 0) { + printk((KERN_WARNING + " Firmware Reload FAILED!!\n")); + } + } + } + } else { + dtmprintk((MYIOC_s_WARN_FMT " TaskMgmt SUCCESS\n", ioc->name)); + + hd->abortSCpnt = NULL; + + } + } + + spin_lock_irqsave(&ioc->FreeQlock, flags); + hd->tmPending = 0; + spin_unlock_irqrestore(&ioc->FreeQlock, flags); + hd->tmState = TM_STATE_NONE; + + return 1; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * This is anyones guess quite frankly. + */ +static int +mptscsih_bios_param(struct scsi_device * sdev, struct block_device *bdev, + sector_t capacity, int geom[]) +{ + int heads; + int sectors; + sector_t cylinders; + ulong dummy; + + heads = 64; + sectors = 32; + + dummy = heads * sectors; + cylinders = capacity; + sector_div(cylinders,dummy); + + /* + * Handle extended translation size for logical drives + * > 1Gb + */ + if ((ulong)capacity >= 0x200000) { + heads = 255; + sectors = 63; + dummy = heads * sectors; + cylinders = capacity; + sector_div(cylinders,dummy); + } + + /* return result */ + geom[0] = heads; + geom[1] = sectors; + geom[2] = cylinders; + + dprintk((KERN_NOTICE + ": bios_param: Id=%i Lun=%i Channel=%i CHS=%i/%i/%i\n", + sdev->id, sdev->lun,sdev->channel,(int)cylinders,heads,sectors)); + + return 0; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * OS entry point to allow host driver to alloc memory + * for each scsi device. Called once per device the bus scan. + * Return non-zero if allocation fails. + * Init memory once per id (not LUN). + */ +static int +mptscsih_slave_alloc(struct scsi_device *device) +{ + struct Scsi_Host *host = device->host; + MPT_SCSI_HOST *hd = (MPT_SCSI_HOST *)host->hostdata; + VirtDevice *vdev; + uint target = device->id; + + if (hd == NULL) + return -ENODEV; + + if ((vdev = hd->Targets[target]) != NULL) + goto out; + + vdev = kmalloc(sizeof(VirtDevice), GFP_KERNEL); + if (!vdev) { + printk(MYIOC_s_ERR_FMT "slave_alloc kmalloc(%zd) FAILED!\n", + hd->ioc->name, sizeof(VirtDevice)); + return -ENOMEM; + } + + memset(vdev, 0, sizeof(VirtDevice)); + vdev->tflags = MPT_TARGET_FLAGS_Q_YES; + vdev->ioc_id = hd->ioc->id; + vdev->target_id = device->id; + vdev->bus_id = device->channel; + vdev->raidVolume = 0; + hd->Targets[device->id] = vdev; + if (hd->ioc->bus_type == SCSI) { + if (hd->ioc->spi_data.isRaid & (1 << device->id)) { + vdev->raidVolume = 1; + ddvtprintk((KERN_INFO + "RAID Volume @ id %d\n", device->id)); + } + } else { + vdev->tflags |= MPT_TARGET_FLAGS_VALID_INQUIRY; + } + + out: + vdev->num_luns++; + return 0; +} + +static int mptscsih_is_raid_volume(MPT_SCSI_HOST *hd, uint id) +{ + int i; + + if (!hd->ioc->spi_data.isRaid || !hd->ioc->spi_data.pIocPg3) + return 0; + + for (i = 0; i < hd->ioc->spi_data.pIocPg3->NumPhysDisks; i++) { + if (id == hd->ioc->spi_data.pIocPg3->PhysDisk[i].PhysDiskID) + return 1; + } + + return 0; +} + +/* + * OS entry point to allow for host driver to free allocated memory + * Called if no device present or device being unloaded + */ +static void +mptscsih_slave_destroy(struct scsi_device *device) +{ + struct Scsi_Host *host = device->host; + MPT_SCSI_HOST *hd = (MPT_SCSI_HOST *)host->hostdata; + VirtDevice *vdev; + uint target = device->id; + uint lun = device->lun; + + if (hd == NULL) + return; + + mptscsih_search_running_cmds(hd, target, lun); + + vdev = hd->Targets[target]; + vdev->luns[0] &= ~(1 << lun); + if (--vdev->num_luns) + return; + + kfree(hd->Targets[target]); + hd->Targets[target] = NULL; + + if (hd->ioc->bus_type == SCSI) { + if (mptscsih_is_raid_volume(hd, target)) { + hd->ioc->spi_data.forceDv |= MPT_SCSICFG_RELOAD_IOC_PG3; + } else { + hd->ioc->spi_data.dvStatus[target] = + MPT_SCSICFG_NEGOTIATE; + + if (!hd->negoNvram) { + hd->ioc->spi_data.dvStatus[target] |= + MPT_SCSICFG_DV_NOT_DONE; + } + } + } +} + +static void +mptscsih_set_queue_depth(struct scsi_device *device, MPT_SCSI_HOST *hd, + VirtDevice *pTarget, int qdepth) +{ + int max_depth; + int tagged; + + if (hd->ioc->bus_type == SCSI) { + if (pTarget->tflags & MPT_TARGET_FLAGS_VALID_INQUIRY) { + if (!(pTarget->tflags & MPT_TARGET_FLAGS_Q_YES)) + max_depth = 1; + else if (((pTarget->inq_data[0] & 0x1f) == 0x00) && + (pTarget->minSyncFactor <= MPT_ULTRA160 )) + max_depth = MPT_SCSI_CMD_PER_DEV_HIGH; + else + max_depth = MPT_SCSI_CMD_PER_DEV_LOW; + } else { + /* error case - No Inq. Data */ + max_depth = 1; + } + } else + max_depth = MPT_SCSI_CMD_PER_DEV_HIGH; + + if (qdepth > max_depth) + qdepth = max_depth; + if (qdepth == 1) + tagged = 0; + else + tagged = MSG_SIMPLE_TAG; + + scsi_adjust_queue_depth(device, tagged, qdepth); +} + + +/* + * OS entry point to adjust the queue_depths on a per-device basis. + * Called once per device the bus scan. Use it to force the queue_depth + * member to 1 if a device does not support Q tags. + * Return non-zero if fails. + */ +static int +mptscsih_slave_configure(struct scsi_device *device) +{ + struct Scsi_Host *sh = device->host; + VirtDevice *pTarget; + MPT_SCSI_HOST *hd = (MPT_SCSI_HOST *)sh->hostdata; + + if ((hd == NULL) || (hd->Targets == NULL)) { + return 0; + } + + dsprintk((MYIOC_s_INFO_FMT + "device @ %p, id=%d, LUN=%d, channel=%d\n", + hd->ioc->name, device, device->id, device->lun, device->channel)); + dsprintk((MYIOC_s_INFO_FMT + "sdtr %d wdtr %d ppr %d inq length=%d\n", + hd->ioc->name, device->sdtr, device->wdtr, + device->ppr, device->inquiry_len)); + + if (device->id > sh->max_id) { + /* error case, should never happen */ + scsi_adjust_queue_depth(device, 0, 1); + goto slave_configure_exit; + } + + pTarget = hd->Targets[device->id]; + + if (pTarget == NULL) { + /* Driver doesn't know about this device. + * Kernel may generate a "Dummy Lun 0" which + * may become a real Lun if a + * "scsi add-single-device" command is executed + * while the driver is active (hot-plug a + * device). LSI Raid controllers need + * queue_depth set to DEV_HIGH for this reason. + */ + scsi_adjust_queue_depth(device, MSG_SIMPLE_TAG, + MPT_SCSI_CMD_PER_DEV_HIGH); + goto slave_configure_exit; + } + + mptscsih_initTarget(hd, device->channel, device->id, device->lun, + device->inquiry, device->inquiry_len ); + mptscsih_set_queue_depth(device, hd, pTarget, MPT_SCSI_CMD_PER_DEV_HIGH); + + dsprintk((MYIOC_s_INFO_FMT + "Queue depth=%d, tflags=%x\n", + hd->ioc->name, device->queue_depth, pTarget->tflags)); + + dsprintk((MYIOC_s_INFO_FMT + "negoFlags=%x, maxOffset=%x, SyncFactor=%x\n", + hd->ioc->name, pTarget->negoFlags, pTarget->maxOffset, pTarget->minSyncFactor)); + +slave_configure_exit: + + dsprintk((MYIOC_s_INFO_FMT + "tagged %d, simple %d, ordered %d\n", + hd->ioc->name,device->tagged_supported, device->simple_tags, + device->ordered_tags)); + + return 0; +} + +static ssize_t +mptscsih_store_queue_depth(struct device *dev, const char *buf, size_t count) +{ + int depth; + struct scsi_device *sdev = to_scsi_device(dev); + MPT_SCSI_HOST *hd = (MPT_SCSI_HOST *) sdev->host->hostdata; + VirtDevice *pTarget; + + depth = simple_strtoul(buf, NULL, 0); + if (depth == 0) + return -EINVAL; + pTarget = hd->Targets[sdev->id]; + if (pTarget == NULL) + return -EINVAL; + mptscsih_set_queue_depth(sdev, (MPT_SCSI_HOST *) sdev->host->hostdata, + pTarget, depth); + return count; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * Private routines... + */ + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* Utility function to copy sense data from the scsi_cmnd buffer + * to the FC and SCSI target structures. + * + */ +static void +copy_sense_data(struct scsi_cmnd *sc, MPT_SCSI_HOST *hd, MPT_FRAME_HDR *mf, SCSIIOReply_t *pScsiReply) +{ + VirtDevice *target; + SCSIIORequest_t *pReq; + u32 sense_count = le32_to_cpu(pScsiReply->SenseCount); + int index; + + /* Get target structure + */ + pReq = (SCSIIORequest_t *) mf; + index = (int) pReq->TargetID; + target = hd->Targets[index]; + + if (sense_count) { + u8 *sense_data; + int req_index; + + /* Copy the sense received into the scsi command block. */ + req_index = le16_to_cpu(mf->u.frame.hwhdr.msgctxu.fld.req_idx); + sense_data = ((u8 *)hd->ioc->sense_buf_pool + (req_index * MPT_SENSE_BUFFER_ALLOC)); + memcpy(sc->sense_buffer, sense_data, SNS_LEN(sc)); + + /* Log SMART data (asc = 0x5D, non-IM case only) if required. + */ + if ((hd->ioc->events) && (hd->ioc->eventTypes & (1 << MPI_EVENT_SCSI_DEVICE_STATUS_CHANGE))) { + if ((sense_data[12] == 0x5D) && (target->raidVolume == 0)) { + int idx; + MPT_ADAPTER *ioc = hd->ioc; + + idx = ioc->eventContext % ioc->eventLogSize; + ioc->events[idx].event = MPI_EVENT_SCSI_DEVICE_STATUS_CHANGE; + ioc->events[idx].eventContext = ioc->eventContext; + + ioc->events[idx].data[0] = (pReq->LUN[1] << 24) || + (MPI_EVENT_SCSI_DEV_STAT_RC_SMART_DATA << 16) || + (pReq->Bus << 8) || pReq->TargetID; + + ioc->events[idx].data[1] = (sense_data[13] << 8) || sense_data[12]; + + ioc->eventContext++; + } + } + } else { + dprintk((MYIOC_s_INFO_FMT "Hmmm... SenseData len=0! (?)\n", + hd->ioc->name)); + } +} + +static u32 +SCPNT_TO_LOOKUP_IDX(struct scsi_cmnd *sc) +{ + MPT_SCSI_HOST *hd; + int i; + + hd = (MPT_SCSI_HOST *) sc->device->host->hostdata; + + for (i = 0; i < hd->ioc->req_depth; i++) { + if (hd->ScsiLookup[i] == sc) { + return i; + } + } + + return -1; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +static int +mptscsih_ioc_reset(MPT_ADAPTER *ioc, int reset_phase) +{ + MPT_SCSI_HOST *hd; + unsigned long flags; + + dtmprintk((KERN_WARNING MYNAM + ": IOC %s_reset routed to SCSI host driver!\n", + reset_phase==MPT_IOC_SETUP_RESET ? "setup" : ( + reset_phase==MPT_IOC_PRE_RESET ? "pre" : "post"))); + + /* If a FW reload request arrives after base installed but + * before all scsi hosts have been attached, then an alt_ioc + * may have a NULL sh pointer. + */ + if ((ioc->sh == NULL) || (ioc->sh->hostdata == NULL)) + return 0; + else + hd = (MPT_SCSI_HOST *) ioc->sh->hostdata; + + if (reset_phase == MPT_IOC_SETUP_RESET) { + dtmprintk((MYIOC_s_WARN_FMT "Setup-Diag Reset\n", ioc->name)); + + /* Clean Up: + * 1. Set Hard Reset Pending Flag + * All new commands go to doneQ + */ + hd->resetPending = 1; + + } else if (reset_phase == MPT_IOC_PRE_RESET) { + dtmprintk((MYIOC_s_WARN_FMT "Pre-Diag Reset\n", ioc->name)); + + /* 2. Flush running commands + * Clean ScsiLookup (and associated memory) + * AND clean mytaskQ + */ + + /* 2b. Reply to OS all known outstanding I/O commands. + */ + mptscsih_flush_running_cmds(hd); + + /* 2c. If there was an internal command that + * has not completed, configuration or io request, + * free these resources. + */ + if (hd->cmdPtr) { + del_timer(&hd->timer); + mpt_free_msg_frame(ioc, hd->cmdPtr); + } + + dtmprintk((MYIOC_s_WARN_FMT "Pre-Reset complete.\n", ioc->name)); + + } else { + dtmprintk((MYIOC_s_WARN_FMT "Post-Diag Reset\n", ioc->name)); + + /* Once a FW reload begins, all new OS commands are + * redirected to the doneQ w/ a reset status. + * Init all control structures. + */ + + /* ScsiLookup initialization + */ + { + int ii; + for (ii=0; ii < hd->ioc->req_depth; ii++) + hd->ScsiLookup[ii] = NULL; + } + + /* 2. Chain Buffer initialization + */ + + /* 4. Renegotiate to all devices, if SCSI + */ + if (ioc->bus_type == SCSI) { + dnegoprintk(("writeSDP1: ALL_IDS USE_NVRAM\n")); + mptscsih_writeSDP1(hd, 0, 0, MPT_SCSICFG_ALL_IDS | MPT_SCSICFG_USE_NVRAM); + } + + /* 5. Enable new commands to be posted + */ + spin_lock_irqsave(&ioc->FreeQlock, flags); + hd->tmPending = 0; + spin_unlock_irqrestore(&ioc->FreeQlock, flags); + hd->resetPending = 0; + hd->tmState = TM_STATE_NONE; + + /* 6. If there was an internal command, + * wake this process up. + */ + if (hd->cmdPtr) { + /* + * Wake up the original calling thread + */ + hd->pLocal = &hd->localReply; + hd->pLocal->completion = MPT_SCANDV_DID_RESET; + scandv_wait_done = 1; + wake_up(&scandv_waitq); + hd->cmdPtr = NULL; + } + + /* 7. Set flag to force DV and re-read IOC Page 3 + */ + if (ioc->bus_type == SCSI) { + ioc->spi_data.forceDv = MPT_SCSICFG_NEED_DV | MPT_SCSICFG_RELOAD_IOC_PG3; + ddvtprintk(("Set reload IOC Pg3 Flag\n")); + } + + dtmprintk((MYIOC_s_WARN_FMT "Post-Reset complete.\n", ioc->name)); + + } + + return 1; /* currently means nothing really */ +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +static int +mptscsih_event_process(MPT_ADAPTER *ioc, EventNotificationReply_t *pEvReply) +{ + MPT_SCSI_HOST *hd; + u8 event = le32_to_cpu(pEvReply->Event) & 0xFF; + + devtprintk((MYIOC_s_INFO_FMT "MPT event (=%02Xh) routed to SCSI host driver!\n", + ioc->name, event)); + + switch (event) { + case MPI_EVENT_UNIT_ATTENTION: /* 03 */ + /* FIXME! */ + break; + case MPI_EVENT_IOC_BUS_RESET: /* 04 */ + case MPI_EVENT_EXT_BUS_RESET: /* 05 */ + hd = NULL; + if (ioc->sh) { + hd = (MPT_SCSI_HOST *) ioc->sh->hostdata; + if (hd && (ioc->bus_type == SCSI) && (hd->soft_resets < -1)) + hd->soft_resets++; + } + break; + case MPI_EVENT_LOGOUT: /* 09 */ + /* FIXME! */ + break; + + /* + * CHECKME! Don't think we need to do + * anything for these, but... + */ + case MPI_EVENT_RESCAN: /* 06 */ + case MPI_EVENT_LINK_STATUS_CHANGE: /* 07 */ + case MPI_EVENT_LOOP_STATE_CHANGE: /* 08 */ + /* + * CHECKME! Falling thru... + */ + break; + + case MPI_EVENT_INTEGRATED_RAID: /* 0B */ +#ifdef MPTSCSIH_ENABLE_DOMAIN_VALIDATION + /* negoNvram set to 0 if DV enabled and to USE_NVRAM if + * if DV disabled. Need to check for target mode. + */ + hd = NULL; + if (ioc->sh) + hd = (MPT_SCSI_HOST *) ioc->sh->hostdata; + + if (hd && (ioc->bus_type == SCSI) && (hd->negoNvram == 0)) { + ScsiCfgData *pSpi; + Ioc3PhysDisk_t *pPDisk; + int numPDisk; + u8 reason; + u8 physDiskNum; + + reason = (le32_to_cpu(pEvReply->Data[0]) & 0x00FF0000) >> 16; + if (reason == MPI_EVENT_RAID_RC_DOMAIN_VAL_NEEDED) { + /* New or replaced disk. + * Set DV flag and schedule DV. + */ + pSpi = &ioc->spi_data; + physDiskNum = (le32_to_cpu(pEvReply->Data[0]) & 0xFF000000) >> 24; + ddvtprintk(("DV requested for phys disk id %d\n", physDiskNum)); + if (pSpi->pIocPg3) { + pPDisk = pSpi->pIocPg3->PhysDisk; + numPDisk =pSpi->pIocPg3->NumPhysDisks; + + while (numPDisk) { + if (physDiskNum == pPDisk->PhysDiskNum) { + pSpi->dvStatus[pPDisk->PhysDiskID] = (MPT_SCSICFG_NEED_DV | MPT_SCSICFG_DV_NOT_DONE); + pSpi->forceDv = MPT_SCSICFG_NEED_DV; + ddvtprintk(("NEED_DV set for phys disk id %d\n", pPDisk->PhysDiskID)); + break; + } + pPDisk++; + numPDisk--; + } + + if (numPDisk == 0) { + /* The physical disk that needs DV was not found + * in the stored IOC Page 3. The driver must reload + * this page. DV routine will set the NEED_DV flag for + * all phys disks that have DV_NOT_DONE set. + */ + pSpi->forceDv = MPT_SCSICFG_NEED_DV | MPT_SCSICFG_RELOAD_IOC_PG3; + ddvtprintk(("phys disk %d not found. Setting reload IOC Pg3 Flag\n", physDiskNum)); + } + } + } + } +#endif + +#if defined(MPT_DEBUG_DV) || defined(MPT_DEBUG_DV_TINY) + printk("Raid Event RF: "); + { + u32 *m = (u32 *)pEvReply; + int ii; + int n = (int)pEvReply->MsgLength; + for (ii=6; ii < n; ii++) + printk(" %08x", le32_to_cpu(m[ii])); + printk("\n"); + } +#endif + break; + + case MPI_EVENT_NONE: /* 00 */ + case MPI_EVENT_LOG_DATA: /* 01 */ + case MPI_EVENT_STATE_CHANGE: /* 02 */ + case MPI_EVENT_EVENT_CHANGE: /* 0A */ + default: + dprintk((KERN_INFO " Ignoring event (=%02Xh)\n", event)); + break; + } + + return 1; /* currently means nothing really */ +} + +static struct device_attribute mptscsih_queue_depth_attr = { + .attr = { + .name = "queue_depth", + .mode = S_IWUSR, + }, + .store = mptscsih_store_queue_depth, +}; + +static struct device_attribute *mptscsih_dev_attrs[] = { + &mptscsih_queue_depth_attr, + NULL, +}; + +static struct scsi_host_template driver_template = { + .proc_name = "mptscsih", + .proc_info = mptscsih_proc_info, + .name = "MPT SCSI Host", + .info = mptscsih_info, + .queuecommand = mptscsih_qcmd, + .slave_alloc = mptscsih_slave_alloc, + .slave_configure = mptscsih_slave_configure, + .slave_destroy = mptscsih_slave_destroy, + .eh_abort_handler = mptscsih_abort, + .eh_device_reset_handler = mptscsih_dev_reset, + .eh_bus_reset_handler = mptscsih_bus_reset, + .eh_host_reset_handler = mptscsih_host_reset, + .bios_param = mptscsih_bios_param, + .can_queue = MPT_SCSI_CAN_QUEUE, + .this_id = -1, + .sg_tablesize = MPT_SCSI_SG_DEPTH, + .max_sectors = 8192, + .cmd_per_lun = 7, + .use_clustering = ENABLE_CLUSTERING, + .sdev_attrs = mptscsih_dev_attrs, +}; + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * mptscsih_initTarget - Target, LUN alloc/free functionality. + * @hd: Pointer to MPT_SCSI_HOST structure + * @bus_id: Bus number (?) + * @target_id: SCSI target id + * @lun: SCSI LUN id + * @data: Pointer to data + * @dlen: Number of INQUIRY bytes + * + * NOTE: It's only SAFE to call this routine if data points to + * sane & valid STANDARD INQUIRY data! + * + * Allocate and initialize memory for this target. + * Save inquiry data. + * + */ +static void +mptscsih_initTarget(MPT_SCSI_HOST *hd, int bus_id, int target_id, u8 lun, char *data, int dlen) +{ + int indexed_lun, lun_index; + VirtDevice *vdev; + ScsiCfgData *pSpi; + char data_56; + + dinitprintk((MYIOC_s_INFO_FMT "initTarget bus=%d id=%d lun=%d hd=%p\n", + hd->ioc->name, bus_id, target_id, lun, hd)); + + /* + * If the peripheral qualifier filter is enabled then if the target reports a 0x1 + * (i.e. The targer is capable of supporting the specified peripheral device type + * on this logical unit; however, the physical device is not currently connected + * to this logical unit) it will be converted to a 0x3 (i.e. The target is not + * capable of supporting a physical device on this logical unit). This is to work + * around a bug in th emid-layer in some distributions in which the mid-layer will + * continue to try to communicate to the LUN and evntually create a dummy LUN. + */ + if (mpt_pq_filter && dlen && (data[0] & 0xE0)) + data[0] |= 0x40; + + /* Is LUN supported? If so, upper 2 bits will be 0 + * in first byte of inquiry data. + */ + if (data[0] & 0xe0) + return; + + if ((vdev = hd->Targets[target_id]) == NULL) { + return; + } + + lun_index = (lun >> 5); /* 32 luns per lun_index */ + indexed_lun = (lun % 32); + vdev->luns[lun_index] |= (1 << indexed_lun); + + if (hd->ioc->bus_type == SCSI) { + if ((data[0] == TYPE_PROCESSOR) && (hd->ioc->spi_data.Saf_Te)) { + /* Treat all Processors as SAF-TE if + * command line option is set */ + vdev->tflags |= MPT_TARGET_FLAGS_SAF_TE_ISSUED; + mptscsih_writeIOCPage4(hd, target_id, bus_id); + }else if ((data[0] == TYPE_PROCESSOR) && + !(vdev->tflags & MPT_TARGET_FLAGS_SAF_TE_ISSUED )) { + if ( dlen > 49 ) { + vdev->tflags |= MPT_TARGET_FLAGS_VALID_INQUIRY; + if ( data[44] == 'S' && + data[45] == 'A' && + data[46] == 'F' && + data[47] == '-' && + data[48] == 'T' && + data[49] == 'E' ) { + vdev->tflags |= MPT_TARGET_FLAGS_SAF_TE_ISSUED; + mptscsih_writeIOCPage4(hd, target_id, bus_id); + } + } + } + if (!(vdev->tflags & MPT_TARGET_FLAGS_VALID_INQUIRY)) { + if ( dlen > 8 ) { + memcpy (vdev->inq_data, data, 8); + } else { + memcpy (vdev->inq_data, data, dlen); + } + + /* If have not done DV, set the DV flag. + */ + pSpi = &hd->ioc->spi_data; + if ((data[0] == TYPE_TAPE) || (data[0] == TYPE_PROCESSOR)) { + if (pSpi->dvStatus[target_id] & MPT_SCSICFG_DV_NOT_DONE) + pSpi->dvStatus[target_id] |= MPT_SCSICFG_NEED_DV; + } + + vdev->tflags |= MPT_TARGET_FLAGS_VALID_INQUIRY; + + + data_56 = 0x0F; /* Default to full capabilities if Inq data length is < 57 */ + if (dlen > 56) { + if ( (!(vdev->tflags & MPT_TARGET_FLAGS_VALID_56))) { + /* Update the target capabilities + */ + data_56 = data[56]; + vdev->tflags |= MPT_TARGET_FLAGS_VALID_56; + } + } + mptscsih_setTargetNegoParms(hd, vdev, data_56); + } else { + /* Initial Inquiry may not request enough data bytes to + * obtain byte 57. DV will; if target doesn't return + * at least 57 bytes, data[56] will be zero. */ + if (dlen > 56) { + if ( (!(vdev->tflags & MPT_TARGET_FLAGS_VALID_56))) { + /* Update the target capabilities + */ + data_56 = data[56]; + vdev->tflags |= MPT_TARGET_FLAGS_VALID_56; + mptscsih_setTargetNegoParms(hd, vdev, data_56); + } + } + } + } +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * Update the target negotiation parameters based on the + * the Inquiry data, adapter capabilities, and NVRAM settings. + * + */ +static void +mptscsih_setTargetNegoParms(MPT_SCSI_HOST *hd, VirtDevice *target, char byte56) +{ + ScsiCfgData *pspi_data = &hd->ioc->spi_data; + int id = (int) target->target_id; + int nvram; + VirtDevice *vdev; + int ii; + u8 width = MPT_NARROW; + u8 factor = MPT_ASYNC; + u8 offset = 0; + u8 version, nfactor; + u8 noQas = 1; + + target->negoFlags = pspi_data->noQas; + + /* noQas == 0 => device supports QAS. Need byte 56 of Inq to determine + * support. If available, default QAS to off and allow enabling. + * If not available, default QAS to on, turn off for non-disks. + */ + + /* Set flags based on Inquiry data + */ + version = target->inq_data[2] & 0x07; + if (version < 2) { + width = 0; + factor = MPT_ULTRA2; + offset = pspi_data->maxSyncOffset; + target->tflags &= ~MPT_TARGET_FLAGS_Q_YES; + } else { + if (target->inq_data[7] & 0x20) { + width = 1; + } + + if (target->inq_data[7] & 0x10) { + factor = pspi_data->minSyncFactor; + if (target->tflags & MPT_TARGET_FLAGS_VALID_56) { + /* bits 2 & 3 show Clocking support */ + if ((byte56 & 0x0C) == 0) + factor = MPT_ULTRA2; + else { + if ((byte56 & 0x03) == 0) + factor = MPT_ULTRA160; + else { + factor = MPT_ULTRA320; + if (byte56 & 0x02) + { + ddvtprintk((KERN_INFO "Enabling QAS due to byte56=%02x on id=%d!\n", byte56, id)); + noQas = 0; + } + if (target->inq_data[0] == TYPE_TAPE) { + if (byte56 & 0x01) + target->negoFlags |= MPT_TAPE_NEGO_IDP; + } + } + } + } else { + ddvtprintk((KERN_INFO "Enabling QAS on id=%d due to ~TARGET_FLAGS_VALID_56!\n", id)); + noQas = 0; + } + + offset = pspi_data->maxSyncOffset; + + /* If RAID, never disable QAS + * else if non RAID, do not disable + * QAS if bit 1 is set + * bit 1 QAS support, non-raid only + * bit 0 IU support + */ + if (target->raidVolume == 1) { + noQas = 0; + } + } else { + factor = MPT_ASYNC; + offset = 0; + } + } + + if ( (target->inq_data[7] & 0x02) == 0) { + target->tflags &= ~MPT_TARGET_FLAGS_Q_YES; + } + + /* Update tflags based on NVRAM settings. (SCSI only) + */ + if (pspi_data->nvram && (pspi_data->nvram[id] != MPT_HOST_NVRAM_INVALID)) { + nvram = pspi_data->nvram[id]; + nfactor = (nvram & MPT_NVRAM_SYNC_MASK) >> 8; + + if (width) + width = nvram & MPT_NVRAM_WIDE_DISABLE ? 0 : 1; + + if (offset > 0) { + /* Ensure factor is set to the + * maximum of: adapter, nvram, inquiry + */ + if (nfactor) { + if (nfactor < pspi_data->minSyncFactor ) + nfactor = pspi_data->minSyncFactor; + + factor = max(factor, nfactor); + if (factor == MPT_ASYNC) + offset = 0; + } else { + offset = 0; + factor = MPT_ASYNC; + } + } else { + factor = MPT_ASYNC; + } + } + + /* Make sure data is consistent + */ + if ((!width) && (factor < MPT_ULTRA2)) { + factor = MPT_ULTRA2; + } + + /* Save the data to the target structure. + */ + target->minSyncFactor = factor; + target->maxOffset = offset; + target->maxWidth = width; + + target->tflags |= MPT_TARGET_FLAGS_VALID_NEGO; + + /* Disable unused features. + */ + if (!width) + target->negoFlags |= MPT_TARGET_NO_NEGO_WIDE; + + if (!offset) + target->negoFlags |= MPT_TARGET_NO_NEGO_SYNC; + + if ( factor > MPT_ULTRA320 ) + noQas = 0; + + /* GEM, processor WORKAROUND + */ + if ((target->inq_data[0] == TYPE_PROCESSOR) || (target->inq_data[0] > 0x08)) { + target->negoFlags |= (MPT_TARGET_NO_NEGO_WIDE | MPT_TARGET_NO_NEGO_SYNC); + pspi_data->dvStatus[id] |= MPT_SCSICFG_BLK_NEGO; + } else { + if (noQas && (pspi_data->noQas == 0)) { + pspi_data->noQas |= MPT_TARGET_NO_NEGO_QAS; + target->negoFlags |= MPT_TARGET_NO_NEGO_QAS; + + /* Disable QAS in a mixed configuration case + */ + + ddvtprintk((KERN_INFO "Disabling QAS due to noQas=%02x on id=%d!\n", noQas, id)); + for (ii = 0; ii < id; ii++) { + if ( (vdev = hd->Targets[ii]) ) { + vdev->negoFlags |= MPT_TARGET_NO_NEGO_QAS; + mptscsih_writeSDP1(hd, 0, ii, vdev->negoFlags); + } + } + } + } + + /* Write SDP1 on this I/O to this target */ + if (pspi_data->dvStatus[id] & MPT_SCSICFG_NEGOTIATE) { + ddvtprintk((KERN_INFO "MPT_SCSICFG_NEGOTIATE on id=%d!\n", id)); + mptscsih_writeSDP1(hd, 0, id, hd->negoNvram); + pspi_data->dvStatus[id] &= ~MPT_SCSICFG_NEGOTIATE; + } else if (pspi_data->dvStatus[id] & MPT_SCSICFG_BLK_NEGO) { + ddvtprintk((KERN_INFO "MPT_SCSICFG_BLK_NEGO on id=%d!\n", id)); + mptscsih_writeSDP1(hd, 0, id, MPT_SCSICFG_BLK_NEGO); + pspi_data->dvStatus[id] &= ~MPT_SCSICFG_BLK_NEGO; + } +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* If DV disabled (negoNvram set to USE_NVARM) or if not LUN 0, return. + * Else set the NEED_DV flag after Read Capacity Issued (disks) + * or Mode Sense (cdroms). + * + * Tapes, initTarget will set this flag on completion of Inquiry command. + * Called only if DV_NOT_DONE flag is set + */ +static void mptscsih_set_dvflags(MPT_SCSI_HOST *hd, SCSIIORequest_t *pReq) +{ + u8 cmd; + ScsiCfgData *pSpi; + + ddvtprintk((" set_dvflags: id=%d lun=%d negoNvram=%x cmd=%x\n", + pReq->TargetID, pReq->LUN[1], hd->negoNvram, pReq->CDB[0])); + + if ((pReq->LUN[1] != 0) || (hd->negoNvram != 0)) + return; + + cmd = pReq->CDB[0]; + + if ((cmd == READ_CAPACITY) || (cmd == MODE_SENSE)) { + pSpi = &hd->ioc->spi_data; + if ((pSpi->isRaid & (1 << pReq->TargetID)) && pSpi->pIocPg3) { + /* Set NEED_DV for all hidden disks + */ + Ioc3PhysDisk_t *pPDisk = pSpi->pIocPg3->PhysDisk; + int numPDisk = pSpi->pIocPg3->NumPhysDisks; + + while (numPDisk) { + pSpi->dvStatus[pPDisk->PhysDiskID] |= MPT_SCSICFG_NEED_DV; + ddvtprintk(("NEED_DV set for phys disk id %d\n", pPDisk->PhysDiskID)); + pPDisk++; + numPDisk--; + } + } + pSpi->dvStatus[pReq->TargetID] |= MPT_SCSICFG_NEED_DV; + ddvtprintk(("NEED_DV set for visible disk id %d\n", pReq->TargetID)); + } +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * If no Target, bus reset on 1st I/O. Set the flag to + * prevent any future negotiations to this device. + */ +static void mptscsih_no_negotiate(MPT_SCSI_HOST *hd, int target_id) +{ + + if ((hd->Targets) && (hd->Targets[target_id] == NULL)) + hd->ioc->spi_data.dvStatus[target_id] |= MPT_SCSICFG_BLK_NEGO; + + return; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * SCSI Config Page functionality ... + */ +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* mptscsih_setDevicePage1Flags - add Requested and Configuration fields flags + * based on width, factor and offset parameters. + * @width: bus width + * @factor: sync factor + * @offset: sync offset + * @requestedPtr: pointer to requested values (updated) + * @configurationPtr: pointer to configuration values (updated) + * @flags: flags to block WDTR or SDTR negotiation + * + * Return: None. + * + * Remark: Called by writeSDP1 and _dv_params + */ +static void +mptscsih_setDevicePage1Flags (u8 width, u8 factor, u8 offset, int *requestedPtr, int *configurationPtr, u8 flags) +{ + u8 nowide = flags & MPT_TARGET_NO_NEGO_WIDE; + u8 nosync = flags & MPT_TARGET_NO_NEGO_SYNC; + + *configurationPtr = 0; + *requestedPtr = width ? MPI_SCSIDEVPAGE1_RP_WIDE : 0; + *requestedPtr |= (offset << 16) | (factor << 8); + + if (width && offset && !nowide && !nosync) { + if (factor < MPT_ULTRA160) { + *requestedPtr |= (MPI_SCSIDEVPAGE1_RP_IU + MPI_SCSIDEVPAGE1_RP_DT); + if ((flags & MPT_TARGET_NO_NEGO_QAS) == 0) + *requestedPtr |= MPI_SCSIDEVPAGE1_RP_QAS; + if (flags & MPT_TAPE_NEGO_IDP) + *requestedPtr |= 0x08000000; + } else if (factor < MPT_ULTRA2) { + *requestedPtr |= MPI_SCSIDEVPAGE1_RP_DT; + } + } + + if (nowide) + *configurationPtr |= MPI_SCSIDEVPAGE1_CONF_WDTR_DISALLOWED; + + if (nosync) + *configurationPtr |= MPI_SCSIDEVPAGE1_CONF_SDTR_DISALLOWED; + + return; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* mptscsih_writeSDP1 - write SCSI Device Page 1 + * @hd: Pointer to a SCSI Host Strucutre + * @portnum: IOC port number + * @target_id: writeSDP1 for single ID + * @flags: MPT_SCSICFG_ALL_IDS, MPT_SCSICFG_USE_NVRAM, MPT_SCSICFG_BLK_NEGO + * + * Return: -EFAULT if read of config page header fails + * or 0 if success. + * + * Remark: If a target has been found, the settings from the + * target structure are used, else the device is set + * to async/narrow. + * + * Remark: Called during init and after a FW reload. + * Remark: We do not wait for a return, write pages sequentially. + */ +static int +mptscsih_writeSDP1(MPT_SCSI_HOST *hd, int portnum, int target_id, int flags) +{ + MPT_ADAPTER *ioc = hd->ioc; + Config_t *pReq; + SCSIDevicePage1_t *pData; + VirtDevice *pTarget; + MPT_FRAME_HDR *mf; + dma_addr_t dataDma; + u16 req_idx; + u32 frameOffset; + u32 requested, configuration, flagsLength; + int ii, nvram; + int id = 0, maxid = 0; + u8 width; + u8 factor; + u8 offset; + u8 bus = 0; + u8 negoFlags; + u8 maxwidth, maxoffset, maxfactor; + + if (ioc->spi_data.sdp1length == 0) + return 0; + + if (flags & MPT_SCSICFG_ALL_IDS) { + id = 0; + maxid = ioc->sh->max_id - 1; + } else if (ioc->sh) { + id = target_id; + maxid = min_t(int, id, ioc->sh->max_id - 1); + } + + for (; id <= maxid; id++) { + + if (id == ioc->pfacts[portnum].PortSCSIID) + continue; + + /* Use NVRAM to get adapter and target maximums + * Data over-riden by target structure information, if present + */ + maxwidth = ioc->spi_data.maxBusWidth; + maxoffset = ioc->spi_data.maxSyncOffset; + maxfactor = ioc->spi_data.minSyncFactor; + if (ioc->spi_data.nvram && (ioc->spi_data.nvram[id] != MPT_HOST_NVRAM_INVALID)) { + nvram = ioc->spi_data.nvram[id]; + + if (maxwidth) + maxwidth = nvram & MPT_NVRAM_WIDE_DISABLE ? 0 : 1; + + if (maxoffset > 0) { + maxfactor = (nvram & MPT_NVRAM_SYNC_MASK) >> 8; + if (maxfactor == 0) { + /* Key for async */ + maxfactor = MPT_ASYNC; + maxoffset = 0; + } else if (maxfactor < ioc->spi_data.minSyncFactor) { + maxfactor = ioc->spi_data.minSyncFactor; + } + } else + maxfactor = MPT_ASYNC; + } + + /* Set the negotiation flags. + */ + negoFlags = ioc->spi_data.noQas; + if (!maxwidth) + negoFlags |= MPT_TARGET_NO_NEGO_WIDE; + + if (!maxoffset) + negoFlags |= MPT_TARGET_NO_NEGO_SYNC; + + if (flags & MPT_SCSICFG_USE_NVRAM) { + width = maxwidth; + factor = maxfactor; + offset = maxoffset; + } else { + width = 0; + factor = MPT_ASYNC; + offset = 0; + //negoFlags = 0; + //negoFlags = MPT_TARGET_NO_NEGO_SYNC; + } + + /* If id is not a raid volume, get the updated + * transmission settings from the target structure. + */ + if (hd->Targets && (pTarget = hd->Targets[id]) && !pTarget->raidVolume) { + width = pTarget->maxWidth; + factor = pTarget->minSyncFactor; + offset = pTarget->maxOffset; + negoFlags = pTarget->negoFlags; + } + +#ifdef MPTSCSIH_ENABLE_DOMAIN_VALIDATION + /* Force to async and narrow if DV has not been executed + * for this ID + */ + if ((hd->ioc->spi_data.dvStatus[id] & MPT_SCSICFG_DV_NOT_DONE) != 0) { + width = 0; + factor = MPT_ASYNC; + offset = 0; + } +#endif + + if (flags & MPT_SCSICFG_BLK_NEGO) + negoFlags = MPT_TARGET_NO_NEGO_WIDE | MPT_TARGET_NO_NEGO_SYNC; + + mptscsih_setDevicePage1Flags(width, factor, offset, + &requested, &configuration, negoFlags); + dnegoprintk(("writeSDP1: id=%d width=%d factor=%x offset=%x negoFlags=%x request=%x config=%x\n", + target_id, width, factor, offset, negoFlags, requested, configuration)); + + /* Get a MF for this command. + */ + if ((mf = mpt_get_msg_frame(ScsiDoneCtx, ioc)) == NULL) { + dprintk((MYIOC_s_WARN_FMT "write SDP1: no msg frames!\n", + ioc->name)); + return -EAGAIN; + } + + ddvprintk((MYIOC_s_INFO_FMT "WriteSDP1 (mf=%p, id=%d, req=0x%x, cfg=0x%x)\n", + hd->ioc->name, mf, id, requested, configuration)); + + + /* Set the request and the data pointers. + * Request takes: 36 bytes (32 bit SGE) + * SCSI Device Page 1 requires 16 bytes + * 40 + 16 <= size of SCSI IO Request = 56 bytes + * and MF size >= 64 bytes. + * Place data at end of MF. + */ + pReq = (Config_t *)mf; + + req_idx = le16_to_cpu(mf->u.frame.hwhdr.msgctxu.fld.req_idx); + frameOffset = ioc->req_sz - sizeof(SCSIDevicePage1_t); + + pData = (SCSIDevicePage1_t *)((u8 *) mf + frameOffset); + dataDma = ioc->req_frames_dma + (req_idx * ioc->req_sz) + frameOffset; + + /* Complete the request frame (same for all requests). + */ + pReq->Action = MPI_CONFIG_ACTION_PAGE_WRITE_CURRENT; + pReq->Reserved = 0; + pReq->ChainOffset = 0; + pReq->Function = MPI_FUNCTION_CONFIG; + pReq->ExtPageLength = 0; + pReq->ExtPageType = 0; + pReq->MsgFlags = 0; + for (ii=0; ii < 8; ii++) { + pReq->Reserved2[ii] = 0; + } + pReq->Header.PageVersion = ioc->spi_data.sdp1version; + pReq->Header.PageLength = ioc->spi_data.sdp1length; + pReq->Header.PageNumber = 1; + pReq->Header.PageType = MPI_CONFIG_PAGETYPE_SCSI_DEVICE; + pReq->PageAddress = cpu_to_le32(id | (bus << 8 )); + + /* Add a SGE to the config request. + */ + flagsLength = MPT_SGE_FLAGS_SSIMPLE_WRITE | ioc->spi_data.sdp1length * 4; + + mpt_add_sge((char *)&pReq->PageBufferSGE, flagsLength, dataDma); + + /* Set up the common data portion + */ + pData->Header.PageVersion = pReq->Header.PageVersion; + pData->Header.PageLength = pReq->Header.PageLength; + pData->Header.PageNumber = pReq->Header.PageNumber; + pData->Header.PageType = pReq->Header.PageType; + pData->RequestedParameters = cpu_to_le32(requested); + pData->Reserved = 0; + pData->Configuration = cpu_to_le32(configuration); + + dprintk((MYIOC_s_INFO_FMT + "write SDP1: id %d pgaddr 0x%x req 0x%x config 0x%x\n", + ioc->name, id, (id | (bus<<8)), + requested, configuration)); + + mpt_put_msg_frame(ScsiDoneCtx, ioc, mf); + } + + return 0; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* mptscsih_writeIOCPage4 - write IOC Page 4 + * @hd: Pointer to a SCSI Host Structure + * @target_id: write IOC Page4 for this ID & Bus + * + * Return: -EAGAIN if unable to obtain a Message Frame + * or 0 if success. + * + * Remark: We do not wait for a return, write pages sequentially. + */ +static int +mptscsih_writeIOCPage4(MPT_SCSI_HOST *hd, int target_id, int bus) +{ + MPT_ADAPTER *ioc = hd->ioc; + Config_t *pReq; + IOCPage4_t *IOCPage4Ptr; + MPT_FRAME_HDR *mf; + dma_addr_t dataDma; + u16 req_idx; + u32 frameOffset; + u32 flagsLength; + int ii; + + /* Get a MF for this command. + */ + if ((mf = mpt_get_msg_frame(ScsiDoneCtx, ioc)) == NULL) { + dprintk((MYIOC_s_WARN_FMT "writeIOCPage4 : no msg frames!\n", + ioc->name)); + return -EAGAIN; + } + + /* Set the request and the data pointers. + * Place data at end of MF. + */ + pReq = (Config_t *)mf; + + req_idx = le16_to_cpu(mf->u.frame.hwhdr.msgctxu.fld.req_idx); + frameOffset = ioc->req_sz - sizeof(IOCPage4_t); + + /* Complete the request frame (same for all requests). + */ + pReq->Action = MPI_CONFIG_ACTION_PAGE_WRITE_CURRENT; + pReq->Reserved = 0; + pReq->ChainOffset = 0; + pReq->Function = MPI_FUNCTION_CONFIG; + pReq->ExtPageLength = 0; + pReq->ExtPageType = 0; + pReq->MsgFlags = 0; + for (ii=0; ii < 8; ii++) { + pReq->Reserved2[ii] = 0; + } + + IOCPage4Ptr = ioc->spi_data.pIocPg4; + dataDma = ioc->spi_data.IocPg4_dma; + ii = IOCPage4Ptr->ActiveSEP++; + IOCPage4Ptr->SEP[ii].SEPTargetID = target_id; + IOCPage4Ptr->SEP[ii].SEPBus = bus; + pReq->Header = IOCPage4Ptr->Header; + pReq->PageAddress = cpu_to_le32(target_id | (bus << 8 )); + + /* Add a SGE to the config request. + */ + flagsLength = MPT_SGE_FLAGS_SSIMPLE_WRITE | + (IOCPage4Ptr->Header.PageLength + ii) * 4; + + mpt_add_sge((char *)&pReq->PageBufferSGE, flagsLength, dataDma); + + dinitprintk((MYIOC_s_INFO_FMT + "writeIOCPage4: MaxSEP=%d ActiveSEP=%d id=%d bus=%d\n", + ioc->name, IOCPage4Ptr->MaxSEP, IOCPage4Ptr->ActiveSEP, target_id, bus)); + + mpt_put_msg_frame(ScsiDoneCtx, ioc, mf); + + return 0; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * Bus Scan and Domain Validation functionality ... + */ + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* + * mptscsih_scandv_complete - Scan and DV callback routine registered + * to Fustion MPT (base) driver. + * + * @ioc: Pointer to MPT_ADAPTER structure + * @mf: Pointer to original MPT request frame + * @mr: 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. + * + * Remark: Sets a completion code and (possibly) saves sense data + * in the IOC member localReply structure. + * Used ONLY for DV and other internal commands. + */ +static int +mptscsih_scandv_complete(MPT_ADAPTER *ioc, MPT_FRAME_HDR *mf, MPT_FRAME_HDR *mr) +{ + MPT_SCSI_HOST *hd; + SCSIIORequest_t *pReq; + int completionCode; + u16 req_idx; + + if ((mf == NULL) || + (mf >= MPT_INDEX_2_MFPTR(ioc, ioc->req_depth))) { + printk(MYIOC_s_ERR_FMT + "ScanDvComplete, %s req frame ptr! (=%p)\n", + ioc->name, mf?"BAD":"NULL", (void *) mf); + goto wakeup; + } + + hd = (MPT_SCSI_HOST *) ioc->sh->hostdata; + del_timer(&hd->timer); + req_idx = le16_to_cpu(mf->u.frame.hwhdr.msgctxu.fld.req_idx); + hd->ScsiLookup[req_idx] = NULL; + pReq = (SCSIIORequest_t *) mf; + + if (mf != hd->cmdPtr) { + printk(MYIOC_s_WARN_FMT "ScanDvComplete (mf=%p, cmdPtr=%p, idx=%d)\n", + hd->ioc->name, (void *)mf, (void *) hd->cmdPtr, req_idx); + } + hd->cmdPtr = NULL; + + ddvprintk((MYIOC_s_INFO_FMT "ScanDvComplete (mf=%p,mr=%p,idx=%d)\n", + hd->ioc->name, mf, mr, req_idx)); + + hd->pLocal = &hd->localReply; + hd->pLocal->scsiStatus = 0; + + /* If target struct exists, clear sense valid flag. + */ + if (mr == NULL) { + completionCode = MPT_SCANDV_GOOD; + } else { + SCSIIOReply_t *pReply; + u16 status; + u8 scsi_status; + + pReply = (SCSIIOReply_t *) mr; + + status = le16_to_cpu(pReply->IOCStatus) & MPI_IOCSTATUS_MASK; + scsi_status = pReply->SCSIStatus; + + ddvtprintk((KERN_NOTICE " IOCStatus=%04xh, SCSIState=%02xh, SCSIStatus=%02xh, IOCLogInfo=%08xh\n", + status, pReply->SCSIState, scsi_status, + le32_to_cpu(pReply->IOCLogInfo))); + + switch(status) { + + case MPI_IOCSTATUS_SCSI_DEVICE_NOT_THERE: /* 0x0043 */ + completionCode = MPT_SCANDV_SELECTION_TIMEOUT; + break; + + case MPI_IOCSTATUS_SCSI_IO_DATA_ERROR: /* 0x0046 */ + case MPI_IOCSTATUS_SCSI_TASK_TERMINATED: /* 0x0048 */ + case MPI_IOCSTATUS_SCSI_IOC_TERMINATED: /* 0x004B */ + case MPI_IOCSTATUS_SCSI_EXT_TERMINATED: /* 0x004C */ + completionCode = MPT_SCANDV_DID_RESET; + break; + + case MPI_IOCSTATUS_SCSI_DATA_UNDERRUN: /* 0x0045 */ + case MPI_IOCSTATUS_SCSI_RECOVERED_ERROR: /* 0x0040 */ + case MPI_IOCSTATUS_SUCCESS: /* 0x0000 */ + if (pReply->Function == MPI_FUNCTION_CONFIG) { + ConfigReply_t *pr = (ConfigReply_t *)mr; + completionCode = MPT_SCANDV_GOOD; + hd->pLocal->header.PageVersion = pr->Header.PageVersion; + hd->pLocal->header.PageLength = pr->Header.PageLength; + hd->pLocal->header.PageNumber = pr->Header.PageNumber; + hd->pLocal->header.PageType = pr->Header.PageType; + + } else if (pReply->Function == MPI_FUNCTION_RAID_ACTION) { + /* If the RAID Volume request is successful, + * return GOOD, else indicate that + * some type of error occurred. + */ + MpiRaidActionReply_t *pr = (MpiRaidActionReply_t *)mr; + if (pr->ActionStatus == MPI_RAID_ACTION_ASTATUS_SUCCESS) + completionCode = MPT_SCANDV_GOOD; + else + completionCode = MPT_SCANDV_SOME_ERROR; + + } else if (pReply->SCSIState & MPI_SCSI_STATE_AUTOSENSE_VALID) { + u8 *sense_data; + int sz; + + /* save sense data in global structure + */ + completionCode = MPT_SCANDV_SENSE; + hd->pLocal->scsiStatus = scsi_status; + sense_data = ((u8 *)hd->ioc->sense_buf_pool + + (req_idx * MPT_SENSE_BUFFER_ALLOC)); + + sz = min_t(int, pReq->SenseBufferLength, + SCSI_STD_SENSE_BYTES); + memcpy(hd->pLocal->sense, sense_data, sz); + + ddvprintk((KERN_NOTICE " Check Condition, sense ptr %p\n", + sense_data)); + } else if (pReply->SCSIState & MPI_SCSI_STATE_AUTOSENSE_FAILED) { + if (pReq->CDB[0] == INQUIRY) + completionCode = MPT_SCANDV_ISSUE_SENSE; + else + completionCode = MPT_SCANDV_DID_RESET; + } + else if (pReply->SCSIState & MPI_SCSI_STATE_NO_SCSI_STATUS) + completionCode = MPT_SCANDV_DID_RESET; + else if (pReply->SCSIState & MPI_SCSI_STATE_TERMINATED) + completionCode = MPT_SCANDV_DID_RESET; + else { + completionCode = MPT_SCANDV_GOOD; + hd->pLocal->scsiStatus = scsi_status; + } + break; + + case MPI_IOCSTATUS_SCSI_PROTOCOL_ERROR: /* 0x0047 */ + if (pReply->SCSIState & MPI_SCSI_STATE_TERMINATED) + completionCode = MPT_SCANDV_DID_RESET; + else + completionCode = MPT_SCANDV_SOME_ERROR; + break; + + default: + completionCode = MPT_SCANDV_SOME_ERROR; + break; + + } /* switch(status) */ + + ddvtprintk((KERN_NOTICE " completionCode set to %08xh\n", + completionCode)); + } /* end of address reply case */ + + hd->pLocal->completion = completionCode; + + /* MF and RF are freed in mpt_interrupt + */ +wakeup: + /* Free Chain buffers (will never chain) in scan or dv */ + //mptscsih_freeChainBuffers(ioc, req_idx); + + /* + * Wake up the original calling thread + */ + scandv_wait_done = 1; + wake_up(&scandv_waitq); + + return 1; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* mptscsih_timer_expired - Call back for timer process. + * Used only for dv functionality. + * @data: Pointer to MPT_SCSI_HOST recast as an unsigned long + * + */ +static void mptscsih_timer_expired(unsigned long data) +{ + MPT_SCSI_HOST *hd = (MPT_SCSI_HOST *) data; + + ddvprintk((MYIOC_s_WARN_FMT "Timer Expired! Cmd %p\n", hd->ioc->name, hd->cmdPtr)); + + if (hd->cmdPtr) { + MPIHeader_t *cmd = (MPIHeader_t *)hd->cmdPtr; + + if (cmd->Function == MPI_FUNCTION_SCSI_IO_REQUEST) { + /* Desire to issue a task management request here. + * TM requests MUST be single threaded. + * If old eh code and no TM current, issue request. + * If new eh code, do nothing. Wait for OS cmd timeout + * for bus reset. + */ + ddvtprintk((MYIOC_s_NOTE_FMT "DV Cmd Timeout: NoOp\n", hd->ioc->name)); + } else { + /* Perform a FW reload */ + if (mpt_HardResetHandler(hd->ioc, NO_SLEEP) < 0) { + printk(MYIOC_s_WARN_FMT "Firmware Reload FAILED!\n", hd->ioc->name); + } + } + } else { + /* This should NEVER happen */ + printk(MYIOC_s_WARN_FMT "Null cmdPtr!!!!\n", hd->ioc->name); + } + + /* No more processing. + * TM call will generate an interrupt for SCSI TM Management. + * The FW will reply to all outstanding commands, callback will finish cleanup. + * Hard reset clean-up will free all resources. + */ + ddvprintk((MYIOC_s_WARN_FMT "Timer Expired Complete!\n", hd->ioc->name)); + + return; +} + +#ifdef MPTSCSIH_ENABLE_DOMAIN_VALIDATION +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* mptscsih_do_raid - Format and Issue a RAID volume request message. + * @hd: Pointer to scsi host structure + * @action: What do be done. + * @id: Logical target id. + * @bus: Target locations bus. + * + * Returns: < 0 on a fatal error + * 0 on success + * + * Remark: Wait to return until reply processed by the ISR. + */ +static int +mptscsih_do_raid(MPT_SCSI_HOST *hd, u8 action, INTERNAL_CMD *io) +{ + MpiRaidActionRequest_t *pReq; + MPT_FRAME_HDR *mf; + int in_isr; + + in_isr = in_interrupt(); + if (in_isr) { + dprintk((MYIOC_s_WARN_FMT "Internal raid request not allowed in ISR context!\n", + hd->ioc->name)); + return -EPERM; + } + + /* Get and Populate a free Frame + */ + if ((mf = mpt_get_msg_frame(ScsiScanDvCtx, hd->ioc)) == NULL) { + ddvprintk((MYIOC_s_WARN_FMT "_do_raid: no msg frames!\n", + hd->ioc->name)); + return -EAGAIN; + } + pReq = (MpiRaidActionRequest_t *)mf; + pReq->Action = action; + pReq->Reserved1 = 0; + pReq->ChainOffset = 0; + pReq->Function = MPI_FUNCTION_RAID_ACTION; + pReq->VolumeID = io->id; + pReq->VolumeBus = io->bus; + pReq->PhysDiskNum = io->physDiskNum; + pReq->MsgFlags = 0; + pReq->Reserved2 = 0; + pReq->ActionDataWord = 0; /* Reserved for this action */ + //pReq->ActionDataSGE = 0; + + mpt_add_sge((char *)&pReq->ActionDataSGE, + MPT_SGE_FLAGS_SSIMPLE_READ | 0, (dma_addr_t) -1); + + ddvprintk((MYIOC_s_INFO_FMT "RAID Volume action %x id %d\n", + hd->ioc->name, action, io->id)); + + hd->pLocal = NULL; + hd->timer.expires = jiffies + HZ*10; /* 10 second timeout */ + scandv_wait_done = 0; + + /* Save cmd pointer, for resource free if timeout or + * FW reload occurs + */ + hd->cmdPtr = mf; + + add_timer(&hd->timer); + mpt_put_msg_frame(ScsiScanDvCtx, hd->ioc, mf); + wait_event(scandv_waitq, scandv_wait_done); + + if ((hd->pLocal == NULL) || (hd->pLocal->completion != MPT_SCANDV_GOOD)) + return -1; + + return 0; +} +#endif /* ~MPTSCSIH_ENABLE_DOMAIN_VALIDATION */ + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_do_cmd - Do internal command. + * @hd: MPT_SCSI_HOST pointer + * @io: INTERNAL_CMD pointer. + * + * Issue the specified internally generated command and do command + * specific cleanup. For bus scan / DV only. + * NOTES: If command is Inquiry and status is good, + * initialize a target structure, save the data + * + * Remark: Single threaded access only. + * + * Return: + * < 0 if an illegal command or no resources + * + * 0 if good + * + * > 0 if command complete but some type of completion error. + */ +static int +mptscsih_do_cmd(MPT_SCSI_HOST *hd, INTERNAL_CMD *io) +{ + MPT_FRAME_HDR *mf; + SCSIIORequest_t *pScsiReq; + SCSIIORequest_t ReqCopy; + int my_idx, ii, dir; + int rc, cmdTimeout; + int in_isr; + char cmdLen; + char CDB[]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + char cmd = io->cmd; + + in_isr = in_interrupt(); + if (in_isr) { + dprintk((MYIOC_s_WARN_FMT "Internal SCSI IO request not allowed in ISR context!\n", + hd->ioc->name)); + return -EPERM; + } + + + /* Set command specific information + */ + switch (cmd) { + case INQUIRY: + cmdLen = 6; + dir = MPI_SCSIIO_CONTROL_READ; + CDB[0] = cmd; + CDB[4] = io->size; + cmdTimeout = 10; + break; + + case TEST_UNIT_READY: + cmdLen = 6; + dir = MPI_SCSIIO_CONTROL_READ; + cmdTimeout = 10; + break; + + case START_STOP: + cmdLen = 6; + dir = MPI_SCSIIO_CONTROL_READ; + CDB[0] = cmd; + CDB[4] = 1; /*Spin up the disk */ + cmdTimeout = 15; + break; + + case REQUEST_SENSE: + cmdLen = 6; + CDB[0] = cmd; + CDB[4] = io->size; + dir = MPI_SCSIIO_CONTROL_READ; + cmdTimeout = 10; + break; + + case READ_BUFFER: + cmdLen = 10; + dir = MPI_SCSIIO_CONTROL_READ; + CDB[0] = cmd; + if (io->flags & MPT_ICFLAG_ECHO) { + CDB[1] = 0x0A; + } else { + CDB[1] = 0x02; + } + + if (io->flags & MPT_ICFLAG_BUF_CAP) { + CDB[1] |= 0x01; + } + CDB[6] = (io->size >> 16) & 0xFF; + CDB[7] = (io->size >> 8) & 0xFF; + CDB[8] = io->size & 0xFF; + cmdTimeout = 10; + break; + + case WRITE_BUFFER: + cmdLen = 10; + dir = MPI_SCSIIO_CONTROL_WRITE; + CDB[0] = cmd; + if (io->flags & MPT_ICFLAG_ECHO) { + CDB[1] = 0x0A; + } else { + CDB[1] = 0x02; + } + CDB[6] = (io->size >> 16) & 0xFF; + CDB[7] = (io->size >> 8) & 0xFF; + CDB[8] = io->size & 0xFF; + cmdTimeout = 10; + break; + + case RESERVE: + cmdLen = 6; + dir = MPI_SCSIIO_CONTROL_READ; + CDB[0] = cmd; + cmdTimeout = 10; + break; + + case RELEASE: + cmdLen = 6; + dir = MPI_SCSIIO_CONTROL_READ; + CDB[0] = cmd; + cmdTimeout = 10; + break; + + case SYNCHRONIZE_CACHE: + cmdLen = 10; + dir = MPI_SCSIIO_CONTROL_READ; + CDB[0] = cmd; +// CDB[1] = 0x02; /* set immediate bit */ + cmdTimeout = 10; + break; + + default: + /* Error Case */ + return -EFAULT; + } + + /* Get and Populate a free Frame + */ + if ((mf = mpt_get_msg_frame(ScsiScanDvCtx, hd->ioc)) == NULL) { + ddvprintk((MYIOC_s_WARN_FMT "No msg frames!\n", + hd->ioc->name)); + return -EBUSY; + } + + pScsiReq = (SCSIIORequest_t *) mf; + + /* Get the request index */ + my_idx = le16_to_cpu(mf->u.frame.hwhdr.msgctxu.fld.req_idx); + ADD_INDEX_LOG(my_idx); /* for debug */ + + if (io->flags & MPT_ICFLAG_PHYS_DISK) { + pScsiReq->TargetID = io->physDiskNum; + pScsiReq->Bus = 0; + pScsiReq->ChainOffset = 0; + pScsiReq->Function = MPI_FUNCTION_RAID_SCSI_IO_PASSTHROUGH; + } else { + pScsiReq->TargetID = io->id; + pScsiReq->Bus = io->bus; + pScsiReq->ChainOffset = 0; + pScsiReq->Function = MPI_FUNCTION_SCSI_IO_REQUEST; + } + + pScsiReq->CDBLength = cmdLen; + pScsiReq->SenseBufferLength = MPT_SENSE_BUFFER_SIZE; + + pScsiReq->Reserved = 0; + + pScsiReq->MsgFlags = mpt_msg_flags(); + /* MsgContext set in mpt_get_msg_fram call */ + + for (ii=0; ii < 8; ii++) + pScsiReq->LUN[ii] = 0; + pScsiReq->LUN[1] = io->lun; + + if (io->flags & MPT_ICFLAG_TAGGED_CMD) + pScsiReq->Control = cpu_to_le32(dir | MPI_SCSIIO_CONTROL_SIMPLEQ); + else + pScsiReq->Control = cpu_to_le32(dir | MPI_SCSIIO_CONTROL_UNTAGGED); + + if (cmd == REQUEST_SENSE) { + pScsiReq->Control = cpu_to_le32(dir | MPI_SCSIIO_CONTROL_UNTAGGED); + ddvprintk((MYIOC_s_INFO_FMT "Untagged! 0x%2x\n", + hd->ioc->name, cmd)); + } + + for (ii=0; ii < 16; ii++) + pScsiReq->CDB[ii] = CDB[ii]; + + pScsiReq->DataLength = cpu_to_le32(io->size); + pScsiReq->SenseBufferLowAddr = cpu_to_le32(hd->ioc->sense_buf_low_dma + + (my_idx * MPT_SENSE_BUFFER_ALLOC)); + + ddvprintk((MYIOC_s_INFO_FMT "Sending Command 0x%x for (%d:%d:%d)\n", + hd->ioc->name, cmd, io->bus, io->id, io->lun)); + + if (dir == MPI_SCSIIO_CONTROL_READ) { + mpt_add_sge((char *) &pScsiReq->SGL, + MPT_SGE_FLAGS_SSIMPLE_READ | io->size, + io->data_dma); + } else { + mpt_add_sge((char *) &pScsiReq->SGL, + MPT_SGE_FLAGS_SSIMPLE_WRITE | io->size, + io->data_dma); + } + + /* The ISR will free the request frame, but we need + * the information to initialize the target. Duplicate. + */ + memcpy(&ReqCopy, pScsiReq, sizeof(SCSIIORequest_t)); + + /* Issue this command after: + * finish init + * add timer + * Wait until the reply has been received + * ScsiScanDvCtx callback function will + * set hd->pLocal; + * set scandv_wait_done and call wake_up + */ + hd->pLocal = NULL; + hd->timer.expires = jiffies + HZ*cmdTimeout; + scandv_wait_done = 0; + + /* Save cmd pointer, for resource free if timeout or + * FW reload occurs + */ + hd->cmdPtr = mf; + + add_timer(&hd->timer); + mpt_put_msg_frame(ScsiScanDvCtx, hd->ioc, mf); + wait_event(scandv_waitq, scandv_wait_done); + + if (hd->pLocal) { + rc = hd->pLocal->completion; + hd->pLocal->skip = 0; + + /* Always set fatal error codes in some cases. + */ + if (rc == MPT_SCANDV_SELECTION_TIMEOUT) + rc = -ENXIO; + else if (rc == MPT_SCANDV_SOME_ERROR) + rc = -rc; + } else { + rc = -EFAULT; + /* This should never happen. */ + ddvprintk((MYIOC_s_INFO_FMT "_do_cmd: Null pLocal!!!\n", + hd->ioc->name)); + } + + return rc; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_synchronize_cache - Send SYNCHRONIZE_CACHE to all disks. + * @hd: Pointer to MPT_SCSI_HOST structure + * @portnum: IOC port number + * + * Uses the ISR, but with special processing. + * MUST be single-threaded. + * + * Return: 0 on completion + */ +static int +mptscsih_synchronize_cache(MPT_SCSI_HOST *hd, int portnum) +{ + MPT_ADAPTER *ioc= hd->ioc; + VirtDevice *pTarget; + SCSIDevicePage1_t *pcfg1Data = NULL; + INTERNAL_CMD iocmd; + CONFIGPARMS cfg; + dma_addr_t cfg1_dma_addr = -1; + ConfigPageHeader_t header1; + int bus = 0; + int id = 0; + int lun; + int indexed_lun, lun_index; + int hostId = ioc->pfacts[portnum].PortSCSIID; + int max_id; + int requested, configuration, data; + int doConfig = 0; + u8 flags, factor; + + max_id = ioc->sh->max_id - 1; + + /* Following parameters will not change + * in this routine. + */ + iocmd.cmd = SYNCHRONIZE_CACHE; + iocmd.flags = 0; + iocmd.physDiskNum = -1; + iocmd.data = NULL; + iocmd.data_dma = -1; + iocmd.size = 0; + iocmd.rsvd = iocmd.rsvd2 = 0; + + /* No SCSI hosts + */ + if (hd->Targets == NULL) + return 0; + + /* Skip the host + */ + if (id == hostId) + id++; + + /* Write SDP1 for all SCSI devices + * Alloc memory and set up config buffer + */ + if (ioc->bus_type == SCSI) { + if (ioc->spi_data.sdp1length > 0) { + pcfg1Data = (SCSIDevicePage1_t *)pci_alloc_consistent(ioc->pcidev, + ioc->spi_data.sdp1length * 4, &cfg1_dma_addr); + + if (pcfg1Data != NULL) { + doConfig = 1; + header1.PageVersion = ioc->spi_data.sdp1version; + header1.PageLength = ioc->spi_data.sdp1length; + header1.PageNumber = 1; + header1.PageType = MPI_CONFIG_PAGETYPE_SCSI_DEVICE; + cfg.hdr = &header1; + cfg.physAddr = cfg1_dma_addr; + cfg.action = MPI_CONFIG_ACTION_PAGE_WRITE_CURRENT; + cfg.dir = 1; + cfg.timeout = 0; + } + } + } + + /* loop through all devices on this port + */ + while (bus < MPT_MAX_BUS) { + iocmd.bus = bus; + iocmd.id = id; + pTarget = hd->Targets[(int)id]; + + if (doConfig) { + + /* Set the negotiation flags */ + if (pTarget && (pTarget = hd->Targets[id]) && !pTarget->raidVolume) { + flags = pTarget->negoFlags; + } else { + flags = hd->ioc->spi_data.noQas; + if (hd->ioc->spi_data.nvram && (hd->ioc->spi_data.nvram[id] != MPT_HOST_NVRAM_INVALID)) { + data = hd->ioc->spi_data.nvram[id]; + + if (data & MPT_NVRAM_WIDE_DISABLE) + flags |= MPT_TARGET_NO_NEGO_WIDE; + + factor = (data & MPT_NVRAM_SYNC_MASK) >> MPT_NVRAM_SYNC_SHIFT; + if ((factor == 0) || (factor == MPT_ASYNC)) + flags |= MPT_TARGET_NO_NEGO_SYNC; + } + } + + /* Force to async, narrow */ + mptscsih_setDevicePage1Flags(0, MPT_ASYNC, 0, &requested, + &configuration, flags); + dnegoprintk(("syncronize cache: id=%d width=0 factor=MPT_ASYNC " + "offset=0 negoFlags=%x request=%x config=%x\n", + id, flags, requested, configuration)); + pcfg1Data->RequestedParameters = le32_to_cpu(requested); + pcfg1Data->Reserved = 0; + pcfg1Data->Configuration = le32_to_cpu(configuration); + cfg.pageAddr = (bus<<8) | id; + mpt_config(hd->ioc, &cfg); + } + + /* If target Ptr NULL or if this target is NOT a disk, skip. + */ + if ((pTarget) && (pTarget->tflags & MPT_TARGET_FLAGS_Q_YES)){ + for (lun=0; lun <= MPT_LAST_LUN; lun++) { + /* If LUN present, issue the command + */ + lun_index = (lun >> 5); /* 32 luns per lun_index */ + indexed_lun = (lun % 32); + if (pTarget->luns[lun_index] & (1<<indexed_lun)) { + iocmd.lun = lun; + (void) mptscsih_do_cmd(hd, &iocmd); + } + } + } + + /* get next relevant device */ + id++; + + if (id == hostId) + id++; + + if (id > max_id) { + id = 0; + bus++; + } + } + + if (pcfg1Data) { + pci_free_consistent(ioc->pcidev, header1.PageLength * 4, pcfg1Data, cfg1_dma_addr); + } + + return 0; +} + +#ifdef MPTSCSIH_ENABLE_DOMAIN_VALIDATION +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_domainValidation - Top level handler for domain validation. + * @hd: Pointer to MPT_SCSI_HOST structure. + * + * Uses the ISR, but with special processing. + * Called from schedule, should not be in interrupt mode. + * While thread alive, do dv for all devices needing dv + * + * Return: None. + */ +static void +mptscsih_domainValidation(void *arg) +{ + MPT_SCSI_HOST *hd; + MPT_ADAPTER *ioc; + unsigned long flags; + int id, maxid, dvStatus, did; + int ii, isPhysDisk; + + spin_lock_irqsave(&dvtaskQ_lock, flags); + dvtaskQ_active = 1; + if (dvtaskQ_release) { + dvtaskQ_active = 0; + spin_unlock_irqrestore(&dvtaskQ_lock, flags); + return; + } + spin_unlock_irqrestore(&dvtaskQ_lock, flags); + + /* For this ioc, loop through all devices and do dv to each device. + * When complete with this ioc, search through the ioc list, and + * for each scsi ioc found, do dv for all devices. Exit when no + * device needs dv. + */ + did = 1; + while (did) { + did = 0; + list_for_each_entry(ioc, &ioc_list, list) { + spin_lock_irqsave(&dvtaskQ_lock, flags); + if (dvtaskQ_release) { + dvtaskQ_active = 0; + spin_unlock_irqrestore(&dvtaskQ_lock, flags); + return; + } + spin_unlock_irqrestore(&dvtaskQ_lock, flags); + + msleep(250); + + /* DV only to SCSI adapters */ + if (ioc->bus_type != SCSI) + continue; + + /* Make sure everything looks ok */ + if (ioc->sh == NULL) + continue; + + hd = (MPT_SCSI_HOST *) ioc->sh->hostdata; + if (hd == NULL) + continue; + + if ((ioc->spi_data.forceDv & MPT_SCSICFG_RELOAD_IOC_PG3) != 0) { + mpt_read_ioc_pg_3(ioc); + if (ioc->spi_data.pIocPg3) { + Ioc3PhysDisk_t *pPDisk = ioc->spi_data.pIocPg3->PhysDisk; + int numPDisk = ioc->spi_data.pIocPg3->NumPhysDisks; + + while (numPDisk) { + if (ioc->spi_data.dvStatus[pPDisk->PhysDiskID] & MPT_SCSICFG_DV_NOT_DONE) + ioc->spi_data.dvStatus[pPDisk->PhysDiskID] |= MPT_SCSICFG_NEED_DV; + + pPDisk++; + numPDisk--; + } + } + ioc->spi_data.forceDv &= ~MPT_SCSICFG_RELOAD_IOC_PG3; + } + + maxid = min_t(int, ioc->sh->max_id, MPT_MAX_SCSI_DEVICES); + + for (id = 0; id < maxid; id++) { + spin_lock_irqsave(&dvtaskQ_lock, flags); + if (dvtaskQ_release) { + dvtaskQ_active = 0; + spin_unlock_irqrestore(&dvtaskQ_lock, flags); + return; + } + spin_unlock_irqrestore(&dvtaskQ_lock, flags); + dvStatus = hd->ioc->spi_data.dvStatus[id]; + + if (dvStatus & MPT_SCSICFG_NEED_DV) { + did++; + hd->ioc->spi_data.dvStatus[id] |= MPT_SCSICFG_DV_PENDING; + hd->ioc->spi_data.dvStatus[id] &= ~MPT_SCSICFG_NEED_DV; + + msleep(250); + + /* If hidden phys disk, block IO's to all + * raid volumes + * else, process normally + */ + isPhysDisk = mptscsih_is_phys_disk(ioc, id); + if (isPhysDisk) { + for (ii=0; ii < MPT_MAX_SCSI_DEVICES; ii++) { + if (hd->ioc->spi_data.isRaid & (1 << ii)) { + hd->ioc->spi_data.dvStatus[ii] |= MPT_SCSICFG_DV_PENDING; + } + } + } + + if (mptscsih_doDv(hd, 0, id) == 1) { + /* Untagged device was busy, try again + */ + hd->ioc->spi_data.dvStatus[id] |= MPT_SCSICFG_NEED_DV; + hd->ioc->spi_data.dvStatus[id] &= ~MPT_SCSICFG_DV_PENDING; + } else { + /* DV is complete. Clear flags. + */ + hd->ioc->spi_data.dvStatus[id] &= ~(MPT_SCSICFG_DV_NOT_DONE | MPT_SCSICFG_DV_PENDING); + } + + if (isPhysDisk) { + for (ii=0; ii < MPT_MAX_SCSI_DEVICES; ii++) { + if (hd->ioc->spi_data.isRaid & (1 << ii)) { + hd->ioc->spi_data.dvStatus[ii] &= ~MPT_SCSICFG_DV_PENDING; + } + } + } + + if (hd->ioc->spi_data.noQas) + mptscsih_qas_check(hd, id); + } + } + } + } + + spin_lock_irqsave(&dvtaskQ_lock, flags); + dvtaskQ_active = 0; + spin_unlock_irqrestore(&dvtaskQ_lock, flags); + + return; +} + +/* Search IOC page 3 to determine if this is hidden physical disk + */ +static int mptscsih_is_phys_disk(MPT_ADAPTER *ioc, int id) +{ + if (ioc->spi_data.pIocPg3) { + Ioc3PhysDisk_t *pPDisk = ioc->spi_data.pIocPg3->PhysDisk; + int numPDisk = ioc->spi_data.pIocPg3->NumPhysDisks; + + while (numPDisk) { + if (pPDisk->PhysDiskID == id) { + return 1; + } + pPDisk++; + numPDisk--; + } + } + return 0; +} + +/* Write SDP1 if no QAS has been enabled + */ +static void mptscsih_qas_check(MPT_SCSI_HOST *hd, int id) +{ + VirtDevice *pTarget; + int ii; + + if (hd->Targets == NULL) + return; + + for (ii=0; ii < MPT_MAX_SCSI_DEVICES; ii++) { + if (ii == id) + continue; + + if ((hd->ioc->spi_data.dvStatus[ii] & MPT_SCSICFG_DV_NOT_DONE) != 0) + continue; + + pTarget = hd->Targets[ii]; + + if ((pTarget != NULL) && (!pTarget->raidVolume)) { + if ((pTarget->negoFlags & hd->ioc->spi_data.noQas) == 0) { + pTarget->negoFlags |= hd->ioc->spi_data.noQas; + dnegoprintk(("writeSDP1: id=%d flags=0\n", id)); + mptscsih_writeSDP1(hd, 0, ii, 0); + } + } else { + if (mptscsih_is_phys_disk(hd->ioc, ii) == 1) { + dnegoprintk(("writeSDP1: id=%d SCSICFG_USE_NVRAM\n", id)); + mptscsih_writeSDP1(hd, 0, ii, MPT_SCSICFG_USE_NVRAM); + } + } + } + return; +} + + + +#define MPT_GET_NVRAM_VALS 0x01 +#define MPT_UPDATE_MAX 0x02 +#define MPT_SET_MAX 0x04 +#define MPT_SET_MIN 0x08 +#define MPT_FALLBACK 0x10 +#define MPT_SAVE 0x20 + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/** + * mptscsih_doDv - Perform domain validation to a target. + * @hd: Pointer to MPT_SCSI_HOST structure. + * @portnum: IOC port number. + * @target: Physical ID of this target + * + * Uses the ISR, but with special processing. + * MUST be single-threaded. + * Test will exit if target is at async & narrow. + * + * Return: None. + */ +static int +mptscsih_doDv(MPT_SCSI_HOST *hd, int bus_number, int id) +{ + MPT_ADAPTER *ioc = hd->ioc; + VirtDevice *pTarget; + SCSIDevicePage1_t *pcfg1Data; + SCSIDevicePage0_t *pcfg0Data; + u8 *pbuf1; + u8 *pbuf2; + u8 *pDvBuf; + dma_addr_t dvbuf_dma = -1; + dma_addr_t buf1_dma = -1; + dma_addr_t buf2_dma = -1; + dma_addr_t cfg1_dma_addr = -1; + dma_addr_t cfg0_dma_addr = -1; + ConfigPageHeader_t header1; + ConfigPageHeader_t header0; + DVPARAMETERS dv; + INTERNAL_CMD iocmd; + CONFIGPARMS cfg; + int dv_alloc = 0; + int rc, sz = 0; + int bufsize = 0; + int dataBufSize = 0; + int echoBufSize = 0; + int notDone; + int patt; + int repeat; + int retcode = 0; + int nfactor = MPT_ULTRA320; + char firstPass = 1; + char doFallback = 0; + char readPage0; + char bus, lun; + char inq0 = 0; + + if (ioc->spi_data.sdp1length == 0) + return 0; + + if (ioc->spi_data.sdp0length == 0) + return 0; + + /* If multiple buses are used, require that the initiator + * id be the same on all buses. + */ + if (id == ioc->pfacts[0].PortSCSIID) + return 0; + + lun = 0; + bus = (u8) bus_number; + ddvtprintk((MYIOC_s_NOTE_FMT + "DV started: bus=%d, id=%d dv @ %p\n", + ioc->name, bus, id, &dv)); + + /* Prep DV structure + */ + memset (&dv, 0, sizeof(DVPARAMETERS)); + dv.id = id; + + /* Populate tmax with the current maximum + * transfer parameters for this target. + * Exit if narrow and async. + */ + dv.cmd = MPT_GET_NVRAM_VALS; + mptscsih_dv_parms(hd, &dv, NULL); + + /* Prep SCSI IO structure + */ + iocmd.id = id; + iocmd.bus = bus; + iocmd.lun = lun; + iocmd.flags = 0; + iocmd.physDiskNum = -1; + iocmd.rsvd = iocmd.rsvd2 = 0; + + pTarget = hd->Targets[id]; + + /* Use tagged commands if possible. + */ + if (pTarget) { + if (pTarget->tflags & MPT_TARGET_FLAGS_Q_YES) + iocmd.flags |= MPT_ICFLAG_TAGGED_CMD; + else { + if (hd->ioc->facts.FWVersion.Word < 0x01000600) + return 0; + + if ((hd->ioc->facts.FWVersion.Word >= 0x01010000) && + (hd->ioc->facts.FWVersion.Word < 0x01010B00)) + return 0; + } + } + + /* Prep cfg structure + */ + cfg.pageAddr = (bus<<8) | id; + cfg.hdr = NULL; + + /* Prep SDP0 header + */ + header0.PageVersion = ioc->spi_data.sdp0version; + header0.PageLength = ioc->spi_data.sdp0length; + header0.PageNumber = 0; + header0.PageType = MPI_CONFIG_PAGETYPE_SCSI_DEVICE; + + /* Prep SDP1 header + */ + header1.PageVersion = ioc->spi_data.sdp1version; + header1.PageLength = ioc->spi_data.sdp1length; + header1.PageNumber = 1; + header1.PageType = MPI_CONFIG_PAGETYPE_SCSI_DEVICE; + + if (header0.PageLength & 1) + dv_alloc = (header0.PageLength * 4) + 4; + + dv_alloc += (2048 + (header1.PageLength * 4)); + + pDvBuf = pci_alloc_consistent(ioc->pcidev, dv_alloc, &dvbuf_dma); + if (pDvBuf == NULL) + return 0; + + sz = 0; + pbuf1 = (u8 *)pDvBuf; + buf1_dma = dvbuf_dma; + sz +=1024; + + pbuf2 = (u8 *) (pDvBuf + sz); + buf2_dma = dvbuf_dma + sz; + sz +=1024; + + pcfg0Data = (SCSIDevicePage0_t *) (pDvBuf + sz); + cfg0_dma_addr = dvbuf_dma + sz; + sz += header0.PageLength * 4; + + /* 8-byte alignment + */ + if (header0.PageLength & 1) + sz += 4; + + pcfg1Data = (SCSIDevicePage1_t *) (pDvBuf + sz); + cfg1_dma_addr = dvbuf_dma + sz; + + /* Skip this ID? Set cfg.hdr to force config page write + */ + { + ScsiCfgData *pspi_data = &hd->ioc->spi_data; + if (pspi_data->nvram && (pspi_data->nvram[id] != MPT_HOST_NVRAM_INVALID)) { + /* Set the factor from nvram */ + nfactor = (pspi_data->nvram[id] & MPT_NVRAM_SYNC_MASK) >> 8; + if (nfactor < pspi_data->minSyncFactor ) + nfactor = pspi_data->minSyncFactor; + + if (!(pspi_data->nvram[id] & MPT_NVRAM_ID_SCAN_ENABLE) || + (pspi_data->PortFlags == MPI_SCSIPORTPAGE2_PORT_FLAGS_OFF_DV) ) { + + ddvprintk((MYIOC_s_NOTE_FMT "DV Skipped: bus, id, lun (%d, %d, %d)\n", + ioc->name, bus, id, lun)); + + dv.cmd = MPT_SET_MAX; + mptscsih_dv_parms(hd, &dv, (void *)pcfg1Data); + cfg.hdr = &header1; + + /* Save the final negotiated settings to + * SCSI device page 1. + */ + cfg.physAddr = cfg1_dma_addr; + cfg.action = MPI_CONFIG_ACTION_PAGE_WRITE_CURRENT; + cfg.dir = 1; + mpt_config(hd->ioc, &cfg); + goto target_done; + } + } + } + + /* Finish iocmd inititialization - hidden or visible disk? */ + if (ioc->spi_data.pIocPg3) { + /* Search IOC page 3 for matching id + */ + Ioc3PhysDisk_t *pPDisk = ioc->spi_data.pIocPg3->PhysDisk; + int numPDisk = ioc->spi_data.pIocPg3->NumPhysDisks; + + while (numPDisk) { + if (pPDisk->PhysDiskID == id) { + /* match */ + iocmd.flags |= MPT_ICFLAG_PHYS_DISK; + iocmd.physDiskNum = pPDisk->PhysDiskNum; + + /* Quiesce the IM + */ + if (mptscsih_do_raid(hd, MPI_RAID_ACTION_QUIESCE_PHYS_IO, &iocmd) < 0) { + ddvprintk((MYIOC_s_ERR_FMT "RAID Queisce FAILED!\n", ioc->name)); + goto target_done; + } + break; + } + pPDisk++; + numPDisk--; + } + } + + /* RAID Volume ID's may double for a physical device. If RAID but + * not a physical ID as well, skip DV. + */ + if ((hd->ioc->spi_data.isRaid & (1 << id)) && !(iocmd.flags & MPT_ICFLAG_PHYS_DISK)) + goto target_done; + + + /* Basic Test. + * Async & Narrow - Inquiry + * Async & Narrow - Inquiry + * Maximum transfer rate - Inquiry + * Compare buffers: + * If compare, test complete. + * If miscompare and first pass, repeat + * If miscompare and not first pass, fall back and repeat + */ + hd->pLocal = NULL; + readPage0 = 0; + sz = SCSI_MAX_INQUIRY_BYTES; + rc = MPT_SCANDV_GOOD; + while (1) { + ddvprintk((MYIOC_s_NOTE_FMT "DV: Start Basic test on id=%d\n", ioc->name, id)); + retcode = 0; + dv.cmd = MPT_SET_MIN; + mptscsih_dv_parms(hd, &dv, (void *)pcfg1Data); + + cfg.hdr = &header1; + cfg.physAddr = cfg1_dma_addr; + cfg.action = MPI_CONFIG_ACTION_PAGE_WRITE_CURRENT; + cfg.dir = 1; + if (mpt_config(hd->ioc, &cfg) != 0) + goto target_done; + + /* Wide - narrow - wide workaround case + */ + if ((rc == MPT_SCANDV_ISSUE_SENSE) && dv.max.width) { + /* Send an untagged command to reset disk Qs corrupted + * when a parity error occurs on a Request Sense. + */ + if ((hd->ioc->facts.FWVersion.Word >= 0x01000600) || + ((hd->ioc->facts.FWVersion.Word >= 0x01010000) && + (hd->ioc->facts.FWVersion.Word < 0x01010B00)) ) { + + iocmd.cmd = REQUEST_SENSE; + iocmd.data_dma = buf1_dma; + iocmd.data = pbuf1; + iocmd.size = 0x12; + if (mptscsih_do_cmd(hd, &iocmd) < 0) + goto target_done; + else { + if (hd->pLocal == NULL) + goto target_done; + rc = hd->pLocal->completion; + if ((rc == MPT_SCANDV_GOOD) || (rc == MPT_SCANDV_SENSE)) { + dv.max.width = 0; + doFallback = 0; + } else + goto target_done; + } + } else + goto target_done; + } + + iocmd.cmd = INQUIRY; + iocmd.data_dma = buf1_dma; + iocmd.data = pbuf1; + iocmd.size = sz; + memset(pbuf1, 0x00, sz); + if (mptscsih_do_cmd(hd, &iocmd) < 0) + goto target_done; + else { + if (hd->pLocal == NULL) + goto target_done; + rc = hd->pLocal->completion; + if (rc == MPT_SCANDV_GOOD) { + if (hd->pLocal->scsiStatus == SAM_STAT_BUSY) { + if ((iocmd.flags & MPT_ICFLAG_TAGGED_CMD) == 0) + retcode = 1; + else + retcode = 0; + + goto target_done; + } + } else if (rc == MPT_SCANDV_SENSE) { + ; + } else { + /* If first command doesn't complete + * with a good status or with a check condition, + * exit. + */ + goto target_done; + } + } + + /* Reset the size for disks + */ + inq0 = (*pbuf1) & 0x1F; + if ((inq0 == 0) && pTarget && !pTarget->raidVolume) { + sz = 0x40; + iocmd.size = sz; + } + + /* Another GEM workaround. Check peripheral device type, + * if PROCESSOR, quit DV. + */ + if (inq0 == TYPE_PROCESSOR) { + mptscsih_initTarget(hd, + bus, + id, + lun, + pbuf1, + sz); + goto target_done; + } + + if (inq0 > 0x08) + goto target_done; + + if (mptscsih_do_cmd(hd, &iocmd) < 0) + goto target_done; + + if (sz == 0x40) { + if ((pTarget->maxWidth == 1) && (pTarget->maxOffset) && (nfactor < 0x0A) + && (pTarget->minSyncFactor > 0x09)) { + if ((pbuf1[56] & 0x04) == 0) + ; + else if ((pbuf1[56] & 0x01) == 1) { + pTarget->minSyncFactor = + nfactor > MPT_ULTRA320 ? nfactor : MPT_ULTRA320; + } else { + pTarget->minSyncFactor = + nfactor > MPT_ULTRA160 ? nfactor : MPT_ULTRA160; + } + + dv.max.factor = pTarget->minSyncFactor; + + if ((pbuf1[56] & 0x02) == 0) { + pTarget->negoFlags |= MPT_TARGET_NO_NEGO_QAS; + hd->ioc->spi_data.noQas = MPT_TARGET_NO_NEGO_QAS; + ddvprintk((MYIOC_s_NOTE_FMT + "DV: Start Basic noQas on id=%d due to pbuf1[56]=%x\n", + ioc->name, id, pbuf1[56])); + } + } + } + + if (doFallback) + dv.cmd = MPT_FALLBACK; + else + dv.cmd = MPT_SET_MAX; + + mptscsih_dv_parms(hd, &dv, (void *)pcfg1Data); + if (mpt_config(hd->ioc, &cfg) != 0) + goto target_done; + + if ((!dv.now.width) && (!dv.now.offset)) + goto target_done; + + iocmd.cmd = INQUIRY; + iocmd.data_dma = buf2_dma; + iocmd.data = pbuf2; + iocmd.size = sz; + memset(pbuf2, 0x00, sz); + if (mptscsih_do_cmd(hd, &iocmd) < 0) + goto target_done; + else if (hd->pLocal == NULL) + goto target_done; + else { + /* Save the return code. + * If this is the first pass, + * read SCSI Device Page 0 + * and update the target max parameters. + */ + rc = hd->pLocal->completion; + doFallback = 0; + if (rc == MPT_SCANDV_GOOD) { + if (!readPage0) { + u32 sdp0_info; + u32 sdp0_nego; + + cfg.hdr = &header0; + cfg.physAddr = cfg0_dma_addr; + cfg.action = MPI_CONFIG_ACTION_PAGE_READ_CURRENT; + cfg.dir = 0; + + if (mpt_config(hd->ioc, &cfg) != 0) + goto target_done; + + sdp0_info = le32_to_cpu(pcfg0Data->Information) & 0x0E; + sdp0_nego = (le32_to_cpu(pcfg0Data->NegotiatedParameters) & 0xFF00 ) >> 8; + + /* Quantum and Fujitsu workarounds. + * Quantum: PPR U320 -> PPR reply with Ultra2 and wide + * Fujitsu: PPR U320 -> Msg Reject and Ultra2 and wide + * Resetart with a request for U160. + */ + if ((dv.now.factor == MPT_ULTRA320) && (sdp0_nego == MPT_ULTRA2)) { + doFallback = 1; + } else { + dv.cmd = MPT_UPDATE_MAX; + mptscsih_dv_parms(hd, &dv, (void *)pcfg0Data); + /* Update the SCSI device page 1 area + */ + pcfg1Data->RequestedParameters = pcfg0Data->NegotiatedParameters; + readPage0 = 1; + } + } + + /* Quantum workaround. Restart this test will the fallback + * flag set. + */ + if (doFallback == 0) { + if (memcmp(pbuf1, pbuf2, sz) != 0) { + if (!firstPass) + doFallback = 1; + } else { + ddvprintk((MYIOC_s_NOTE_FMT + "DV:Inquiry compared id=%d, calling initTarget\n", ioc->name, id)); + hd->ioc->spi_data.dvStatus[id] &= ~MPT_SCSICFG_DV_NOT_DONE; + mptscsih_initTarget(hd, + bus, + id, + lun, + pbuf1, + sz); + break; /* test complete */ + } + } + + + } else if (rc == MPT_SCANDV_ISSUE_SENSE) + doFallback = 1; /* set fallback flag */ + else if ((rc == MPT_SCANDV_DID_RESET) || + (rc == MPT_SCANDV_SENSE) || + (rc == MPT_SCANDV_FALLBACK)) + doFallback = 1; /* set fallback flag */ + else + goto target_done; + + firstPass = 0; + } + } + ddvprintk((MYIOC_s_NOTE_FMT "DV: Basic test on id=%d completed OK.\n", ioc->name, id)); + + if (mpt_dv == 0) + goto target_done; + + inq0 = (*pbuf1) & 0x1F; + + /* Continue only for disks + */ + if (inq0 != 0) + goto target_done; + + if ( ioc->spi_data.PortFlags == MPI_SCSIPORTPAGE2_PORT_FLAGS_BASIC_DV_ONLY ) + goto target_done; + + /* Start the Enhanced Test. + * 0) issue TUR to clear out check conditions + * 1) read capacity of echo (regular) buffer + * 2) reserve device + * 3) do write-read-compare data pattern test + * 4) release + * 5) update nego parms to target struct + */ + cfg.hdr = &header1; + cfg.physAddr = cfg1_dma_addr; + cfg.action = MPI_CONFIG_ACTION_PAGE_WRITE_CURRENT; + cfg.dir = 1; + + iocmd.cmd = TEST_UNIT_READY; + iocmd.data_dma = -1; + iocmd.data = NULL; + iocmd.size = 0; + notDone = 1; + while (notDone) { + if (mptscsih_do_cmd(hd, &iocmd) < 0) + goto target_done; + + if (hd->pLocal == NULL) + goto target_done; + + rc = hd->pLocal->completion; + if (rc == MPT_SCANDV_GOOD) + notDone = 0; + else if (rc == MPT_SCANDV_SENSE) { + u8 skey = hd->pLocal->sense[2] & 0x0F; + u8 asc = hd->pLocal->sense[12]; + u8 ascq = hd->pLocal->sense[13]; + ddvprintk((MYIOC_s_INFO_FMT + "SenseKey:ASC:ASCQ = (%x:%02x:%02x)\n", + ioc->name, skey, asc, ascq)); + + if (skey == UNIT_ATTENTION) + notDone++; /* repeat */ + else if ((skey == NOT_READY) && + (asc == 0x04)&&(ascq == 0x01)) { + /* wait then repeat */ + mdelay (2000); + notDone++; + } else if ((skey == NOT_READY) && (asc == 0x3A)) { + /* no medium, try read test anyway */ + notDone = 0; + } else { + /* All other errors are fatal. + */ + ddvprintk((MYIOC_s_INFO_FMT "DV: fatal error.", + ioc->name)); + goto target_done; + } + } else + goto target_done; + } + + iocmd.cmd = READ_BUFFER; + iocmd.data_dma = buf1_dma; + iocmd.data = pbuf1; + iocmd.size = 4; + iocmd.flags |= MPT_ICFLAG_BUF_CAP; + + dataBufSize = 0; + echoBufSize = 0; + for (patt = 0; patt < 2; patt++) { + if (patt == 0) + iocmd.flags |= MPT_ICFLAG_ECHO; + else + iocmd.flags &= ~MPT_ICFLAG_ECHO; + + notDone = 1; + while (notDone) { + bufsize = 0; + + /* If not ready after 8 trials, + * give up on this device. + */ + if (notDone > 8) + goto target_done; + + if (mptscsih_do_cmd(hd, &iocmd) < 0) + goto target_done; + else if (hd->pLocal == NULL) + goto target_done; + else { + rc = hd->pLocal->completion; + ddvprintk(("ReadBuffer Comp Code %d", rc)); + ddvprintk((" buff: %0x %0x %0x %0x\n", + pbuf1[0], pbuf1[1], pbuf1[2], pbuf1[3])); + + if (rc == MPT_SCANDV_GOOD) { + notDone = 0; + if (iocmd.flags & MPT_ICFLAG_ECHO) { + bufsize = ((pbuf1[2] & 0x1F) <<8) | pbuf1[3]; + } else { + bufsize = pbuf1[1]<<16 | pbuf1[2]<<8 | pbuf1[3]; + } + } else if (rc == MPT_SCANDV_SENSE) { + u8 skey = hd->pLocal->sense[2] & 0x0F; + u8 asc = hd->pLocal->sense[12]; + u8 ascq = hd->pLocal->sense[13]; + ddvprintk((MYIOC_s_INFO_FMT + "SenseKey:ASC:ASCQ = (%x:%02x:%02x)\n", + ioc->name, skey, asc, ascq)); + if (skey == ILLEGAL_REQUEST) { + notDone = 0; + } else if (skey == UNIT_ATTENTION) { + notDone++; /* repeat */ + } else if ((skey == NOT_READY) && + (asc == 0x04)&&(ascq == 0x01)) { + /* wait then repeat */ + mdelay (2000); + notDone++; + } else { + /* All other errors are fatal. + */ + ddvprintk((MYIOC_s_INFO_FMT "DV: fatal error.", + ioc->name)); + goto target_done; + } + } else { + /* All other errors are fatal + */ + goto target_done; + } + } + } + + if (iocmd.flags & MPT_ICFLAG_ECHO) + echoBufSize = bufsize; + else + dataBufSize = bufsize; + } + sz = 0; + iocmd.flags &= ~MPT_ICFLAG_BUF_CAP; + + /* Use echo buffers if possible, + * Exit if both buffers are 0. + */ + if (echoBufSize > 0) { + iocmd.flags |= MPT_ICFLAG_ECHO; + if (dataBufSize > 0) + bufsize = min(echoBufSize, dataBufSize); + else + bufsize = echoBufSize; + } else if (dataBufSize == 0) + goto target_done; + + ddvprintk((MYIOC_s_INFO_FMT "%s Buffer Capacity %d\n", ioc->name, + (iocmd.flags & MPT_ICFLAG_ECHO) ? "Echo" : " ", bufsize)); + + /* Data buffers for write-read-compare test max 1K. + */ + sz = min(bufsize, 1024); + + /* --- loop ---- + * On first pass, always issue a reserve. + * On additional loops, only if a reset has occurred. + * iocmd.flags indicates if echo or regular buffer + */ + for (patt = 0; patt < 4; patt++) { + ddvprintk(("Pattern %d\n", patt)); + if ((iocmd.flags & MPT_ICFLAG_RESERVED) && (iocmd.flags & MPT_ICFLAG_DID_RESET)) { + iocmd.cmd = TEST_UNIT_READY; + iocmd.data_dma = -1; + iocmd.data = NULL; + iocmd.size = 0; + if (mptscsih_do_cmd(hd, &iocmd) < 0) + goto target_done; + + iocmd.cmd = RELEASE; + iocmd.data_dma = -1; + iocmd.data = NULL; + iocmd.size = 0; + if (mptscsih_do_cmd(hd, &iocmd) < 0) + goto target_done; + else if (hd->pLocal == NULL) + goto target_done; + else { + rc = hd->pLocal->completion; + ddvprintk(("Release rc %d\n", rc)); + if (rc == MPT_SCANDV_GOOD) + iocmd.flags &= ~MPT_ICFLAG_RESERVED; + else + goto target_done; + } + iocmd.flags &= ~MPT_ICFLAG_RESERVED; + } + iocmd.flags &= ~MPT_ICFLAG_DID_RESET; + + repeat = 5; + while (repeat && (!(iocmd.flags & MPT_ICFLAG_RESERVED))) { + iocmd.cmd = RESERVE; + iocmd.data_dma = -1; + iocmd.data = NULL; + iocmd.size = 0; + if (mptscsih_do_cmd(hd, &iocmd) < 0) + goto target_done; + else if (hd->pLocal == NULL) + goto target_done; + else { + rc = hd->pLocal->completion; + if (rc == MPT_SCANDV_GOOD) { + iocmd.flags |= MPT_ICFLAG_RESERVED; + } else if (rc == MPT_SCANDV_SENSE) { + /* Wait if coming ready + */ + u8 skey = hd->pLocal->sense[2] & 0x0F; + u8 asc = hd->pLocal->sense[12]; + u8 ascq = hd->pLocal->sense[13]; + ddvprintk((MYIOC_s_INFO_FMT + "DV: Reserve Failed: ", ioc->name)); + ddvprintk(("SenseKey:ASC:ASCQ = (%x:%02x:%02x)\n", + skey, asc, ascq)); + + if ((skey == NOT_READY) && (asc == 0x04)&& + (ascq == 0x01)) { + /* wait then repeat */ + mdelay (2000); + notDone++; + } else { + ddvprintk((MYIOC_s_INFO_FMT + "DV: Reserved Failed.", ioc->name)); + goto target_done; + } + } else { + ddvprintk((MYIOC_s_INFO_FMT "DV: Reserved Failed.", + ioc->name)); + goto target_done; + } + } + } + + mptscsih_fillbuf(pbuf1, sz, patt, 1); + iocmd.cmd = WRITE_BUFFER; + iocmd.data_dma = buf1_dma; + iocmd.data = pbuf1; + iocmd.size = sz; + if (mptscsih_do_cmd(hd, &iocmd) < 0) + goto target_done; + else if (hd->pLocal == NULL) + goto target_done; + else { + rc = hd->pLocal->completion; + if (rc == MPT_SCANDV_GOOD) + ; /* Issue read buffer */ + else if (rc == MPT_SCANDV_DID_RESET) { + /* If using echo buffers, reset to data buffers. + * Else do Fallback and restart + * this test (re-issue reserve + * because of bus reset). + */ + if ((iocmd.flags & MPT_ICFLAG_ECHO) && (dataBufSize >= bufsize)) { + iocmd.flags &= ~MPT_ICFLAG_ECHO; + } else { + dv.cmd = MPT_FALLBACK; + mptscsih_dv_parms(hd, &dv, (void *)pcfg1Data); + + if (mpt_config(hd->ioc, &cfg) != 0) + goto target_done; + + if ((!dv.now.width) && (!dv.now.offset)) + goto target_done; + } + + iocmd.flags |= MPT_ICFLAG_DID_RESET; + patt = -1; + continue; + } else if (rc == MPT_SCANDV_SENSE) { + /* Restart data test if UA, else quit. + */ + u8 skey = hd->pLocal->sense[2] & 0x0F; + ddvprintk((MYIOC_s_INFO_FMT + "SenseKey:ASC:ASCQ = (%x:%02x:%02x)\n", ioc->name, skey, + hd->pLocal->sense[12], hd->pLocal->sense[13])); + if (skey == UNIT_ATTENTION) { + patt = -1; + continue; + } else if (skey == ILLEGAL_REQUEST) { + if (iocmd.flags & MPT_ICFLAG_ECHO) { + if (dataBufSize >= bufsize) { + iocmd.flags &= ~MPT_ICFLAG_ECHO; + patt = -1; + continue; + } + } + goto target_done; + } + else + goto target_done; + } else { + /* fatal error */ + goto target_done; + } + } + + iocmd.cmd = READ_BUFFER; + iocmd.data_dma = buf2_dma; + iocmd.data = pbuf2; + iocmd.size = sz; + if (mptscsih_do_cmd(hd, &iocmd) < 0) + goto target_done; + else if (hd->pLocal == NULL) + goto target_done; + else { + rc = hd->pLocal->completion; + if (rc == MPT_SCANDV_GOOD) { + /* If buffers compare, + * go to next pattern, + * else, do a fallback and restart + * data transfer test. + */ + if (memcmp (pbuf1, pbuf2, sz) == 0) { + ; /* goto next pattern */ + } else { + /* Miscompare with Echo buffer, go to data buffer, + * if that buffer exists. + * Miscompare with Data buffer, check first 4 bytes, + * some devices return capacity. Exit in this case. + */ + if (iocmd.flags & MPT_ICFLAG_ECHO) { + if (dataBufSize >= bufsize) + iocmd.flags &= ~MPT_ICFLAG_ECHO; + else + goto target_done; + } else { + if (dataBufSize == (pbuf2[1]<<16 | pbuf2[2]<<8 | pbuf2[3])) { + /* Argh. Device returning wrong data. + * Quit DV for this device. + */ + goto target_done; + } + + /* Had an actual miscompare. Slow down.*/ + dv.cmd = MPT_FALLBACK; + mptscsih_dv_parms(hd, &dv, (void *)pcfg1Data); + + if (mpt_config(hd->ioc, &cfg) != 0) + goto target_done; + + if ((!dv.now.width) && (!dv.now.offset)) + goto target_done; + } + + patt = -1; + continue; + } + } else if (rc == MPT_SCANDV_DID_RESET) { + /* Do Fallback and restart + * this test (re-issue reserve + * because of bus reset). + */ + dv.cmd = MPT_FALLBACK; + mptscsih_dv_parms(hd, &dv, (void *)pcfg1Data); + + if (mpt_config(hd->ioc, &cfg) != 0) + goto target_done; + + if ((!dv.now.width) && (!dv.now.offset)) + goto target_done; + + iocmd.flags |= MPT_ICFLAG_DID_RESET; + patt = -1; + continue; + } else if (rc == MPT_SCANDV_SENSE) { + /* Restart data test if UA, else quit. + */ + u8 skey = hd->pLocal->sense[2] & 0x0F; + ddvprintk((MYIOC_s_INFO_FMT + "SenseKey:ASC:ASCQ = (%x:%02x:%02x)\n", ioc->name, skey, + hd->pLocal->sense[12], hd->pLocal->sense[13])); + if (skey == UNIT_ATTENTION) { + patt = -1; + continue; + } + else + goto target_done; + } else { + /* fatal error */ + goto target_done; + } + } + + } /* --- end of patt loop ---- */ + +target_done: + if (iocmd.flags & MPT_ICFLAG_RESERVED) { + iocmd.cmd = RELEASE; + iocmd.data_dma = -1; + iocmd.data = NULL; + iocmd.size = 0; + if (mptscsih_do_cmd(hd, &iocmd) < 0) + printk(MYIOC_s_INFO_FMT "DV: Release failed. id %d", + ioc->name, id); + else if (hd->pLocal) { + if (hd->pLocal->completion == MPT_SCANDV_GOOD) + iocmd.flags &= ~MPT_ICFLAG_RESERVED; + } else { + printk(MYIOC_s_INFO_FMT "DV: Release failed. id %d", + ioc->name, id); + } + } + + + /* Set if cfg1_dma_addr contents is valid + */ + if ((cfg.hdr != NULL) && (retcode == 0)){ + /* If disk, not U320, disable QAS + */ + if ((inq0 == 0) && (dv.now.factor > MPT_ULTRA320)) { + hd->ioc->spi_data.noQas = MPT_TARGET_NO_NEGO_QAS; + ddvprintk((MYIOC_s_NOTE_FMT + "noQas set due to id=%d has factor=%x\n", ioc->name, id, dv.now.factor)); + } + + dv.cmd = MPT_SAVE; + mptscsih_dv_parms(hd, &dv, (void *)pcfg1Data); + + /* Double writes to SDP1 can cause problems, + * skip save of the final negotiated settings to + * SCSI device page 1. + * + cfg.hdr = &header1; + cfg.physAddr = cfg1_dma_addr; + cfg.action = MPI_CONFIG_ACTION_PAGE_WRITE_CURRENT; + cfg.dir = 1; + mpt_config(hd->ioc, &cfg); + */ + } + + /* If this is a RAID Passthrough, enable internal IOs + */ + if (iocmd.flags & MPT_ICFLAG_PHYS_DISK) { + if (mptscsih_do_raid(hd, MPI_RAID_ACTION_ENABLE_PHYS_IO, &iocmd) < 0) + ddvprintk((MYIOC_s_ERR_FMT "RAID Enable FAILED!\n", ioc->name)); + } + + /* Done with the DV scan of the current target + */ + if (pDvBuf) + pci_free_consistent(ioc->pcidev, dv_alloc, pDvBuf, dvbuf_dma); + + ddvtprintk((MYIOC_s_INFO_FMT "DV Done id=%d\n", + ioc->name, id)); + + return retcode; +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* mptscsih_dv_parms - perform a variety of operations on the + * parameters used for negotiation. + * @hd: Pointer to a SCSI host. + * @dv: Pointer to a structure that contains the maximum and current + * negotiated parameters. + */ +static void +mptscsih_dv_parms(MPT_SCSI_HOST *hd, DVPARAMETERS *dv,void *pPage) +{ + VirtDevice *pTarget; + SCSIDevicePage0_t *pPage0; + SCSIDevicePage1_t *pPage1; + int val = 0, data, configuration; + u8 width = 0; + u8 offset = 0; + u8 factor = 0; + u8 negoFlags = 0; + u8 cmd = dv->cmd; + u8 id = dv->id; + + switch (cmd) { + case MPT_GET_NVRAM_VALS: + ddvprintk((MYIOC_s_NOTE_FMT "Getting NVRAM: ", + hd->ioc->name)); + /* Get the NVRAM values and save in tmax + * If not an LVD bus, the adapter minSyncFactor has been + * already throttled back. + */ + if ((hd->Targets)&&((pTarget = hd->Targets[(int)id]) != NULL) && !pTarget->raidVolume) { + width = pTarget->maxWidth; + offset = pTarget->maxOffset; + factor = pTarget->minSyncFactor; + negoFlags = pTarget->negoFlags; + } else { + if (hd->ioc->spi_data.nvram && (hd->ioc->spi_data.nvram[id] != MPT_HOST_NVRAM_INVALID)) { + data = hd->ioc->spi_data.nvram[id]; + width = data & MPT_NVRAM_WIDE_DISABLE ? 0 : 1; + if ((offset = hd->ioc->spi_data.maxSyncOffset) == 0) + factor = MPT_ASYNC; + else { + factor = (data & MPT_NVRAM_SYNC_MASK) >> MPT_NVRAM_SYNC_SHIFT; + if ((factor == 0) || (factor == MPT_ASYNC)){ + factor = MPT_ASYNC; + offset = 0; + } + } + } else { + width = MPT_NARROW; + offset = 0; + factor = MPT_ASYNC; + } + + /* Set the negotiation flags */ + negoFlags = hd->ioc->spi_data.noQas; + if (!width) + negoFlags |= MPT_TARGET_NO_NEGO_WIDE; + + if (!offset) + negoFlags |= MPT_TARGET_NO_NEGO_SYNC; + } + + /* limit by adapter capabilities */ + width = min(width, hd->ioc->spi_data.maxBusWidth); + offset = min(offset, hd->ioc->spi_data.maxSyncOffset); + factor = max(factor, hd->ioc->spi_data.minSyncFactor); + + /* Check Consistency */ + if (offset && (factor < MPT_ULTRA2) && !width) + factor = MPT_ULTRA2; + + dv->max.width = width; + dv->max.offset = offset; + dv->max.factor = factor; + dv->max.flags = negoFlags; + ddvprintk((" id=%d width=%d factor=%x offset=%x flags=%x\n", + id, width, factor, offset, negoFlags)); + break; + + case MPT_UPDATE_MAX: + ddvprintk((MYIOC_s_NOTE_FMT + "Updating with SDP0 Data: ", hd->ioc->name)); + /* Update tmax values with those from Device Page 0.*/ + pPage0 = (SCSIDevicePage0_t *) pPage; + if (pPage0) { + val = cpu_to_le32(pPage0->NegotiatedParameters); + dv->max.width = val & MPI_SCSIDEVPAGE0_NP_WIDE ? 1 : 0; + dv->max.offset = (val&MPI_SCSIDEVPAGE0_NP_NEG_SYNC_OFFSET_MASK) >> 16; + dv->max.factor = (val&MPI_SCSIDEVPAGE0_NP_NEG_SYNC_PERIOD_MASK) >> 8; + } + + dv->now.width = dv->max.width; + dv->now.offset = dv->max.offset; + dv->now.factor = dv->max.factor; + ddvprintk(("id=%d width=%d factor=%x offset=%x flags=%x\n", + id, dv->now.width, dv->now.factor, dv->now.offset, dv->now.flags)); + break; + + case MPT_SET_MAX: + ddvprintk((MYIOC_s_NOTE_FMT "Setting Max: ", + hd->ioc->name)); + /* Set current to the max values. Update the config page.*/ + dv->now.width = dv->max.width; + dv->now.offset = dv->max.offset; + dv->now.factor = dv->max.factor; + dv->now.flags = dv->max.flags; + + pPage1 = (SCSIDevicePage1_t *)pPage; + if (pPage1) { + mptscsih_setDevicePage1Flags (dv->now.width, dv->now.factor, + dv->now.offset, &val, &configuration, dv->now.flags); + dnegoprintk(("Setting Max: id=%d width=%d factor=%x offset=%x negoFlags=%x request=%x config=%x\n", + id, dv->now.width, dv->now.factor, dv->now.offset, dv->now.flags, val, configuration)); + pPage1->RequestedParameters = le32_to_cpu(val); + pPage1->Reserved = 0; + pPage1->Configuration = le32_to_cpu(configuration); + } + + ddvprintk(("id=%d width=%d factor=%x offset=%x flags=%x request=%x configuration=%x\n", + id, dv->now.width, dv->now.factor, dv->now.offset, dv->now.flags, val, configuration)); + break; + + case MPT_SET_MIN: + ddvprintk((MYIOC_s_NOTE_FMT "Setting Min: ", + hd->ioc->name)); + /* Set page to asynchronous and narrow + * Do not update now, breaks fallback routine. */ + width = MPT_NARROW; + offset = 0; + factor = MPT_ASYNC; + negoFlags = dv->max.flags; + + pPage1 = (SCSIDevicePage1_t *)pPage; + if (pPage1) { + mptscsih_setDevicePage1Flags (width, factor, + offset, &val, &configuration, negoFlags); + dnegoprintk(("Setting Min: id=%d width=%d factor=%x offset=%x negoFlags=%x request=%x config=%x\n", + id, width, factor, offset, negoFlags, val, configuration)); + pPage1->RequestedParameters = le32_to_cpu(val); + pPage1->Reserved = 0; + pPage1->Configuration = le32_to_cpu(configuration); + } + ddvprintk(("id=%d width=%d factor=%x offset=%x request=%x config=%x negoFlags=%x\n", + id, width, factor, offset, val, configuration, negoFlags)); + break; + + case MPT_FALLBACK: + ddvprintk((MYIOC_s_NOTE_FMT + "Fallback: Start: offset %d, factor %x, width %d \n", + hd->ioc->name, dv->now.offset, + dv->now.factor, dv->now.width)); + width = dv->now.width; + offset = dv->now.offset; + factor = dv->now.factor; + if ((offset) && (dv->max.width)) { + if (factor < MPT_ULTRA160) + factor = MPT_ULTRA160; + else if (factor < MPT_ULTRA2) { + factor = MPT_ULTRA2; + width = MPT_WIDE; + } else if ((factor == MPT_ULTRA2) && width) { + factor = MPT_ULTRA2; + width = MPT_NARROW; + } else if (factor < MPT_ULTRA) { + factor = MPT_ULTRA; + width = MPT_WIDE; + } else if ((factor == MPT_ULTRA) && width) { + width = MPT_NARROW; + } else if (factor < MPT_FAST) { + factor = MPT_FAST; + width = MPT_WIDE; + } else if ((factor == MPT_FAST) && width) { + factor = MPT_FAST; + width = MPT_NARROW; + } else if (factor < MPT_SCSI) { + factor = MPT_SCSI; + width = MPT_WIDE; + } else if ((factor == MPT_SCSI) && width) { + factor = MPT_SCSI; + width = MPT_NARROW; + } else { + factor = MPT_ASYNC; + offset = 0; + } + + } else if (offset) { + width = MPT_NARROW; + if (factor < MPT_ULTRA) + factor = MPT_ULTRA; + else if (factor < MPT_FAST) + factor = MPT_FAST; + else if (factor < MPT_SCSI) + factor = MPT_SCSI; + else { + factor = MPT_ASYNC; + offset = 0; + } + + } else { + width = MPT_NARROW; + factor = MPT_ASYNC; + } + dv->max.flags |= MPT_TARGET_NO_NEGO_QAS; + dv->max.flags &= ~MPT_TAPE_NEGO_IDP; + + dv->now.width = width; + dv->now.offset = offset; + dv->now.factor = factor; + dv->now.flags = dv->max.flags; + + pPage1 = (SCSIDevicePage1_t *)pPage; + if (pPage1) { + mptscsih_setDevicePage1Flags (width, factor, offset, &val, + &configuration, dv->now.flags); + dnegoprintk(("Finish: id=%d width=%d offset=%d factor=%x flags=%x request=%x config=%x\n", + id, width, offset, factor, dv->now.flags, val, configuration)); + + pPage1->RequestedParameters = le32_to_cpu(val); + pPage1->Reserved = 0; + pPage1->Configuration = le32_to_cpu(configuration); + } + + ddvprintk(("Finish: id=%d offset=%d factor=%x width=%d request=%x config=%x\n", + id, dv->now.offset, dv->now.factor, dv->now.width, val, configuration)); + break; + + case MPT_SAVE: + ddvprintk((MYIOC_s_NOTE_FMT + "Saving to Target structure: ", hd->ioc->name)); + ddvprintk(("id=%d width=%x factor=%x offset=%d flags=%x\n", + id, dv->now.width, dv->now.factor, dv->now.offset, dv->now.flags)); + + /* Save these values to target structures + * or overwrite nvram (phys disks only). + */ + + if ((hd->Targets)&&((pTarget = hd->Targets[(int)id]) != NULL) && !pTarget->raidVolume ) { + pTarget->maxWidth = dv->now.width; + pTarget->maxOffset = dv->now.offset; + pTarget->minSyncFactor = dv->now.factor; + pTarget->negoFlags = dv->now.flags; + } else { + /* Preserv all flags, use + * read-modify-write algorithm + */ + if (hd->ioc->spi_data.nvram) { + data = hd->ioc->spi_data.nvram[id]; + + if (dv->now.width) + data &= ~MPT_NVRAM_WIDE_DISABLE; + else + data |= MPT_NVRAM_WIDE_DISABLE; + + if (!dv->now.offset) + factor = MPT_ASYNC; + + data &= ~MPT_NVRAM_SYNC_MASK; + data |= (dv->now.factor << MPT_NVRAM_SYNC_SHIFT) & MPT_NVRAM_SYNC_MASK; + + hd->ioc->spi_data.nvram[id] = data; + } + } + break; + } +} + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ +/* mptscsih_fillbuf - fill a buffer with a special data pattern + * cleanup. For bus scan only. + * + * @buffer: Pointer to data buffer to be filled. + * @size: Number of bytes to fill + * @index: Pattern index + * @width: bus width, 0 (8 bits) or 1 (16 bits) + */ +static void +mptscsih_fillbuf(char *buffer, int size, int index, int width) +{ + char *ptr = buffer; + int ii; + char byte; + short val; + + switch (index) { + case 0: + + if (width) { + /* Pattern: 0000 FFFF 0000 FFFF + */ + for (ii=0; ii < size; ii++, ptr++) { + if (ii & 0x02) + *ptr = 0xFF; + else + *ptr = 0x00; + } + } else { + /* Pattern: 00 FF 00 FF + */ + for (ii=0; ii < size; ii++, ptr++) { + if (ii & 0x01) + *ptr = 0xFF; + else + *ptr = 0x00; + } + } + break; + + case 1: + if (width) { + /* Pattern: 5555 AAAA 5555 AAAA 5555 + */ + for (ii=0; ii < size; ii++, ptr++) { + if (ii & 0x02) + *ptr = 0xAA; + else + *ptr = 0x55; + } + } else { + /* Pattern: 55 AA 55 AA 55 + */ + for (ii=0; ii < size; ii++, ptr++) { + if (ii & 0x01) + *ptr = 0xAA; + else + *ptr = 0x55; + } + } + break; + + case 2: + /* Pattern: 00 01 02 03 04 05 + * ... FE FF 00 01.. + */ + for (ii=0; ii < size; ii++, ptr++) + *ptr = (char) ii; + break; + + case 3: + if (width) { + /* Wide Pattern: FFFE 0001 FFFD 0002 + * ... 4000 DFFF 8000 EFFF + */ + byte = 0; + for (ii=0; ii < size/2; ii++) { + /* Create the base pattern + */ + val = (1 << byte); + /* every 64 (0x40) bytes flip the pattern + * since we fill 2 bytes / iteration, + * test for ii = 0x20 + */ + if (ii & 0x20) + val = ~(val); + + if (ii & 0x01) { + *ptr = (char)( (val & 0xFF00) >> 8); + ptr++; + *ptr = (char)(val & 0xFF); + byte++; + byte &= 0x0F; + } else { + val = ~val; + *ptr = (char)( (val & 0xFF00) >> 8); + ptr++; + *ptr = (char)(val & 0xFF); + } + + ptr++; + } + } else { + /* Narrow Pattern: FE 01 FD 02 FB 04 + * .. 7F 80 01 FE 02 FD ... 80 7F + */ + byte = 0; + for (ii=0; ii < size; ii++, ptr++) { + /* Base pattern - first 32 bytes + */ + if (ii & 0x01) { + *ptr = (1 << byte); + byte++; + byte &= 0x07; + } else { + *ptr = (char) (~(1 << byte)); + } + + /* Flip the pattern every 32 bytes + */ + if (ii & 0x20) + *ptr = ~(*ptr); + } + } + break; + } +} +#endif /* ~MPTSCSIH_ENABLE_DOMAIN_VALIDATION */ + +/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/ + +module_init(mptscsih_init); +module_exit(mptscsih_exit); |