#include <sys/types.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/scsi/scsi.h>
#include <sys/scsi/adapters/mfi/mfi.h>
#include <sys/scsi/adapters/mfi/mfi_evt.h>
#include <sys/scsi/adapters/mfi/mfi_pd.h>
#include <sys/cpuvar.h>
#include "lmrc.h"
#include "lmrc_reg.h"
#include "lmrc_raid.h"
#include "lmrc_phys.h"
static uint32_t lmrc_read_reg(lmrc_t *, uint32_t);
static void lmrc_write_reg(lmrc_t *, uint32_t, uint32_t);
static int lmrc_transition_to_ready(lmrc_t *);
static void lmrc_process_mptmfi_passthru(lmrc_t *, lmrc_mpt_cmd_t *);
static int lmrc_poll_mfi(lmrc_t *, lmrc_mfi_cmd_t *, uint8_t);
static boolean_t lmrc_check_fw_fault(lmrc_t *);
static int lmrc_get_event_log_info(lmrc_t *, mfi_evt_log_info_t *);
static void lmrc_aen_handler(void *);
static void lmrc_complete_aen(lmrc_t *, lmrc_mfi_cmd_t *);
static int lmrc_register_aen(lmrc_t *, uint32_t);
static uint32_t
lmrc_read_reg_1(lmrc_t *lmrc, uint32_t reg)
{
uint32_t *addr = (uint32_t *)((uintptr_t)lmrc->l_regmap + reg);
return (ddi_get32(lmrc->l_reghandle, addr));
}
static uint32_t
lmrc_read_reg(lmrc_t *lmrc, uint32_t reg)
{
if (lmrc->l_class != LMRC_ACLASS_AERO)
return (lmrc_read_reg_1(lmrc, reg));
for (uint_t i = 0; i < 3; i++) {
uint32_t val = lmrc_read_reg_1(lmrc, reg);
if (val != 0)
return (val);
}
return (0);
}
static void
lmrc_write_reg(lmrc_t *lmrc, uint32_t reg, uint32_t val)
{
uint32_t *addr = (uint32_t *)((uintptr_t)lmrc->l_regmap + reg);
ddi_put32(lmrc->l_reghandle, addr, val);
}
static void
lmrc_write_reg64(lmrc_t *lmrc, uint32_t reg, uint64_t val)
{
uint64_t *addr = (uint64_t *)((uintptr_t)lmrc->l_regmap + reg);
ddi_put64(lmrc->l_reghandle, addr, val);
}
void
lmrc_disable_intr(lmrc_t *lmrc)
{
uint32_t mask = lmrc_read_reg(lmrc, MPI2_HOST_INTERRUPT_MASK_OFFSET);
mask |= MPI2_HIM_RESET_IRQ_MASK;
mask |= MPI2_HIM_REPLY_INT_MASK;
mask |= MPI2_HIM_IOC2SYS_DB_MASK;
lmrc_write_reg(lmrc, MPI2_HOST_INTERRUPT_MASK_OFFSET, mask);
(void) lmrc_read_reg(lmrc, MPI2_HOST_INTERRUPT_MASK_OFFSET);
}
void
lmrc_enable_intr(lmrc_t *lmrc)
{
uint32_t mask = lmrc_read_reg(lmrc, MPI2_HOST_INTERRUPT_MASK_OFFSET);
mask &= ~MPI2_HIM_REPLY_INT_MASK;
mask &= ~MPI2_HIM_IOC2SYS_DB_MASK;
lmrc_write_reg(lmrc, MPI2_HOST_INTERRUPT_STATUS_OFFSET, 0);
(void) lmrc_read_reg(lmrc, MPI2_HOST_INTERRUPT_STATUS_OFFSET);
lmrc_write_reg(lmrc, MPI2_HOST_INTERRUPT_MASK_OFFSET, mask);
(void) lmrc_read_reg(lmrc, MPI2_HOST_INTERRUPT_MASK_OFFSET);
}
uint_t
lmrc_intr_ack(lmrc_t *lmrc)
{
uint32_t mask =
MPI2_HIS_REPLY_DESCRIPTOR_INTERRUPT | MPI2_HIS_IOC2SYS_DB_STATUS;
uint32_t status;
status = lmrc_read_reg(lmrc, MPI2_HOST_INTERRUPT_STATUS_OFFSET);
if ((status & mask) == 0)
return (DDI_INTR_UNCLAIMED);
if (lmrc_check_acc_handle(lmrc->l_reghandle) != DDI_SUCCESS) {
ddi_fm_service_impact(lmrc->l_dip, DDI_SERVICE_LOST);
return (DDI_INTR_UNCLAIMED);
}
return (DDI_INTR_CLAIMED);
}
void
lmrc_send_atomic_request(lmrc_t *lmrc, lmrc_atomic_req_desc_t req_desc)
{
if (lmrc->l_atomic_desc_support) {
lmrc_write_reg(lmrc,
MPI26_ATOMIC_REQUEST_DESCRIPTOR_POST_OFFSET,
req_desc.rd_reg);
} else {
lmrc_req_desc_t rd;
bzero(&rd, sizeof (rd));
rd.rd_atomic = req_desc;
lmrc_send_request(lmrc, rd);
}
}
void
lmrc_send_request(lmrc_t *lmrc, lmrc_req_desc_t req_desc)
{
lmrc_write_reg64(lmrc, MPI2_REQUEST_DESCRIPTOR_POST_LOW_OFFSET,
req_desc.rd_reg);
}
lmrc_atomic_req_desc_t
lmrc_build_atomic_request(lmrc_t *lmrc, lmrc_mpt_cmd_t *mpt, uint8_t flags)
{
lmrc_atomic_req_desc_t req_desc;
VERIFY3U(mpt->mpt_smid, !=, 0);
mpt->mpt_queue = CPU->cpu_id % lmrc->l_max_reply_queues;
bzero(&req_desc, sizeof (req_desc));
req_desc.rd_atomic.RequestFlags = flags;
req_desc.rd_atomic.MSIxIndex = mpt->mpt_queue;
req_desc.rd_atomic.SMID = mpt->mpt_smid;
return (req_desc);
}
static Mpi2ReplyDescriptorsUnion_t *
lmrc_get_next_reply_desc(lmrc_t *lmrc, int queue)
{
Mpi2ReplyDescriptorsUnion_t *desc;
desc = lmrc->l_reply_dma.ld_buf;
desc += (queue * lmrc->l_reply_alloc_sz) / sizeof (*desc);
desc += lmrc->l_last_reply_idx[queue];
VERIFY3S(ddi_dma_sync(lmrc->l_reply_dma.ld_hdl,
(void *)desc - lmrc->l_reply_dma.ld_buf, sizeof (*desc),
DDI_DMA_SYNC_FORKERNEL), ==, DDI_SUCCESS);
if (desc->Words.Low == MPI2_RPY_DESCRIPT_UNUSED_WORD0_MARK ||
desc->Words.High == MPI2_RPY_DESCRIPT_UNUSED_WORD1_MARK)
return (NULL);
lmrc->l_last_reply_idx[queue]++;
if (lmrc->l_last_reply_idx[queue] >= lmrc->l_reply_q_depth)
lmrc->l_last_reply_idx[queue] = 0;
return (desc);
}
static void
lmrc_write_rphi(lmrc_t *lmrc, uint32_t queue)
{
int reg = 0;
uint32_t val = (queue << 24) | lmrc->l_last_reply_idx[queue];
if (lmrc->l_intr_type != DDI_INTR_TYPE_MSIX)
VERIFY3U(queue, ==, 0);
if (lmrc->l_msix_combined) {
reg = queue / 8;
val &= 0x07ffffff;
}
lmrc_write_reg(lmrc, lmrc->l_rphi[reg], val);
}
static void
lmrc_process_mpt_pkt(lmrc_t *lmrc, struct scsi_pkt *pkt, uint8_t status,
uint8_t ex_status, uint32_t data_len)
{
pkt->pkt_statistics = 0;
pkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD |
STATE_XFERRED_DATA | STATE_GOT_STATUS;
pkt->pkt_resid = pkt->pkt_dma_len - data_len;
switch (status) {
case MFI_STAT_OK:
case MFI_STAT_LD_CC_IN_PROGRESS:
case MFI_STAT_LD_RECON_IN_PROGRESS:
pkt->pkt_reason = CMD_CMPLT;
pkt->pkt_scbp[0] = STATUS_GOOD;
break;
case MFI_STAT_SCSI_DONE_WITH_ERROR:
case MFI_STAT_LD_LBA_OUT_OF_RANGE: {
struct scsi_arq_status *arq =
(struct scsi_arq_status *)pkt->pkt_scbp;
pkt->pkt_reason = CMD_CMPLT;
arq->sts_status.sts_chk = 1;
pkt->pkt_state |= STATE_ARQ_DONE;
arq->sts_rqpkt_reason = CMD_CMPLT;
arq->sts_rqpkt_resid = 0;
arq->sts_rqpkt_state |= STATE_GOT_BUS | STATE_GOT_TARGET |
STATE_SENT_CMD | STATE_XFERRED_DATA;
*(uint8_t *)&arq->sts_rqpkt_status = STATUS_GOOD;
break;
}
case MFI_STAT_LD_OFFLINE:
case MFI_STAT_DEVICE_NOT_FOUND:
pkt->pkt_reason = CMD_DEV_GONE;
pkt->pkt_statistics = STAT_DISCON;
break;
default:
dev_err(lmrc->l_dip, CE_WARN, "!command failed, status = %x, "
"ex_status = %x, cdb[0] = %x", status, ex_status,
pkt->pkt_cdbp[0]);
pkt->pkt_reason = CMD_TRAN_ERR;
break;
}
}
int
lmrc_poll_for_reply(lmrc_t *lmrc, lmrc_mpt_cmd_t *mpt)
{
clock_t max_wait = LMRC_IO_TIMEOUT * MILLISEC * 10;
Mpi25SCSIIORequest_t *io_req = mpt->mpt_io_frame;
Mpi2ReplyDescriptorsUnion_t *desc;
uint16_t desc_smid;
VERIFY(ddi_in_panic());
do {
desc = lmrc_get_next_reply_desc(lmrc, mpt->mpt_queue);
if (desc == NULL) {
if (max_wait == 0)
return (TRAN_FATAL_ERROR);
drv_usecwait(100);
max_wait--;
continue;
}
desc_smid = desc->SCSIIOSuccess.SMID;
desc->Words.Low = MPI2_RPY_DESCRIPT_UNUSED_WORD0_MARK;
desc->Words.High = MPI2_RPY_DESCRIPT_UNUSED_WORD1_MARK;
lmrc_write_rphi(lmrc, mpt->mpt_queue);
} while (desc == NULL || desc_smid != mpt->mpt_smid);
VERIFY3S(ddi_dma_sync(lmrc->l_ioreq_dma.ld_hdl,
(void *)io_req - lmrc->l_ioreq_dma.ld_buf,
LMRC_MPI2_RAID_DEFAULT_IO_FRAME_SIZE, DDI_DMA_SYNC_FORKERNEL),
==, DDI_SUCCESS);
if (io_req->Function == LMRC_MPI2_FUNCTION_LD_IO_REQUEST ||
io_req->Function == MPI2_FUNCTION_SCSI_IO_REQUEST) {
lmrc_process_mpt_pkt(lmrc, mpt->mpt_pkt,
io_req->VendorRegion.rc_status,
io_req->VendorRegion.rc_exstatus, io_req->DataLength);
}
return (TRAN_ACCEPT);
}
int
lmrc_process_replies(lmrc_t *lmrc, uint8_t queue)
{
int nprocessed = 0;
Mpi2ReplyDescriptorsUnion_t *desc;
for (desc = lmrc_get_next_reply_desc(lmrc, queue);
desc != NULL;
desc = lmrc_get_next_reply_desc(lmrc, queue)) {
Mpi2SCSIIOSuccessReplyDescriptor_t *reply =
&desc->SCSIIOSuccess;
uint16_t smid = reply->SMID;
lmrc_mpt_cmd_t *mpt = lmrc->l_mpt_cmds[smid - 1];
lmrc_tgt_t *tgt = NULL;
Mpi25SCSIIORequest_t *io_req;
struct scsi_pkt *pkt;
struct scsi_device *sd;
VERIFY3U(reply->SMID, <=, lmrc->l_max_fw_cmds);
mutex_enter(&mpt->mpt_lock);
mpt->mpt_complete = B_TRUE;
pkt = mpt->mpt_pkt;
io_req = mpt->mpt_io_frame;
VERIFY3S(ddi_dma_sync(lmrc->l_ioreq_dma.ld_hdl,
(void *)io_req - lmrc->l_ioreq_dma.ld_buf,
LMRC_MPI2_RAID_DEFAULT_IO_FRAME_SIZE,
DDI_DMA_SYNC_FORKERNEL), ==, DDI_SUCCESS);
switch (io_req->Function) {
case MPI2_FUNCTION_SCSI_TASK_MGMT:
VERIFY0(pkt);
VERIFY0(list_link_active(&mpt->mpt_node));
cv_signal(&mpt->mpt_cv);
break;
case MPI2_FUNCTION_SCSI_IO_REQUEST:
case LMRC_MPI2_FUNCTION_LD_IO_REQUEST:
VERIFY(pkt != NULL);
sd = scsi_address_device(&pkt->pkt_address);
VERIFY(sd != NULL);
tgt = scsi_device_hba_private_get(sd);
VERIFY(tgt != NULL);
lmrc_process_mpt_pkt(lmrc, pkt,
io_req->VendorRegion.rc_status,
io_req->VendorRegion.rc_exstatus,
io_req->DataLength);
break;
case LMRC_MPI2_FUNCTION_PASSTHRU_IO_REQUEST:
VERIFY0(pkt);
VERIFY0(list_link_active(&mpt->mpt_node));
lmrc_process_mptmfi_passthru(lmrc, mpt);
break;
default:
mutex_exit(&mpt->mpt_lock);
dev_err(lmrc->l_dip, CE_PANIC,
"reply received for unknown Function %x",
io_req->Function);
}
mutex_exit(&mpt->mpt_lock);
if (pkt != NULL) {
lmrc_tgt_rem_active_mpt(tgt, mpt);
atomic_dec_uint(&lmrc->l_fw_outstanding_cmds);
scsi_hba_pkt_comp(pkt);
}
desc->Words.Low = MPI2_RPY_DESCRIPT_UNUSED_WORD0_MARK;
desc->Words.High = MPI2_RPY_DESCRIPT_UNUSED_WORD1_MARK;
nprocessed++;
if (nprocessed % LMRC_THRESHOLD_REPLY_COUNT == 0)
lmrc_write_rphi(lmrc, queue);
}
if (nprocessed != 0 && nprocessed % LMRC_THRESHOLD_REPLY_COUNT != 0)
lmrc_write_rphi(lmrc, queue);
return (DDI_INTR_CLAIMED);
}
int
lmrc_build_mptmfi_passthru(lmrc_t *lmrc, lmrc_mfi_cmd_t *mfi)
{
Mpi25SCSIIORequest_t *io_req;
const ddi_dma_cookie_t *cookie;
lmrc_mpt_cmd_t *mpt;
mpt = lmrc_get_mpt(lmrc);
if (mpt == NULL)
return (DDI_FAILURE);
ASSERT(mutex_owned(&mpt->mpt_lock));
mfi->mfi_mpt = mpt;
mpt->mpt_mfi = mfi;
io_req = mpt->mpt_io_frame;
io_req->Function = LMRC_MPI2_FUNCTION_PASSTHRU_IO_REQUEST;
io_req->ChainOffset = lmrc->l_chain_offset_mfi_pthru;
cookie = ddi_dma_cookie_one(mfi->mfi_frame_dma.ld_hdl);
lmrc_dma_build_sgl(lmrc, mpt, cookie, 1);
VERIFY3S(ddi_dma_sync(lmrc->l_ioreq_dma.ld_hdl,
(void *)io_req - lmrc->l_ioreq_dma.ld_buf,
LMRC_MPI2_RAID_DEFAULT_IO_FRAME_SIZE, DDI_DMA_SYNC_FORDEV),
==, DDI_SUCCESS);
mutex_exit(&mpt->mpt_lock);
return (DDI_SUCCESS);
}
static void
lmrc_process_mptmfi_passthru(lmrc_t *lmrc, lmrc_mpt_cmd_t *mpt)
{
lmrc_mfi_cmd_t *mfi;
mfi_header_t *hdr;
VERIFY3P(mpt->mpt_mfi, !=, NULL);
mfi = mpt->mpt_mfi;
VERIFY0(list_link_active(&mfi->mfi_node));
hdr = &mfi->mfi_frame->mf_hdr;
if ((hdr->mh_flags & MFI_FRAME_DIR_READ) != 0)
(void) ddi_dma_sync(mfi->mfi_data_dma.ld_hdl, 0,
mfi->mfi_data_dma.ld_len, DDI_DMA_SYNC_FORKERNEL);
switch (hdr->mh_cmd) {
case MFI_CMD_DCMD:
case MFI_CMD_LD_SCSI_IO:
case MFI_CMD_PD_SCSI_IO:
case MFI_CMD_ABORT:
mutex_enter(&mfi->mfi_lock);
if (mfi->mfi_callback != NULL)
mfi->mfi_callback(lmrc, mfi);
mutex_exit(&mfi->mfi_lock);
break;
case MFI_CMD_INVALID:
default:
dev_err(lmrc->l_dip, CE_PANIC,
"invalid MFI cmd completion received, cmd = %x",
hdr->mh_cmd);
break;
}
}
void
lmrc_issue_mfi(lmrc_t *lmrc, lmrc_mfi_cmd_t *mfi, lmrc_mfi_cmd_cb_t *cb)
{
mfi_header_t *hdr = &mfi->mfi_frame->mf_hdr;
lmrc_atomic_req_desc_t req_desc;
ASSERT(mutex_owned(&mfi->mfi_lock));
if ((hdr->mh_flags & MFI_FRAME_DONT_POST_IN_REPLY_QUEUE) == 0) {
VERIFY3U(cb, !=, NULL);
mfi->mfi_callback = cb;
} else {
VERIFY3U(cb, ==, NULL);
}
hdr->mh_cmd_status = MFI_STAT_INVALID_STATUS;
req_desc = lmrc_build_atomic_request(lmrc, mfi->mfi_mpt,
MPI2_REQ_DESCRIPT_FLAGS_SCSI_IO);
(void) ddi_dma_sync(mfi->mfi_frame_dma.ld_hdl, 0,
mfi->mfi_frame_dma.ld_len, DDI_DMA_SYNC_FORDEV);
if ((hdr->mh_flags & MFI_FRAME_DIR_WRITE) != 0)
(void) ddi_dma_sync(mfi->mfi_data_dma.ld_hdl, 0,
mfi->mfi_data_dma.ld_len, DDI_DMA_SYNC_FORDEV);
lmrc_send_atomic_request(lmrc, req_desc);
}
static int
lmrc_poll_mfi(lmrc_t *lmrc, lmrc_mfi_cmd_t *mfi, uint8_t max_wait)
{
mfi_header_t *hdr = &mfi->mfi_frame->mf_hdr;
lmrc_dma_t *dma = &mfi->mfi_frame_dma;
clock_t timeout = ddi_get_lbolt() + drv_usectohz(max_wait * MICROSEC);
clock_t now;
ASSERT(mutex_owned(&mfi->mfi_lock));
do {
(void) ddi_dma_sync(dma->ld_hdl, 0, dma->ld_len,
DDI_DMA_SYNC_FORKERNEL);
if (hdr->mh_cmd_status != MFI_STAT_INVALID_STATUS)
break;
(void) cv_reltimedwait(&mfi->mfi_cv, &mfi->mfi_lock,
drv_usectohz(MILLISEC), TR_MILLISEC);
now = ddi_get_lbolt();
} while (!lmrc->l_fw_fault && now <= timeout);
if (hdr->mh_cmd_status != MFI_STAT_INVALID_STATUS)
return (DDI_SUCCESS);
if (now > timeout) {
dev_err(lmrc->l_dip, CE_WARN,
"!%s: command timeout after %ds", __func__, max_wait);
cv_signal(&lmrc->l_thread_cv);
}
return (DDI_FAILURE);
}
int
lmrc_wait_mfi(lmrc_t *lmrc, lmrc_mfi_cmd_t *mfi, uint8_t max_wait)
{
mfi_header_t *hdr = &mfi->mfi_frame->mf_hdr;
lmrc_dma_t *dma = &mfi->mfi_frame_dma;
clock_t timeout = ddi_get_lbolt() + drv_usectohz(max_wait * MICROSEC);
int ret;
ASSERT(mutex_owned(&mfi->mfi_lock));
do {
ret = cv_timedwait(&mfi->mfi_cv, &mfi->mfi_lock, timeout);
(void) ddi_dma_sync(dma->ld_hdl, 0, dma->ld_len,
DDI_DMA_SYNC_FORKERNEL);
} while (!lmrc->l_fw_fault &&
hdr->mh_cmd_status == MFI_STAT_INVALID_STATUS && ret != -1);
if (!lmrc->l_fw_fault && ret != -1)
return (DDI_SUCCESS);
if (ret == -1) {
dev_err(lmrc->l_dip, CE_WARN, "!%s: blocked command timeout "
"after %ds, cmd = %d, status = %d", __func__, max_wait,
hdr->mh_cmd, hdr->mh_cmd_status);
cv_signal(&lmrc->l_thread_cv);
}
return (DDI_FAILURE);
}
void
lmrc_wakeup_mfi(lmrc_t *lmrc, lmrc_mfi_cmd_t *cmd)
{
ASSERT(mutex_owned(&cmd->mfi_lock));
cv_signal(&cmd->mfi_cv);
}
int
lmrc_issue_blocked_mfi(lmrc_t *lmrc, lmrc_mfi_cmd_t *mfi)
{
mfi_header_t *hdr = &mfi->mfi_frame->mf_hdr;
int ret;
mutex_enter(&mfi->mfi_lock);
lmrc_issue_mfi(lmrc, mfi, lmrc_wakeup_mfi);
ret = lmrc_wait_mfi(lmrc, mfi, LMRC_INTERNAL_CMD_WAIT_TIME);
mutex_exit(&mfi->mfi_lock);
if (ret == DDI_SUCCESS && hdr->mh_cmd_status == MFI_STAT_OK)
return (DDI_SUCCESS);
dev_err(lmrc->l_dip, CE_WARN,
"!%s: blocked command failure, cmd = %d, status = %d",
__func__, hdr->mh_cmd, hdr->mh_cmd_status);
return (ret);
}
static void
lmrc_abort_cb(lmrc_t *lmrc, lmrc_mfi_cmd_t *mfi)
{
mfi_header_t *hdr = &mfi->mfi_frame->mf_hdr;
if (hdr->mh_cmd_status == MFI_STAT_OK)
hdr->mh_cmd_status = MFI_STAT_NOT_FOUND;
}
static int
lmrc_abort_cmd(lmrc_t *lmrc, lmrc_mfi_cmd_t *mfi_to_abort)
{
lmrc_mfi_cmd_t *mfi = lmrc_get_mfi(lmrc);
mfi_header_t *hdr = &mfi->mfi_frame->mf_hdr;
mfi_abort_payload_t *abort = &mfi->mfi_frame->mf_abort;
lmrc_mfi_cmd_cb_t *orig_cb = mfi_to_abort->mfi_callback;
int ret;
ASSERT(mutex_owned(&mfi_to_abort->mfi_lock));
mfi_to_abort->mfi_callback = lmrc_abort_cb;
hdr->mh_cmd = MFI_CMD_ABORT;
abort->ma_abort_context = mfi_to_abort->mfi_idx;
lmrc_dma_set_addr64(&mfi_to_abort->mfi_frame_dma,
&abort->ma_abort_mfi_phys_addr);
mutex_enter(&mfi->mfi_lock);
lmrc_issue_mfi(lmrc, mfi, lmrc_wakeup_mfi);
mutex_exit(&mfi_to_abort->mfi_lock);
ret = lmrc_wait_mfi(lmrc, mfi, LMRC_INTERNAL_CMD_WAIT_TIME);
mutex_exit(&mfi->mfi_lock);
if (ret != DDI_SUCCESS && hdr->mh_cmd_status != MFI_STAT_NOT_FOUND) {
mutex_enter(&mfi_to_abort->mfi_lock);
mfi_to_abort->mfi_callback = orig_cb;
goto out;
}
mutex_enter(&mfi_to_abort->mfi_lock);
ret = lmrc_poll_mfi(lmrc, mfi_to_abort, LMRC_INTERNAL_CMD_WAIT_TIME);
mfi_to_abort->mfi_callback = orig_cb;
if (ret != DDI_SUCCESS)
goto out;
if (mfi_to_abort->mfi_callback != NULL)
mfi_to_abort->mfi_callback(lmrc, mfi_to_abort);
out:
lmrc_put_mfi(mfi);
ASSERT(mutex_owned(&mfi_to_abort->mfi_lock));
return (ret);
}
static boolean_t
lmrc_check_fw_fault(lmrc_t *lmrc)
{
uint32_t status = lmrc_read_reg(lmrc, MPI26_SCRATCHPAD0_OFFSET);
uint32_t fw_state = LMRC_FW_STATE(status);
if (fw_state == LMRC_FW_STATE_FAULT)
return (B_TRUE);
return (B_FALSE);
}
static boolean_t
lmrc_wait_for_reg(lmrc_t *lmrc, uint32_t reg, uint32_t bits, uint32_t exp,
uint64_t max_wait)
{
uint32_t val;
uint64_t i;
max_wait *= MILLISEC / 100;
for (i = 0; i < max_wait; i++) {
delay(drv_usectohz(100 * MILLISEC));
val = lmrc_read_reg(lmrc, reg);
if ((val & bits) == exp)
return (B_TRUE);
}
return (B_FALSE);
}
static int
lmrc_hard_reset(lmrc_t *lmrc)
{
int ret = DDI_SUCCESS;
lmrc_write_reg(lmrc, MPI2_WRITE_SEQUENCE_OFFSET,
MPI2_WRSEQ_FLUSH_KEY_VALUE);
lmrc_write_reg(lmrc, MPI2_WRITE_SEQUENCE_OFFSET,
MPI2_WRSEQ_1ST_KEY_VALUE);
lmrc_write_reg(lmrc, MPI2_WRITE_SEQUENCE_OFFSET,
MPI2_WRSEQ_2ND_KEY_VALUE);
lmrc_write_reg(lmrc, MPI2_WRITE_SEQUENCE_OFFSET,
MPI2_WRSEQ_3RD_KEY_VALUE);
lmrc_write_reg(lmrc, MPI2_WRITE_SEQUENCE_OFFSET,
MPI2_WRSEQ_4TH_KEY_VALUE);
lmrc_write_reg(lmrc, MPI2_WRITE_SEQUENCE_OFFSET,
MPI2_WRSEQ_5TH_KEY_VALUE);
lmrc_write_reg(lmrc, MPI2_WRITE_SEQUENCE_OFFSET,
MPI2_WRSEQ_6TH_KEY_VALUE);
if (!lmrc_wait_for_reg(lmrc, MPI2_HOST_DIAGNOSTIC_OFFSET,
MPI2_DIAG_DIAG_WRITE_ENABLE, MPI2_DIAG_DIAG_WRITE_ENABLE,
LMRC_RESET_TIMEOUT)) {
dev_err(lmrc->l_dip, CE_WARN, "diag unlock failed");
return (DDI_FAILURE);
}
lmrc_write_reg(lmrc, MPI2_HOST_DIAGNOSTIC_OFFSET,
lmrc_read_reg(lmrc, MPI2_HOST_DIAGNOSTIC_OFFSET) |
MPI2_DIAG_RESET_ADAPTER);
delay(drv_usectohz(MPI2_HARD_RESET_PCIE_FIRST_READ_DELAY_MICRO_SEC));
if ((lmrc_read_reg(lmrc, MPI2_HOST_DIAGNOSTIC_OFFSET) &
MPI2_DIAG_RESET_ADAPTER) == 0)
goto out;
delay(drv_usectohz(MPI2_HARD_RESET_PCIE_SECOND_READ_DELAY_MICRO_SEC));
if ((lmrc_read_reg(lmrc, MPI2_HOST_DIAGNOSTIC_OFFSET) &
MPI2_DIAG_RESET_ADAPTER) == 0)
goto out;
ret = DDI_FAILURE;
out:
lmrc_write_reg(lmrc, MPI2_WRITE_SEQUENCE_OFFSET,
MPI2_WRSEQ_FLUSH_KEY_VALUE);
return (ret);
}
static int
lmrc_reset_ctrl(lmrc_t *lmrc)
{
uint32_t status, fw_state, reset_adapter;
int max_wait, i;
if (lmrc->l_disable_online_ctrl_reset)
return (DDI_FAILURE);
status = lmrc_read_reg(lmrc, MPI26_SCRATCHPAD0_OFFSET);
fw_state = LMRC_FW_STATE(status);
reset_adapter = LMRC_FW_RESET_ADAPTER(status);
if (fw_state == LMRC_FW_STATE_FAULT && reset_adapter == 0) {
dev_err(lmrc->l_dip, CE_WARN,
"FW in fault state, but reset not supported");
goto out;
}
for (i = 0; i < LMRC_MAX_RESET_TRIES; i++) {
dev_err(lmrc->l_dip, CE_WARN, "resetting...");
if (lmrc_hard_reset(lmrc) != DDI_SUCCESS)
continue;
max_wait = LMRC_IO_TIMEOUT * MILLISEC / 100;
do {
status = lmrc_read_reg(lmrc, MPI26_SCRATCHPAD0_OFFSET);
fw_state = LMRC_FW_STATE(status);
if (fw_state <= LMRC_FW_STATE_FW_INIT)
delay(drv_usectohz(100 * MILLISEC));
} while (fw_state <= LMRC_FW_STATE_FW_INIT && max_wait > 0);
if (fw_state <= LMRC_FW_STATE_FW_INIT) {
dev_err(lmrc->l_dip, CE_WARN,
"fw state <= LMRC_FW_STATE_FW_INIT, state = %x",
fw_state);
continue;
}
return (DDI_SUCCESS);
}
dev_err(lmrc->l_dip, CE_WARN, "reset failed");
out:
lmrc_write_reg(lmrc, MPI2_DOORBELL_OFFSET, MFI_STOP_ADP);
(void) lmrc_read_reg(lmrc, MPI2_DOORBELL_OFFSET);
return (DDI_FAILURE);
}
static void
lmrc_tgt_complete_cmd(void *arg)
{
lmrc_scsa_cmd_t *cmd = arg;
struct scsi_pkt *pkt;
lmrc_t *lmrc;
mutex_enter(&cmd->sc_mpt->mpt_lock);
if (cmd->sc_mpt->mpt_complete) {
mutex_exit(&cmd->sc_mpt->mpt_lock);
return;
}
lmrc = cmd->sc_mpt->mpt_lmrc;
pkt = cmd->sc_mpt->mpt_pkt;
pkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET | STATE_SENT_CMD;
pkt->pkt_reason = CMD_RESET;
pkt->pkt_statistics = STAT_BUS_RESET;
mutex_exit(&cmd->sc_mpt->mpt_lock);
lmrc_tgt_rem_active_mpt(cmd->sc_tgt, cmd->sc_mpt);
atomic_dec_uint(&lmrc->l_fw_outstanding_cmds);
scsi_hba_pkt_comp(pkt);
}
static void
lmrc_tgt_complete_cmds(lmrc_t *lmrc, lmrc_tgt_t *tgt)
{
lmrc_mpt_cmd_t *mpt;
mutex_enter(&tgt->tgt_mpt_active_lock);
if (list_is_empty(&tgt->tgt_mpt_active)) {
mutex_exit(&tgt->tgt_mpt_active_lock);
return;
}
for (mpt = lmrc_tgt_first_active_mpt(tgt);
mpt != NULL;
mpt = lmrc_tgt_next_active_mpt(tgt, mpt)) {
lmrc_scsa_cmd_t *cmd = mpt->mpt_pkt->pkt_ha_private;
ASSERT(mutex_owned(&mpt->mpt_lock));
VERIFY(mpt->mpt_pkt != NULL);
VERIFY(cmd != NULL);
if (mpt->mpt_complete)
continue;
taskq_dispatch_ent(lmrc->l_taskq, lmrc_tgt_complete_cmd, cmd,
TQ_NOSLEEP, &mpt->mpt_tqent);
}
mutex_exit(&tgt->tgt_mpt_active_lock);
}
static int
lmrc_tgt_timeout_cmds(lmrc_t *lmrc, lmrc_tgt_t *tgt)
{
lmrc_mpt_cmd_t *mpt;
int ret = DDI_SUCCESS;
mutex_enter(&tgt->tgt_mpt_active_lock);
if (list_is_empty(&tgt->tgt_mpt_active))
goto out;
for (mpt = lmrc_tgt_first_active_mpt(tgt);
mpt != NULL;
mpt = lmrc_tgt_next_active_mpt(tgt, mpt)) {
hrtime_t now;
ASSERT(mutex_owned(&mpt->mpt_lock));
VERIFY(mpt->mpt_pkt != NULL);
if (mpt->mpt_complete)
continue;
now = gethrtime();
if (now > mpt->mpt_timeout) {
mpt->mpt_timeout = now + LMRC_IO_TIMEOUT * NANOSEC;
if (lmrc_abort_mpt(lmrc, tgt, mpt) != 1) {
mutex_exit(&mpt->mpt_lock);
ret = DDI_FAILURE;
goto out;
}
}
}
out:
mutex_exit(&tgt->tgt_mpt_active_lock);
return (ret);
}
void
lmrc_thread(void *arg)
{
lmrc_t *lmrc = arg;
do {
int i;
mutex_enter(&lmrc->l_thread_lock);
(void) cv_reltimedwait(&lmrc->l_thread_cv, &lmrc->l_thread_lock,
drv_usectohz(60 * MICROSEC), TR_SEC);
mutex_exit(&lmrc->l_thread_lock);
if (lmrc->l_thread_stop)
continue;
lmrc->l_fw_fault = lmrc_check_fw_fault(lmrc);
if (!lmrc->l_fw_fault) {
for (i = 0; i < ARRAY_SIZE(lmrc->l_targets); i++) {
if (lmrc_tgt_timeout_cmds(lmrc,
&lmrc->l_targets[i]) != DDI_SUCCESS) {
lmrc->l_fw_fault = B_TRUE;
break;
}
}
}
if (lmrc->l_fw_fault) {
int ret;
lmrc_disable_intr(lmrc);
ret = lmrc_reset_ctrl(lmrc);
(void) lmrc_abort_outstanding_mfi(lmrc,
LMRC_MAX_MFI_CMDS);
for (i = 0; i < ARRAY_SIZE(lmrc->l_targets); i++)
lmrc_tgt_complete_cmds(lmrc,
&lmrc->l_targets[i]);
if (ret != DDI_SUCCESS) {
dev_err(lmrc->l_dip, CE_WARN, "reset failed");
continue;
}
if (lmrc_transition_to_ready(lmrc) != DDI_SUCCESS)
continue;
if (lmrc_ioc_init(lmrc) != DDI_SUCCESS)
continue;
lmrc_enable_intr(lmrc);
if (lmrc_start_aen(lmrc) != DDI_SUCCESS) {
dev_err(lmrc->l_dip, CE_WARN,
"failed to re-initiate AEN");
continue;
}
lmrc->l_fw_fault = lmrc_check_fw_fault(lmrc);
}
} while (!lmrc->l_thread_stop);
thread_exit();
}
static int
lmrc_transition_to_ready(lmrc_t *lmrc)
{
uint32_t status, new_status;
uint32_t fw_state;
uint8_t max_wait;
uint_t i;
status = lmrc_read_reg(lmrc, MPI26_SCRATCHPAD0_OFFSET);
fw_state = LMRC_FW_STATE(status);
max_wait = LMRC_RESET_TIMEOUT;
while (fw_state != LMRC_FW_STATE_READY) {
switch (fw_state) {
case LMRC_FW_STATE_FAULT:
dev_err(lmrc->l_dip, CE_NOTE, "FW is in fault state!");
if (lmrc_reset_ctrl(lmrc) != DDI_SUCCESS)
return (DDI_FAILURE);
break;
case LMRC_FW_STATE_WAIT_HANDSHAKE:
lmrc_write_reg(lmrc, MPI2_DOORBELL_OFFSET,
MFI_INIT_CLEAR_HANDSHAKE | MFI_INIT_HOTPLUG);
break;
case LMRC_FW_STATE_BOOT_MSG_PENDING:
lmrc_write_reg(lmrc, MPI2_DOORBELL_OFFSET,
MFI_INIT_HOTPLUG);
break;
case LMRC_FW_STATE_OPERATIONAL:
lmrc_disable_intr(lmrc);
lmrc_write_reg(lmrc, MPI2_DOORBELL_OFFSET,
MFI_RESET_FLAGS);
(void) lmrc_wait_for_reg(lmrc, MPI2_DOORBELL_OFFSET, 1,
0, 10);
break;
case LMRC_FW_STATE_UNDEFINED:
case LMRC_FW_STATE_BB_INIT:
case LMRC_FW_STATE_FW_INIT:
case LMRC_FW_STATE_FW_INIT_2:
case LMRC_FW_STATE_DEVICE_SCAN:
case LMRC_FW_STATE_FLUSH_CACHE:
break;
default:
dev_err(lmrc->l_dip, CE_WARN, "Unknown FW state %x",
fw_state);
return (DDI_FAILURE);
}
for (i = 0; i < max_wait * 1000; i++) {
new_status = lmrc_read_reg(lmrc,
MPI26_SCRATCHPAD0_OFFSET);
if (status != new_status)
break;
delay(drv_usectohz(MILLISEC));
}
if (new_status == status) {
dev_err(lmrc->l_dip, CE_WARN,
"FW state (%x) hasn't changed in %d seconds",
fw_state, max_wait);
return (DDI_FAILURE);
}
status = new_status;
fw_state = LMRC_FW_STATE(status);
}
if (lmrc_check_acc_handle(lmrc->l_reghandle) != DDI_FM_OK)
return (DDI_FAILURE);
return (DDI_SUCCESS);
}
int
lmrc_adapter_init(lmrc_t *lmrc)
{
uint32_t reg;
int ret;
int i;
ret = lmrc_transition_to_ready(lmrc);
if (ret != DDI_SUCCESS)
return (ret);
reg = lmrc_read_reg(lmrc, MPI26_SCRATCHPAD2_OFFSET);
lmrc->l_max_raid_map_sz = LMRC_MAX_RAID_MAP_SZ(reg);
lmrc->l_max_reply_queues = 1;
lmrc->l_rphi[0] = MPI2_REPLY_POST_HOST_INDEX_OFFSET;
reg = lmrc_read_reg(lmrc, MPI26_SCRATCHPAD0_OFFSET);
if (LMRC_FW_MSIX_ENABLED(reg)) {
lmrc->l_fw_msix_enabled = B_TRUE;
reg = lmrc_read_reg(lmrc, MPI26_SCRATCHPAD1_OFFSET);
lmrc->l_max_reply_queues = LMRC_MAX_REPLY_QUEUES_EXT(reg);
if (lmrc->l_max_reply_queues > LMRC_MAX_REPLY_POST_HOST_INDEX) {
lmrc->l_msix_combined = B_TRUE;
lmrc->l_rphi[0] =
MPI25_SUP_REPLY_POST_HOST_INDEX_OFFSET;
}
for (i = 1; i < LMRC_MAX_REPLY_POST_HOST_INDEX; i++) {
lmrc->l_rphi[i] = i * 0x10 +
MPI25_SUP_REPLY_POST_HOST_INDEX_OFFSET;
}
}
reg = lmrc_read_reg(lmrc, MPI26_SCRATCHPAD0_OFFSET);
lmrc->l_max_fw_cmds = LMRC_FW_MAX_CMD(reg) - 1;
if (lmrc->l_max_fw_cmds < LMRC_MAX_MFI_CMDS) {
dev_err(lmrc->l_dip, CE_WARN, "!max_fw_cmds too low: %d",
lmrc->l_max_fw_cmds);
return (DDI_FAILURE);
}
lmrc->l_max_scsi_cmds = lmrc->l_max_fw_cmds - LMRC_MAX_MFI_CMDS;
lmrc->l_reply_q_depth = (lmrc->l_max_fw_cmds + 1 + 15) / 16 * 16 * 2;
lmrc->l_reply_alloc_sz =
sizeof (Mpi2ReplyDescriptorsUnion_t) * lmrc->l_reply_q_depth;
lmrc->l_io_frames_alloc_sz = LMRC_MPI2_RAID_DEFAULT_IO_FRAME_SIZE *
(lmrc->l_max_fw_cmds + 2);
reg = lmrc_read_reg(lmrc, MPI26_SCRATCHPAD1_OFFSET);
lmrc->l_max_chain_frame_sz = LMRC_MAX_CHAIN_SIZE(reg) *
(LMRC_EXT_CHAIN_SIZE_SUPPORT(reg) ? LMRC_1MB_IO : LMRC_256K_IO);
lmrc->l_64bit_dma_support = LMRC_64BIT_DMA_SUPPORT(reg);
lmrc->l_max_sge_in_main_msg =
(LMRC_MPI2_RAID_DEFAULT_IO_FRAME_SIZE -
offsetof(Mpi25SCSIIORequest_t, SGL)) / sizeof (Mpi25SGEIOUnion_t);
lmrc->l_max_sge_in_chain =
lmrc->l_max_chain_frame_sz / sizeof (Mpi25SGEIOUnion_t);
lmrc->l_max_num_sge =
lmrc->l_max_sge_in_main_msg + lmrc->l_max_sge_in_chain - 2;
lmrc->l_chain_offset_io_request =
(LMRC_MPI2_RAID_DEFAULT_IO_FRAME_SIZE -
sizeof (Mpi25SGEIOUnion_t)) / sizeof (Mpi25SGEIOUnion_t);
lmrc->l_chain_offset_mfi_pthru =
offsetof(Mpi25SCSIIORequest_t, SGL) / sizeof (Mpi25SGEIOUnion_t);
reg = lmrc_read_reg(lmrc, MPI26_SCRATCHPAD3_OFFSET);
if (LMRC_NVME_PAGE_SHIFT(reg) > LMRC_DEFAULT_NVME_PAGE_SHIFT) {
lmrc->l_nvme_page_sz = 1 << LMRC_NVME_PAGE_SHIFT(reg);
dev_err(lmrc->l_dip, CE_NOTE, "!NVME page size: %ld",
lmrc->l_nvme_page_sz);
}
reg = lmrc_read_reg(lmrc, MPI26_SCRATCHPAD1_OFFSET);
lmrc->l_fw_sync_cache_support = LMRC_SYNC_CACHE_SUPPORT(reg);
if (lmrc->l_class == LMRC_ACLASS_AERO) {
reg = lmrc_read_reg(lmrc, MPI26_SCRATCHPAD1_OFFSET);
lmrc->l_atomic_desc_support =
LMRC_ATOMIC_DESCRIPTOR_SUPPORT(reg);
}
return (DDI_SUCCESS);
}
int
lmrc_ioc_init(lmrc_t *lmrc)
{
lmrc_mfi_cmd_t *mfi = lmrc_get_mfi(lmrc);
mfi_header_t *hdr = &mfi->mfi_frame->mf_hdr;
mfi_init_payload_t *init = &mfi->mfi_frame->mf_init;
lmrc_req_desc_t req_desc;
Mpi2IOCInitRequest_t *IOCInitMsg;
lmrc_dma_t dma;
int ret = DDI_SUCCESS;
ret = lmrc_dma_alloc(lmrc, lmrc->l_dma_attr, &dma,
sizeof (Mpi2IOCInitRequest_t), 256, DDI_DMA_CONSISTENT);
if (ret != DDI_SUCCESS) {
lmrc_put_mfi(mfi);
dev_err(lmrc->l_dip, CE_WARN,
"!%s: failed to allocate IOC command", __func__);
return (DDI_FAILURE);
}
IOCInitMsg = dma.ld_buf;
IOCInitMsg->Function = MPI2_FUNCTION_IOC_INIT;
IOCInitMsg->WhoInit = MPI2_WHOINIT_HOST_DRIVER;
IOCInitMsg->MsgVersion = MPI2_VERSION;
IOCInitMsg->HeaderVersion = MPI2_HEADER_VERSION;
IOCInitMsg->SystemRequestFrameSize =
LMRC_MPI2_RAID_DEFAULT_IO_FRAME_SIZE / 4;
IOCInitMsg->ReplyDescriptorPostQueueDepth = lmrc->l_reply_q_depth;
lmrc_dma_set_addr64(&lmrc->l_reply_dma,
(uint64_t *)&IOCInitMsg->ReplyDescriptorPostQueueAddress);
lmrc_dma_set_addr64(&lmrc->l_ioreq_dma,
(uint64_t *)&IOCInitMsg->SystemRequestFrameBaseAddress);
IOCInitMsg->HostMSIxVectors = lmrc->l_max_reply_queues;
IOCInitMsg->HostPageSize = LMRC_DEFAULT_NVME_PAGE_SHIFT;
hdr->mh_cmd = MFI_CMD_INIT;
hdr->mh_cmd_status = MFI_STAT_INVALID_STATUS;
hdr->mh_flags = MFI_FRAME_DONT_POST_IN_REPLY_QUEUE;
hdr->mh_drv_opts.mc_support_additional_msix = 1;
hdr->mh_drv_opts.mc_support_max_255lds = 1;
hdr->mh_drv_opts.mc_support_ndrive_r1_lb = 1;
hdr->mh_drv_opts.mc_support_security_protocol_cmds_fw = 1;
hdr->mh_drv_opts.mc_support_ext_io_size = 1;
hdr->mh_data_xfer_len = lmrc_dma_get_size(&dma);
lmrc_dma_set_addr64(&dma, &init->mi_queue_info_new_phys_addr);
lmrc_dma_set_addr64(&mfi->mfi_frame_dma, &req_desc.rd_reg);
VERIFY0(req_desc.rd_mfa_io.RequestFlags);
req_desc.rd_mfa_io.RequestFlags = LMRC_REQ_DESCRIPT_FLAGS_MFA;
lmrc_disable_intr(lmrc);
if (!lmrc_wait_for_reg(lmrc, MPI2_DOORBELL_OFFSET, 1, 0, 10))
return (DDI_FAILURE);
(void) ddi_dma_sync(dma.ld_hdl, 0, dma.ld_len, DDI_DMA_SYNC_FORDEV);
(void) ddi_dma_sync(mfi->mfi_frame_dma.ld_hdl, 0,
mfi->mfi_frame_dma.ld_len, DDI_DMA_SYNC_FORDEV);
lmrc_send_request(lmrc, req_desc);
mutex_enter(&mfi->mfi_lock);
ret = lmrc_poll_mfi(lmrc, mfi, LMRC_INTERNAL_CMD_WAIT_TIME);
mutex_exit(&mfi->mfi_lock);
if (ret != DDI_SUCCESS) {
if (hdr->mh_cmd_status != MFI_STAT_INVALID_STATUS)
dev_err(lmrc->l_dip, CE_WARN,
"!IOC Init failed, status = 0x%x",
hdr->mh_cmd_status);
}
lmrc_dma_free(&dma);
lmrc_put_mfi(mfi);
return (ret);
}
static int
lmrc_get_ctrl_info(lmrc_t *lmrc)
{
mfi_ctrl_info_t *ci = lmrc->l_ctrl_info;
lmrc_mfi_cmd_t *mfi;
int ret;
mfi = lmrc_get_dcmd(lmrc, MFI_FRAME_DIR_READ, MFI_DCMD_CTRL_GET_INFO,
sizeof (mfi_ctrl_info_t), 1);
if (mfi == NULL)
return (DDI_FAILURE);
ret = lmrc_issue_blocked_mfi(lmrc, mfi);
if (ret != DDI_SUCCESS)
goto out;
(void) ddi_dma_sync(mfi->mfi_data_dma.ld_hdl, 0,
mfi->mfi_data_dma.ld_len, DDI_DMA_SYNC_FORKERNEL);
bcopy(mfi->mfi_data_dma.ld_buf, ci, sizeof (mfi_ctrl_info_t));
out:
lmrc_put_dcmd(lmrc, mfi);
return (ret);
}
int
lmrc_fw_init(lmrc_t *lmrc)
{
int drv_max_lds = MFI_MAX_LOGICAL_DRIVES;
mfi_ctrl_info_t *ci = lmrc->l_ctrl_info;
int ret;
ret = lmrc_get_ctrl_info(lmrc);
if (ret != DDI_SUCCESS) {
dev_err(lmrc->l_dip, CE_WARN, "!Unable to get FW ctrl info.");
return (DDI_FAILURE);
}
lmrc->l_disable_online_ctrl_reset =
ci->ci_prop.cp_disable_online_ctrl_reset == 1;
lmrc->l_max_256_vd_support =
ci->ci_adapter_opts3.ao3_support_max_ext_lds == 1;
if (ci->ci_max_lds > 64) {
lmrc->l_max_256_vd_support = B_TRUE;
drv_max_lds = LMRC_MAX_LOGICAL_DRIVES_EXT;
}
lmrc->l_fw_supported_vd_count = min(ci->ci_max_lds, drv_max_lds);
lmrc->l_fw_supported_pd_count = min(ci->ci_max_pds,
MFI_MAX_PHYSICAL_DRIVES);
lmrc->l_max_map_sz = lmrc->l_current_map_sz =
lmrc->l_max_raid_map_sz * LMRC_MIN_MAP_SIZE;
lmrc->l_use_seqnum_jbod_fp =
ci->ci_adapter_opts3.ao3_use_seq_num_jbod_FP != 0;
lmrc->l_pdmap_tgtid_support =
ci->ci_adapter_opts4.ao4_support_pd_map_target_id != 0;
return (DDI_SUCCESS);
}
int
lmrc_ctrl_shutdown(lmrc_t *lmrc)
{
lmrc_mfi_cmd_t *mfi = list_remove_head(&lmrc->l_mfi_cmd_list);
mfi_header_t *hdr;
mfi_dcmd_payload_t *dcmd;
if (mfi == NULL)
return (DDI_FAILURE);
hdr = &mfi->mfi_frame->mf_hdr;
dcmd = &mfi->mfi_frame->mf_dcmd;
hdr->mh_cmd = MFI_CMD_DCMD;
hdr->mh_flags = MFI_FRAME_DONT_POST_IN_REPLY_QUEUE;
dcmd->md_opcode = MFI_DCMD_CTRL_SHUTDOWN;
lmrc_disable_intr(lmrc);
lmrc_issue_mfi(lmrc, mfi, NULL);
return (DDI_SUCCESS);
}
void
lmrc_tgt_init(lmrc_tgt_t *tgt, uint16_t dev_id, char *addr,
mfi_pd_info_t *pd_info)
{
rw_enter(&tgt->tgt_lock, RW_WRITER);
bzero(&tgt->tgt_dev_id,
sizeof (lmrc_tgt_t) - offsetof(lmrc_tgt_t, tgt_dev_id));
tgt->tgt_dev_id = dev_id;
tgt->tgt_pd_info = pd_info;
tgt->tgt_interconnect_type = INTERCONNECT_SAS;
if (pd_info == NULL) {
tgt->tgt_type = DTYPE_DIRECT;
} else {
tgt->tgt_type = pd_info->pd_scsi_dev_type;
}
(void) strlcpy(tgt->tgt_wwnstr, addr, sizeof (tgt->tgt_wwnstr));
if (scsi_wwnstr_to_wwn(tgt->tgt_wwnstr, &tgt->tgt_wwn) != DDI_SUCCESS) {
tgt->tgt_interconnect_type = INTERCONNECT_PARALLEL;
tgt->tgt_wwn = dev_id;
}
rw_exit(&tgt->tgt_lock);
}
void
lmrc_tgt_clear(lmrc_tgt_t *tgt)
{
rw_enter(&tgt->tgt_lock, RW_WRITER);
if (tgt->tgt_pd_info != NULL)
kmem_free(tgt->tgt_pd_info, sizeof (mfi_pd_info_t));
bzero(&tgt->tgt_dev_id,
sizeof (lmrc_tgt_t) - offsetof(lmrc_tgt_t, tgt_dev_id));
tgt->tgt_dev_id = LMRC_DEVHDL_INVALID;
rw_exit(&tgt->tgt_lock);
}
lmrc_tgt_t *
lmrc_tgt_find(lmrc_t *lmrc, struct scsi_device *sd)
{
const char *ua = scsi_device_unit_address(sd);
char *comma, wwnstr[SCSI_WWN_BUFLEN];
uint64_t wwn;
unsigned long tgtid;
lmrc_tgt_t *tgt;
size_t i;
VERIFY(ua != NULL);
(void) strlcpy(wwnstr, ua, sizeof (wwnstr));
if (ddi_strtoul(wwnstr, &comma, 10, &tgtid) == 0 &&
*comma == ',' &&
tgtid <= lmrc->l_fw_supported_vd_count) {
tgt = &lmrc->l_targets[tgtid];
rw_enter(&tgt->tgt_lock, RW_READER);
if (tgt->tgt_dev_id == tgtid &&
tgt->tgt_wwn == tgtid) {
return (tgt);
}
rw_exit(&tgt->tgt_lock);
}
comma = strchr(wwnstr, ',');
if (comma != NULL)
*comma = '\0';
if (scsi_wwnstr_to_wwn(wwnstr, &wwn) == DDI_SUCCESS) {
for (i = 0; i < ARRAY_SIZE(lmrc->l_targets); i++) {
tgt = &lmrc->l_targets[i];
rw_enter(&tgt->tgt_lock, RW_READER);
if (tgt->tgt_wwn == wwn) {
return (tgt);
}
rw_exit(&tgt->tgt_lock);
}
} else {
for (i = 0; i < ARRAY_SIZE(lmrc->l_targets); i++) {
tgt = &lmrc->l_targets[i];
rw_enter(&tgt->tgt_lock, RW_READER);
if (strcmp(tgt->tgt_wwnstr, wwnstr) == 0) {
return (tgt);
}
rw_exit(&tgt->tgt_lock);
}
}
return (NULL);
}
lmrc_mpt_cmd_t *
lmrc_get_mpt(lmrc_t *lmrc)
{
lmrc_mpt_cmd_t *mpt;
Mpi25SCSIIORequest_t *io_req;
mutex_enter(&lmrc->l_mpt_cmd_lock);
mpt = list_remove_head(&lmrc->l_mpt_cmd_list);
mutex_exit(&lmrc->l_mpt_cmd_lock);
if (mpt == NULL)
return (NULL);
mutex_enter(&mpt->mpt_lock);
bzero(mpt->mpt_io_frame, LMRC_MPI2_RAID_DEFAULT_IO_FRAME_SIZE);
bzero(mpt->mpt_chain_dma.ld_buf, mpt->mpt_chain_dma.ld_len);
bzero(mpt->mpt_sense_dma.ld_buf, mpt->mpt_sense_dma.ld_len);
mpt->mpt_mfi = NULL;
mpt->mpt_pkt = NULL;
io_req = mpt->mpt_io_frame;
io_req->SGLOffset0 = offsetof(Mpi25SCSIIORequest_t, SGL) / 4;
mpt->mpt_complete = B_FALSE;
cv_init(&mpt->mpt_cv, NULL, CV_DRIVER, NULL);
return (mpt);
}
void
lmrc_put_mpt(lmrc_mpt_cmd_t *mpt)
{
lmrc_t *lmrc = mpt->mpt_lmrc;
VERIFY(lmrc != NULL);
ASSERT0(list_link_active(&mpt->mpt_node));
ASSERT(mutex_owned(&mpt->mpt_lock));
cv_destroy(&mpt->mpt_cv);
mutex_enter(&lmrc->l_mpt_cmd_lock);
list_insert_tail(&lmrc->l_mpt_cmd_list, mpt);
mutex_exit(&lmrc->l_mpt_cmd_lock);
mutex_exit(&mpt->mpt_lock);
}
lmrc_mfi_cmd_t *
lmrc_get_mfi(lmrc_t *lmrc)
{
lmrc_mfi_cmd_t *mfi;
mutex_enter(&lmrc->l_mfi_cmd_lock);
mfi = list_remove_head(&lmrc->l_mfi_cmd_list);
mutex_exit(&lmrc->l_mfi_cmd_lock);
VERIFY(mfi != NULL);
mutex_enter(&mfi->mfi_lock);
bzero(mfi->mfi_frame, sizeof (mfi_frame_t));
mfi->mfi_frame->mf_hdr.mh_context = mfi->mfi_idx;
mfi->mfi_callback = NULL;
cv_init(&mfi->mfi_cv, NULL, CV_DRIVER, NULL);
mutex_exit(&mfi->mfi_lock);
return (mfi);
}
void
lmrc_put_mfi(lmrc_mfi_cmd_t *mfi)
{
lmrc_t *lmrc = mfi->mfi_lmrc;
VERIFY(lmrc != NULL);
ASSERT0(list_link_active(&mfi->mfi_node));
mutex_enter(&mfi->mfi_lock);
cv_destroy(&mfi->mfi_cv);
mutex_enter(&lmrc->l_mfi_cmd_lock);
list_insert_tail(&lmrc->l_mfi_cmd_list, mfi);
mutex_exit(&lmrc->l_mfi_cmd_lock);
mutex_exit(&mfi->mfi_lock);
}
int
lmrc_abort_outstanding_mfi(lmrc_t *lmrc, const size_t ncmd)
{
int ret;
int i;
for (i = 0; i < ncmd; i++) {
lmrc_mfi_cmd_t *mfi = lmrc->l_mfi_cmds[i];
mutex_enter(&mfi->mfi_lock);
if (list_link_active(&mfi->mfi_node)) {
mutex_exit(&mfi->mfi_lock);
continue;
}
if (lmrc->l_fw_fault) {
if (mfi->mfi_callback != NULL)
mfi->mfi_callback(lmrc, mfi);
mutex_exit(&mfi->mfi_lock);
continue;
}
ret = lmrc_abort_cmd(lmrc, mfi);
mutex_exit(&mfi->mfi_lock);
if (ret != DDI_SUCCESS)
return (ret);
lmrc_dma_free(&mfi->mfi_data_dma);
lmrc_put_mfi(mfi);
}
return (DDI_SUCCESS);
}
lmrc_mfi_cmd_t *
lmrc_get_dcmd(lmrc_t *lmrc, uint16_t flags, uint32_t opcode, uint32_t xferlen,
uint_t align)
{
lmrc_mfi_cmd_t *mfi = lmrc_get_mfi(lmrc);
mfi_header_t *hdr = &mfi->mfi_frame->mf_hdr;
mfi_dcmd_payload_t *dcmd = &mfi->mfi_frame->mf_dcmd;
lmrc_dma_t *dma = &mfi->mfi_data_dma;
int ret;
hdr->mh_cmd = MFI_CMD_DCMD;
hdr->mh_flags = flags;
dcmd->md_opcode = opcode;
if ((flags & MFI_FRAME_DIR_READ) != 0 ||
(flags & MFI_FRAME_DIR_WRITE) != 0) {
ret = lmrc_dma_alloc(lmrc, lmrc->l_dma_attr, dma, xferlen,
align, DDI_DMA_CONSISTENT);
if (ret != DDI_SUCCESS) {
lmrc_put_mfi(mfi);
return (NULL);
}
hdr->mh_flags |= MFI_FRAME_SGL64;
hdr->mh_sge_count = 1;
hdr->mh_data_xfer_len = lmrc_dma_get_size(dma);
dcmd->md_sgl.ms64_length = lmrc_dma_get_size(dma);
lmrc_dma_set_addr64(dma, &dcmd->md_sgl.ms64_phys_addr);
}
return (mfi);
}
void
lmrc_put_dcmd(lmrc_t *lmrc, lmrc_mfi_cmd_t *mfi)
{
lmrc_dma_free(&mfi->mfi_data_dma);
lmrc_put_mfi(mfi);
}
static int
lmrc_get_event_log_info(lmrc_t *lmrc, mfi_evt_log_info_t *eli)
{
lmrc_mfi_cmd_t *mfi;
int ret;
mfi = lmrc_get_dcmd(lmrc, MFI_FRAME_DIR_READ,
MFI_DCMD_CTRL_EVENT_GET_INFO, sizeof (mfi_evt_log_info_t), 1);
if (mfi == NULL)
return (DDI_FAILURE);
ret = lmrc_issue_blocked_mfi(lmrc, mfi);
if (ret != DDI_SUCCESS)
goto out;
bcopy(mfi->mfi_data_dma.ld_buf, eli, sizeof (mfi_evt_log_info_t));
out:
lmrc_put_dcmd(lmrc, mfi);
return (ret);
}
static void
lmrc_aen_handler(void *arg)
{
lmrc_mfi_cmd_t *mfi = arg;
lmrc_t *lmrc = mfi->mfi_lmrc;
mfi_evt_detail_t *evt = mfi->mfi_data_dma.ld_buf;
mfi_dcmd_payload_t *dcmd = &mfi->mfi_frame->mf_dcmd;
int ret = DDI_FAILURE;
switch (evt->evt_code) {
case MFI_EVT_CFG_CLEARED:
case MFI_EVT_CTRL_HOST_BUS_SCAN_REQD:
case MFI_EVT_FOREIGN_CFG_IMPORTED:
ret = lmrc_get_pd_list(lmrc);
if (ret != DDI_SUCCESS)
break;
ret = lmrc_get_ld_list(lmrc);
break;
case MFI_EVT_CTRL_PROP_CHANGED:
ret = lmrc_get_ctrl_info(lmrc);
break;
case MFI_EVT_CTRL_PATROL_READ_START:
case MFI_EVT_CTRL_PATROL_READ_RESUMED:
case MFI_EVT_CTRL_PATROL_READ_COMPLETE:
case MFI_EVT_CTRL_PATROL_READ_CANT_START:
case MFI_EVT_CTRL_PERF_COLLECTION:
case MFI_EVT_CTRL_BOOTDEV_SET:
case MFI_EVT_CTRL_BOOTDEV_RESET:
case MFI_EVT_CTRL_PERSONALITY_CHANGE:
case MFI_EVT_CTRL_PERSONALITY_CHANGE_PEND:
case MFI_EVT_CTRL_NR_OF_VALID_SNAPDUMP:
break;
default:
if ((evt->evt_cl.evt_locale & MFI_EVT_LOCALE_LD) != 0)
ret = lmrc_raid_aen_handler(lmrc, evt);
else if ((evt->evt_cl.evt_locale & MFI_EVT_LOCALE_PD) != 0)
ret = lmrc_phys_aen_handler(lmrc, evt);
if (ret != DDI_SUCCESS) {
dev_err(lmrc->l_dip, CE_NOTE, "!unknown AEN received, "
"seqnum = %d, timestamp = %d, code = %x, "
"locale = %x, class = %d, argtype = %d",
evt->evt_seqnum, evt->evt_timestamp, evt->evt_code,
evt->evt_cl.evt_locale, evt->evt_cl.evt_class,
evt->evt_argtype);
}
}
dev_err(lmrc->l_dip, CE_NOTE, "!%s", evt->evt_descr);
dcmd->md_mbox_32[0] = evt->evt_seqnum + 1;
mutex_enter(&mfi->mfi_lock);
lmrc_issue_mfi(lmrc, mfi, lmrc_complete_aen);
mutex_exit(&mfi->mfi_lock);
}
static void
lmrc_complete_aen(lmrc_t *lmrc, lmrc_mfi_cmd_t *mfi)
{
mfi_header_t *hdr = &mfi->mfi_frame->mf_hdr;
ASSERT(mutex_owned(&mfi->mfi_lock));
if (hdr->mh_cmd_status != MFI_STAT_OK) {
if (hdr->mh_cmd_status == MFI_STAT_NOT_FOUND)
return;
dev_err(lmrc->l_dip, CE_WARN,
"!AEN failed, status = %d",
hdr->mh_cmd_status);
taskq_dispatch_ent(lmrc->l_taskq, (task_func_t *)lmrc_put_mfi,
mfi, TQ_NOSLEEP, &mfi->mfi_tqent);
return;
}
taskq_dispatch_ent(lmrc->l_taskq, lmrc_aen_handler, mfi, TQ_NOSLEEP,
&mfi->mfi_tqent);
}
static int
lmrc_register_aen(lmrc_t *lmrc, uint32_t seqnum)
{
lmrc_mfi_cmd_t *mfi;
mfi_dcmd_payload_t *dcmd;
mfi = lmrc_get_dcmd(lmrc, MFI_FRAME_DIR_READ, MFI_DCMD_CTRL_EVENT_WAIT,
sizeof (mfi_evt_detail_t), 1);
if (mfi == NULL)
return (DDI_FAILURE);
dcmd = &mfi->mfi_frame->mf_dcmd;
dcmd->md_mbox_32[0] = seqnum;
dcmd->md_mbox_16[2] = MFI_EVT_LOCALE_ALL;
dcmd->md_mbox_8[7] = MFI_EVT_CLASS_DEBUG;
mutex_enter(&mfi->mfi_lock);
lmrc_issue_mfi(lmrc, mfi, lmrc_complete_aen);
mutex_exit(&mfi->mfi_lock);
return (DDI_SUCCESS);
}
int
lmrc_start_aen(lmrc_t *lmrc)
{
mfi_evt_log_info_t eli;
int ret;
bzero(&eli, sizeof (eli));
ret = lmrc_get_event_log_info(lmrc, &eli);
if (ret != DDI_SUCCESS)
return (ret);
ret = lmrc_register_aen(lmrc, eli.eli_newest_seqnum + 1);
return (ret);
}