/*
* 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-2002 LSI Logic Corporation
* Original author: Steven J. Ralston
* (mailto:sjralston1@netscape.net)
* (mailto:Pam.Delaney@lsil.com)
*
* $Id: mptscsih.c,v 1.95 2002/06/20 13:28:17 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/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/kdev_t.h>
#include <linux/blkdev.h>
#include <linux/blk.h> /* for io_request_lock (spinlock) decl */
#include <linux/delay.h> /* for mdelay */
#include <linux/interrupt.h> /* needed for in_interrupt() proto */
#include <linux/reboot.h> /* notifier code */
#include "../../scsi/scsi.h"
#include "../../scsi/hosts.h"
#include "../../scsi/sd.h"
#include "mptbase.h"
#include "mptscsih.h"
#include "isense.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");
/* Set string for command line args from insmod */
#ifdef MODULE
char *mptscsih = 0;
MODULE_PARM(mptscsih, "s");
#endif
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
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_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 occured 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(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_io_direction(Scsi_Cmnd *cmd);
static int mptscsih_AddSGE(MPT_SCSI_HOST *hd, Scsi_Cmnd *SCpnt,
SCSIIORequest_t *pReq, int req_idx);
static int mptscsih_getFreeChainBuffer(MPT_SCSI_HOST *hd, int *retIndex);
static void mptscsih_freeChainBuffers(MPT_SCSI_HOST *hd, int req_idx);
static int mptscsih_initChainBuffers (MPT_SCSI_HOST *hd, int init);
static void copy_sense_data(Scsi_Cmnd *sc, MPT_SCSI_HOST *hd, MPT_FRAME_HDR *mf, SCSIIOReply_t *pScsiReply);
#ifndef MPT_SCSI_USE_NEW_EH
static void search_taskQ_for_cmd(Scsi_Cmnd *sc, MPT_SCSI_HOST *hd);
#else
static int mptscsih_tm_pending_wait(MPT_SCSI_HOST * hd);
#endif
static u32 SCPNT_TO_LOOKUP_IDX(Scsi_Cmnd *sc);
static MPT_FRAME_HDR *mptscsih_search_pendingQ(MPT_SCSI_HOST *hd, int scpnt_idx);
static void post_pendingQ_commands(MPT_SCSI_HOST *hd);
static int mptscsih_TMHandler(MPT_SCSI_HOST *hd, u8 type, u8 target, u8 lun, int ctx2abort, int sleepFlag);
static int mptscsih_IssueTaskMgmt(MPT_SCSI_HOST *hd, u8 type, u8 target, u8 lun, int ctx2abort, int sleepFlag);
static int mptscsih_ioc_reset(MPT_ADAPTER *ioc, int post_reset);
static int mptscsih_event_process(MPT_ADAPTER *ioc, EventNotificationReply_t *pEvReply);
static VirtDevice *mptscsih_initTarget(MPT_SCSI_HOST *hd, int bus_id, int target_id, u8 lun, char *data, int dlen);
void mptscsih_setTargetNegoParms(MPT_SCSI_HOST *hd, VirtDevice *target, char byte56);
static void clear_sense_flag(MPT_SCSI_HOST *hd, SCSIIORequest_t *pReq);
static void mptscsih_set_dvflags(MPT_SCSI_HOST *hd, SCSIIORequest_t *pReq, char *data);
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_scandv_complete(MPT_ADAPTER *ioc, MPT_FRAME_HDR *mf, MPT_FRAME_HDR *r);
static void mptscsih_timer_expired(unsigned long data);
static void mptscsih_taskmgmt_timeout(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);
#ifndef MPTSCSIH_DISABLE_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);
static void mptscsih_doDv(MPT_SCSI_HOST *hd, int portnum, 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
static int mptscsih_setup(char *str);
static int mptscsih_halt(struct notifier_block *nb, ulong event, void *buf);
/*
* Reboot Notification
*/
static struct notifier_block mptscsih_notifier = {
mptscsih_halt, NULL, 0
};
/*
* Private data...
*/
static int mpt_scsi_hosts = 0;
static atomic_t queue_depth;
static int ScsiDoneCtx = -1;
static int ScsiTaskCtx = -1;
static int ScsiScanDvCtx = -1; /* Used only for bus scan and dv */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,28)
static struct proc_dir_entry proc_mpt_scsihost =
{
low_ino: PROC_SCSI_MPT,
namelen: 8,
name: "mptscsih",
mode: S_IFDIR | S_IRUGO | S_IXUGO,
nlink: 2,
};
#endif
#define SNS_LEN(scp) sizeof((scp)->sense_buffer)
#ifndef MPT_SCSI_USE_NEW_EH
/*
* Stuff to handle single-threading SCSI TaskMgmt
* (abort/reset) requests...
*/
static spinlock_t mytaskQ_lock = SPIN_LOCK_UNLOCKED;
static int mytaskQ_bh_active = 0;
static struct tq_struct mptscsih_ptaskfoo;
static atomic_t mpt_taskQdepth;
#endif
#ifndef MPTSCSIH_DISABLE_DOMAIN_VALIDATION
/*
* Domain Validation task structure
*/
static spinlock_t dvtaskQ_lock = SPIN_LOCK_UNLOCKED;
static int dvtaskQ_active = 0;
static int dvtaskQ_release = 0;
static struct tq_struct mptscsih_dvTask;
#endif
/*
* Wait Queue setup
*/
static DECLARE_WAIT_QUEUE_HEAD (scandv_waitq);
static int scandv_wait_done = 1;
/* Driver default setup
*/
static struct mptscsih_driver_setup
driver_setup = MPTSCSIH_DRIVER_SETUP;
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* 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)
{
Scsi_Cmnd *sc;
MPT_SCSI_HOST *hd;
SCSIIORequest_t *pScsiReq;
SCSIIOReply_t *pScsiReply;
#ifndef MPT_SCSI_USE_NEW_EH
unsigned long flags;
#endif
u16 req_idx;
hd = (MPT_SCSI_HOST *) ioc->sh->hostdata;
if ((mf == NULL) ||
(mf >= MPT_INDEX_2_MFPTR(ioc, ioc->req_depth))) {
printk(MYIOC_s_ERR_FMT "%s req frame ptr! (=%p)!\n",
ioc->name, mf?"BAD":"NULL", (void *) mf);
/* return 1; CHECKME SteveR. Don't free. */
return 0;
}
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;
atomic_dec(&queue_depth);
/* writeSDP1 will use the ScsiDoneCtx
* There is no processing for the reply.
* Just return to the calling function.
*/
if (hdr->Function == MPI_FUNCTION_SCSI_IO_REQUEST)
printk(MYIOC_s_ERR_FMT "NULL ScsiCmd ptr!\n", ioc->name);
mptscsih_freeChainBuffers(hd, req_idx);
return 1;
}
dmfprintk((MYIOC_s_INFO_FMT "ScsiDone (mf=%p,mr=%p,sc=%p)\n",
ioc->name, mf, mr, sc));
atomic_dec(&queue_depth);
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 */
/* If regular Inquiry cmd - save inquiry data
*/
if (pScsiReq->CDB[0] == INQUIRY && !(pScsiReq->CDB[1] & 0x3)) {
int dlen;
dlen = le32_to_cpu(pScsiReq->DataLength);
if (dlen >= SCSI_STD_INQUIRY_BYTES) {
mptscsih_initTarget(hd,
hd->port,
sc->target,
pScsiReq->LUN[1],
sc->buffer,
dlen);
}
}
clear_sense_flag(hd, pScsiReq);
if (hd->is_spi)
mptscsih_set_dvflags(hd, pScsiReq, sc->buffer);
} else {
u32 xfer_cnt;
u16 status;
u8 scsi_state;
status = le16_to_cpu(pScsiReply->IOCStatus) & MPI_IOCSTATUS_MASK;
scsi_state = pScsiReply->SCSIState;
dprintk((KERN_NOTICE " Uh-Oh! (%d:%d:%d) mf=%p, mr=%p, sc=%p\n",
ioc->id, pScsiReq->TargetID, pScsiReq->LUN[1],
mf, mr, sc));
dprintk((KERN_NOTICE " IOCStatus=%04xh, SCSIState=%02xh"
", SCSIStatus=%02xh, IOCLogInfo=%08xh\n",
status, scsi_state, pScsiReply->SCSIStatus,
le32_to_cpu(pScsiReply->IOCLogInfo)));
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) {
dprintk((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 = STS_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;
break;
case MPI_IOCSTATUS_SCSI_TASK_TERMINATED: /* 0x0048 */
#ifndef MPT_SCSI_USE_NEW_EH
search_taskQ_for_cmd(sc, hd);
#endif
/* Linux handles an unsolicited DID_RESET better
* than an unsolicited DID_ABORT.
*/
sc->result = DID_RESET << 16;
/* GEM Workaround. */
if (hd->is_spi)
mptscsih_no_negotiate(hd, sc->target);
break;
case MPI_IOCSTATUS_SCSI_IOC_TERMINATED: /* 0x004B */
case MPI_IOCSTATUS_SCSI_EXT_TERMINATED: /* 0x004C */
#ifndef MPT_SCSI_USE_NEW_EH
search_taskQ_for_cmd(sc, hd);
#endif
sc->result = DID_RESET << 16;
/* GEM Workaround. */
if (hd->is_spi)
mptscsih_no_negotiate(hd, sc->target);
break;
case MPI_IOCSTATUS_SCSI_RESIDUAL_MISMATCH: /* 0x0049 */
case MPI_IOCSTATUS_SCSI_DATA_UNDERRUN: /* 0x0045 */
/*
* YIKES! I just discovered that SCSI IO which
* returns check condition, SenseKey=05 (ILLEGAL REQUEST)
* and ASC/ASCQ=94/01 (LSI Logic RAID vendor specific),
* comes down this path!
* Do upfront check for valid SenseData and give it
* precedence!
*/
sc->result = (DID_OK << 16) | pScsiReply->SCSIStatus;
clear_sense_flag(hd, pScsiReq);
if (pScsiReply->SCSIState & MPI_SCSI_STATE_AUTOSENSE_VALID) {
/* Have already saved the status and sense data
*/
;
} else if (pScsiReply->SCSIState & (MPI_SCSI_STATE_AUTOSENSE_FAILED | MPI_SCSI_STATE_NO_SCSI_STATUS)) {
/* What to do?
*/
sc->result = DID_SOFT_ERROR << 16;
}
else if (pScsiReply->SCSIState & MPI_SCSI_STATE_TERMINATED) {
/* Not real sure here either... */
sc->result = DID_RESET << 16;
}
/* Give report and update residual count.
*/
xfer_cnt = le32_to_cpu(pScsiReply->TransferCount);
dprintk((KERN_NOTICE " sc->underflow={report ERR if < %02xh bytes xfer'd}\n",
sc->underflow));
dprintk((KERN_NOTICE " ActBytesXferd=%02xh\n", xfer_cnt));
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,0)
sc->resid = sc->request_bufflen - xfer_cnt;
dprintk((KERN_NOTICE " SET sc->resid=%02xh\n", sc->resid));
#endif
/* Report Queue Full
*/
if (sc->result == MPI_SCSI_STATUS_TASK_SET_FULL)
mptscsih_report_queue_full(sc, pScsiReply, pScsiReq);
/* If regular Inquiry cmd and some data was transferred,
* save inquiry data
*/
if ( pScsiReq->CDB[0] == INQUIRY
&& !(pScsiReq->CDB[1] & 0x3)
&& xfer_cnt >= SCSI_STD_INQUIRY_BYTES
) {
mptscsih_initTarget(hd,
hd->port,
sc->target,
pScsiReq->LUN[1],
sc->buffer,
xfer_cnt);
}
if (hd->is_spi)
mptscsih_set_dvflags(hd, pScsiReq, sc->buffer);
break;
case MPI_IOCSTATUS_SCSI_RECOVERED_ERROR: /* 0x0040 */
case MPI_IOCSTATUS_SUCCESS: /* 0x0000 */
sc->result = (DID_OK << 16) | pScsiReply->SCSIStatus;
clear_sense_flag(hd, pScsiReq);
if (pScsiReply->SCSIState & MPI_SCSI_STATE_AUTOSENSE_VALID) {
/*
* If running agains 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);
#ifndef MPT_SCSI_USE_NEW_EH
/* ADDED 20011120 -sralston
* Scsi mid-layer (old_eh) doesn't seem to like it
* when RAID returns SCSIStatus=02 (CHECK CONDITION),
* SenseKey=01 (RECOVERED ERROR), ASC/ASCQ=95/01.
* Seems to be * treating this as a IO error:-(
*
* So just lie about it altogether here.
*
* NOTE: It still gets reported to syslog via
* mpt_ScsiHost_ErrorReport from copy_sense_data
* call far above.
*/
if ( pScsiReply->SCSIStatus == STS_CHECK_CONDITION
&& SD_Sense_Key(sc->sense_buffer) == SK_RECOVERED_ERROR
) {
sc->result = 0;
}
#endif
}
else if (pScsiReply->SCSIState &
(MPI_SCSI_STATE_AUTOSENSE_FAILED | MPI_SCSI_STATE_NO_SCSI_STATUS)
) {
/*
* What to do?
*/
sc->result = DID_SOFT_ERROR << 16;
}
else if (pScsiReply->SCSIState & MPI_SCSI_STATE_TERMINATED) {
/* Not real sure here either... */
sc->result = DID_RESET << 16;
}
else if (pScsiReply->SCSIState & 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
*/
/* If regular Inquiry cmd - save inquiry data
*/
xfer_cnt = le32_to_cpu(pScsiReply->TransferCount);
if ( sc->result == (DID_OK << 16)
&& pScsiReq->CDB[0] == INQUIRY
&& !(pScsiReq->CDB[1] & 0x3)
&& xfer_cnt >= SCSI_STD_INQUIRY_BYTES
) {
mptscsih_initTarget(hd,
hd->port,
sc->target,
pScsiReq->LUN[1],
sc->buffer,
xfer_cnt);
}
if (hd->is_spi)
mptscsih_set_dvflags(hd, pScsiReq, sc->buffer);
break;
case MPI_IOCSTATUS_SCSI_PROTOCOL_ERROR: /* 0x0047 */
if (pScsiReply->SCSIState & MPI_SCSI_STATE_TERMINATED) {
/* Not real sure here either... */
sc->result = DID_RESET << 16;
} else
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) */
dprintk((KERN_NOTICE " sc->result set to %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, scsi_to_pci_dma_dir(sc->sc_data_direction));
} else if (sc->request_bufflen) {
scPrivate *my_priv;
my_priv = (scPrivate *) &sc->SCp;
pci_unmap_single(ioc->pcidev, (dma_addr_t)(ulong)my_priv->p1,
sc->request_bufflen,
scsi_to_pci_dma_dir(sc->sc_data_direction));
}
hd->ScsiLookup[req_idx] = NULL;
sc->host_scribble = NULL; /* CHECKME! - Do we need to clear this??? */
MPT_HOST_LOCK(flags);
sc->scsi_done(sc); /* Issue the command callback */
MPT_HOST_UNLOCK(flags);
/* Free Chain buffers */
mptscsih_freeChainBuffers(hd, req_idx);
return 1;
}
#ifndef MPT_SCSI_USE_NEW_EH /* { */
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* search_taskQ - Search SCSI task mgmt request queue for specific
* request type.
* @remove: (Boolean) Should request be removed if found?
* @sc: Pointer to Scsi_Cmnd structure
* @task_type: Task type to search for
*
* Returns pointer to MPT request frame if found, or %NULL if request
* was not found.
*/
static MPT_FRAME_HDR *
search_taskQ(int remove, Scsi_Cmnd *sc, MPT_SCSI_HOST *hd, u8 task_type)
{
MPT_FRAME_HDR *mf = NULL;
unsigned long flags;
int count = 0;
int list_sz;
dprintk((KERN_INFO MYNAM ": search_taskQ(%d,sc=%p,%d) called\n",
remove, sc, task_type));
spin_lock_irqsave(&hd->ioc->FreeQlock, flags);
list_sz = hd->taskQcnt;
if (! Q_IS_EMPTY(&hd->taskQ)) {
mf = hd->taskQ.head;
do {
count++;
if (mf->u.frame.linkage.argp1 == sc &&
mf->u.frame.linkage.arg1 == task_type) {
if (remove) {
Q_DEL_ITEM(&mf->u.frame.linkage);
hd->taskQcnt--;
atomic_dec(&mpt_taskQdepth);
/* Don't save mf into nextmf because
* exit after command has been deleted.
*/
/* Place the MF back on the FreeQ */
Q_ADD_TAIL(&hd->ioc->FreeQ,
&mf->u.frame.linkage,
MPT_FRAME_HDR);
#ifdef MFCNT
hd->ioc->mfcnt--;
#endif
}
break;
}
} while ((mf = mf->u.frame.linkage.forw) != (MPT_FRAME_HDR*)&hd->taskQ);
if (mf == (MPT_FRAME_HDR*)&hd->taskQ) {
mf = NULL;
}
}
spin_unlock_irqrestore(&hd->ioc->FreeQlock, flags);
if (list_sz) {
dprintk((KERN_INFO " Results=%p (%sFOUND%s)!\n",
mf,
mf ? "" : "NOT_",
(mf && remove) ? "+REMOVED" : "" ));
dprintk((KERN_INFO " (searched thru %d of %d items on taskQ)\n",
count,
list_sz ));
}
return mf;
}
/*
* clean_taskQ - Clean the SCSI task mgmt request for
* this SCSI host instance.
* @hd: MPT_SCSI_HOST pointer
*
* Returns: None.
*/
static void
clean_taskQ(MPT_SCSI_HOST *hd)
{
MPT_FRAME_HDR *mf = NULL;
MPT_FRAME_HDR *nextmf = NULL;
MPT_ADAPTER *ioc = hd->ioc;
unsigned long flags;
dprintk((KERN_INFO MYNAM ": clean_taskQ called\n"));
spin_lock_irqsave(&ioc->FreeQlock, flags);
if (! Q_IS_EMPTY(&hd->taskQ)) {
mf = hd->taskQ.head;
do {
Q_DEL_ITEM(&mf->u.frame.linkage);
hd->taskQcnt--;
atomic_dec(&mpt_taskQdepth);
nextmf = mf->u.frame.linkage.forw;
/* Place the MF back on the FreeQ */
Q_ADD_TAIL(&ioc->FreeQ, &mf->u.frame.linkage,
MPT_FRAME_HDR);
#ifdef MFCNT
hd->ioc->mfcnt--;
#endif
} while ((mf = nextmf) != (MPT_FRAME_HDR*)&hd->taskQ);
}
spin_unlock_irqrestore(&ioc->FreeQlock, flags);
return;
}
/*
* search_taskQ_for_cmd - Search the SCSI task mgmt request queue for
* the specified command. If found, delete
* @hd: MPT_SCSI_HOST pointer
*
* Returns: None.
*/
static void
search_taskQ_for_cmd(Scsi_Cmnd *sc, MPT_SCSI_HOST *hd)
{
MPT_FRAME_HDR *mf = NULL;
unsigned long flags;
int count = 0;
dprintk((KERN_INFO MYNAM ": search_taskQ_for_cmd(sc=%p) called\n", sc));
spin_lock_irqsave(&hd->ioc->FreeQlock, flags);
if (! Q_IS_EMPTY(&hd->taskQ)) {
mf = hd->taskQ.head;
do {
count++;
if (mf->u.frame.linkage.argp1 == sc) {
Q_DEL_ITEM(&mf->u.frame.linkage);
hd->taskQcnt--;
atomic_dec(&mpt_taskQdepth);
dprintk((KERN_INFO MYNAM
": Cmd %p found! Deleting.\n", sc));
/* Don't save mf into nextmf because
* exit after command has been deleted.
*/
/* Place the MF back on the FreeQ */
Q_ADD_TAIL(&hd->ioc->FreeQ,
&mf->u.frame.linkage,
MPT_FRAME_HDR);
#ifdef MFCNT
hd->ioc->mfcnt--;
#endif
break;
}
} while ((mf = mf->u.frame.linkage.forw) != (MPT_FRAME_HDR*)&hd->taskQ);
}
spin_unlock_irqrestore(&hd->ioc->FreeQlock, flags);
return;
}
#endif /* } MPT_SCSI_USE_NEW_EH */
/*
* Flush all commands on the doneQ.
* Lock Q when deleting/adding members
* Lock io_request_lock for OS callback.
*/
static void
flush_doneQ(MPT_SCSI_HOST *hd)
{
MPT_DONE_Q *buffer;
Scsi_Cmnd *SCpnt;
unsigned long flags;
/* Flush the doneQ.
*/
dprintk((KERN_INFO MYNAM ": flush_doneQ called\n"));
while (1) {
spin_lock_irqsave(&hd->freedoneQlock, flags);
if (Q_IS_EMPTY(&hd->doneQ)) {
spin_unlock_irqrestore(&hd->freedoneQlock, flags);
break;
}
buffer = hd->doneQ.head;
/* Delete from Q
*/
Q_DEL_ITEM(buffer);
/* Set the Scsi_Cmnd pointer
*/
SCpnt = (Scsi_Cmnd *) buffer->argp;
buffer->argp = NULL;
/* Add to the freeQ
*/
Q_ADD_TAIL(&hd->freeQ.head, buffer, MPT_DONE_Q);
spin_unlock_irqrestore(&hd->freedoneQlock, flags);
/* Do the OS callback.
*/
MPT_HOST_LOCK(flags);
SCpnt->scsi_done(SCpnt);
MPT_HOST_UNLOCK(flags);
}
return;
}
/*
* Search the doneQ for a specific command. If found, delete from Q.
* Calling function will finish processing.
*/
static void
search_doneQ_for_cmd(MPT_SCSI_HOST *hd, Scsi_Cmnd *SCpnt)
{
unsigned long flags;
MPT_DONE_Q *buffer;
spin_lock_irqsave(&hd->freedoneQlock, flags);
if (!Q_IS_EMPTY(&hd->doneQ)) {
buffer = hd->doneQ.head;
do {
Scsi_Cmnd *sc = (Scsi_Cmnd *) buffer->argp;
if (SCpnt == sc) {
Q_DEL_ITEM(buffer);
SCpnt->result = sc->result;
/* Set the Scsi_Cmnd pointer
*/
buffer->argp = NULL;
/* Add to the freeQ
*/
Q_ADD_TAIL(&hd->freeQ.head, buffer, MPT_DONE_Q);
break;
}
} while ((buffer = buffer->forw) != (MPT_DONE_Q *) &hd->doneQ);
}
spin_unlock_irqrestore(&hd->freedoneQlock, flags);
return;
}
/*
* 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)
{
Scsi_Cmnd *SCpnt = NULL;
MPT_FRAME_HDR *mf = NULL;
int ii;
int max = hd->ioc->req_depth;
#ifndef MPT_SCSI_USE_NEW_EH
unsigned long flags;
#endif
dprintk((KERN_INFO MYNAM ": flush_ScsiLookup called\n"));
for (ii= 0; ii < max; ii++) {
if ((SCpnt = hd->ScsiLookup[ii]) != NULL) {
/* Command found.
*/
#ifndef MPT_SCSI_USE_NEW_EH
/* Search taskQ, if found, delete.
*/
search_taskQ_for_cmd(SCpnt, hd);
#endif
/* Search pendingQ, if found,
* delete from Q. If found, do not decrement
* queue_depth, command never posted.
*/
if (mptscsih_search_pendingQ(hd, ii) == NULL)
atomic_dec(&queue_depth);
/* Null ScsiLookup index
*/
hd->ScsiLookup[ii] = NULL;
mf = MPT_INDEX_2_MFPTR(hd->ioc, ii);
dmfprintk(( "flush: ScsiDone (mf=%p,sc=%p)\n",
mf, SCpnt));
/* Set status
* Do OS callback
* Free chain buffers
* Free message frame
*/
SCpnt->result = DID_RESET << 16;
SCpnt->host_scribble = NULL;
MPT_HOST_LOCK(flags);
SCpnt->scsi_done(SCpnt); /* Issue the command callback */
MPT_HOST_UNLOCK(flags);
/* Free Chain buffers */
mptscsih_freeChainBuffers(hd, ii);
/* Free Message frames */
mpt_free_msg_frame(ScsiDoneCtx, hd->ioc->id, mf);
}
}
return;
}
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* mptscsih_initChainBuffers - Allocate memory for and initialize
* chain buffers, chain buffer control arrays and spinlock.
* @hd: Pointer to MPT_SCSI_HOST structure
* @init: If set, initialize the spin lock.
*/
static int
mptscsih_initChainBuffers (MPT_SCSI_HOST *hd, int init)
{
MPT_FRAME_HDR *chain;
u8 *mem;
unsigned long flags;
int sz, ii, numChain;
/* Chain buffer allocations
* Allocate and initialize tracker structures
*/
if (hd->ioc->req_sz <= 64)
numChain = MPT_SG_REQ_64_SCALE * hd->ioc->req_depth;
else if (hd->ioc->req_sz <= 96)
numChain = MPT_SG_REQ_96_SCALE * hd->ioc->req_depth;
else
numChain = MPT_SG_REQ_128_SCALE * hd->ioc->req_depth;
sz = numChain * sizeof(int);
if (hd->ReqToChain == NULL) {
mem = kmalloc(sz, GFP_ATOMIC);
if (mem == NULL)
return -1;
hd->ReqToChain = (int *) mem;
} else {
mem = (u8 *) hd->ReqToChain;
}
memset(mem, 0xFF, sz);
if (hd->ChainToChain == NULL) {
mem = kmalloc(sz, GFP_ATOMIC);
if (mem == NULL)
return -1;
hd->ChainToChain = (int *) mem;
} else {
mem = (u8 *) hd->ChainToChain;
}
memset(mem, 0xFF, sz);
if (hd->ChainBuffer == NULL) {
/* Allocate free chain buffer pool
*/
sz = numChain * hd->ioc->req_sz;
mem = pci_alloc_consistent(hd->ioc->pcidev, sz, &hd->ChainBufferDMA);
if (mem == NULL)
return -1;
hd->ChainBuffer = (u8*)mem;
} else {
mem = (u8 *) hd->ChainBuffer;
}
memset(mem, 0, sz);
dprintk((KERN_INFO " ChainBuffer @ %p(%p), sz=%d\n",
hd->ChainBuffer, (void *)(ulong)hd->ChainBufferDMA, sz));
/* Initialize the free chain Q.
*/
if (init) {
spin_lock_init(&hd->FreeChainQlock);
}
spin_lock_irqsave (&hd->FreeChainQlock, flags);
Q_INIT(&hd->FreeChainQ, MPT_FRAME_HDR);
/* Post the chain buffers to the FreeChainQ.
*/
mem = (u8 *)hd->ChainBuffer;
for (ii=0; ii < numChain; ii++) {
chain = (MPT_FRAME_HDR *) mem;
Q_ADD_TAIL(&hd->FreeChainQ.head, &chain->u.frame.linkage, MPT_FRAME_HDR);
mem += hd->ioc->req_sz;
}
spin_unlock_irqrestore(&hd->FreeChainQlock, flags);
return 0;
}
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* 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(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->host != NULL && sc->host->hostdata != NULL)
ioc_str = ((MPT_SCSI_HOST *)sc->host->hostdata)->ioc->name;
printk(MYIOC_s_WARN_FMT "Device (%d:%d:%d) reported QUEUE_FULL!\n",
ioc_str, 0, sc->target, sc->lun);
last_queue_full = time;
}
}
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
static int BeenHereDoneThat = 0;
/* SCSI host fops start here... */
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/**
* mptscsih_detect - Register MPT adapter(s) as SCSI host(s) with
* linux scsi mid-layer.
* @tpnt: Pointer to Scsi_Host_Template structure
*
* (linux Scsi_Host_Template.detect routine)
*
* Returns number of SCSI host adapters that were successfully
* registered with the linux scsi mid-layer via the scsi_register()
* API call.
*/
int
mptscsih_detect(Scsi_Host_Template *tpnt)
{
struct Scsi_Host *sh = NULL;
MPT_SCSI_HOST *hd = NULL;
MPT_ADAPTER *this;
MPT_DONE_Q *freedoneQ;
unsigned long flags;
int sz, ii;
int numSGE = 0;
int scale;
u8 *mem;
if (! BeenHereDoneThat++) {
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);
#ifndef MPT_SCSI_USE_NEW_EH
spin_lock_init(&mytaskQ_lock);
#endif
if (mpt_event_register(ScsiDoneCtx, mptscsih_event_process) == 0) {
dprintk((KERN_INFO MYNAM ": Registered for IOC event notifications\n"));
} else {
/* FIXME! */
}
if (mpt_reset_register(ScsiDoneCtx, mptscsih_ioc_reset) == 0) {
dprintk((KERN_INFO MYNAM ": Registered for IOC reset notifications\n"));
} else {
/* FIXME! */
}
}
dprintk((KERN_INFO MYNAM ": mpt_scsih_detect()\n"));
#ifdef MODULE
/* Evaluate the command line arguments, if any */
if (mptscsih)
mptscsih_setup(mptscsih);
#endif
#ifndef MPT_SCSI_USE_NEW_EH
atomic_set(&mpt_taskQdepth, 0);
#endif
this = mpt_adapter_find_first();
while (this != NULL) {
int portnum;
for (portnum=0; portnum < this->facts.NumberOfPorts; portnum++) {
/* 20010215 -sralston
* Added sanity check on SCSI Initiator-mode enabled
* for this MPT adapter.
*/
if (!(this->pfacts[portnum].ProtocolFlags & MPI_PORTFACTS_PROTOCOL_INITIATOR)) {
printk(MYIOC_s_WARN_FMT "Skipping because SCSI Initiator mode is NOT enabled!\n",
this->name);
continue;
}
/* 20010202 -sralston
* Added sanity check on readiness of the MPT adapter.
*/
if (this->last_state != MPI_IOC_STATE_OPERATIONAL) {
printk(MYIOC_s_WARN_FMT "Skipping because it's not operational!\n",
this->name);
continue;
}
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,3,0)
tpnt->proc_dir = &proc_mpt_scsihost;
#endif
sh = scsi_register(tpnt, sizeof(MPT_SCSI_HOST));
if (sh != NULL) {
save_flags(flags);
cli();
sh->io_port = 0;
sh->n_io_port = 0;
sh->irq = 0;
/* 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 ((int)this->chip_type > (int)FC929)
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->this_id = this->pfacts[portnum].PortSCSIID;
/* OS entry to allow host drivers to force
* a queue depth on a per device basis.
*/
sh->select_queue_depths = mptscsih_select_queue_depths;
/* 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 = this->req_sz/(sizeof(dma_addr_t) + sizeof(u32));
if (sizeof(dma_addr_t) == sizeof(u64)) {
numSGE = (scale - 1) * (this->facts.MaxChainDepth-1) + scale +
(this->req_sz - 60) / (sizeof(dma_addr_t) + sizeof(u32));
} else {
numSGE = 1 + (scale - 1) * (this->facts.MaxChainDepth-1) + scale +
(this->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",
this->name, numSGE, sh->sg_tablesize));
sh->sg_tablesize = numSGE;
}
/* Set the pci device pointer in Scsi_Host structure.
*/
scsi_set_pci_device(sh, this->pcidev);
restore_flags(flags);
hd = (MPT_SCSI_HOST *) sh->hostdata;
hd->ioc = this;
if ((int)this->chip_type > (int)FC929)
hd->is_spi = 1;
if (DmpService &&
(this->chip_type == FC919 || this->chip_type == FC929))
hd->is_multipath = 1;
hd->port = 0; /* FIXME! */
/* SCSI needs Scsi_Cmnd lookup table!
* (with size equal to req_depth*PtrSz!)
*/
sz = hd->ioc->req_depth * sizeof(void *);
mem = kmalloc(sz, GFP_ATOMIC);
if (mem == NULL)
goto done;
memset(mem, 0, sz);
hd->ScsiLookup = (struct scsi_cmnd **) mem;
dprintk((MYIOC_s_INFO_FMT "ScsiLookup @ %p, sz=%d\n",
this->name, hd->ScsiLookup, sz));
if (mptscsih_initChainBuffers(hd, 1) < 0)
goto done;
/* Allocate memory for free and doneQ's
*/
sz = sh->can_queue * sizeof(MPT_DONE_Q);
mem = kmalloc(sz, GFP_ATOMIC);
if (mem == NULL)
goto done;
memset(mem, 0xFF, sz);
hd->memQ = mem;
/* Initialize the free, done and pending Qs.
*/
Q_INIT(&hd->freeQ, MPT_DONE_Q);
Q_INIT(&hd->doneQ, MPT_DONE_Q);
Q_INIT(&hd->pendingQ, MPT_DONE_Q);
spin_lock_init(&hd->freedoneQlock);
mem = hd->memQ;
for (ii=0; ii < sh->can_queue; ii++) {
freedoneQ = (MPT_DONE_Q *) mem;
Q_ADD_TAIL(&hd->freeQ.head, freedoneQ, MPT_DONE_Q);
mem += sizeof(MPT_DONE_Q);
}
/* Initialize this Scsi_Host
* internal task Q.
*/
Q_INIT(&hd->taskQ, MPT_FRAME_HDR);
hd->taskQcnt = 0;
/* 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)
goto done;
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;
#ifdef MPT_SCSI_USE_NEW_EH
hd->tmState = TM_STATE_NONE;
#endif
hd->resetPending = 0;
hd->abortSCpnt = NULL;
hd->tmPtr = NULL;
hd->numTMrequests = 0;
/* Clear the pointer used to store
* single-threaded commands, i.e., those
* issued during a bus scan, dv and
* configuration pages.
*/
hd->cmdPtr = NULL;
/* Attach the SCSI Host to the IOC structure
*/
this->sh = sh;
/* 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;
init_timer(&hd->TMtimer);
hd->TMtimer.data = (unsigned long) hd;
hd->TMtimer.function = mptscsih_taskmgmt_timeout;
hd->qtag_tick = jiffies;
/* Moved Earlier Pam D */
/* this->sh = sh; */
if (hd->is_spi) {
/* Update with the driver setup
* values.
*/
if (hd->ioc->spi_data.maxBusWidth > driver_setup.max_width)
hd->ioc->spi_data.maxBusWidth = driver_setup.max_width;
if (hd->ioc->spi_data.minSyncFactor < driver_setup.min_sync_fac)
hd->ioc->spi_data.minSyncFactor = driver_setup.min_sync_fac;
if (hd->ioc->spi_data.minSyncFactor == MPT_ASYNC)
hd->ioc->spi_data.maxSyncOffset = 0;
hd->negoNvram = 0;
#ifdef MPTSCSIH_DISABLE_DOMAIN_VALIDATION
hd->negoNvram = MPT_SCSICFG_USE_NVRAM;
#endif
if (driver_setup.dv == 0)
hd->negoNvram = MPT_SCSICFG_USE_NVRAM;
hd->ioc->spi_data.forceDv = 0;
for (ii=0; ii < MPT_MAX_SCSI_DEVICES; ii++)
hd->ioc->spi_data.dvStatus[ii] = MPT_SCSICFG_NEGOTIATE;
ddvprintk((MYIOC_s_INFO_FMT
"dv %x width %x factor %x \n",
hd->ioc->name, driver_setup.dv,
driver_setup.max_width,
driver_setup.min_sync_fac));
}
mpt_scsi_hosts++;
}
} /* for each adapter port */
this = mpt_adapter_find_next(this);
}
done:
if (mpt_scsi_hosts > 0)
register_reboot_notifier(&mptscsih_notifier);
return mpt_scsi_hosts;
}
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
static char *info_kbuf = NULL;
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/**
* mptscsih_release - Unregister SCSI host from linux scsi mid-layer
* @host: Pointer to Scsi_Host structure
*
* (linux Scsi_Host_Template.release routine)
* This routine releases all resources associated with the SCSI host
* adapter.
*
* Returns 0 for success.
*/
int
mptscsih_release(struct Scsi_Host *host)
{
MPT_SCSI_HOST *hd;
int count;
unsigned long flags;
hd = (MPT_SCSI_HOST *) host->hostdata;
#ifndef MPT_SCSI_USE_NEW_EH
#ifndef MPTSCSIH_DISABLE_DOMAIN_VALIDATION
spin_lock_irqsave(&dvtaskQ_lock, flags);
dvtaskQ_release = 1;
spin_unlock_irqrestore(&dvtaskQ_lock, flags);
#endif
count = 10 * HZ;
spin_lock_irqsave(&mytaskQ_lock, flags);
if (mytaskQ_bh_active) {
spin_unlock_irqrestore(&mytaskQ_lock, flags);
dprintk((KERN_INFO MYNAM ": Info: Zapping TaskMgmt thread!\n"));
clean_taskQ(hd);
while(mytaskQ_bh_active && --count) {
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(1);
}
} else {
spin_unlock_irqrestore(&mytaskQ_lock, flags);
}
if (!count)
printk(KERN_ERR MYNAM ": ERROR - TaskMgmt thread still active!\n");
#endif
#ifndef MPTSCSIH_DISABLE_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
unregister_reboot_notifier(&mptscsih_notifier);
if (hd != NULL) {
int sz1, sz2, sz3, sztarget=0;
int szchain = 0;
int szQ = 0;
int scale;
/* Synchronize disk caches
*/
(void) mptscsih_synchronize_cache(hd, 0);
sz1 = sz2 = sz3 = 0;
if (hd->ioc->req_sz <= 64)
scale = MPT_SG_REQ_64_SCALE;
else if (hd->ioc->req_sz <= 96)
scale = MPT_SG_REQ_96_SCALE;
else
scale = MPT_SG_REQ_128_SCALE;
if (hd->ScsiLookup != NULL) {
sz1 = hd->ioc->req_depth * sizeof(void *);
kfree(hd->ScsiLookup);
hd->ScsiLookup = NULL;
}
if (hd->ReqToChain != NULL) {
szchain += scale * hd->ioc->req_depth * sizeof(int);
kfree(hd->ReqToChain);
hd->ReqToChain = NULL;
}
if (hd->ChainToChain != NULL) {
szchain += scale * hd->ioc->req_depth * sizeof(int);
kfree(hd->ChainToChain);
hd->ChainToChain = NULL;
}
if (hd->ChainBuffer != NULL) {
sz2 = scale * hd->ioc->req_depth * hd->ioc->req_sz;
szchain += sz2;
pci_free_consistent(hd->ioc->pcidev, sz2,
hd->ChainBuffer, hd->ChainBufferDMA);
hd->ChainBuffer = NULL;
}
if (hd->memQ != NULL) {
szQ = host->can_queue * sizeof(MPT_DONE_Q);
kfree(hd->memQ);
hd->memQ = NULL;
}
if (hd->Targets != NULL) {
int max, ii;
/*
* Free any target structures that were allocated.
*/
if (hd->is_spi) {
max = MPT_MAX_SCSI_DEVICES;
} else {
max = MPT_MAX_FC_DEVICES;
}
for (ii=0; ii < max; ii++) {
if (hd->Targets[ii]) {
kfree(hd->Targets[ii]);
hd->Targets[ii] = NULL;
sztarget += sizeof(VirtDevice);
}
}
/*
* Free pointer array.
*/
sz3 = max * sizeof(void *);
kfree(hd->Targets);
hd->Targets = NULL;
}
dprintk((MYIOC_s_INFO_FMT "Free'd ScsiLookup (%d), chain (%d) and Target (%d+%d) memory\n",
hd->ioc->name, sz1, szchain, sz3, sztarget));
dprintk(("Free'd done and free Q (%d) memory\n", szQ));
}
/* NULL the Scsi_Host pointer
*/
hd->ioc->sh = NULL;
scsi_unregister(host);
if (mpt_scsi_hosts) {
if (--mpt_scsi_hosts == 0) {
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);
}
}
return 0;
}
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/**
* mptscsih_halt - Process the reboot notification
* @nb: Pointer to a struct notifier_block (ignored)
* @event: event (SYS_HALT, SYS_RESTART, SYS_POWER_OFF)
* @buf: Pointer to a data buffer (ignored)
*
* This routine called if a system shutdown or reboot is to occur.
*
* Return NOTIFY_DONE if this is something other than a reboot message.
* NOTIFY_OK if this is a reboot message.
*/
static int
mptscsih_halt(struct notifier_block *nb, ulong event, void *buf)
{
MPT_ADAPTER *ioc = NULL;
MPT_SCSI_HOST *hd = NULL;
/* Ignore all messages other than reboot message
*/
if ((event != SYS_RESTART) && (event != SYS_HALT)
&& (event != SYS_POWER_OFF))
return (NOTIFY_DONE);
for (ioc = mpt_adapter_find_first(); ioc != NULL; ioc = mpt_adapter_find_next(ioc)) {
/* Flush the cache of this adapter
*/
if (ioc->sh) {
hd = (MPT_SCSI_HOST *) ioc->sh->hostdata;
if (hd) {
mptscsih_synchronize_cache(hd, 0);
}
}
}
unregister_reboot_notifier(&mptscsih_notifier);
return NOTIFY_OK;
}
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/**
* 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.
*/
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';
mpt_print_ioc_summary(h->ioc, info_kbuf, &size, 0, 0);
info_kbuf[size-1] = '\0';
return info_kbuf;
}
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
static int max_qd = 1;
#if 0
static int index_log[128];
static int index_ent = 0;
static __inline__ void ADD_INDEX_LOG(int req_ent)
{
int i = index_ent++;
index_log[i & (128 - 1)] = req_ent;
}
#else
#define ADD_INDEX_LOG(req_ent) do { } while(0)
#endif
#ifdef DROP_TEST
#define DROP_IOC 1 /* IOC to force failures */
#define DROP_TARGET 3 /* Target ID to force failures */
#define DROP_THIS_CMD 10000 /* iteration to drop command */
static int dropCounter = 0;
static int dropTestOK = 0; /* num did good */
static int dropTestBad = 0; /* num did bad */
static int dropTestNum = 0; /* total = good + bad + incomplete */
static int numTotCmds = 0;
static MPT_FRAME_HDR *dropMfPtr = NULL;
static int numTMrequested = 0;
#endif
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/*
* mptscsih_put_msgframe - Wrapper routine to post message frame to F/W.
* @context: Call back context (ScsiDoneCtx, ScsiScanDvCtx)
* @id: IOC id number
* @mf: Pointer to message frame
*
* Handles the call to mptbase for posting request and queue depth
* tracking.
*
* Returns none.
*/
static void
mptscsih_put_msgframe(int context, int id, MPT_FRAME_HDR *mf)
{
/* Main banana... */
atomic_inc(&queue_depth);
if (atomic_read(&queue_depth) > max_qd) {
max_qd = atomic_read(&queue_depth);
dprintk((KERN_INFO MYNAM ": Queue depth now %d.\n", max_qd));
}
mpt_put_msg_frame(context, id, mf);
return;
}
/*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
/**
* 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)
*/
int
mptscsih_qcmd(Scsi_Cmnd *SCpnt, void (*done)(Scsi_Cmnd *))
{
MPT_SCSI_HOST *hd;
MPT_FRAME_HDR *mf;
SCSIIORequest_t *pScsiReq;
VirtDevice *pTarget;
MPT_DONE_Q *buffer = NULL;
unsigned long flags;
int target;
int lun;
int datadir;
u32 datalen;
u32 scsictl;
u32 scsidir;
u32 qtag;
u32 cmd_len;
int my_idx;
int ii;
int rc;
int did_errcode;
int issueCmd;
did_errcode = 0;
hd = (MPT_SCSI_HOST *) SCpnt->host->hostdata;
target = SCpnt->target;
lun = SCpnt->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));
/* 20000617 -sralston
* GRRRRR... Shouldn't have to do this but...
* Do explicit check for REQUEST_SENSE and cached SenseData.
* If yes, return cached SenseData.
*/
if (SCpnt->cmnd[0] == REQUEST_SENSE) {
u8 *dest = NULL;
int sz;
if (pTarget && (pTarget->tflags & MPT_TARGET_FLAGS_VALID_SENSE)) {
pTarget->tflags &= ~MPT_TARGET_FLAGS_VALID_SENSE; //sjr-moved-here
if (!SCpnt->use_sg) {
dest = SCpnt->request_buffer;
} else {
struct scatterlist *sg = (struct scatterlist *) SCpnt->request_buffer;
if (sg)
dest = (u8 *)(ulong)sg_dma_address(sg);
}
if (dest) {
sz = MIN (SCSI_STD_SENSE_BYTES, SCpnt->request_bufflen);
memcpy(dest, pTarget->sense, sz);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,3,0)
SCpnt->resid = SCpnt->request_bufflen - sz;
#endif
SCpnt->result = 0;
SCpnt->scsi_done(SCpnt);
//sjr-moved-up//pTarget->tflags &= ~MPT_TARGET_FLAGS_VALID_SENSE;
return 0;
}
}
}
if (hd->resetPending) {
/* Prevent new commands from being issued
* while reloading the FW.
*/
did_errcode = 1;
goto did_error;
}
/*
* Put together a MPT SCSI request...
*/
if ((mf = mpt_get_msg_frame(ScsiDoneCtx, hd->ioc->id)) == NULL) {
dprintk((MYIOC_s_WARN_FMT "QueueCmd, no msg frames!!\n",
hd->ioc->name));
did_errcode = 2;
goto did_error;
}
pScsiReq = (SCSIIORequest_t *) mf;
my_idx = le16_to_cpu(mf->u.frame.hwhdr.msgctxu.fld.req_idx);
ADD_INDEX_LOG(my_idx);
/*
* The scsi layer should be handling this stuff
* (In 2.3.x it does -DaveM)
*/
/* 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...
*/
datadir = mptscsih_io_direction(SCpnt);
if (datadir == SCSI_DATA_READ) {
datalen = SCpnt->request_bufflen;
scsidir = MPI_SCSIIO_CONTROL_READ; /* DATA IN (host<--ioc<--dev) */
} else if (datadir == SCSI_DATA_WRITE) {
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.
*/
qtag = MPI_SCSIIO_CONTROL_UNTAGGED;
if (pTarget && (pTarget->tflags & MPT_TARGET_FLAGS_Q_YES)
&& (SCpnt->device->tagged_supported)) {
/*
* Some drives are too stupid to handle fairness issues
* with tagged queueing. We throw in the odd ordered
* tag to stop them starving themselves.
*/
if ((jiffies - hd->qtag_tick) > (5*HZ)) {
qtag = MPI_SCSIIO_CONTROL_ORDEREDQ;
hd->qtag_tick = jiffies;
}
else
qtag = MPI_SCSIIO_CONTROL_SIMPLEQ;
}
scsictl = scsidir | qtag;
/* Use the above information to set up the message frame
*/
pScsiReq->TargetID = target;
pScsiReq->Bus = hd->port;
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.
*/
rc = SUCCESS;
if (datalen == 0) {
/* Add a NULL SGE */
mpt_add_sge((char *)&pScsiReq->SGL, MPT_SGE_FLAGS_SSIMPLE_READ | 0,
(dma_addr_t) -1);
} else {
/* Add a 32 or 64 bit SGE */
rc = mptscsih_AddSGE(hd, SCpnt, pScsiReq, my_idx);
}
if (rc == SUCCESS) {
hd->ScsiLookup[my_idx] = SCpnt;
SCpnt->host_scribble = NULL;
#ifdef DROP_TEST
numTotCmds++;
/* If the IOC number and target match, increment
* counter. If counter matches DROP_THIS, do not
* issue command to FW to force a reset.
* Save the MF pointer so we can free resources
* when task mgmt completes.
*/
if ((hd->ioc->id == DROP_IOC) && (target == DROP_TARGET)) {
dropCounter++;
if (dropCounter == DROP_THIS_CMD) {
dropCounter = 0;
/* If global is set, then we are already
* doing something - so keep issuing commands.
*/
if (dropMfPtr == NULL) {
dropTestNum++;
dropMfPtr = mf;
atomic_inc(&queue_depth);
printk(MYIOC_s_INFO_FMT
"Dropped SCSI cmd (%p)\n",
hd->ioc->name, SCpnt);
printk("mf (%p) req (%4x) tot cmds (%d)\n",
mf, my_idx, numTotCmds);
return 0;
}
}
}
#endif
/* SCSI specific processing */
issueCmd = 1;
if (hd->is_spi) {
int dvStatus = hd->ioc->spi_data.dvStatus[target];
if (dvStatus || hd->ioc->spi_data.forceDv) {
/* Write SDP1 on this I/O to this target */
if (dvStatus & MPT_SCSICFG_NEGOTIATE) {
mptscsih_writeSDP1(hd, 0, target, hd->negoNvram);
dvStatus &= ~MPT_SCSICFG_NEGOTIATE;
hd->ioc->spi_data.dvStatus[target] = dvStatus;
} else if (dvStatus & MPT_SCSICFG_BLK_NEGO) {
mptscsih_writeSDP1(hd, 0, target, MPT_SCSICFG_BLK_NEGO);
dvStatus &= ~MPT_SCSICFG_BLK_NEGO;
hd->ioc->spi_data.dvStatus[target] = dvStatus;
}
#ifndef MPTSCSIH_DISABLE_DOMAIN_VALIDATION
if ((dvStatus & MPT_SCSICFG_NEED_DV) || hd->ioc->spi_data.forceDv) {
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);
mptscsih_dvTask.sync = 0;
mptscsih_dvTask.routine = mptscsih_domainValidation;
mptscsih_dvTask.data = (void *) hd;
SCHEDULE_TASK(&mptscsih_dvTask);
} else {
spin_unlock_irqrestore(&dvtaskQ_lock, lflags);
}
hd->ioc->spi_data.forceDv = 0;
}
/* Trying to do DV to this target, extend timeout.
* Wait to issue intil flag is clear
*/
if (dvStatus & MPT_SCSICFG_DV_PENDING) {
mod_timer(&SCpnt->eh_timeout, jiffies + 40 * HZ);
issueCmd = 0;
}
#endif
}
}
if (issueCmd) {
mptscsih_put_msgframe(ScsiDoneCtx, hd->ioc->id, mf);
dmfprintk((MYIOC_s_INFO_FMT "Issued SCSI cmd (%p)\n",
hd->ioc->name, SCpnt));
} else {
ddvtprintk((MYIOC_s_INFO_FMT "Pending SCSI cmd (%p)\n",
hd->ioc->name, SCpnt));
/* Place this command on the pendingQ if possible */
spin_lock_irqsave(&hd->freedoneQlock, flags);
if (!Q_IS_EMPTY(&hd->freeQ)) {
buffer = hd->freeQ.head;
Q_DEL_ITEM(buffer);
/* Save the mf pointer
*/
buffer->argp =