aac.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 2000 Michael Smith
* Copyright (c) 2001 Scott Long
* Copyright (c) 2000 BSDi
* All rights reserved.
*
* 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, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
*
* 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
* 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.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/ddidmareq.h>
#include <sys/byteorder.h>
#include "aac_regs.h"
#include "aac.h"
char _depends_on[] = "misc/scsi";
/* #define AAC_DEBUG */
#ifdef AAC_DEBUG
static char *get_cmd_desc(uchar_t);
#else
#define AACDB_PRINT(fmt)
#endif /* ifdef AAC_DEBUG */
int count = 0; \
while (!(cond)) { \
drv_usecwait(1); \
break; \
} \
(count)++; \
} \
}
/*
* SCSA function prototypes
*/
/*
* Interrupt handler funtions
*/
/*
* Internal funciton in attach
*/
static int aac_check_card_type(struct aac_softstate *);
static int aac_common_attach(struct aac_softstate *);
static int aac_get_container(struct aac_softstate *);
static int aac_setup_comm_space(struct aac_softstate *);
static int aac_hba_setup(struct aac_softstate *);
/*
* Internal funciton in attach
*/
static int aac_check_card_type(struct aac_softstate *);
uint16_t);
int (*)(caddr_t));
static void aac_release_sync_fib(struct aac_softstate *);
/*
* hardware queue operation funcitons
*/
/*
* fib queue operation functions
*/
/*
* slot operation functions
*/
static int aac_create_slots(struct aac_softstate *);
static void aac_destroy_slots(struct aac_softstate *);
/*
* Internal funcitons
*/
static void aac_start_waiting_io(struct aac_softstate *);
static void aac_drain_comp_q(struct aac_softstate *);
/*
* timeout handling thread function
*/
static void aac_daemon(void*);
static struct dev_ops aac_dev_ops = {
0,
NULL,
NULL,
};
static struct modldrv aac_modldrv = {
"AAC Driver %I%",
};
static struct modlinkage aac_modlinkage = {
};
static struct aac_softstate *aac_softstatep;
/* desc will be used in inquiry(0x12) command the length should be 16 bytes */
static struct aac_card_type aac_cards[] = {
{0x9005, 0x285, 0x9005, 0x285, "Adatpec 2200S "},
{0x9005, 0x285, 0x9005, 0x286, "Adaptec 2120S "},
{0x9005, 0x285, 0x9005, 0x290, "Adaptec 2410SA "},
{0x9005, 0x285, 0x1028, 0x287, "Dell PERC 320/DC"},
{0x1028, 0xa, 0x1028, 0x121, "Dell PERC 3/Di"},
{0x1028, 0xa, 0x1028, 0x11b, "Dell PERC 3/Di"},
{0x1028, 0xa, 0x1028, 0x106, "Dell PERC 3/Di"},
{0x1028, 0x8, 0x1028, 0xcf, "Dell PERC 3/Di"},
{0x1028, 0x2, 0x1028, 0xd9, "Dell PERC 3/Di"},
{0x1028, 0x2, 0x1028, 0xd1, "Dell PERC 3/Di"},
{0x1028, 0x4, 0x1028, 0xd0, "Dell PERC 3/Si"},
{0x1028, 0x3, 0x1028, 0x3, "Dell PERC 3/Si"},
{0x1028, 0x2, 0x1028, 0x2, "Dell PERC 3/Di"},
{0x1028, 0x1, 0x1028, 0x1, "Dell PERC 3/Di"},
{0, 0, 0, 0, "AAC card "},
{0, 0, 0, 0, NULL}
};
static ddi_device_acc_attr_t aac_acc_attr = {
};
static struct {
int size;
int notify;
} aac_qinfo[] = {
};
/* dma attribute */
0x2000ull, /* lowest usable address */
/* (2200 and 2120 cannot dma below 8192 */
0xffffffffull, /* high DMA address range */
0x0000ffffull, /* DMA counter register */
4, /* DMA address alignment */
1, /* DMA burstsizes */
1, /* min effective DMA size */
0xffffffffull, /* max DMA xfer size */
0xffffffffull, /* segment boundary */
AAC_NSEG, /* s/g list length */
512, /* granularity of device */
0, /* DMA transfer flags */
};
0x2000ull, /* lowest usable address */
/* (2200 and 2120 cannot dma below 8192 */
0xffffffffull, /* high DMA address range */
0x0000ffffull, /* DMA counter register */
4, /* DMA address alignment */
1, /* DMA burstsizes */
1, /* min effective DMA size */
0xffffffffull, /* max DMA xfer size */
0xffffffffull, /* segment boundary */
1, /* s/g list length */
1, /* granularity of device */
0, /* DMA transfer flags */
};
int
_init(void)
{
int retval = 0;
DBCALLED();
sizeof (struct aac_softstate), 0)) != 0)
goto error;
ddi_soft_state_fini((void*)&aac_softstatep);
goto error;
}
ddi_soft_state_fini((void*)&aac_softstatep);
goto error;
}
return (retval);
return (retval);
}
int
{
DBCALLED();
}
/*
* an HBA driver cannot be unload unless you reboot,
* so this function will be of no use
*/
int
_fini(void)
{
int err;
DBCALLED();
goto error;
ddi_soft_state_fini((void*)&aac_softstatep);
return (0);
return (err);
}
static int
{
int instance;
struct aac_softstate *softs;
int attach_state = 0;
DBCALLED();
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_FAILURE);
default:
return (DDI_FAILURE);
}
/* get soft state */
goto error;
}
/* check the card type */
/* we have found the right card and everything is OK */
} else
goto error;
/* map pci mem space */
goto error;
}
/* Connect interrupt handler */
if (ddi_intr_hilevel(dip, 0)) {
"High level interrupt is not supported!"));
goto error;
}
!= DDI_SUCCESS) {
"Can not get interrupt block cookie!"));
goto error;
}
/* init mutexes */
!= DDI_SUCCESS) {
"Can not setup interrupt handler!"));
goto error;
}
!= DDI_SUCCESS) {
"Can not setup soft interrupt handler!"));
goto error;
}
goto error;
/*
* everything has been set up till now,
* we will do some common attach
*/
goto error;
/* common attach is OK, so we are attached! */
return (DDI_SUCCESS);
if (attach_state & AAC_SCSI_TRAN_SETUP) {
(void) scsi_hba_detach(dip);
}
if (attach_state & AAC_SOFT_INTR_SETUP) {
}
if (attach_state & AAC_KMUTEX_INITED) {
}
if (attach_state & AAC_PCI_MEM_MAPPED)
if (attach_state & AAC_CARD_DETECTED)
return (DDI_FAILURE);
}
static int
{
int instance;
struct aac_softstate *softs;
DBCALLED();
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
return (DDI_FAILURE);
default:
return (DDI_FAILURE);
}
if (softs->timeout_id != 0) {
softs->timeout_id = 0;
} else
(void) scsi_hba_detach(dip);
return (DDI_SUCCESS);
}
static uint_t
{
struct aac_softstate *softs;
return (DDI_INTR_CLAIMED);
} else
return (DDI_INTR_UNCLAIMED);
}
void
{
struct scsi_arq_status *arqstat;
arqstat->sts_rqpkt_resid = 0;
arqstat->sts_rqpkt_statistics = 0;
}
static uint_t
{
struct aac_softstate *softs;
/* ack the int */
(void) AAC_STATUS_GET(softs);
do {
if (acp) {
}
}
} while (acp);
return (DDI_INTR_CLAIMED);
/* ack the int */
(void) AAC_STATUS_GET(softs);
return (DDI_INTR_CLAIMED);
} else if (status & AAC_DB_COMMAND_READY) {
(void) AAC_STATUS_GET(softs);
return (DDI_INTR_CLAIMED);
} else
return (DDI_INTR_UNCLAIMED);
}
static int
{
int card_type_index;
int card_found;
/* map pci configuration space */
return (AACERR);
}
card_type_index = 0;
card_found = 0;
card_found = 1;
break;
}
}
/* make sure we can talk to this board */
if (card_found) {
if ((pci_cmd & PCI_COMM_ME) == 0) {
/* force the busmaster enable bit on */
pci_cmd |= PCI_COMM_ME;
pci_cmd);
if ((pci_cmd & PCI_COMM_ME) == 0) {
"?Cannot enable busmaster bit");
goto error;
}
}
if ((pci_cmd & PCI_COMM_MAE) == 0) {
goto error;
}
} else
return (card_type_index); /* card type detected */
return (AACERR); /* Not found a matched card */
}
static int
{
DBCALLED();
/* wait card to be ready */
goto error;
}
/* get supported options */
!= AACOK) {
goto error;
}
/* Setup communication space with the card */
goto error;
}
/* setup containers */
goto error;
}
/* create a thread for command timeout */
return (AACOK);
return (AACERR);
}
static int
{
/* fill in mailbox */
/* ensure the sync command doorbell flag is cleared */
/* then set it to signal the adapter */
/* spin waiting for the command to complete */
"Sent sync command timed out!"));
return (AACERR);
}
/* clear the completion flag */
/* get the command status */
if (status != 1)
return (AACERR);
return (AACOK);
}
static int
{
int err;
if (datasize > AAC_FIB_SIZE)
return (AACERR);
/* setup sync fib */
/* Give the FIB to the controller, wait for a response. */
return (AACERR);
}
return (AACOK);
}
static struct aac_fib *
{
return (NULL);
} else
return (fib);
}
static void
{
}
/* remove cmd from queue */
static struct aac_cmd *
aac_cmd_dequeue(struct aac_cmd_queue *q)
{
if (q->q_head) {
}
return (ac);
}
/* add a cmd to the tail of q */
static void
{
if (!q->q_head) { /* empty queue */
} else {
}
}
static int
{
DBCALLED();
/*
* wrap the queue first before we check the queue to see
* if it is full
*/
pi = 0;
/* xxx queue full */
return (AACERR);
}
/* fill in queue entry */
/* update producer index */
return (AACOK);
}
static struct aac_cmd *
{
int unfull = 0;
DBCALLED();
/* check for queue empty */
goto out;
}
/* check for queue full */
unfull = 1;
/*
* the controller does not wrap the queue,
* so we have to do it by ourselves
*/
ci = 0;
/* fetch the entry */
/* update consumer index */
out:
if (unfull)
return (acp);
}
static int
{
struct aac_mntinfo *mi;
int i = 0, count = 0;
/* loop over possible containers */
do {
/* request information on #i container */
sizeof (struct aac_fib_header) + \
sizeof (struct aac_mntinfo)) == AACERR) {
"Error probe container %d", i));
continue;
}
if (i == 0) { /* the first time */
}
"Container found: id=%d, size=%u, type=%d, name=%s",
}
i++;
} while ((i < count) && (i < AAC_MAX_LD));
return (AACOK);
}
static int
{
int qoffset;
struct aac_adapter_init *initp;
if (ddi_dma_alloc_handle(
NULL,
"Cannot alloc dma handle for communication area"));
goto error;
}
if (ddi_dma_mem_alloc(
sizeof (struct aac_comm_space),
NULL,
&rlen,
"Cannot alloc mem for communication area"));
goto error;
}
cookien = 0;
NULL,
sizeof (struct aac_comm_space),
NULL,
&cookie,
&cookien) != DDI_DMA_MAPPED) {
"dma bind failed for communication area"));
cookien = 0;
goto error;
}
/* init slot */
goto error;
qoffset = (comm_space_phyaddr + \
if (qoffset)
/* init queue table */
/* init queue entries */
/* Send init structure to the card */
if (aac_sync_mbcommand(softs,
"Cannot send init structrue to adapter"));
goto error;
}
return (AACOK);
if (cookien)
if (softs->comm_space_acc_handle) {
}
if (softs->comm_space_dma_handle) {
}
return (AACERR);
}
static void
int capacity)
{
struct mode_format *page3p;
struct mode_geometry *page4p;
struct mode_header *headerp;
unsigned int ncyl;
return;
switch (pagecode) {
case SD_MODE_SENSE_PAGE3_CODE:
break;
case SD_MODE_SENSE_PAGE4_CODE:
break;
default:
break;
}
}
/*ARGSUSED*/
static int
{
struct aac_softstate *softs;
DBCALLED();
/* only support container that has been detected and valid */
return (DDI_SUCCESS);
else
return (DDI_FAILURE);
}
/*
* tran_reset() will reset the adapter to support the fault recovery
* functionality. Till now there is no firmware manual available from
* Adaptec. We have requested IHV team for further documents and support
* from Adaptec, there is no result yet.
*
* If the tran_reset() return a FAILURE to the sd, the system will not
* continue to dump the core. But core dump is an crucial method to analyze
* problems in panic. Now we adopt a work around solution, that is return
* a fake SUCCESS to sd during panic, which will force the system continue
* to dump core though the core may have problems in some situtation because
* some on-the-fly commands will continue DMAing data to the memory.
* In addition, the work around core dump method may not be performed
* successfully if the panic is caused by the HBA itself. So the work around
* solution is not a good example for the implementation of tran_reset(),
* the most reasonable approach should be to reset the adapter.
*
* IHV team is continue seeking support from Adaptec. Once further support
* from Adaptec is available, we will implement a true reset function in
* tran_reset().
*/
/*ARGSUSED*/
static int
{
struct aac_softstate *softs;
if (ddi_in_panic()) {
return (0);
/*
* If the system is in panic, the tran_reset() will return a
* fake SUCCESS to sd, then the system would continue dump the
* core by poll commands. This is a work around for dumping
* core in panic.
*
* Note: Some on-the-fly command will continue DMAing data to
* the memory when the core is dumping, which may cause
* some flaws in the dumped core file, so a cmn_err()
* will be printed out to warn users. However, for most
* cases, the core file will be fine.
*/
"reset. This means that memory being used "
"by the hba for DMA based reads could have "
"been updated after we panic'd.");
return (1);
}
else
return (0);
}
static void
{
if (reason == CMD_INCOMPLETE)
}
}
static void
{
/* free dma mapping */
if (ac->buf_dma_handle) {
}
}
static int
{
struct aac_softstate *softs;
int ret_val;
int capacity;
DBCALLED();
"Cannot send cmd to invalid target: %d and lun: %d",
return (TRAN_FATAL_ERROR);
}
switch (cmd) {
case SCMD_INQUIRY: /* inquiry */
struct scsi_inquiry inq;
/* The EVDP and pagecode is not */
/* supported */
} else {
}
} else
}
break;
case SCMD_READ_CAPACITY: /* read capacity */
struct scsi_capacity cp;
} else
}
break;
case SCMD_READ: /* read_6 */
case SCMD_READ_G1: /* read_10 */
case SCMD_WRITE: /* write_6 */
case SCMD_WRITE_G1: /* write_10 */
/* fill in correct blkno */
struct aac_blockread *br =
(struct aac_blockread *) \
br->BlockNumber =
} else {
struct aac_blockwrite *bw =
(struct aac_blockwrite *) \
bw->BlockNumber =
}
/* poll pkt */
== AACOK) {
} else {
}
} else {
/* async pkt */
else
}
} else
break;
case SCMD_MODE_SENSE: /* mode_sense_6 */
case SCMD_MODE_SENSE_G1: /* mode_sense_10 */
break;
case SCMD_TEST_UNIT_READY:
case SCMD_REQUEST_SENSE:
case SCMD_FORMAT:
case SCMD_START_STOP:
case SCMD_SYNCHRONIZE_CACHE:
} else
}
}
break;
default: /* unknown command */
break;
}
return (ret_val);
}
static int
{
int rval;
struct aac_softstate *softs;
DBCALLED();
/* We don't allow inquiring about capabilities for other targets */
return (-1);
}
return (-1);
}
switch (scsi_hba_lookup_capstr(cap)) {
case SCSI_CAP_ARQ:
rval = 1;
break;
case SCSI_CAP_GEOMETRY:
break;
case SCSI_CAP_SECTOR_SIZE:
break;
case SCSI_CAP_TOTAL_SECTORS:
/* number of sectors */
break;
default:
rval = -1;
break;
}
return (rval);
}
/*ARGSUSED*/
static int
int whom)
{
int rval;
struct aac_softstate *softs;
DBCALLED();
/* We don't allow inquiring about capabilities for other targets */
"Set Cap not supported, string = %s, whom=%d",
return (-1);
}
switch (scsi_hba_lookup_capstr(cap)) {
case SCSI_CAP_ARQ:
rval = 1;
break;
case SCSI_CAP_TOTAL_SECTORS:
rval = 1;
break;
case SCSI_CAP_SECTOR_SIZE:
rval = 0;
break;
default:
rval = -1;
break;
}
return (rval);
}
static void
{
DBCALLED();
}
static struct scsi_pkt *
{
struct aac_softstate *softs;
int err;
int testflag = 0;
int slen;
DBCALLED();
return (NULL);
/* alloc pkt */
testflag = 1;
/* force auto request sense */
sizeof (struct scsi_arq_status) : statuslen;
arg);
return (NULL);
}
/*
* we will still use this point for we
* want to fake some infomation in tran_start
*/
/* set cmd flags according to pkt alloc flags */
if (flags & PKT_CONSISTENT)
if (flags & PKT_DMA_PARTIAL)
}
return (pkt);
/* we need to transfer data, so we alloc dma resources for this pkt */
/* set dma flags */
if (BUF_IS_READ(bp)) {
} else {
}
if (flags & PKT_CONSISTENT)
if (flags & PKT_DMA_PARTIAL)
/* alloc buf dma handle */
err = DDI_SUCCESS;
if (!ac->buf_dma_handle)
&ac->buf_dma_handle);
if (err != DDI_SUCCESS) {
"Can't allocate dma handle, errorno=%d", err));
return (NULL);
}
/* bind buf */
switch (err) {
case DDI_DMA_PARTIAL_MAP:
== DDI_FAILURE) {
"Cannot get number of DMA windows"));
(void) ddi_dma_unbind_handle(
ac->buf_dma_handle);
goto error_out;
}
break;
case DDI_DMA_MAPPED:
break;
default:
return (NULL);
}
}
if (transfer_num == 0) {
return (NULL);
}
"b_bcount=%d, total_xfer=%d, pkt_resid = %d",
return (pkt);
}
/*
* tran_sync_pkt(9E) - explicit DMA synchronization
*/
/*ARGSUSED*/
static void
{
DBCALLED();
if (ac->buf_dma_handle) {
}
}
/*
* tran_dmafree(9E) - deallocate DMA resources allocated for command
*/
/*ARGSUSED*/
static void
{
DBCALLED();
}
}
/*ARGSUSED*/
static int
{
return (0);
}
/*ARGSUSED*/
static int
{
return (0);
}
static int
{
int err;
return (AACERR);
if (err != DDI_SUCCESS) {
return (AACERR);
}
return (AACOK);
}
static size_t
{
int count;
struct aac_sg_table *sgmap;
struct scsi_address *ap;
return (0); /* nothing to be transfered */
/* fill in fib header */
*fib_datasizep = sizeof (struct aac_fib_header);
/* fill in fib data */
struct aac_blockread *br;
*fib_datasizep += sizeof (struct aac_blockread);
br->ContainerId =
} else {
struct aac_blockwrite *bw;
*fib_datasizep += sizeof (struct aac_blockwrite);
bw->ContainerId =
}
cur_total_xfer = 0;
if (cookien == 0) { /* we need to move window */
int err;
if (err != DDI_SUCCESS)
return (0);
} else
return (0);
}
/* move cookie and window to build s/g map */
count = 0;
count++;
cookien--;
if (cookien > 0)
else
break;
}
/* calculate fib data size */
return (ac->total_xfer);
}
static void
{
/* check wait queue, if this function do not be called only in */
/* aac_intr we will enter q_wait_mutex to check q_head again */
/* if fib can not enqueu, the card is in a abnormal */
/* status, there will be no interrupt to us */
return;
}
}
static void
{
do {
}
} while (ac);
}
static int
{
int i;
for (i = 0; i < AAC_HOST_FIBS; i++) {
if (ddi_dma_alloc_handle(
NULL,
"Cannot alloc dma handle for slot fib area"));
break;
}
if (ddi_dma_mem_alloc(
NULL,
&rlen,
"Cannot alloc mem for slot fib area"));
break;
}
NULL,
NULL,
&cookie,
&cookien) != DDI_DMA_MAPPED) {
"dma bind failed for slot fib area"));
break;
}
}
softs->free_io_slot_head = 0;
softs->total_slotn = i;
return (i);
}
static void
{
int i;
for (i = 0; i < AAC_HOST_FIBS; i++) {
if (slotp->fib_phyaddr) {
slotp->fib_phyaddr = 0;
}
}
}
static struct aac_slot *
{
int i;
return (NULL);
}
i = softs->free_io_slot_head;
}
static void
{
}
static void
{
else
}
static int
{
int ret_val;
}
}
return (ret_val);
}
static int
{
} else {
}
return (retval);
}
static void
{
}
static void
aac_daemon(void* arg)
{
int i, i_do = 0;
/* check slot */
for (i = 0; i < softs->total_slotn; i++) {
continue;
continue;
}
continue;
}
continue;
}
i_do = 1;
}
if (i_do == 1) {
i_do = 0;
}
if ((softs->timeout_id != 0) &&
}
#ifdef AAC_DEBUG
/* -------------------------debug aid functions-------------------------- */
char g_szCmdDesc[][30] = {
"UNKNOWN", "TEST UNIT READY", "INQUIRY",
"START STOP UNIT", "READ CAPACITY", "MODE SENSE",
"SCMD PRINT", "READ_6", "WRITE_6", "SYNCHRONIZE CACHE",
};
static char *
{
switch (cmd) {
case 0:
return (g_szCmdDesc[1]);
case 0x12:
return (g_szCmdDesc[2]);
case 0x1b:
return (g_szCmdDesc[3]);
case 0x25:
return (g_szCmdDesc[4]);
case 0x1a:
return (g_szCmdDesc[5]);
case 0x5e:
return (g_szCmdDesc[6]);
case 0x08:
return (g_szCmdDesc[7]);
case 0x0a:
return (g_szCmdDesc[8]);
case 0x35:
return (g_szCmdDesc[9]);
case 0x28:
return (g_szCmdDesc[10]);
case 0x2a:
return (g_szCmdDesc[11]);
case 0x1e:
return (g_szCmdDesc[12]);
default:
return (g_szCmdDesc[0]);
}
}
#endif