freebsd-dev/sys/dev/mpt/mpt_freebsd.c

1291 lines
34 KiB
C

/* $FreeBSD$ */
/*
* FreeBSD/CAM specific routines for LSI '909 FC adapters.
* FreeBSD Version.
*
* Copyright (c) 2000, 2001 by Greg Ansley
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice immediately at the beginning of the file, without modification,
* this list of conditions, and the following disclaimer.
* 2. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* 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 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Additional Copyright (c) 2002 by Matthew Jacob under same license.
*/
#include <dev/mpt/mpt_freebsd.h>
static void mpt_poll(struct cam_sim *);
static timeout_t mpttimeout;
static void mpt_action(struct cam_sim *, union ccb *);
void
mpt_cam_attach(mpt_softc_t *mpt)
{
struct cam_devq *devq;
struct cam_sim *sim;
int maxq;
mpt->bus = 0;
maxq = (mpt->mpt_global_credits < MPT_MAX_REQUESTS)?
mpt->mpt_global_credits : MPT_MAX_REQUESTS;
/*
* Create the device queue for our SIM(s).
*/
devq = cam_simq_alloc(maxq);
if (devq == NULL) {
return;
}
/*
* Construct our SIM entry.
*/
sim = cam_sim_alloc(mpt_action, mpt_poll, "mpt", mpt,
mpt->unit, 1, maxq, devq);
if (sim == NULL) {
cam_simq_free(devq);
return;
}
/*
* Register exactly the bus.
*/
if (xpt_bus_register(sim, 0) != CAM_SUCCESS) {
cam_sim_free(sim, TRUE);
return;
}
if (xpt_create_path(&mpt->path, NULL, cam_sim_path(sim),
CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) {
xpt_bus_deregister(cam_sim_path(sim));
cam_sim_free(sim, TRUE);
return;
}
mpt->sim = sim;
}
void
mpt_cam_detach(mpt_softc_t *mpt)
{
if (mpt->sim != NULL) {
xpt_free_path(mpt->path);
xpt_bus_deregister(cam_sim_path(mpt->sim));
cam_sim_free(mpt->sim, TRUE);
mpt->sim = NULL;
}
}
/* This routine is used after a system crash to dump core onto the
* swap device.
*/
static void
mpt_poll(struct cam_sim *sim)
{
mpt_softc_t *mpt = (mpt_softc_t *) cam_sim_softc(sim);
MPT_LOCK(mpt);
mpt_intr(mpt);
MPT_UNLOCK(mpt);
}
/*
* This routine is called if the 9x9 does not return completion status
* for a command after a CAM specified time.
*/
static void
mpttimeout(void *arg)
{
request_t *req;
union ccb *ccb = arg;
mpt_softc_t *mpt;
mpt = ccb->ccb_h.ccb_mpt_ptr;
MPT_LOCK(mpt);
req = ccb->ccb_h.ccb_req_ptr;
mpt->timeouts++;
device_printf(mpt->dev,
"time out on request index = 0x%02x sequence = 0x%08x\n",
req->index, req->sequence);
mpt_check_doorbell(mpt);
device_printf(mpt->dev, "Status %08X; Mask %08X; Doorbell %08X\n",
mpt_read(mpt, MPT_OFFSET_INTR_STATUS),
mpt_read(mpt, MPT_OFFSET_INTR_MASK),
mpt_read(mpt, MPT_OFFSET_DOORBELL) );
printf("request state %s\n", mpt_req_state(req->debug));
if (ccb != req->ccb) {
printf("time out: ccb %p != req->ccb %p\n",
ccb,req->ccb);
}
mpt_print_scsi_io_request((MSG_SCSI_IO_REQUEST *)req->req_vbuf);
req->debug = REQ_TIMEOUT;
req->ccb = NULL;
ccb->ccb_h.status = CAM_CMD_TIMEOUT;
ccb->ccb_h.status |= CAM_RELEASE_SIMQ;
MPTLOCK_2_CAMLOCK(mpt);
xpt_done(ccb);
CAMLOCK_2_MPTLOCK(mpt);
MPT_UNLOCK(mpt);
}
/*
* Callback routine from "bus_dmamap_load" or in simple case called directly.
*
* Takes a list of physical segments and builds the SGL for SCSI IO command
* and forwards the commard to the IOC after one last check that CAM has not
* aborted the transaction.
*/
static void
mpt_execute_req(void *arg, bus_dma_segment_t *dm_segs, int nseg, int error)
{
request_t *req;
union ccb *ccb;
mpt_softc_t *mpt;
MSG_SCSI_IO_REQUEST *mpt_req;
SGE_SIMPLE32 *se;
req = (request_t *)arg;
ccb = req->ccb;
mpt = ccb->ccb_h.ccb_mpt_ptr;
req = ccb->ccb_h.ccb_req_ptr;
mpt_req = req->req_vbuf;
if (error == 0 && nseg > MPT_SGL_MAX) {
error = EFBIG;
}
if (error != 0) {
if (error != EFBIG)
device_printf(mpt->dev, "bus_dmamap_load returned %d\n",
error);
if (ccb->ccb_h.status == CAM_REQ_INPROG) {
xpt_freeze_devq(ccb->ccb_h.path, 1);
ccb->ccb_h.status = CAM_DEV_QFRZN;
if (error == EFBIG)
ccb->ccb_h.status |= CAM_REQ_TOO_BIG;
else
ccb->ccb_h.status |= CAM_REQ_CMP_ERR;
}
ccb->ccb_h.status |= CAM_RELEASE_SIMQ;
ccb->ccb_h.status &= ~CAM_SIM_QUEUED;
xpt_done(ccb);
CAMLOCK_2_MPTLOCK(mpt);
mpt_free_request(mpt, req);
MPTLOCK_2_CAMLOCK(mpt);
return;
}
if (nseg > MPT_NSGL_FIRST(mpt)) {
int i, nleft = nseg;
u_int32_t flags;
bus_dmasync_op_t op;
SGE_CHAIN32 *ce;
mpt_req->DataLength = ccb->csio.dxfer_len;
flags = MPI_SGE_FLAGS_SIMPLE_ELEMENT;
if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT)
flags |= MPI_SGE_FLAGS_HOST_TO_IOC;
se = (SGE_SIMPLE32 *) &mpt_req->SGL;
for (i = 0; i < MPT_NSGL_FIRST(mpt) - 1; i++, se++, dm_segs++) {
u_int32_t tf;
bzero(se, sizeof (*se));
se->Address = dm_segs->ds_addr;
MPI_pSGE_SET_LENGTH(se, dm_segs->ds_len);
tf = flags;
if (i == MPT_NSGL_FIRST(mpt) - 2) {
tf |= MPI_SGE_FLAGS_LAST_ELEMENT;
}
MPI_pSGE_SET_FLAGS(se, tf);
nleft -= 1;
}
/*
* Tell the IOC where to find the first chain element
*/
mpt_req->ChainOffset = ((char *)se - (char *)mpt_req) >> 2;
/*
* Until we're finished with all segments...
*/
while (nleft) {
int ntodo;
/*
* Construct the chain element that point to the
* next segment.
*/
ce = (SGE_CHAIN32 *) se++;
if (nleft > MPT_NSGL(mpt)) {
ntodo = MPT_NSGL(mpt) - 1;
ce->NextChainOffset = (MPT_RQSL(mpt) -
sizeof (SGE_SIMPLE32)) >> 2;
} else {
ntodo = nleft;
ce->NextChainOffset = 0;
}
ce->Length = ntodo * sizeof (SGE_SIMPLE32);
ce->Address = req->req_pbuf +
((char *)se - (char *)mpt_req);
ce->Flags = MPI_SGE_FLAGS_CHAIN_ELEMENT;
for (i = 0; i < ntodo; i++, se++, dm_segs++) {
u_int32_t tf;
bzero(se, sizeof (*se));
se->Address = dm_segs->ds_addr;
MPI_pSGE_SET_LENGTH(se, dm_segs->ds_len);
tf = flags;
if (i == ntodo - 1) {
tf |= MPI_SGE_FLAGS_LAST_ELEMENT;
if (ce->NextChainOffset == 0) {
tf |=
MPI_SGE_FLAGS_END_OF_LIST |
MPI_SGE_FLAGS_END_OF_BUFFER;
}
}
MPI_pSGE_SET_FLAGS(se, tf);
nleft -= 1;
}
}
if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN)
op = BUS_DMASYNC_PREREAD;
else
op = BUS_DMASYNC_PREWRITE;
if (!(ccb->ccb_h.flags & (CAM_SG_LIST_PHYS|CAM_DATA_PHYS))) {
bus_dmamap_sync(mpt->buffer_dmat, req->dmap, op);
}
} else if (nseg > 0) {
int i;
u_int32_t flags;
bus_dmasync_op_t op;
mpt_req->DataLength = ccb->csio.dxfer_len;
flags = MPI_SGE_FLAGS_SIMPLE_ELEMENT;
if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT)
flags |= MPI_SGE_FLAGS_HOST_TO_IOC;
/* Copy the segments into our SG list */
se = (SGE_SIMPLE32 *) &mpt_req->SGL;
for (i = 0; i < nseg; i++, se++, dm_segs++) {
u_int32_t tf;
bzero(se, sizeof (*se));
se->Address = dm_segs->ds_addr;
MPI_pSGE_SET_LENGTH(se, dm_segs->ds_len);
tf = flags;
if (i == nseg - 1) {
tf |=
MPI_SGE_FLAGS_LAST_ELEMENT |
MPI_SGE_FLAGS_END_OF_BUFFER |
MPI_SGE_FLAGS_END_OF_LIST;
}
MPI_pSGE_SET_FLAGS(se, tf);
}
if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN)
op = BUS_DMASYNC_PREREAD;
else
op = BUS_DMASYNC_PREWRITE;
if (!(ccb->ccb_h.flags & (CAM_SG_LIST_PHYS|CAM_DATA_PHYS))) {
bus_dmamap_sync(mpt->buffer_dmat, req->dmap, op);
}
} else {
se = (SGE_SIMPLE32 *) &mpt_req->SGL;
/*
* No data to transfer so we just make a single simple SGL
* with zero length.
*/
MPI_pSGE_SET_FLAGS(se,
(MPI_SGE_FLAGS_LAST_ELEMENT | MPI_SGE_FLAGS_END_OF_BUFFER |
MPI_SGE_FLAGS_SIMPLE_ELEMENT | MPI_SGE_FLAGS_END_OF_LIST));
}
/*
* Last time we need to check if this CCB needs to be aborted.
*/
if (ccb->ccb_h.status != CAM_REQ_INPROG) {
if (nseg && (ccb->ccb_h.flags & CAM_SG_LIST_PHYS) == 0)
bus_dmamap_unload(mpt->buffer_dmat, req->dmap);
CAMLOCK_2_MPTLOCK(mpt);
mpt_free_request(mpt, req);
MPTLOCK_2_CAMLOCK(mpt);
ccb->ccb_h.status |= CAM_RELEASE_SIMQ;
ccb->ccb_h.status &= ~CAM_SIM_QUEUED;
xpt_done(ccb);
return;
}
ccb->ccb_h.status |= CAM_SIM_QUEUED;
if (ccb->ccb_h.timeout != CAM_TIME_INFINITY) {
ccb->ccb_h.timeout_ch =
timeout(mpttimeout, (caddr_t)ccb,
(ccb->ccb_h.timeout * hz) / 1000);
} else {
callout_handle_init(&ccb->ccb_h.timeout_ch);
}
if (mpt->verbose > 1)
mpt_print_scsi_io_request(mpt_req);
CAMLOCK_2_MPTLOCK(mpt);
mpt_send_cmd(mpt, req);
MPTLOCK_2_CAMLOCK(mpt);
}
/* Convert a CAM SCSI I/O ccb into a MPT request to pass the FC Chip */
/* Including building a Scatter gather list of physical page to transfer */
static int
mpt_start(union ccb *ccb)
{
request_t *req;
struct mpt_softc *mpt;
MSG_SCSI_IO_REQUEST *mpt_req;
struct ccb_scsiio *csio = &ccb->csio;
struct ccb_hdr *ccbh = &ccb->ccb_h;
/* Get the pointer for the physical addapter */
mpt = ccb->ccb_h.ccb_mpt_ptr;
/* Get a request structure off the free list */
CAMLOCK_2_MPTLOCK(mpt);
if ((req = mpt_get_request(mpt)) == NULL) {
MPTLOCK_2_CAMLOCK(mpt);
return (CAM_REQUEUE_REQ);
}
MPTLOCK_2_CAMLOCK(mpt);
/* Link the ccb and the request structure so we can find */
/* the other knowing either the request or the ccb */
req->ccb = ccb;
ccb->ccb_h.ccb_req_ptr = req;
/* Now we build the command for the IOC */
mpt_req = req->req_vbuf;
bzero(mpt_req, sizeof *mpt_req);
mpt_req->Function = MPI_FUNCTION_SCSI_IO_REQUEST;
mpt_req->Bus = mpt->bus;
mpt_req->SenseBufferLength =
(csio->sense_len < MPT_SENSE_SIZE) ?
csio->sense_len : MPT_SENSE_SIZE;
/* We use the message context to find the request structure when we */
/* Get the command competion interrupt from the FC IOC. */
mpt_req->MsgContext = req->index;
/* Which physical device to do the I/O on */
mpt_req->TargetID = ccb->ccb_h.target_id;
mpt_req->LUN[1] = ccb->ccb_h.target_lun;
/* Set the direction of the transfer */
if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN)
mpt_req->Control = MPI_SCSIIO_CONTROL_READ;
else if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_OUT)
mpt_req->Control = MPI_SCSIIO_CONTROL_WRITE;
else
mpt_req->Control = MPI_SCSIIO_CONTROL_NODATATRANSFER;
if ((ccb->ccb_h.flags & CAM_TAG_ACTION_VALID) != 0) {
switch(ccb->csio.tag_action) {
case MSG_HEAD_OF_Q_TAG:
mpt_req->Control |= MPI_SCSIIO_CONTROL_HEADOFQ;
break;
case MSG_ACA_TASK:
mpt_req->Control |= MPI_SCSIIO_CONTROL_ACAQ;
break;
case MSG_ORDERED_Q_TAG:
mpt_req->Control |= MPI_SCSIIO_CONTROL_ORDEREDQ;
break;
case MSG_SIMPLE_Q_TAG:
default:
mpt_req->Control |= MPI_SCSIIO_CONTROL_SIMPLEQ;
break;
}
} else {
if (mpt->is_fc)
mpt_req->Control |= MPI_SCSIIO_CONTROL_SIMPLEQ;
else
mpt_req->Control |= MPI_SCSIIO_CONTROL_UNTAGGED;
}
if (mpt->is_fc == 0 && (ccb->ccb_h.flags & CAM_DIS_DISCONNECT) != 0) {
mpt_req->Control |= MPI_SCSIIO_CONTROL_NO_DISCONNECT;
}
/* Copy the scsi command block into place */
if ((ccb->ccb_h.flags & CAM_CDB_POINTER) != 0)
bcopy(csio->cdb_io.cdb_ptr, mpt_req->CDB, csio->cdb_len);
else
bcopy(csio->cdb_io.cdb_bytes, mpt_req->CDB, csio->cdb_len);
mpt_req->CDBLength = csio->cdb_len;
mpt_req->DataLength = csio->dxfer_len;
mpt_req->SenseBufferLowAddr = req->sense_pbuf;
/*
* If we have any data to send with this command,
* map it into bus space.
*/
if ((ccbh->flags & CAM_DIR_MASK) != CAM_DIR_NONE) {
if ((ccbh->flags & CAM_SCATTER_VALID) == 0) {
/*
* We've been given a pointer to a single buffer.
*/
if ((ccbh->flags & CAM_DATA_PHYS) == 0) {
/*
* Virtual address that needs to translated into
* one or more physical pages.
*/
int error;
error = bus_dmamap_load(mpt->buffer_dmat,
req->dmap, csio->data_ptr, csio->dxfer_len,
mpt_execute_req, req, 0);
if (error == EINPROGRESS) {
/*
* So as to maintain ordering,
* freeze the controller queue
* until our mapping is
* returned.
*/
xpt_freeze_simq(mpt->sim, 1);
ccbh->status |= CAM_RELEASE_SIMQ;
}
} else {
/*
* We have been given a pointer to single
* physical buffer.
*/
struct bus_dma_segment seg;
seg.ds_addr = (bus_addr_t)csio->data_ptr;
seg.ds_len = csio->dxfer_len;
mpt_execute_req(req, &seg, 1, 0);
}
} else {
/*
* We have been given a list of addresses.
* These case could be easily done but they are not
* currently generated by the CAM subsystem so there
* is no point in wasting the time right now.
*/
struct bus_dma_segment *segs;
if ((ccbh->flags & CAM_SG_LIST_PHYS) == 0) {
mpt_execute_req(req, NULL, 0, EFAULT);
} else {
/* Just use the segments provided */
segs = (struct bus_dma_segment *)csio->data_ptr;
mpt_execute_req(req, segs, csio->sglist_cnt,
(csio->sglist_cnt < MPT_SGL_MAX)?
0 : EFBIG);
}
}
} else {
mpt_execute_req(req, NULL, 0, 0);
}
return (CAM_REQ_INPROG);
}
static int
mpt_bus_reset(union ccb *ccb)
{
int error;
request_t *req;
mpt_softc_t *mpt;
MSG_SCSI_TASK_MGMT *reset_req;
/* Get the pointer for the physical adapter */
mpt = ccb->ccb_h.ccb_mpt_ptr;
/* Get a request structure off the free list */
if ((req = mpt_get_request(mpt)) == NULL) {
return (CAM_REQUEUE_REQ);
}
/* Link the ccb and the request structure so we can find */
/* the other knowing either the request or the ccb */
req->ccb = ccb;
ccb->ccb_h.ccb_req_ptr = req;
reset_req = req->req_vbuf;
bzero(reset_req, sizeof *reset_req);
reset_req->Function = MPI_FUNCTION_SCSI_TASK_MGMT;
reset_req->MsgContext = req->index;
reset_req->TaskType = MPI_SCSITASKMGMT_TASKTYPE_RESET_BUS;
if (mpt->is_fc) {
/*
* Should really be TARGET_RESET_OPTION
*/
reset_req->MsgFlags =
MPI_SCSITASKMGMT_MSGFLAGS_LIP_RESET_OPTION;
}
/* Which physical device Reset */
reset_req->TargetID = ccb->ccb_h.target_id;
reset_req->LUN[1] = ccb->ccb_h.target_lun;
ccb->ccb_h.status |= CAM_SIM_QUEUED;
error = mpt_send_handshake_cmd(mpt,
sizeof (MSG_SCSI_TASK_MGMT), reset_req);
if (error) {
device_printf(mpt->dev,
"mpt_bus_reset: mpt_send_handshake return %d\n", error);
return (CAM_REQ_CMP_ERR);
} else {
return (CAM_REQ_CMP);
}
}
/*
* Process an asynchronous event from the IOC.
*/
void
mpt_notify(mpt_softc_t *mpt, void *vmsg, u_int32_t reply)
{
MSG_DEFAULT_REPLY *dmsg = vmsg;
if (dmsg->Function == MPI_FUNCTION_EVENT_NOTIFICATION) {
MSG_EVENT_NOTIFY_REPLY *msg = vmsg;
switch(msg->Event) {
case MPI_EVENT_LOG_DATA:
/* Some error occured that LSI wants logged */
device_printf(mpt->dev,
"\tEvtLogData: IOCLogInfo: 0x%08x\n",
msg->IOCLogInfo);
device_printf(mpt->dev, "\tEvtLogData: Event Data:");
{
int i;
for (i = 0; i < msg->EventDataLength; i++) {
device_printf(mpt->dev,
" %08X", msg->Data[i]);
}
}
device_printf(mpt->dev, "\n");
break;
case MPI_EVENT_UNIT_ATTENTION:
device_printf(mpt->dev,
"Bus: 0x%02x TargetID: 0x%02x\n",
(msg->Data[0] >> 8) & 0xff, msg->Data[0] & 0xff);
break;
case MPI_EVENT_IOC_BUS_RESET:
/* We generated a bus reset */
device_printf(mpt->dev, "IOC Bus Reset Port: %d\n",
(msg->Data[0] >> 8) & 0xff);
break;
case MPI_EVENT_EXT_BUS_RESET:
/* Someone else generated a bus reset */
device_printf(mpt->dev, "Ext Bus Reset\n");
/*
* These replies don't return EventData like the MPI
* spec says they do
*/
/* xpt_async(AC_BUS_RESET, path, NULL); */
break;
case MPI_EVENT_RESCAN:
/*
* In general this means a device has been added
* to the loop.
*/
device_printf(mpt->dev,
"Rescan Port: %d\n", (msg->Data[0] >> 8) & 0xff);
/* xpt_async(AC_FOUND_DEVICE, path, NULL); */
break;
case MPI_EVENT_LINK_STATUS_CHANGE:
device_printf(mpt->dev, "Port %d: LinkState: %s\n",
(msg->Data[1] >> 8) & 0xff,
((msg->Data[0] & 0xff) == 0)? "Failed" : "Active");
break;
case MPI_EVENT_LOOP_STATE_CHANGE:
switch ((msg->Data[0] >> 16) & 0xff) {
case 0x01:
device_printf(mpt->dev,
"Port 0x%x: FC LinkEvent: LIP(%02X,%02X) (Loop Initialization)\n",
(msg->Data[1] >> 8) & 0xff,
(msg->Data[0] >> 8) & 0xff,
(msg->Data[0] ) & 0xff);
switch ((msg->Data[0] >> 8) & 0xff) {
case 0xF7:
if ((msg->Data[0] & 0xff) == 0xF7) {
printf("Device needs AL_PA\n");
} else {
printf("Device %02X doesn't like FC performance\n",
msg->Data[0] & 0xFF);
}
break;
case 0xF8:
if ((msg->Data[0] & 0xff) == 0xF7) {
printf("Device had loop failure at its receiver prior to acquiring AL_PA\n");
} else {
printf("Device %02X detected loop failure at its receiver\n",
msg->Data[0] & 0xFF);
}
break;
default:
printf("Device %02X requests that device %02X reset itself\n",
msg->Data[0] & 0xFF,
(msg->Data[0] >> 8) & 0xFF);
break;
}
break;
case 0x02:
device_printf(mpt->dev, "Port 0x%x: FC LinkEvent: LPE(%02X,%02X) (Loop Port Enable)\n",
(msg->Data[1] >> 8) & 0xff, /* Port */
(msg->Data[0] >> 8) & 0xff, /* Character 3 */
(msg->Data[0] ) & 0xff /* Character 4 */
);
break;
case 0x03:
device_printf(mpt->dev, "Port 0x%x: FC LinkEvent: LPB(%02X,%02X) (Loop Port Bypass)\n",
(msg->Data[1] >> 8) & 0xff, /* Port */
(msg->Data[0] >> 8) & 0xff, /* Character 3 */
(msg->Data[0] ) & 0xff /* Character 4 */
);
break;
default:
device_printf(mpt->dev, "Port 0x%x: FC LinkEvent: Unknown FC event (%02X %02X %02X)\n",
(msg->Data[1] >> 8) & 0xff, /* Port */
(msg->Data[0] >> 16) & 0xff, /* Event */
(msg->Data[0] >> 8) & 0xff, /* Character 3 */
(msg->Data[0] ) & 0xff /* Character 4 */
);
}
break;
case MPI_EVENT_LOGOUT:
device_printf(mpt->dev, "FC Logout Port: %d N_PortID: %02X\n",
(msg->Data[1] >> 8) & 0xff,
msg->Data[0]);
break;
case MPI_EVENT_EVENT_CHANGE:
/* This is just an acknowledgement of our
mpt_send_event_request */
break;
default:
device_printf(mpt->dev, "Unknown event %X\n", msg->Event);
}
mpt_free_reply(mpt, (reply << 1));
} else if (dmsg->Function == MPI_FUNCTION_PORT_ENABLE) {
MSG_PORT_ENABLE_REPLY *msg = vmsg;
int index = msg->MsgContext & ~0x80000000;
if (mpt->verbose > 1) {
device_printf(mpt->dev, "enable port reply idx %d\n",
index);
}
if (index >= 0 && index < MPT_MAX_REQUESTS) {
request_t *req = &mpt->requests[index];
req->debug = REQ_DONE;
}
mpt_free_reply(mpt, (reply << 1));
} else if (dmsg->Function == MPI_FUNCTION_CONFIG) {
MSG_CONFIG_REPLY *msg = vmsg;
int index = msg->MsgContext & ~0x80000000;
if (index >= 0 && index < MPT_MAX_REQUESTS) {
request_t *req = &mpt->requests[index];
req->debug = REQ_DONE;
req->sequence = reply;
} else {
mpt_free_reply(mpt, (reply << 1));
}
} else {
device_printf(mpt->dev, "unknown mpt_notify: %x\n",
dmsg->Function);
}
}
void
mpt_done(mpt_softc_t *mpt, u_int32_t reply)
{
int index;
request_t *req;
union ccb *ccb;
MSG_REQUEST_HEADER *mpt_req;
MSG_SCSI_IO_REPLY *mpt_reply;
index = -1; /* Shutup the complier */
if ((reply & MPT_CONTEXT_REPLY) == 0) {
/* context reply */
mpt_reply = NULL;
index = reply & MPT_CONTEXT_MASK;
} else {
unsigned *pReply;
bus_dmamap_sync(mpt->reply_dmat, mpt->reply_dmap,
BUS_DMASYNC_POSTREAD);
/* address reply (Error) */
mpt_reply = MPT_REPLY_PTOV(mpt, reply);
if (mpt->verbose > 1) {
pReply = (unsigned *) mpt_reply;
device_printf(mpt->dev, "Address Reply (index %u)\n",
mpt_reply->MsgContext & 0xffff);
device_printf(mpt->dev, "%08X %08X %08X %08X\n",
pReply[0], pReply[1], pReply[2], pReply[3]);
device_printf(mpt->dev, "%08X %08X %08X %08X\n",
pReply[4], pReply[5], pReply[6], pReply[7]);
device_printf(mpt->dev, "%08X %08X %08X %08X\n\n",
pReply[8], pReply[9], pReply[10], pReply[11]);
}
index = mpt_reply->MsgContext;
}
/* Address reply with MessageContext high bit set */
/* This is most likely a notify message so we try */
/* to process it then free it */
if ((index & 0x80000000) != 0) {
if (mpt_reply != NULL) {
mpt_notify(mpt, mpt_reply, reply);
} else {
device_printf(mpt->dev,
"mpt_done: index 0x%x, NULL reply\n", index);
}
return;
}
/* Did we end up with a valid index into the table? */
if (index < 0 || index >= MPT_MAX_REQUESTS) {
printf("mpt_done: invalid index (%x) in reply\n", index);
return;
}
req = &mpt->requests[index];
/* Make sure memory hasn't been trashed */
if (req->index != index) {
printf("mpt_done: corrupted request struct");
return;
}
/* Short cut for task management replys; nothing more for us to do */
mpt_req = req->req_vbuf;
if (mpt_req->Function == MPI_FUNCTION_SCSI_TASK_MGMT) {
if (mpt->verbose > 1) {
device_printf(mpt->dev, "mpt_done: TASK MGMT\n");
}
goto done;
}
if (mpt_req->Function == MPI_FUNCTION_PORT_ENABLE) {
goto done;
}
/*
* At this point it better be a SCSI IO command, but don't
* crash if it isn't
*/
if (mpt_req->Function != MPI_FUNCTION_SCSI_IO_REQUEST) {
goto done;
}
/* Recover the CAM control block from the request structure */
ccb = req->ccb;
/* Can't have had a SCSI command with out a CAM control block */
if (ccb == NULL || (ccb->ccb_h.status & CAM_SIM_QUEUED) == 0) {
device_printf(mpt->dev,
"mpt_done: corrupted ccb, index = 0x%02x seq = 0x%08x",
req->index, req->sequence);
printf(" request state %s\nmpt_request:\n",
mpt_req_state(req->debug));
mpt_print_scsi_io_request((MSG_SCSI_IO_REQUEST *)req->req_vbuf);
if (mpt_reply != NULL) {
printf("\nmpt_done: reply:\n");
mpt_print_reply(MPT_REPLY_PTOV(mpt, reply));
} else {
printf("\nmpt_done: context reply: 0x%08x\n", reply);
}
goto done;
}
untimeout(mpttimeout, ccb, ccb->ccb_h.timeout_ch);
if ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) {
bus_dmasync_op_t op;
if ((ccb->ccb_h.flags & CAM_DIR_MASK) == CAM_DIR_IN) {
op = BUS_DMASYNC_POSTREAD;
} else {
op = BUS_DMASYNC_POSTWRITE;
}
bus_dmamap_sync(mpt->buffer_dmat, req->dmap, op);
bus_dmamap_unload(mpt->buffer_dmat, req->dmap);
}
ccb->csio.resid = 0;
if (mpt_reply == NULL) {
/* Context reply; report that the command was successfull */
ccb->ccb_h.status = CAM_REQ_CMP;
ccb->csio.scsi_status = SCSI_STATUS_OK;
ccb->ccb_h.status |= CAM_RELEASE_SIMQ;
ccb->ccb_h.status &= ~CAM_SIM_QUEUED;
xpt_done(ccb);
goto done;
}
ccb->csio.scsi_status = mpt_reply->SCSIStatus;
switch(mpt_reply->IOCStatus) {
case MPI_IOCSTATUS_SCSI_DATA_OVERRUN:
ccb->ccb_h.status = CAM_DATA_RUN_ERR | CAM_DEV_QFRZN;
break;
case MPI_IOCSTATUS_SCSI_DATA_UNDERRUN:
/*
* Yikes, Tagged queue full comes through this path!
*
* So we'll change it to a status error and anything
* that returns status should probably be a status
* error as well.
*/
if (mpt_reply->SCSIState & MPI_SCSI_STATE_NO_SCSI_STATUS) {
ccb->ccb_h.status = CAM_DATA_RUN_ERR | CAM_DEV_QFRZN;
break;
}
ccb->csio.resid =
ccb->csio.dxfer_len - mpt_reply->TransferCount;
/* Fall through */
case MPI_IOCSTATUS_SUCCESS:
case MPI_IOCSTATUS_SCSI_RECOVERED_ERROR:
switch (ccb->csio.scsi_status) {
case SCSI_STATUS_OK:
ccb->ccb_h.status = CAM_REQ_CMP;
break;
case SCSI_STATUS_QUEUE_FULL:
ccb->ccb_h.status = CAM_SCSI_STATUS_ERROR;
#if 0
/* XXX This seams to freaze up the device */
/* But without it the tag queue total does */
/* Not get reduced properly! */
xpt_freeze_devq(ccb->ccb_h.path, /*count*/1);
ccb->ccb_h.status |= CAM_DEV_QFRZN;
#endif
break;
default:
ccb->ccb_h.status =
CAM_SCSI_STATUS_ERROR | CAM_DEV_QFRZN;
xpt_freeze_devq(ccb->ccb_h.path, /*count*/1);
break;
}
break;
case MPI_IOCSTATUS_BUSY:
case MPI_IOCSTATUS_INSUFFICIENT_RESOURCES:
ccb->ccb_h.status = CAM_BUSY;
break;
case MPI_IOCSTATUS_SCSI_INVALID_BUS:
case MPI_IOCSTATUS_SCSI_INVALID_TARGETID:
case MPI_IOCSTATUS_SCSI_DEVICE_NOT_THERE:
ccb->ccb_h.status = CAM_DEV_NOT_THERE | CAM_DEV_QFRZN;
break;
case MPI_IOCSTATUS_SCSI_RESIDUAL_MISMATCH:
ccb->ccb_h.status = CAM_DATA_RUN_ERR | CAM_DEV_QFRZN;
break;
case MPI_IOCSTATUS_SCSI_PROTOCOL_ERROR:
case MPI_IOCSTATUS_SCSI_IO_DATA_ERROR:
ccb->ccb_h.status = CAM_UNCOR_PARITY | CAM_DEV_QFRZN;
break;
case MPI_IOCSTATUS_SCSI_TASK_TERMINATED:
/* Terminated due to task managment request */
ccb->ccb_h.status = CAM_REQ_CMP;
break;
case MPI_IOCSTATUS_SCSI_TASK_MGMT_FAILED:
ccb->ccb_h.status = CAM_UA_TERMIO | CAM_DEV_QFRZN;
break;
/*
case MPI_IOCSTATUS_SCSI_IOC_TERMINATED:
ccb->ccb_h.status = CAM_SCSI_BUS_RESET;
break;
*/
/*
* We always get this when a drive is pulled.
* Just kill the device but not the bus.
*/
case MPI_IOCSTATUS_SCSI_EXT_TERMINATED:
case MPI_IOCSTATUS_SCSI_IOC_TERMINATED:
ccb->ccb_h.status = CAM_SCSI_BUS_RESET | CAM_DEV_QFRZN;
ccb->ccb_h.status |= CAM_RELEASE_SIMQ;
break;
default:
ccb->ccb_h.status = CAM_UNREC_HBA_ERROR;
break;
}
if ((mpt_reply->SCSIState & MPI_SCSI_STATE_AUTOSENSE_VALID) != 0) {
if (ccb->ccb_h.flags & (CAM_SENSE_PHYS | CAM_SENSE_PTR)) {
ccb->ccb_h.status |= CAM_AUTOSENSE_FAIL;
} else {
ccb->ccb_h.status |= CAM_AUTOSNS_VALID;
ccb->csio.sense_resid = mpt_reply->SenseCount;
bcopy(req->sense_vbuf, &ccb->csio.sense_data,
ccb->csio.sense_len);
}
} else if (mpt_reply->SCSIState & MPI_SCSI_STATE_AUTOSENSE_FAILED) {
ccb->ccb_h.status &= ~CAM_STATUS_MASK;
ccb->ccb_h.status |= CAM_AUTOSENSE_FAIL;
}
if ((mpt_reply->MsgFlags & 0x80) == 0)
ccb->ccb_h.status |= CAM_RELEASE_SIMQ;
ccb->ccb_h.status &= ~CAM_SIM_QUEUED;
MPTLOCK_2_CAMLOCK(mpt);
xpt_done(ccb);
CAMLOCK_2_MPTLOCK(mpt);
done:
/* If IOC done with this request free it up */
if (mpt_reply == NULL || (mpt_reply->MsgFlags & 0x80) == 0)
mpt_free_request(mpt, req);
/* If address reply; give the buffer back to the IOC */
if (mpt_reply != NULL)
mpt_free_reply(mpt, (reply << 1));
}
static void
mpt_action(struct cam_sim *sim, union ccb *ccb)
{
int tgt, error;
mpt_softc_t *mpt;
struct ccb_trans_settings *cts;
CAM_DEBUG(ccb->ccb_h.path, CAM_DEBUG_TRACE, ("mpt_action\n"));
mpt = (mpt_softc_t *)cam_sim_softc(sim);
ccb->ccb_h.ccb_mpt_ptr = mpt;
switch (ccb->ccb_h.func_code) {
case XPT_RESET_BUS:
if (mpt->verbose > 1)
device_printf(mpt->dev, "XPT_RESET_BUS\n");
CAMLOCK_2_MPTLOCK(mpt);
error = mpt_bus_reset(ccb);
MPTLOCK_2_CAMLOCK(mpt);
switch (error) {
case CAM_REQ_INPROG:
ccb->ccb_h.status |= CAM_SIM_QUEUED;
break;
case CAM_REQUEUE_REQ:
xpt_freeze_simq(sim, 1);
ccb->ccb_h.status = CAM_REQUEUE_REQ;
xpt_done(ccb);
break;
case CAM_REQ_CMP:
ccb->ccb_h.status &= ~CAM_SIM_QUEUED;
ccb->ccb_h.status |= CAM_REQ_CMP;
xpt_done(ccb);
break;
default:
ccb->ccb_h.status = CAM_REQ_CMP_ERR;
xpt_done(ccb);
}
break;
case XPT_SCSI_IO: /* Execute the requested I/O operation */
/*
* Do a couple of preliminary checks...
*/
if ((ccb->ccb_h.flags & CAM_CDB_POINTER) != 0) {
if ((ccb->ccb_h.flags & CAM_CDB_PHYS) != 0) {
ccb->ccb_h.status = CAM_REQ_INVALID;
xpt_done(ccb);
break;
}
}
/* Max supported CDB length is 16 bytes */
if (ccb->csio.cdb_len > 16) {
ccb->ccb_h.status = CAM_REQ_INVALID;
xpt_done(ccb);
return;
}
ccb->csio.scsi_status = SCSI_STATUS_OK;
error = mpt_start(ccb);
switch (error) {
case CAM_REQ_INPROG:
ccb->ccb_h.status |= CAM_SIM_QUEUED;
break;
case CAM_REQUEUE_REQ:
xpt_freeze_simq(sim, 1);
ccb->ccb_h.status = CAM_REQUEUE_REQ;
xpt_done(ccb);
break;
case CAM_REQ_CMP:
ccb->ccb_h.status &= ~CAM_SIM_QUEUED;
xpt_done(ccb);
break;
default:
ccb->ccb_h.status = CAM_REQ_CMP_ERR;
xpt_done(ccb);
}
break;
case XPT_ABORT:
/*
* XXX: Need to implement
*/
ccb->ccb_h.status = CAM_UA_ABORT;
xpt_done(ccb);
break;
#ifdef CAM_NEW_TRAN_CODE
#define IS_CURRENT_SETTINGS(c) (c->type == CTS_TYPE_CURRENT_SETTINGS)
#else
#define IS_CURRENT_SETTINGS(c) (c->flags & CCB_TRANS_CURRENT_SETTINGS)
#endif
#define DP_DISC 0x1
#define DP_TQING 0x2
#define DP_WIDE 0x4
case XPT_SET_TRAN_SETTINGS: /* Nexus Settings */
cts = &ccb->cts;
if (!IS_CURRENT_SETTINGS(cts)) {
ccb->ccb_h.status = CAM_REQ_INVALID;
xpt_done(ccb);
break;
}
/* XXX: need to implement */
ccb->ccb_h.status = CAM_REQ_CMP;
xpt_done(ccb);
break;
case XPT_GET_TRAN_SETTINGS:
cts = &ccb->cts;
tgt = cts->ccb_h.target_id;
if (mpt->is_fc) {
#ifndef CAM_NEW_TRAN_CODE
/*
* a lot of normal SCSI things don't make sense.
*/
cts->flags = CCB_TRANS_TAG_ENB | CCB_TRANS_DISC_ENB;
cts->valid = CCB_TRANS_DISC_VALID | CCB_TRANS_TQ_VALID;
/*
* How do you measure the width of a high
* speed serial bus? Well, in bytes.
*
* Offset and period make no sense, though, so we set
* (above) a 'base' transfer speed to be gigabit.
*/
cts->bus_width = MSG_EXT_WDTR_BUS_8_BIT;
#else
struct ccb_trans_settings_fc *fc =
&cts->xport_specific.fc;
cts->protocol = PROTO_SCSI;
cts->protocol_version = SCSI_REV_2;
cts->transport = XPORT_FC;
cts->transport_version = 0;
fc->valid = CTS_FC_VALID_SPEED;
fc->bitrate = 100000; /* XXX: Need for 2Gb/s */
/* XXX: need a port database for each target */
#endif
} else {
#ifdef CAM_NEW_TRAN_CODE
struct ccb_trans_settings_scsi *scsi =
&cts->proto_specific.scsi;
struct ccb_trans_settings_spi *spi =
&cts->xport_specific.spi;
#endif
u_int8_t dval, pval, oval;
if (IS_CURRENT_SETTINGS(cts)) {
dval = 0;
if (mpt->mpt_dev_page0[tgt].
NegotiatedParameters &
MPI_SCSIDEVPAGE0_NP_WIDE)
dval |= DP_WIDE;
if (mpt->mpt_port_page2.DeviceSettings[tgt].
DeviceFlags &
MPI_SCSIPORTPAGE2_DEVICE_DISCONNECT_ENABLE)
dval |= DP_DISC;
if (mpt->mpt_port_page2.DeviceSettings[tgt].
DeviceFlags &
MPI_SCSIPORTPAGE2_DEVICE_TAG_QUEUE_ENABLE)
dval |= DP_TQING;
oval = (mpt->mpt_dev_page0[tgt].
NegotiatedParameters >> 16);
pval = (mpt->mpt_dev_page0[tgt].
NegotiatedParameters >> 8);
} else {
/*
* XXX: Fix wrt NVRAM someday. Attempts
* XXX: to read port page2 device data
* XXX: just returns zero in these areas.
*/
dval = DP_WIDE|DP_DISC|DP_TQING;
oval = (mpt->mpt_port_page0.Capabilities >> 16);
pval = (mpt->mpt_port_page0.Capabilities >> 8);
}
#ifndef CAM_NEW_TRAN_CODE
cts->flags &= ~(CCB_TRANS_DISC_ENB|CCB_TRANS_TAG_ENB);
if (dval & DP_DISC) {
cts->flags |= CCB_TRANS_DISC_ENB;
}
if (dval & DP_TQING) {
cts->flags |= CCB_TRANS_TAG_ENB;
}
if (dval & DP_WIDE) {
cts->bus_width = MSG_EXT_WDTR_BUS_16_BIT;
} else {
cts->bus_width = MSG_EXT_WDTR_BUS_8_BIT;
}
cts->valid = CCB_TRANS_BUS_WIDTH_VALID |
CCB_TRANS_DISC_VALID | CCB_TRANS_TQ_VALID;
if (oval) {
cts->sync_period = pval;
cts->sync_offset = oval;
cts->valid |=
CCB_TRANS_SYNC_RATE_VALID |
CCB_TRANS_SYNC_OFFSET_VALID;
}
#else
cts->protocol = PROTO_SCSI;
cts->protocol_version = SCSI_REV_2;
cts->transport = XPORT_SPI;
cts->transport_version = 2;
scsi->flags &= ~CTS_SCSI_FLAGS_TAG_ENB;
spi->flags &= ~CTS_SPI_FLAGS_DISC_ENB;
if (dval & DP_DISC) {
spi->flags |= CTS_SPI_FLAGS_DISC_ENB;
}
if (dval & DP_TQING) {
scsi->flags |= CTS_SCSI_FLAGS_TAG_ENB;
}
if (oval && pval) {
spi->sync_offset = oval;
spi->sync_period = pval;
spi->valid |= CTS_SPI_VALID_SYNC_OFFSET;
spi->valid |= CTS_SPI_VALID_SYNC_RATE;
}
spi->valid |= CTS_SPI_VALID_BUS_WIDTH;
if (dval & DP_WIDE) {
spi->bus_width = MSG_EXT_WDTR_BUS_16_BIT;
} else {
spi->bus_width = MSG_EXT_WDTR_BUS_8_BIT;
}
if (cts->ccb_h.target_lun != CAM_LUN_WILDCARD) {
scsi->valid = CTS_SCSI_VALID_TQ;
spi->valid |= CTS_SPI_VALID_DISC;
} else {
scsi->valid = 0;
}
#endif
if (mpt->verbose > 1) {
device_printf(mpt->dev,
"GET %s targ %d flags %x off %x per %x\n",
IS_CURRENT_SETTINGS(cts)? "ACTIVE" :
"NVRAM", tgt, dval, oval, pval);
}
}
ccb->ccb_h.status = CAM_REQ_CMP;
xpt_done(ccb);
break;
case XPT_CALC_GEOMETRY:
{
struct ccb_calc_geometry *ccg;
u_int32_t secs_per_cylinder;
u_int32_t size_mb;
ccg = &ccb->ccg;
if (ccg->block_size == 0) {
ccb->ccb_h.status = CAM_REQ_INVALID;
xpt_done(ccb);
break;
}
size_mb = ccg->volume_size /((1024L * 1024L) / ccg->block_size);
if (size_mb > 1024) {
ccg->heads = 255;
ccg->secs_per_track = 63;
} else {
ccg->heads = 64;
ccg->secs_per_track = 32;
}
secs_per_cylinder = ccg->heads * ccg->secs_per_track;
ccg->cylinders = ccg->volume_size / secs_per_cylinder;
ccb->ccb_h.status = CAM_REQ_CMP;
xpt_done(ccb);
break;
}
case XPT_PATH_INQ: /* Path routing inquiry */
{
struct ccb_pathinq *cpi = &ccb->cpi;
cpi->version_num = 1;
cpi->target_sprt = 0;
cpi->hba_eng_cnt = 0;
cpi->max_lun = 7;
cpi->bus_id = cam_sim_bus(sim);
if (mpt->is_fc) {
cpi->max_target = 255;
cpi->hba_misc = PIM_NOBUSRESET;
cpi->initiator_id = cpi->max_target + 1;
cpi->base_transfer_speed = 100000;
cpi->hba_inquiry = PI_TAG_ABLE;
} else {
cpi->initiator_id = mpt->mpt_ini_id;
cpi->base_transfer_speed = 3300;
cpi->hba_inquiry = PI_SDTR_ABLE|PI_TAG_ABLE|PI_WIDE_16;
cpi->hba_misc = 0;
cpi->max_target = 15;
}
strncpy(cpi->sim_vid, "FreeBSD", SIM_IDLEN);
strncpy(cpi->hba_vid, "LSI", HBA_IDLEN);
strncpy(cpi->dev_name, cam_sim_name(sim), DEV_IDLEN);
cpi->unit_number = cam_sim_unit(sim);
cpi->ccb_h.status = CAM_REQ_CMP;
xpt_done(ccb);
break;
}
default:
ccb->ccb_h.status = CAM_REQ_INVALID;
xpt_done(ccb);
break;
}
}