#include <sys/scsi/adapters/smrt/smrt.h>
static void
smrt_physical_free(smrt_physical_t *smpt)
{
VERIFY(list_is_empty(&smpt->smpt_targets));
VERIFY(smpt->smpt_info != NULL);
kmem_free(smpt->smpt_info, sizeof (*smpt->smpt_info));
list_destroy(&smpt->smpt_targets);
kmem_free(smpt, sizeof (*smpt));
}
static boolean_t
smrt_physical_visible(PhysDevAddr_t *addr, smrt_identify_physical_drive_t *info)
{
if (addr->Mode == SMRT_CISS_MODE_MASKED) {
return (B_FALSE);
}
if ((info->sipd_more_flags & (SMRT_MORE_FLAGS_LOGVOL |
SMRT_MORE_FLAGS_SPARE)) != 0) {
return (B_FALSE);
}
return (B_TRUE);
}
smrt_physical_t *
smrt_phys_lookup_by_ua(smrt_t *smrt, const char *ua)
{
VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
if (strlen(ua) < SCSI_WWN_UA_STRLEN)
return (NULL);
for (smrt_physical_t *smpt = list_head(&smrt->smrt_physicals);
smpt != NULL; smpt = list_next(&smrt->smrt_physicals, smpt)) {
char wwnstr[SCSI_WWN_BUFLEN];
(void) scsi_wwn_to_wwnstr(smpt->smpt_wwn, 1, wwnstr);
if (strncmp(wwnstr, ua, SCSI_WWN_UA_STRLEN) != 0)
continue;
if (ua[SCSI_WWN_UA_STRLEN] != '\0' &&
ua[SCSI_WWN_UA_STRLEN] != ',') {
continue;
}
return (smpt);
}
return (NULL);
}
static smrt_physical_t *
smrt_phys_lookup_by_wwn(smrt_t *smrt, uint64_t wwn)
{
VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
for (smrt_physical_t *smpt = list_head(&smrt->smrt_physicals);
smpt != NULL; smpt = list_next(&smrt->smrt_physicals, smpt)) {
if (wwn == smpt->smpt_wwn)
return (smpt);
}
return (NULL);
}
static int
smrt_phys_identify(smrt_t *smrt, smrt_identify_physical_drive_t *info,
uint16_t bmic, uint16_t timeout)
{
smrt_command_t *smcm = NULL;
smrt_identify_physical_drive_t *sipd;
smrt_identify_physical_drive_req_t sipdr;
int ret;
size_t sz, copysz;
sz = sizeof (smrt_identify_physical_drive_t);
sz = P2ROUNDUP_TYPED(sz, 512, size_t);
if ((smcm = smrt_command_alloc(smrt, SMRT_CMDTYPE_INTERNAL,
KM_NOSLEEP)) == NULL || smrt_command_attach_internal(smrt, smcm,
sizeof (*sipd), KM_NOSLEEP) != 0) {
ret = ENOMEM;
goto out;
}
sipd = smcm->smcm_internal->smcmi_va;
smrt_write_controller_lun_addr(&smcm->smcm_va_cmd->Header.LUN);
smcm->smcm_va_cmd->Request.CDBLen = sizeof (sipdr);
smcm->smcm_va_cmd->Request.Timeout = LE_16(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(&sipdr, sizeof (sipdr));
sipdr.sipdr_opcode = CISS_SCMD_BMIC_READ;
sipdr.sipdr_lun = 0;
sipdr.sipdr_bmic_index1 = bmic & 0x00ff;
sipdr.sipdr_command = CISS_BMIC_IDENTIFY_PHYSICAL_DEVICE;
sipdr.sipdr_bmic_index2 = (bmic & 0xff00) >> 8;
bcopy(&sipdr, &smcm->smcm_va_cmd->Request.CDB[0],
MIN(CISS_CDBLEN, sizeof (sipdr)));
mutex_enter(&smrt->smrt_mutex);
smcm->smcm_status |= SMRT_CMD_STATUS_POLLED;
if ((ret = smrt_submit(smrt, smcm)) != 0) {
mutex_exit(&smrt->smrt_mutex);
goto out;
}
smcm->smcm_expiry = gethrtime() + timeout * NANOSEC;
if ((ret = smrt_poll_for(smrt, smcm)) != 0) {
VERIFY3S(ret, ==, ETIMEDOUT);
VERIFY0(smcm->smcm_status & SMRT_CMD_STATUS_POLL_COMPLETE);
smcm->smcm_status |= SMRT_CMD_STATUS_ABANDONED;
smcm->smcm_status &= ~SMRT_CMD_STATUS_POLLED;
smcm = NULL;
mutex_exit(&smrt->smrt_mutex);
goto out;
}
mutex_exit(&smrt->smrt_mutex);
if (smcm->smcm_status & SMRT_CMD_STATUS_RESET_SENT) {
ret = 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 physical "
"device error: status 0x%x", ei->CommandStatus);
ret = EIO;
goto out;
}
copysz = MIN(sizeof (*sipd), sz - ei->ResidualCnt);
} else {
copysz = sizeof (*sipd);
}
sz = MIN(sizeof (*sipd), copysz);
bcopy(sipd, info, sizeof (*sipd));
ret = 0;
out:
if (smcm != NULL) {
smrt_command_free(smcm);
}
return (ret);
}
static int
smrt_read_phys_ext(smrt_t *smrt, smrt_report_physical_lun_t *smrpl,
uint16_t timeout, uint64_t gen)
{
smrt_report_physical_lun_extent_t *extents = smrpl->smrpl_data.extents;
uint32_t count = BE_32(smrpl->smrpl_datasize) /
sizeof (smrt_report_physical_lun_extent_t);
uint32_t i;
VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
if (count > SMRT_MAX_PHYSDEV) {
count = SMRT_MAX_PHYSDEV;
}
for (i = 0; i < count; i++) {
int ret;
smrt_physical_t *smpt;
smrt_identify_physical_drive_t *info;
smrt_report_physical_opdi_t *opdi;
uint16_t bmic;
uint64_t wwn, satawwn;
char name[SCSI_MAXNAMELEN];
opdi = &extents[i].srple_extdata.srple_opdi;
mutex_exit(&smrt->smrt_mutex);
info = kmem_zalloc(sizeof (*info), KM_NOSLEEP);
if (info == NULL) {
mutex_enter(&smrt->smrt_mutex);
return (ENOMEM);
}
bmic = smrt_lun_addr_to_bmic(&extents[i].srple_addr);
ret = smrt_phys_identify(smrt, info, bmic, timeout);
if (ret != 0) {
mutex_enter(&smrt->smrt_mutex);
kmem_free(info, sizeof (*info));
return (ret);
}
wwn = *(uint64_t *)opdi->srpo_wwid;
wwn = BE_64(wwn);
if (opdi->srpo_dtype == SMRT_DTYPE_SATA &&
smrt_sata_determine_wwn(smrt, &extents[i].srple_addr,
&satawwn, timeout) == 0) {
wwn = satawwn;
}
mutex_enter(&smrt->smrt_mutex);
smpt = smrt_phys_lookup_by_wwn(smrt, wwn);
if (smpt != NULL) {
if (bcmp(smpt->smpt_info->sipd_model, info->sipd_model,
sizeof (info->sipd_model)) != 0 ||
bcmp(smpt->smpt_info->sipd_serial,
info->sipd_serial, sizeof (info->sipd_serial)) !=
0 || smpt->smpt_dtype != opdi->srpo_dtype) {
dev_err(smrt->smrt_dip, CE_PANIC, "physical "
"target with wwn 0x%" PRIx64 " changed "
"model, serial, or type unexpectedly: "
"smrt_physical_t %p, phys info: %p", wwn,
smpt, info);
}
if (ddi_in_panic() &&
(opdi->srpo_dtype == SMRT_DTYPE_SATA ||
opdi->srpo_dtype == SMRT_DTYPE_SAS)) {
boolean_t visible;
visible = smrt_physical_visible(
&smpt->smpt_addr.PhysDev, smpt->smpt_info);
if (visible != smpt->smpt_visible) {
dev_err(smrt->smrt_dip, CE_PANIC,
"physical target with wwn 0x%"
PRIx64 " changed visibility status "
"unexpectedly", wwn);
}
}
kmem_free(smpt->smpt_info, sizeof (*smpt->smpt_info));
smpt->smpt_info = NULL;
} else {
smpt = kmem_zalloc(sizeof (smrt_physical_t),
KM_NOSLEEP);
if (smpt == NULL) {
kmem_free(info, sizeof (*info));
return (ENOMEM);
}
smpt->smpt_wwn = wwn;
smpt->smpt_dtype = opdi->srpo_dtype;
list_create(&smpt->smpt_targets, sizeof (smrt_target_t),
offsetof(smrt_target_t, smtg_link_lun));
smpt->smpt_ctlr = smrt;
list_insert_tail(&smrt->smrt_physicals, smpt);
}
VERIFY3P(smpt->smpt_info, ==, NULL);
switch (smpt->smpt_dtype) {
case SMRT_DTYPE_SATA:
case SMRT_DTYPE_SAS:
smpt->smpt_supported = B_TRUE;
smpt->smpt_visible =
smrt_physical_visible(&extents[i].srple_addr, info);
break;
case SMRT_DTYPE_SES:
smpt->smpt_supported = B_TRUE;
smpt->smpt_visible =
smrt_physical_visible(&extents[i].srple_addr, info);
break;
default:
smpt->smpt_visible = B_FALSE;
smpt->smpt_supported = B_FALSE;
}
smpt->smpt_info = info;
smpt->smpt_addr.PhysDev = extents[i].srple_addr;
smpt->smpt_bmic = bmic;
smpt->smpt_gen = gen;
(void) scsi_wwn_to_wwnstr(smpt->smpt_wwn, 1, name);
if (!ddi_in_panic() && smpt->smpt_visible &&
scsi_hba_tgtmap_set_add(smrt->smrt_phys_tgtmap,
SCSI_TGT_SCSI_DEVICE, name, NULL) != DDI_SUCCESS) {
return (EIO);
}
}
return (0);
}
int
smrt_phys_discover(smrt_t *smrt, uint16_t timeout, uint64_t gen)
{
smrt_command_t *smcm;
smrt_report_physical_lun_t *smrpl;
smrt_report_physical_lun_req_t smrplr;
int r;
if ((smcm = smrt_command_alloc(smrt, SMRT_CMDTYPE_INTERNAL,
KM_NOSLEEP)) == NULL || smrt_command_attach_internal(smrt, smcm,
sizeof (*smrpl), KM_NOSLEEP) != 0) {
r = ENOMEM;
mutex_enter(&smrt->smrt_mutex);
goto out;
}
smrpl = smcm->smcm_internal->smcmi_va;
smrt_write_controller_lun_addr(&smcm->smcm_va_cmd->Header.LUN);
smcm->smcm_va_cmd->Request.CDBLen = sizeof (smrplr);
smcm->smcm_va_cmd->Request.Timeout = LE_16(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(&smrplr, sizeof (smrplr));
smrplr.smrplr_opcode = CISS_SCMD_REPORT_PHYSICAL_LUNS;
smrplr.smrplr_extflag = SMRT_REPORT_PHYSICAL_LUN_EXT_OPDI;
smrplr.smrplr_datasize = BE_32(sizeof (smrt_report_physical_lun_t));
bcopy(&smrplr, &smcm->smcm_va_cmd->Request.CDB[0],
MIN(CISS_CDBLEN, sizeof (smrplr)));
mutex_enter(&smrt->smrt_mutex);
smcm->smcm_status |= SMRT_CMD_STATUS_POLLED;
if ((r = smrt_submit(smrt, smcm)) != 0) {
goto out;
}
smcm->smcm_expiry = gethrtime() + timeout * NANOSEC;
if ((r = smrt_poll_for(smrt, smcm)) != 0) {
VERIFY3S(r, ==, ETIMEDOUT);
VERIFY0(smcm->smcm_status & SMRT_CMD_STATUS_POLL_COMPLETE);
smcm->smcm_status |= SMRT_CMD_STATUS_ABANDONED;
smcm->smcm_status &= ~SMRT_CMD_STATUS_POLLED;
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, "physical target "
"discovery error: status 0x%x", ei->CommandStatus);
r = EIO;
goto out;
}
}
if ((smrpl->smrpl_extflag & SMRT_REPORT_PHYSICAL_LUN_EXT_MASK) !=
SMRT_REPORT_PHYSICAL_LUN_EXT_OPDI) {
r = 0;
goto out;
}
if (!ddi_in_panic() &&
scsi_hba_tgtmap_set_begin(smrt->smrt_phys_tgtmap) != DDI_SUCCESS) {
dev_err(smrt->smrt_dip, CE_WARN, "failed to begin target map "
"observation on %s", SMRT_IPORT_PHYS);
r = EIO;
goto out;
}
r = smrt_read_phys_ext(smrt, smrpl, timeout, gen);
if (r == 0 && !ddi_in_panic()) {
if (scsi_hba_tgtmap_set_end(smrt->smrt_phys_tgtmap, 0) !=
DDI_SUCCESS) {
dev_err(smrt->smrt_dip, CE_WARN, "failed to end target "
"map observation on %s", SMRT_IPORT_PHYS);
r = EIO;
}
} else if (r != 0 && !ddi_in_panic()) {
if (scsi_hba_tgtmap_set_flush(smrt->smrt_phys_tgtmap) !=
DDI_SUCCESS) {
dev_err(smrt->smrt_dip, CE_WARN, "failed to end target "
"map observation on %s", SMRT_IPORT_PHYS);
r = EIO;
}
}
if (r == 0) {
smrt_physical_t *smpt, *next;
for (smpt = list_head(&smrt->smrt_physicals), next = NULL;
smpt != NULL; smpt = next) {
next = list_next(&smrt->smrt_physicals, smpt);
if (smpt->smpt_visible || smpt->smpt_gen == gen)
continue;
list_remove(&smrt->smrt_physicals, smpt);
smrt_physical_free(smpt);
}
smrt->smrt_last_phys_discovery = gethrtime();
for (smpt = list_head(&smrt->smrt_physicals), next = NULL;
smpt != NULL; smpt = next) {
if (smpt->smpt_supported || smpt->smpt_unsup_warn)
continue;
smpt->smpt_unsup_warn = B_TRUE;
dev_err(smrt->smrt_dip, CE_WARN, "encountered "
"unsupported device with device type %d",
smpt->smpt_dtype);
}
}
out:
mutex_exit(&smrt->smrt_mutex);
if (smcm != NULL) {
smrt_command_free(smcm);
}
return (r);
}
void
smrt_phys_tgtmap_activate(void *arg, char *addr, scsi_tgtmap_tgt_type_t type,
void **privpp)
{
smrt_t *smrt = arg;
smrt_physical_t *smpt;
VERIFY3S(type, ==, SCSI_TGT_SCSI_DEVICE);
mutex_enter(&smrt->smrt_mutex);
smpt = smrt_phys_lookup_by_ua(smrt, addr);
VERIFY(smpt != NULL);
VERIFY(smpt->smpt_supported);
VERIFY(smpt->smpt_visible);
*privpp = NULL;
mutex_exit(&smrt->smrt_mutex);
}
boolean_t
smrt_phys_tgtmap_deactivate(void *arg, char *addr, scsi_tgtmap_tgt_type_t type,
void *priv, scsi_tgtmap_deact_rsn_t reason)
{
smrt_t *smrt = arg;
smrt_physical_t *smpt;
VERIFY3S(type, ==, SCSI_TGT_SCSI_DEVICE);
VERIFY3P(priv, ==, NULL);
mutex_enter(&smrt->smrt_mutex);
smpt = smrt_phys_lookup_by_ua(smrt, addr);
if (smpt == NULL || !smpt->smpt_visible) {
mutex_exit(&smrt->smrt_mutex);
return (B_FALSE);
}
list_remove(&smrt->smrt_physicals, smpt);
smrt_physical_free(smpt);
mutex_exit(&smrt->smrt_mutex);
return (B_FALSE);
}
void
smrt_phys_teardown(smrt_t *smrt)
{
smrt_physical_t *smpt;
while ((smpt = list_remove_head(&smrt->smrt_physicals)) != NULL) {
smrt_physical_free(smpt);
}
}