#include <sys/scsi/adapters/smrt/smrt.h>
static void
smrt_logvol_free(smrt_volume_t *smlv)
{
VERIFY(list_is_empty(&smlv->smlv_targets));
list_destroy(&smlv->smlv_targets);
kmem_free(smlv, sizeof (*smlv));
}
smrt_volume_t *
smrt_logvol_lookup_by_id(smrt_t *smrt, unsigned long id)
{
VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
for (smrt_volume_t *smlv = list_head(&smrt->smrt_volumes);
smlv != NULL; smlv = list_next(&smrt->smrt_volumes, smlv)) {
if (smlv->smlv_addr.LogDev.VolId == id) {
return (smlv);
}
}
return (NULL);
}
static int
smrt_read_logvols(smrt_t *smrt, smrt_report_logical_lun_t *smrll, uint64_t gen)
{
smrt_report_logical_lun_ent_t *ents = smrll->smrll_data.ents;
uint32_t count = BE_32(smrll->smrll_datasize) /
sizeof (smrt_report_logical_lun_ent_t);
VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
if (count > SMRT_MAX_LOGDRV) {
count = SMRT_MAX_LOGDRV;
}
for (unsigned i = 0; i < count; i++) {
smrt_volume_t *smlv;
char id[SCSI_MAXNAMELEN];
DTRACE_PROBE2(read_logvol, unsigned, i,
smrt_report_logical_lun_ent_t *, &ents[i]);
if ((smlv = smrt_logvol_lookup_by_id(smrt,
ents[i].smrle_addr.VolId)) == NULL) {
if ((smlv = kmem_zalloc(sizeof (*smlv), KM_NOSLEEP)) ==
NULL) {
return (ENOMEM);
}
list_create(&smlv->smlv_targets,
sizeof (smrt_target_t),
offsetof(smrt_target_t, smtg_link_lun));
smlv->smlv_ctlr = smrt;
list_insert_tail(&smrt->smrt_volumes, smlv);
}
smlv->smlv_addr.LogDev = ents[i].smrle_addr;
smlv->smlv_gen = gen;
(void) snprintf(id, sizeof (id), "%x",
smlv->smlv_addr.LogDev.VolId);
if (!ddi_in_panic() &&
scsi_hba_tgtmap_set_add(smrt->smrt_virt_tgtmap,
SCSI_TGT_SCSI_DEVICE, id, NULL) != DDI_SUCCESS) {
return (EIO);
}
}
return (0);
}
static int
smrt_read_logvols_ext(smrt_t *smrt, smrt_report_logical_lun_t *smrll,
uint64_t gen)
{
smrt_report_logical_lun_extent_t *extents =
smrll->smrll_data.extents;
uint32_t count = BE_32(smrll->smrll_datasize) /
sizeof (smrt_report_logical_lun_extent_t);
VERIFY(MUTEX_HELD(&smrt->smrt_mutex));
if (count > SMRT_MAX_LOGDRV) {
count = SMRT_MAX_LOGDRV;
}
for (unsigned i = 0; i < count; i++) {
smrt_volume_t *smlv;
char id[SCSI_MAXNAMELEN];
DTRACE_PROBE2(read_logvol_ext, unsigned, i,
smrt_report_logical_lun_extent_t *, &extents[i]);
if ((smlv = smrt_logvol_lookup_by_id(smrt,
extents[i].smrle_addr.VolId)) != NULL) {
if ((smlv->smlv_flags & SMRT_VOL_FLAG_WWN) &&
bcmp(extents[i].smrle_wwn, smlv->smlv_wwn,
16) != 0) {
dev_err(smrt->smrt_dip, CE_PANIC, "logical "
"volume %u WWN changed unexpectedly", i);
}
} else {
if ((smlv = kmem_zalloc(sizeof (*smlv), KM_NOSLEEP)) ==
NULL) {
return (ENOMEM);
}
bcopy(extents[i].smrle_wwn, smlv->smlv_wwn, 16);
smlv->smlv_flags |= SMRT_VOL_FLAG_WWN;
list_create(&smlv->smlv_targets,
sizeof (smrt_target_t),
offsetof(smrt_target_t, smtg_link_lun));
smlv->smlv_ctlr = smrt;
list_insert_tail(&smrt->smrt_volumes, smlv);
}
smlv->smlv_addr.LogDev = extents[i].smrle_addr;
smlv->smlv_gen = gen;
(void) snprintf(id, sizeof (id), "%x",
smlv->smlv_addr.LogDev.VolId);
if (!ddi_in_panic() &&
scsi_hba_tgtmap_set_add(smrt->smrt_virt_tgtmap,
SCSI_TGT_SCSI_DEVICE, id, NULL) != DDI_SUCCESS) {
return (EIO);
}
}
return (0);
}
int
smrt_logvol_discover(smrt_t *smrt, uint16_t timeout, uint64_t gen)
{
smrt_command_t *smcm;
smrt_report_logical_lun_t *smrll;
smrt_report_logical_lun_req_t smrllr = { 0 };
int r;
if ((smcm = smrt_command_alloc(smrt, SMRT_CMDTYPE_INTERNAL,
KM_NOSLEEP)) == NULL || smrt_command_attach_internal(smrt, smcm,
sizeof (smrt_report_logical_lun_t), KM_NOSLEEP) != 0) {
r = ENOMEM;
mutex_enter(&smrt->smrt_mutex);
goto out;
}
smrll = smcm->smcm_internal->smcmi_va;
smrt_write_controller_lun_addr(&smcm->smcm_va_cmd->Header.LUN);
smcm->smcm_va_cmd->Request.CDBLen = sizeof (smrllr);
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(&smrllr, sizeof (smrllr));
smrllr.smrllr_opcode = CISS_SCMD_REPORT_LOGICAL_LUNS;
smrllr.smrllr_extflag = 1;
smrllr.smrllr_datasize = htonl(sizeof (smrt_report_logical_lun_t));
bcopy(&smrllr, &smcm->smcm_va_cmd->Request.CDB[0],
MIN(CISS_CDBLEN, sizeof (smrllr)));
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, "logical volume "
"discovery error: status 0x%x", ei->CommandStatus);
r = EIO;
goto out;
}
}
if (!ddi_in_panic() &&
scsi_hba_tgtmap_set_begin(smrt->smrt_virt_tgtmap) != DDI_SUCCESS) {
dev_err(smrt->smrt_dip, CE_WARN, "failed to begin target map "
"observation on %s", SMRT_IPORT_VIRT);
r = EIO;
goto out;
}
if ((smrll->smrll_extflag & 0x1) != 0) {
r = smrt_read_logvols_ext(smrt, smrll, gen);
} else {
r = smrt_read_logvols(smrt, smrll, gen);
}
if (r == 0 && !ddi_in_panic()) {
if (scsi_hba_tgtmap_set_end(smrt->smrt_virt_tgtmap, 0) !=
DDI_SUCCESS) {
dev_err(smrt->smrt_dip, CE_WARN, "failed to end target "
"map observation on %s", SMRT_IPORT_VIRT);
r = EIO;
}
} else if (r != 0 && !ddi_in_panic()) {
if (scsi_hba_tgtmap_set_flush(smrt->smrt_virt_tgtmap) !=
DDI_SUCCESS) {
dev_err(smrt->smrt_dip, CE_WARN, "failed to end target "
"map observation on %s", SMRT_IPORT_VIRT);
r = EIO;
}
}
if (r == 0) {
smrt->smrt_last_log_discovery = gethrtime();
}
out:
mutex_exit(&smrt->smrt_mutex);
if (smcm != NULL) {
smrt_command_free(smcm);
}
return (r);
}
void
smrt_logvol_tgtmap_activate(void *arg, char *addr, scsi_tgtmap_tgt_type_t type,
void **privpp)
{
smrt_t *smrt = arg;
unsigned long volume;
char *eptr;
VERIFY(type == SCSI_TGT_SCSI_DEVICE);
VERIFY0(ddi_strtoul(addr, &eptr, 16, &volume));
VERIFY3S(*eptr, ==, '\0');
VERIFY3S(volume, >=, 0);
VERIFY3S(volume, <, SMRT_MAX_LOGDRV);
mutex_enter(&smrt->smrt_mutex);
VERIFY(smrt_logvol_lookup_by_id(smrt, volume) != NULL);
mutex_exit(&smrt->smrt_mutex);
*privpp = NULL;
}
boolean_t
smrt_logvol_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_volume_t *smlv;
unsigned long volume;
char *eptr;
VERIFY(type == SCSI_TGT_SCSI_DEVICE);
VERIFY(priv == NULL);
VERIFY0(ddi_strtoul(addr, &eptr, 16, &volume));
VERIFY3S(*eptr, ==, '\0');
VERIFY3S(volume, >=, 0);
VERIFY3S(volume, <, SMRT_MAX_LOGDRV);
mutex_enter(&smrt->smrt_mutex);
smlv = smrt_logvol_lookup_by_id(smrt, volume);
VERIFY(smlv != NULL);
list_remove(&smrt->smrt_volumes, smlv);
smrt_logvol_free(smlv);
mutex_exit(&smrt->smrt_mutex);
return (B_FALSE);
}
void
smrt_logvol_teardown(smrt_t *smrt)
{
smrt_volume_t *smlv;
while ((smlv = list_remove_head(&smrt->smrt_volumes)) != NULL) {
smrt_logvol_free(smlv);
}
}