#include <sys/scsi/adapters/smrt/smrt.h>
static int smrt_ctlr_versions(smrt_t *, uint16_t, smrt_versions_t *);
static void smrt_discover(void *);
unsigned smrt_ciss_init_time = 90;
uint_t smrt_event_intervention_threshold = 1000;
uint16_t
smrt_lun_addr_to_bmic(PhysDevAddr_t *paddr)
{
uint16_t id;
id = (paddr->Target[1].PeripDev.Bus - 1) << 8;
id += paddr->Target[1].PeripDev.Dev;
return (id);
}
void
smrt_write_lun_addr_phys(LUNAddr_t *lun, boolean_t masked, unsigned bus,
unsigned target)
{
lun->PhysDev.Mode = masked ? MASK_PERIPHERIAL_DEV_ADDR :
PERIPHERIAL_DEV_ADDR;
lun->PhysDev.TargetId = target;
lun->PhysDev.Bus = bus;
bzero(&lun->PhysDev.Target, sizeof (lun->PhysDev.Target));
}
void
smrt_write_controller_lun_addr(LUNAddr_t *lun)
{
smrt_write_lun_addr_phys(lun, B_TRUE, 0, 0);
}
void
smrt_write_message_common(smrt_command_t *smcm, uint8_t type, int timeout_secs)
{
switch (type) {
case CISS_MSG_ABORT:
case CISS_MSG_RESET:
case CISS_MSG_NOP:
break;
default:
panic("unknown message type");
}
smcm->smcm_va_cmd->Request.Type.Type = CISS_TYPE_MSG;
smcm->smcm_va_cmd->Request.Type.Attribute = CISS_ATTR_HEADOFQUEUE;
smcm->smcm_va_cmd->Request.Type.Direction = CISS_XFER_NONE;
smcm->smcm_va_cmd->Request.Timeout = LE_16(timeout_secs);
smcm->smcm_va_cmd->Request.CDBLen = CISS_CDBLEN;
smcm->smcm_va_cmd->Request.CDB[0] = type;
}
void
smrt_write_message_abort_one(smrt_command_t *smcm, uint32_t tag)
{
smrt_tag_t cisstag;
smrt_write_lun_addr_phys(&smcm->smcm_va_cmd->Header.LUN,
B_TRUE, 0, 0);
smrt_write_message_common(smcm, CISS_MSG_ABORT, 0);
smcm->smcm_va_cmd->Request.CDB[1] = CISS_ABORT_TASK;
bzero(&cisstag, sizeof (cisstag));
cisstag.tag_value = tag;
bcopy(&cisstag, &smcm->smcm_va_cmd->Request.CDB[4],
sizeof (cisstag));
}
void
smrt_write_message_abort_all(smrt_command_t *smcm, LUNAddr_t *addr)
{
smcm->smcm_va_cmd->Header.LUN = *addr;
smrt_write_message_common(smcm, CISS_MSG_ABORT, 0);
smcm->smcm_va_cmd->Request.CDB[1] = CISS_ABORT_TASKSET;
}
void
smrt_write_message_event_notify(smrt_command_t *smcm)
{
smrt_event_notify_req_t senr;
smrt_write_controller_lun_addr(&smcm->smcm_va_cmd->Header.LUN);
smcm->smcm_va_cmd->Request.Type.Type = CISS_TYPE_CMD;
smcm->smcm_va_cmd->Request.Type.Attribute = CISS_ATTR_ORDERED;
smcm->smcm_va_cmd->Request.Type.Direction = CISS_XFER_READ;
smcm->smcm_va_cmd->Request.Timeout = 0;
smcm->smcm_va_cmd->Request.CDBLen = sizeof (senr);
bzero(&senr, sizeof (senr));
senr.senr_opcode = CISS_SCMD_READ;
senr.senr_subcode = CISS_BMIC_NOTIFY_ON_EVENT;
senr.senr_flags = BE_32(0);
senr.senr_size = BE_32(SMRT_EVENT_NOTIFY_BUFLEN);
bcopy(&senr, &smcm->smcm_va_cmd->Request.CDB[0],
MIN(CISS_CDBLEN, sizeof (senr)));
}
void
smrt_write_message_cancel_event_notify(smrt_command_t *smcm)
{
smrt_event_notify_req_t senr;
smrt_write_controller_lun_addr(&smcm->smcm_va_cmd->Header.LUN);
smcm->smcm_va_cmd->Request.Type.Type = CISS_TYPE_CMD;
smcm->smcm_va_cmd->Request.Type.Attribute = CISS_ATTR_ORDERED;
smcm->smcm_va_cmd->Request.Type.Direction = CISS_XFER_WRITE;
smcm->smcm_va_cmd->Request.Timeout = LE_16(SMRT_ASYNC_CANCEL_TIMEOUT);
smcm->smcm_va_cmd->Request.CDBLen = sizeof (senr);
bzero(&senr, sizeof (senr));
senr.senr_opcode = CISS_SCMD_WRITE;
senr.senr_subcode = CISS_BMIC_NOTIFY_ON_EVENT_CANCEL;
senr.senr_size = BE_32(SMRT_EVENT_NOTIFY_BUFLEN);
bcopy(&senr, &smcm->smcm_va_cmd->Request.CDB[0],
MIN(CISS_CDBLEN, sizeof (senr)));
}
void
smrt_write_message_reset_ctlr(smrt_command_t *smcm)
{
smrt_write_lun_addr_phys(&smcm->smcm_va_cmd->Header.LUN,
B_TRUE, 0, 0);
smrt_write_message_common(smcm, CISS_MSG_RESET, 0);
smcm->smcm_va_cmd->Request.CDB[1] = CISS_RESET_CTLR;
}
void
smrt_write_message_nop(smrt_command_t *smcm, int timeout_secs)
{
smrt_write_lun_addr_phys(&smcm->smcm_va_cmd->Header.LUN,
B_TRUE, 0, 0);
smrt_write_message_common(smcm, CISS_MSG_NOP, timeout_secs);
}
void
smrt_periodic(void *arg)
{
smrt_t *smrt = arg;
mutex_enter(&smrt->smrt_mutex);
if ((smrt->smrt_status & SMRT_CTLR_DISCOVERY_PERIODIC) != 0 &&
(smrt->smrt_status & SMRT_CTLR_DISCOVERY_RUNNING) == 0 &&
(smrt->smrt_status & SMRT_CTLR_STATUS_RESETTING) == 0) {
if (ddi_taskq_dispatch(smrt->smrt_discover_taskq,
smrt_discover, smrt, DDI_NOSLEEP) != DDI_SUCCESS) {
smrt->smrt_stats.smrts_discovery_tq_errors++;
} else {
smrt->smrt_status &= ~SMRT_CTLR_DISCOVERY_PERIODIC;
}
}
if (!(smrt->smrt_status & SMRT_CTLR_STATUS_RUNNING)) {
mutex_exit(&smrt->smrt_mutex);
return;
}
smrt_lockup_check(smrt);
smrt->smrt_event_count = 0;
for (smrt_command_t *smcm = avl_first(&smrt->smrt_inflight);
smcm != NULL; smcm = AVL_NEXT(&smrt->smrt_inflight, smcm)) {
if (smcm->smcm_status & SMRT_CMD_STATUS_POLLED) {
continue;
}
if (smcm->smcm_status & SMRT_CMD_STATUS_ABORT_SENT) {
continue;
}
if (list_link_active(&smcm->smcm_link_abort)) {
continue;
}
if (smcm->smcm_expiry == 0) {
continue;
}
if (gethrtime() > smcm->smcm_expiry) {
list_insert_tail(&smrt->smrt_abortq, smcm);
smcm->smcm_status |= SMRT_CMD_STATUS_TIMEOUT;
}
}
(void) smrt_process_abortq(smrt);
if (smrt->smrt_status & SMRT_CTLR_ASYNC_INTERVENTION) {
smrt->smrt_stats.smrts_events_intervened++;
if (smrt_submit(smrt, smrt->smrt_event_cmd) == 0) {
smrt->smrt_status &= ~SMRT_CTLR_ASYNC_INTERVENTION;
}
}
mutex_exit(&smrt->smrt_mutex);
}
int
smrt_retrieve(smrt_t *smrt)
{
VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
switch (smrt->smrt_ctlr_mode) {
case SMRT_CTLR_MODE_SIMPLE:
smrt_retrieve_simple(smrt);
return (DDI_SUCCESS);
case SMRT_CTLR_MODE_UNKNOWN:
break;
}
panic("unknown controller mode");
}
static void
smrt_set_new_tag(smrt_t *smrt, smrt_command_t *smcm)
{
VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
for (;;) {
uint32_t new_tag = smrt->smrt_next_tag;
if (++smrt->smrt_next_tag > SMRT_MAX_TAG_NUMBER) {
smrt->smrt_next_tag = SMRT_MIN_TAG_NUMBER;
}
if (smrt_lookup_inflight(smrt, new_tag) != NULL) {
continue;
}
smcm->smcm_tag = new_tag;
smcm->smcm_va_cmd->Header.Tag.tag_value = new_tag;
return;
}
}
int
smrt_submit(smrt_t *smrt, smrt_command_t *smcm)
{
VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
VERIFY(smcm->smcm_type != SMRT_CMDTYPE_PREINIT);
if (smcm->smcm_status & SMRT_CMD_IGNORE_RUNNING) {
VERIFY(smcm->smcm_status & SMRT_CMD_STATUS_POLLED);
}
if (!(smrt->smrt_status & SMRT_CTLR_STATUS_RUNNING) &&
!(smcm->smcm_status & SMRT_CMD_IGNORE_RUNNING)) {
return (EIO);
}
if (avl_numnodes(&smrt->smrt_inflight) >= smrt->smrt_maxcmds) {
return (EAGAIN);
}
if (ddi_dma_sync(smcm->smcm_contig.smdma_dma_handle, 0, 0,
DDI_DMA_SYNC_FORDEV) != DDI_SUCCESS) {
dev_err(smrt->smrt_dip, CE_PANIC, "DMA sync failure");
return (EIO);
}
VERIFY(!(smcm->smcm_status & SMRT_CMD_STATUS_USED));
smcm->smcm_status |= SMRT_CMD_STATUS_USED;
smrt_set_new_tag(smrt, smcm);
avl_index_t where;
if (avl_find(&smrt->smrt_inflight, smcm, &where) != NULL) {
dev_err(smrt->smrt_dip, CE_PANIC, "duplicate submit tag %x",
smcm->smcm_tag);
}
avl_insert(&smrt->smrt_inflight, smcm, where);
if (smrt->smrt_stats.smrts_max_inflight <
avl_numnodes(&smrt->smrt_inflight)) {
smrt->smrt_stats.smrts_max_inflight =
avl_numnodes(&smrt->smrt_inflight);
}
VERIFY(!(smcm->smcm_status & SMRT_CMD_STATUS_INFLIGHT));
smcm->smcm_status |= SMRT_CMD_STATUS_INFLIGHT;
smcm->smcm_time_submit = gethrtime();
switch (smrt->smrt_ctlr_mode) {
case SMRT_CTLR_MODE_SIMPLE:
smrt_submit_simple(smrt, smcm);
return (0);
case SMRT_CTLR_MODE_UNKNOWN:
break;
}
panic("unknown controller mode");
}
static void
smrt_process_finishq_sync(smrt_command_t *smcm)
{
smrt_t *smrt = smcm->smcm_ctlr;
if (ddi_dma_sync(smcm->smcm_contig.smdma_dma_handle, 0, 0,
DDI_DMA_SYNC_FORCPU) != DDI_SUCCESS) {
dev_err(smrt->smrt_dip, CE_PANIC, "finishq DMA sync failure");
}
}
static void
smrt_process_finishq_one(smrt_command_t *smcm)
{
smrt_t *smrt = smcm->smcm_ctlr;
VERIFY(!(smcm->smcm_status & SMRT_CMD_STATUS_COMPLETE));
smcm->smcm_status |= SMRT_CMD_STATUS_COMPLETE;
switch (smcm->smcm_type) {
case SMRT_CMDTYPE_INTERNAL:
cv_broadcast(&smcm->smcm_ctlr->smrt_cv_finishq);
return;
case SMRT_CMDTYPE_SCSA:
smrt_hba_complete(smcm);
return;
case SMRT_CMDTYPE_EVENT:
smrt_event_complete(smcm);
return;
case SMRT_CMDTYPE_ABORTQ:
mutex_exit(&smrt->smrt_mutex);
smrt_command_free(smcm);
mutex_enter(&smrt->smrt_mutex);
return;
case SMRT_CMDTYPE_PREINIT:
dev_err(smrt->smrt_dip, CE_PANIC, "preinit command "
"completed after initialisation");
return;
}
panic("unknown command type");
}
void
smrt_process_finishq(smrt_t *smrt)
{
smrt_command_t *smcm;
VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
while ((smcm = list_remove_head(&smrt->smrt_finishq)) != NULL) {
smrt_process_finishq_sync(smcm);
if (list_link_active(&smcm->smcm_link_abort)) {
list_remove(&smrt->smrt_abortq, smcm);
smcm->smcm_status &= ~SMRT_CMD_STATUS_TIMEOUT;
}
if (smcm->smcm_status & SMRT_CMD_STATUS_ABANDONED) {
mutex_exit(&smrt->smrt_mutex);
smrt_command_free(smcm);
mutex_enter(&smrt->smrt_mutex);
continue;
}
if (smcm->smcm_status & SMRT_CMD_STATUS_POLLED) {
smcm->smcm_status |= SMRT_CMD_STATUS_POLL_COMPLETE;
continue;
}
smrt_process_finishq_one(smcm);
}
cv_broadcast(&smrt->smrt_cv_finishq);
}
void
smrt_process_abortq(smrt_t *smrt)
{
smrt_command_t *smcm;
smrt_command_t *abort_smcm = NULL;
VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
if (list_is_empty(&smrt->smrt_abortq)) {
goto out;
}
another:
mutex_exit(&smrt->smrt_mutex);
if ((abort_smcm = smrt_command_alloc(smrt, SMRT_CMDTYPE_ABORTQ,
KM_NOSLEEP)) == NULL) {
mutex_enter(&smrt->smrt_mutex);
goto out;
}
mutex_enter(&smrt->smrt_mutex);
while ((smcm = list_remove_head(&smrt->smrt_abortq)) != NULL) {
if (!(smcm->smcm_status & SMRT_CMD_STATUS_INFLIGHT)) {
continue;
}
if (smcm->smcm_status & SMRT_CMD_STATUS_ABORT_SENT) {
continue;
}
smrt_write_message_abort_one(abort_smcm, smcm->smcm_tag);
if (smrt_submit(smrt, abort_smcm) != 0) {
list_insert_head(&smrt->smrt_abortq, smcm);
goto out;
}
smcm->smcm_status |= SMRT_CMD_STATUS_ABORT_SENT;
smcm->smcm_abort_time = gethrtime();
smcm->smcm_abort_tag = abort_smcm->smcm_tag;
abort_smcm = NULL;
goto another;
}
out:
cv_broadcast(&smrt->smrt_cv_finishq);
if (abort_smcm != NULL) {
mutex_exit(&smrt->smrt_mutex);
smrt_command_free(abort_smcm);
mutex_enter(&smrt->smrt_mutex);
}
}
int
smrt_poll_for(smrt_t *smrt, smrt_command_t *smcm)
{
VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
VERIFY(smcm->smcm_status & SMRT_CMD_STATUS_POLLED);
while (!(smcm->smcm_status & SMRT_CMD_STATUS_POLL_COMPLETE)) {
if (smcm->smcm_expiry != 0) {
if (smcm->smcm_expiry < gethrtime()) {
return (ETIMEDOUT);
}
}
if (ddi_in_panic()) {
(void) smrt_retrieve(smrt);
smrt_process_finishq(smrt);
drv_usecwait(100);
continue;
}
if (smcm->smcm_expiry == 0) {
cv_wait(&smrt->smrt_cv_finishq, &smrt->smrt_mutex);
} else {
(void) cv_timedwait_sig_hrtime(&smrt->smrt_cv_finishq,
&smrt->smrt_mutex, smcm->smcm_expiry);
}
}
smrt_process_finishq_one(smcm);
return (0);
}
void
smrt_intr_set(smrt_t *smrt, boolean_t enabled)
{
uint32_t imr = smrt_get32(smrt, CISS_I2O_INTERRUPT_MASK);
switch (smrt->smrt_ctlr_mode) {
case SMRT_CTLR_MODE_SIMPLE:
if (enabled) {
imr &= ~CISS_IMR_BIT_SIMPLE_INTR_DISABLE;
} else {
imr |= CISS_IMR_BIT_SIMPLE_INTR_DISABLE;
}
smrt_put32(smrt, CISS_I2O_INTERRUPT_MASK, imr);
return;
case SMRT_CTLR_MODE_UNKNOWN:
break;
}
panic("unknown controller mode");
}
int
smrt_cfgtbl_flush(smrt_t *smrt)
{
uint32_t idr = smrt_get32(smrt, CISS_I2O_INBOUND_DOORBELL);
idr |= CISS_IDR_BIT_CFGTBL_CHANGE;
smrt_put32(smrt, CISS_I2O_INBOUND_DOORBELL, idr);
for (unsigned i = 0; i < smrt_ciss_init_time; i++) {
idr = smrt_get32(smrt, CISS_I2O_INBOUND_DOORBELL);
if ((idr & CISS_IDR_BIT_CFGTBL_CHANGE) == 0) {
return (DDI_SUCCESS);
}
delay(drv_usectohz(1000000));
}
dev_err(smrt->smrt_dip, CE_WARN, "time out expired before controller "
"configuration completed");
return (DDI_FAILURE);
}
int
smrt_cfgtbl_transport_has_support(smrt_t *smrt, int xport)
{
VERIFY(xport == CISS_CFGTBL_XPORT_SIMPLE);
uint32_t xport_active = ddi_get32(smrt->smrt_ct_handle,
&smrt->smrt_ct->TransportSupport);
if ((xport_active & xport) == 0) {
dev_err(smrt->smrt_dip, CE_WARN, "controller does not support "
"method \"%s\"", xport == CISS_CFGTBL_XPORT_SIMPLE ?
"simple" : "performant");
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
void
smrt_cfgtbl_transport_set(smrt_t *smrt, int xport)
{
VERIFY(xport == CISS_CFGTBL_XPORT_SIMPLE);
ddi_put32(smrt->smrt_ct_handle, &smrt->smrt_ct->TransportRequest,
xport);
}
int
smrt_cfgtbl_transport_confirm(smrt_t *smrt, int xport)
{
VERIFY(xport == CISS_CFGTBL_XPORT_SIMPLE);
uint32_t xport_active = ddi_get32(smrt->smrt_ct_handle,
&smrt->smrt_ct->TransportActive);
if ((xport_active & xport) == 0) {
dev_err(smrt->smrt_dip, CE_WARN, "failed to enable transport "
"method \"%s\"", xport == CISS_CFGTBL_XPORT_SIMPLE ?
"simple" : "performant");
return (DDI_FAILURE);
}
if ((xport_active & CISS_CFGTBL_READY_FOR_COMMANDS) == 0) {
dev_err(smrt->smrt_dip, CE_WARN, "controller not ready to "
"accept commands");
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
uint32_t
smrt_ctlr_get_maxsgelements(smrt_t *smrt)
{
return (ddi_get32(smrt->smrt_ct_handle, &smrt->smrt_ct->MaxSGElements));
}
uint32_t
smrt_ctlr_get_cmdsoutmax(smrt_t *smrt)
{
return (ddi_get32(smrt->smrt_ct_handle, &smrt->smrt_ct->CmdsOutMax));
}
static uint32_t
smrt_ctlr_get_hostdrvsup(smrt_t *smrt)
{
return (ddi_get32(smrt->smrt_ct_handle,
&smrt->smrt_ct->HostDrvrSupport));
}
int
smrt_ctlr_init(smrt_t *smrt)
{
uint8_t signature[4] = { 'C', 'I', 'S', 'S' };
int e;
if ((e = smrt_ctlr_wait_for_state(smrt,
SMRT_WAIT_STATE_READY)) != DDI_SUCCESS) {
return (e);
}
for (unsigned i = 0; i < 4; i++) {
if (ddi_get8(smrt->smrt_ct_handle,
&smrt->smrt_ct->Signature[i]) != signature[i]) {
dev_err(smrt->smrt_dip, CE_WARN, "invalid signature "
"detected");
return (DDI_FAILURE);
}
}
if ((e = smrt_ctlr_init_simple(smrt)) != DDI_SUCCESS) {
return (e);
}
smrt->smrt_host_support = smrt_ctlr_get_hostdrvsup(smrt);
smrt->smrt_bus_support = ddi_get32(smrt->smrt_ct_handle,
&smrt->smrt_ct->BusTypes);
smrt->smrt_last_heartbeat = ddi_get32(smrt->smrt_ct_handle,
&smrt->smrt_ct->HeartBeat);
smrt->smrt_last_heartbeat_time = gethrtime();
if ((e = smrt_ctlr_versions(smrt, SMRT_DISCOVER_TIMEOUT,
&smrt->smrt_versions)) != 0) {
dev_err(smrt->smrt_dip, CE_WARN, "could not identify "
"controller (%d)", e);
return (DDI_FAILURE);
}
dev_err(smrt->smrt_dip, CE_NOTE, "!firmware rev %s",
smrt->smrt_versions.smrtv_firmware_rev);
return (DDI_SUCCESS);
}
void
smrt_ctlr_teardown(smrt_t *smrt)
{
smrt->smrt_status &= ~SMRT_CTLR_STATUS_RUNNING;
switch (smrt->smrt_ctlr_mode) {
case SMRT_CTLR_MODE_SIMPLE:
smrt_ctlr_teardown_simple(smrt);
return;
case SMRT_CTLR_MODE_UNKNOWN:
return;
}
panic("unknown controller mode");
}
int
smrt_ctlr_wait_for_state(smrt_t *smrt, smrt_wait_state_t state)
{
unsigned wait_usec = 100 * 1000;
unsigned wait_count = SMRT_WAIT_DELAY_SECONDS * 1000000 / wait_usec;
VERIFY(state == SMRT_WAIT_STATE_READY ||
state == SMRT_WAIT_STATE_UNREADY);
for (unsigned i = 0; i < wait_count; i++) {
uint32_t spr = smrt_get32(smrt, CISS_I2O_SCRATCHPAD);
switch (state) {
case SMRT_WAIT_STATE_READY:
if (spr == CISS_SCRATCHPAD_INITIALISED) {
return (DDI_SUCCESS);
}
break;
case SMRT_WAIT_STATE_UNREADY:
if (spr != CISS_SCRATCHPAD_INITIALISED) {
return (DDI_SUCCESS);
}
break;
}
if (ddi_in_panic()) {
drv_usecwait(wait_usec);
} else {
delay(drv_usectohz(wait_usec));
}
}
dev_err(smrt->smrt_dip, CE_WARN, "time out waiting for controller "
"to enter state \"%s\"", state == SMRT_WAIT_STATE_READY ?
"ready": "unready");
return (DDI_FAILURE);
}
void
smrt_lockup_check(smrt_t *smrt)
{
uint32_t heartbeat = ddi_get32(smrt->smrt_ct_handle,
&smrt->smrt_ct->HeartBeat);
VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
if (heartbeat != smrt->smrt_last_heartbeat) {
smrt->smrt_last_heartbeat = heartbeat;
smrt->smrt_last_heartbeat_time = gethrtime();
return;
}
uint32_t odr = smrt_get32(smrt, CISS_I2O_OUTBOUND_DOORBELL_STATUS);
uint32_t spr = smrt_get32(smrt, CISS_I2O_SCRATCHPAD);
if ((odr & CISS_ODR_BIT_LOCKUP) != 0) {
dev_err(smrt->smrt_dip, CE_PANIC, "HP SmartArray firmware has "
"reported a critical fault (odr %08x spr %08x)",
odr, spr);
}
if (gethrtime() > smrt->smrt_last_heartbeat_time + 60 * NANOSEC) {
dev_err(smrt->smrt_dip, CE_PANIC, "HP SmartArray firmware has "
"stopped responding (odr %08x spr %08x)",
odr, spr);
}
}
static int
smrt_ctlr_identify(smrt_t *smrt, uint16_t timeout,
smrt_identify_controller_t *resp)
{
smrt_command_t *smcm;
smrt_identify_controller_req_t smicr;
int r;
size_t sz;
sz = P2ROUNDUP_TYPED(sizeof (*resp), 512, size_t);
if ((smcm = smrt_command_alloc_preinit(smrt, sz, KM_SLEEP)) == NULL) {
return (ENOMEM);
}
smrt_write_controller_lun_addr(&smcm->smcm_va_cmd->Header.LUN);
smcm->smcm_va_cmd->Request.CDBLen = sizeof (smicr);
smcm->smcm_va_cmd->Request.Timeout = timeout;
smcm->smcm_va_cmd->Request.Type.Type = CISS_TYPE_CMD;
smcm->smcm_va_cmd->Request.Type.Attribute = CISS_ATTR_SIMPLE;
smcm->smcm_va_cmd->Request.Type.Direction = CISS_XFER_READ;
bzero(&smicr, sizeof (smicr));
smicr.smicr_opcode = CISS_SCMD_BMIC_READ;
smicr.smicr_lun = 0;
smicr.smicr_command = CISS_BMIC_IDENTIFY_CONTROLLER;
bcopy(&smicr, &smcm->smcm_va_cmd->Request.CDB[0],
MIN(CISS_CDBLEN, sizeof (smicr)));
smcm->smcm_status |= SMRT_CMD_STATUS_POLLED;
smcm->smcm_expiry = gethrtime() + timeout * NANOSEC;
if ((r = smrt_preinit_command_simple(smrt, smcm)) != 0) {
VERIFY3S(r, ==, ETIMEDOUT);
VERIFY0(smcm->smcm_status & SMRT_CMD_STATUS_POLL_COMPLETE);
smcm = NULL;
goto out;
}
if (smcm->smcm_status & SMRT_CMD_STATUS_RESET_SENT) {
r = EIO;
goto out;
}
if (smcm->smcm_status & SMRT_CMD_STATUS_ERROR) {
ErrorInfo_t *ei = smcm->smcm_va_err;
if (ei->CommandStatus != CISS_CMD_DATA_UNDERRUN) {
dev_err(smrt->smrt_dip, CE_WARN, "identify "
"controller error: status 0x%x",
ei->CommandStatus);
r = EIO;
goto out;
}
}
if (resp != NULL) {
bcopy(smcm->smcm_internal->smcmi_va, resp, sizeof (*resp));
}
r = 0;
out:
if (smcm != NULL) {
smrt_command_free(smcm);
}
return (r);
}
static void
smrt_copy_firmware_version(uint8_t *src, char *dst)
{
for (unsigned i = 0; i < 4; i++) {
char c = src[i] <= 0x7f ? (char)(src[i] & 0x7f) : '?';
if (isalnum(c) || c == '.' || c == ' ') {
dst[i] = c;
} else {
dst[i] = '?';
}
}
dst[4] = '\0';
}
static int
smrt_ctlr_versions(smrt_t *smrt, uint16_t timeout, smrt_versions_t *smrtv)
{
smrt_identify_controller_t smic;
int r;
if ((r = smrt_ctlr_identify(smrt, timeout, &smic)) != 0) {
return (r);
}
smrtv->smrtv_hardware_version = smic.smic_hardware_version;
smrt_copy_firmware_version(smic.smic_firmware_rev,
smrtv->smrtv_firmware_rev);
smrt_copy_firmware_version(smic.smic_recovery_rev,
smrtv->smrtv_recovery_rev);
smrt_copy_firmware_version(smic.smic_bootblock_rev,
smrtv->smrtv_bootblock_rev);
return (0);
}
int
smrt_ctlr_reset(smrt_t *smrt)
{
smrt_command_t *smcm, *smcm_nop;
int r;
VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
if (ddi_in_panic()) {
goto skip_check;
}
if (smrt->smrt_status & SMRT_CTLR_STATUS_RESETTING) {
while (smrt->smrt_status & SMRT_CTLR_STATUS_RESETTING) {
cv_wait(&smrt->smrt_cv_finishq, &smrt->smrt_mutex);
}
return (0);
}
smrt->smrt_status |= SMRT_CTLR_STATUS_RESETTING;
smrt->smrt_last_reset_start = gethrtime();
smrt->smrt_stats.smrts_ctlr_resets++;
skip_check:
mutex_exit(&smrt->smrt_mutex);
if ((smcm = smrt_command_alloc(smrt, SMRT_CMDTYPE_INTERNAL,
KM_NOSLEEP)) == NULL) {
mutex_enter(&smrt->smrt_mutex);
return (ENOMEM);
}
if ((smcm_nop = smrt_command_alloc(smrt, SMRT_CMDTYPE_INTERNAL,
KM_NOSLEEP)) == NULL) {
smrt_command_free(smcm);
mutex_enter(&smrt->smrt_mutex);
return (ENOMEM);
}
mutex_enter(&smrt->smrt_mutex);
smrt_write_message_reset_ctlr(smcm);
smrt_intr_set(smrt, B_FALSE);
dev_err(smrt->smrt_dip, CE_WARN, "attempting controller soft reset");
smcm->smcm_status |= SMRT_CMD_STATUS_POLLED;
if ((r = smrt_submit(smrt, smcm)) != 0) {
dev_err(smrt->smrt_dip, CE_PANIC, "soft reset failed: "
"submit failed (%d)", r);
}
for (smrt_command_t *t = avl_first(&smrt->smrt_inflight);
t != NULL; t = AVL_NEXT(&smrt->smrt_inflight, t)) {
t->smcm_status |= SMRT_CMD_STATUS_RESET_SENT;
}
smrt->smrt_status &= ~SMRT_CTLR_STATUS_RUNNING;
smcm->smcm_status |= SMRT_CMD_STATUS_ABANDONED;
if (smrt_ctlr_wait_for_state(smrt, SMRT_WAIT_STATE_UNREADY) !=
DDI_SUCCESS) {
dev_err(smrt->smrt_dip, CE_PANIC, "soft reset failed: "
"controller did not become unready");
}
dev_err(smrt->smrt_dip, CE_NOTE, "soft reset: controller unready");
if (smrt_ctlr_wait_for_state(smrt, SMRT_WAIT_STATE_READY) !=
DDI_SUCCESS) {
dev_err(smrt->smrt_dip, CE_PANIC, "soft reset failed: "
"controller did not come become ready");
}
dev_err(smrt->smrt_dip, CE_NOTE, "soft reset: controller ready");
if (ddi_in_panic()) {
drv_usecwait(10 * MICROSEC);
} else {
delay(drv_usectohz(10 * MICROSEC));
}
smrt_ctlr_teardown(smrt);
if (smrt_ctlr_init(smrt) != DDI_SUCCESS) {
dev_err(smrt->smrt_dip, CE_PANIC, "soft reset failed: "
"controller transport could not be configured");
}
dev_err(smrt->smrt_dip, CE_NOTE, "soft reset: controller configured");
smrt_write_message_nop(smcm_nop, 0);
smcm_nop->smcm_status |= SMRT_CMD_STATUS_POLLED |
SMRT_CMD_IGNORE_RUNNING;
if ((r = smrt_submit(smrt, smcm_nop)) != 0) {
dev_err(smrt->smrt_dip, CE_PANIC, "soft reset failed: "
"ping could not be submitted (%d)", r);
}
VERIFY(smcm_nop->smcm_status & SMRT_CMD_STATUS_INFLIGHT);
for (unsigned i = 0; i < 600; i++) {
smrt_retrieve_simple(smrt);
if (!(smcm_nop->smcm_status & SMRT_CMD_STATUS_INFLIGHT)) {
VERIFY(list_link_active(&smcm_nop->smcm_link_finish));
list_remove(&smrt->smrt_finishq, smcm_nop);
smrt_process_finishq_sync(smcm_nop);
smcm_nop->smcm_status |= SMRT_CMD_STATUS_POLL_COMPLETE;
smrt_process_finishq_one(smcm_nop);
break;
}
if (ddi_in_panic()) {
drv_usecwait(100 * 1000);
} else {
delay(drv_usectohz(100 * 1000));
}
}
if (!(smcm_nop->smcm_status & SMRT_CMD_STATUS_COMPLETE)) {
dev_err(smrt->smrt_dip, CE_PANIC, "soft reset failed: "
"ping did not complete");
} else if (smcm_nop->smcm_status & SMRT_CMD_STATUS_ERROR) {
dev_err(smrt->smrt_dip, CE_WARN, "soft reset: ping completed "
"in error (status %u)",
(unsigned)smcm_nop->smcm_va_err->CommandStatus);
} else {
dev_err(smrt->smrt_dip, CE_NOTE, "soft reset: ping completed");
}
smrt_command_t *nt;
for (smrt_command_t *t = avl_first(&smrt->smrt_inflight);
t != NULL; t = nt) {
nt = AVL_NEXT(&smrt->smrt_inflight, t);
if (t->smcm_status & SMRT_CMD_STATUS_RESET_SENT) {
avl_remove(&smrt->smrt_inflight, t);
t->smcm_status &= ~SMRT_CMD_STATUS_INFLIGHT;
list_insert_tail(&smrt->smrt_finishq, t);
}
}
if (!ddi_in_panic()) {
mutex_exit(&smrt->smrt_mutex);
ddi_taskq_wait(smrt->smrt_discover_taskq);
mutex_enter(&smrt->smrt_mutex);
}
smrt_intr_set(smrt, B_TRUE);
smrt->smrt_status &= ~SMRT_CTLR_STATUS_RESETTING;
smrt->smrt_status |= SMRT_CTLR_DISCOVERY_REQUIRED;
VERIFY0(smrt->smrt_status & SMRT_CTLR_DISCOVERY_RUNNING);
smrt->smrt_status &= ~(SMRT_CTLR_DISCOVERY_MASK);
smrt_discover(smrt);
if (!ddi_in_panic()) {
while (smrt->smrt_status & SMRT_CTLR_DISCOVERY_REQUIRED) {
cv_wait(&smrt->smrt_cv_finishq, &smrt->smrt_mutex);
}
}
smrt->smrt_status |= SMRT_CTLR_STATUS_RUNNING;
smrt->smrt_last_reset_finish = gethrtime();
cv_broadcast(&smrt->smrt_cv_finishq);
smrt_process_finishq(smrt);
mutex_exit(&smrt->smrt_mutex);
smrt_command_free(smcm_nop);
mutex_enter(&smrt->smrt_mutex);
return (0);
}
int
smrt_event_init(smrt_t *smrt)
{
int ret;
smrt_command_t *event, *cancel;
event = smrt_command_alloc(smrt, SMRT_CMDTYPE_EVENT, KM_NOSLEEP);
if (event == NULL)
return (ENOMEM);
if (smrt_command_attach_internal(smrt, event, SMRT_EVENT_NOTIFY_BUFLEN,
KM_NOSLEEP) != 0) {
smrt_command_free(event);
return (ENOMEM);
}
smrt_write_message_event_notify(event);
cancel = smrt_command_alloc(smrt, SMRT_CMDTYPE_INTERNAL, KM_NOSLEEP);
if (cancel == NULL) {
smrt_command_free(event);
return (ENOMEM);
}
if (smrt_command_attach_internal(smrt, cancel, SMRT_EVENT_NOTIFY_BUFLEN,
KM_NOSLEEP) != 0) {
smrt_command_free(event);
smrt_command_free(cancel);
return (ENOMEM);
}
smrt_write_message_cancel_event_notify(cancel);
cv_init(&smrt->smrt_event_queue, NULL, CV_DRIVER, NULL);
mutex_enter(&smrt->smrt_mutex);
if ((ret = smrt_submit(smrt, event)) != 0) {
mutex_exit(&smrt->smrt_mutex);
smrt_command_free(event);
smrt_command_free(cancel);
return (ret);
}
smrt->smrt_event_cmd = event;
smrt->smrt_event_cancel_cmd = cancel;
mutex_exit(&smrt->smrt_mutex);
return (0);
}
void
smrt_event_complete(smrt_command_t *smcm)
{
smrt_event_notify_t *sen;
boolean_t log, rescan;
boolean_t intervene = B_FALSE;
smrt_t *smrt = smcm->smcm_ctlr;
VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
VERIFY3P(smcm, ==, smrt->smrt_event_cmd);
VERIFY0(smrt->smrt_status & SMRT_CTLR_ASYNC_INTERVENTION);
smrt->smrt_stats.smrts_events_received++;
if (smrt->smrt_status & SMRT_CTLR_STATUS_DETACHING) {
cv_signal(&smrt->smrt_event_queue);
return;
}
if (smrt->smrt_status & SMRT_CTLR_STATUS_RESETTING) {
intervene = B_TRUE;
goto clean;
}
if (smcm->smcm_status & SMRT_CMD_STATUS_ERROR) {
ErrorInfo_t *ei = smcm->smcm_va_err;
intervene = B_TRUE;
smrt->smrt_stats.smrts_events_errors++;
dev_err(smrt->smrt_dip, CE_WARN, "!event notification request "
"error: status 0x%x", ei->CommandStatus);
goto clean;
}
sen = smcm->smcm_internal->smcmi_va;
log = rescan = B_FALSE;
switch (sen->sen_class) {
case SMRT_EVENT_CLASS_PROTOCOL:
if (sen->sen_subclass == SMRT_EVENT_PROTOCOL_SUBCLASS_ERROR) {
rescan = B_TRUE;
}
break;
case SMRT_EVENT_CLASS_HOTPLUG:
log = B_TRUE;
if (sen->sen_subclass == SMRT_EVENT_HOTPLUG_SUBCLASS_DRIVE) {
rescan = B_TRUE;
}
break;
case SMRT_EVENT_CLASS_HWERROR:
case SMRT_EVENT_CLASS_ENVIRONMENT:
log = B_TRUE;
break;
case SMRT_EVENT_CLASS_PHYS:
log = B_TRUE;
if (sen->sen_subclass == SMRT_EVENT_PHYS_SUBCLASS_STATE) {
rescan = B_TRUE;
}
break;
case SMRT_EVENT_CLASS_LOGVOL:
rescan = B_TRUE;
log = B_TRUE;
break;
default:
break;
}
if (log) {
const char *rmsg;
if (rescan == B_TRUE) {
rmsg = "rescanning";
} else {
rmsg = "not rescanning";
}
if (sen->sen_message[0] != '\0') {
sen->sen_message[sizeof (sen->sen_message) - 1] = '\0';
dev_err(smrt->smrt_dip, CE_NOTE, "!controller event "
"class/sub-class/detail %x, %x, %x: %s; %s devices",
sen->sen_class, sen->sen_subclass, sen->sen_detail,
sen->sen_message, rmsg);
} else {
dev_err(smrt->smrt_dip, CE_NOTE, "!controller event "
"class/sub-class/detail %x, %x, %x; %s devices",
sen->sen_class, sen->sen_subclass, sen->sen_detail,
rmsg);
}
}
if (rescan)
smrt_discover_request(smrt);
clean:
mutex_exit(&smrt->smrt_mutex);
smrt_command_reuse(smcm);
bzero(smcm->smcm_internal->smcmi_va, SMRT_EVENT_NOTIFY_BUFLEN);
mutex_enter(&smrt->smrt_mutex);
if (smrt->smrt_status & SMRT_CTLR_STATUS_DETACHING) {
cv_signal(&smrt->smrt_event_queue);
return;
}
if ((smrt->smrt_status & SMRT_CTLR_STATUS_RESETTING) != 0 ||
intervene == B_TRUE) {
smrt->smrt_status |= SMRT_CTLR_ASYNC_INTERVENTION;
return;
}
smrt->smrt_event_count++;
if (smrt->smrt_event_count > smrt_event_intervention_threshold) {
smrt->smrt_status |= SMRT_CTLR_ASYNC_INTERVENTION;
return;
}
if (smrt_submit(smrt, smcm) != 0) {
smrt->smrt_status |= SMRT_CTLR_ASYNC_INTERVENTION;
}
}
void
smrt_event_fini(smrt_t *smrt)
{
int ret;
smrt_command_t *event, *cancel;
mutex_enter(&smrt->smrt_mutex);
if (smrt->smrt_status & SMRT_CTLR_ASYNC_INTERVENTION) {
smrt->smrt_status &= ~SMRT_CTLR_ASYNC_INTERVENTION;
goto free;
}
smrt->smrt_event_cancel_cmd->smcm_status |= SMRT_CMD_STATUS_POLLED;
if ((ret = smrt_submit(smrt, smrt->smrt_event_cancel_cmd)) != 0) {
if (smrt_ctlr_reset(smrt) != 0) {
dev_err(smrt->smrt_dip, CE_WARN, "failed to reset "
"device after failure to submit cancellation "
"(%d), abandoning smrt_command_t at address %p",
ret, smrt->smrt_event_cmd);
smrt->smrt_event_cmd = NULL;
goto free;
}
}
smrt->smrt_event_cancel_cmd->smcm_expiry = gethrtime() +
SMRT_ASYNC_CANCEL_TIMEOUT * NANOSEC;
if ((ret = smrt_poll_for(smrt, smrt->smrt_event_cancel_cmd)) != 0) {
VERIFY3S(ret, ==, ETIMEDOUT);
VERIFY0(smrt->smrt_event_cancel_cmd->smcm_status &
SMRT_CMD_STATUS_POLL_COMPLETE);
if (smrt_ctlr_reset(smrt) != 0) {
dev_err(smrt->smrt_dip, CE_WARN, "failed to reset "
"device after failure to poll for async "
"cancellation command abandoning smrt_command_t "
"event command at address %p and cancellation "
"command at %p", smrt->smrt_event_cmd,
smrt->smrt_event_cancel_cmd);
smrt->smrt_event_cmd = NULL;
smrt->smrt_event_cancel_cmd = NULL;
goto free;
}
}
if (smrt->smrt_event_cancel_cmd->smcm_status &
SMRT_CMD_STATUS_RESET_SENT) {
goto free;
}
if (smrt->smrt_event_cancel_cmd->smcm_status & SMRT_CMD_STATUS_ERROR) {
ErrorInfo_t *ei = smrt->smrt_event_cancel_cmd->smcm_va_err;
if (ei->CommandStatus != CISS_CMD_TARGET_STATUS &&
smrt_ctlr_reset(smrt) != 0) {
dev_err(smrt->smrt_dip, CE_WARN, "failed to reset "
"device after receiving an error on the async "
"cancellation command (%d); abandoning "
"smrt_command_t event command at address %p and "
"cancellation command at %p", ei->CommandStatus,
smrt->smrt_event_cmd, smrt->smrt_event_cancel_cmd);
smrt->smrt_event_cmd = NULL;
smrt->smrt_event_cancel_cmd = NULL;
goto free;
}
}
free:
event = smrt->smrt_event_cmd;
smrt->smrt_event_cmd = NULL;
cancel = smrt->smrt_event_cancel_cmd;
smrt->smrt_event_cancel_cmd = NULL;
mutex_exit(&smrt->smrt_mutex);
if (event != NULL)
smrt_command_free(event);
if (cancel != NULL)
smrt_command_free(cancel);
cv_destroy(&smrt->smrt_event_queue);
}
static void
smrt_discover_panic_check(smrt_t *smrt)
{
smrt_target_t *smtg;
ASSERT(MUTEX_HELD(&smrt->smrt_mutex));
for (smtg = list_head(&smrt->smrt_targets); smtg != NULL;
smtg = list_next(&smrt->smrt_targets, smtg)) {
uint64_t gen;
if (smtg->smtg_physical) {
smrt_physical_t *smpt = smtg->smtg_lun.smtg_phys;
if (!smpt->smpt_visible)
continue;
gen = smpt->smpt_gen;
} else {
smrt_volume_t *smlv = smtg->smtg_lun.smtg_vol;
gen = smlv->smlv_gen;
}
if (gen != smrt->smrt_discover_gen) {
dev_err(smrt->smrt_dip, CE_WARN, "target %s "
"disappeared during post-panic discovery",
scsi_device_unit_address(smtg->smtg_scsi_dev));
smtg->smtg_gone = B_TRUE;
}
}
}
static void
smrt_discover(void *arg)
{
int log = 0, phys = 0;
smrt_t *smrt = arg;
uint64_t gen;
boolean_t runphys, runvirt;
mutex_enter(&smrt->smrt_mutex);
smrt->smrt_status |= SMRT_CTLR_DISCOVERY_RUNNING;
smrt->smrt_status &= ~SMRT_CTLR_DISCOVERY_REQUESTED;
smrt->smrt_discover_gen++;
gen = smrt->smrt_discover_gen;
runphys = smrt->smrt_phys_tgtmap != NULL;
runvirt = smrt->smrt_virt_tgtmap != NULL;
mutex_exit(&smrt->smrt_mutex);
if (runphys)
phys = smrt_phys_discover(smrt, SMRT_DISCOVER_TIMEOUT, gen);
if (runvirt)
log = smrt_logvol_discover(smrt, SMRT_DISCOVER_TIMEOUT, gen);
mutex_enter(&smrt->smrt_mutex);
if (phys != 0 || log != 0) {
if (!ddi_in_panic()) {
smrt->smrt_status |= SMRT_CTLR_DISCOVERY_PERIODIC;
} else {
panic("smrt_t %p failed to perform discovery after "
"a reset in panic context, unable to continue. "
"logvol: %d, phys: %d", smrt, log, phys);
}
} else {
if (!ddi_in_panic() &&
smrt->smrt_status & SMRT_CTLR_DISCOVERY_REQUIRED) {
smrt->smrt_status &= ~SMRT_CTLR_DISCOVERY_REQUIRED;
cv_broadcast(&smrt->smrt_cv_finishq);
}
if (ddi_in_panic()) {
smrt_discover_panic_check(smrt);
}
}
smrt->smrt_status &= ~SMRT_CTLR_DISCOVERY_RUNNING;
if (smrt->smrt_status & SMRT_CTLR_DISCOVERY_REQUESTED)
smrt->smrt_status |= SMRT_CTLR_DISCOVERY_PERIODIC;
mutex_exit(&smrt->smrt_mutex);
}
void
smrt_discover_request(smrt_t *smrt)
{
boolean_t run;
ASSERT(MUTEX_HELD(&smrt->smrt_mutex));
if (ddi_in_panic()) {
smrt_discover(smrt);
return;
}
run = (smrt->smrt_status & SMRT_CTLR_DISCOVERY_MASK) == 0;
smrt->smrt_status |= SMRT_CTLR_DISCOVERY_REQUESTED;
if (run && ddi_taskq_dispatch(smrt->smrt_discover_taskq,
smrt_discover, smrt, DDI_NOSLEEP) != DDI_SUCCESS) {
smrt->smrt_status |= SMRT_CTLR_DISCOVERY_PERIODIC;
smrt->smrt_stats.smrts_discovery_tq_errors++;
}
}