1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright (c) 2017, Joyent, Inc. 14 */ 15 16 #include <sys/scsi/adapters/smrt/smrt.h> 17 18 static void 19 smrt_logvol_free(smrt_volume_t *smlv) 20 { 21 /* 22 * By this stage of teardown, all of the SCSI target drivers 23 * must have been detached from this logical volume. 24 */ 25 VERIFY(list_is_empty(&smlv->smlv_targets)); 26 list_destroy(&smlv->smlv_targets); 27 28 kmem_free(smlv, sizeof (*smlv)); 29 } 30 31 smrt_volume_t * 32 smrt_logvol_lookup_by_id(smrt_t *smrt, unsigned long id) 33 { 34 VERIFY(MUTEX_HELD(&smrt->smrt_mutex)); 35 36 for (smrt_volume_t *smlv = list_head(&smrt->smrt_volumes); 37 smlv != NULL; smlv = list_next(&smrt->smrt_volumes, smlv)) { 38 if (smlv->smlv_addr.LogDev.VolId == id) { 39 return (smlv); 40 } 41 } 42 43 return (NULL); 44 } 45 46 static int 47 smrt_read_logvols(smrt_t *smrt, smrt_report_logical_lun_t *smrll, uint64_t gen) 48 { 49 smrt_report_logical_lun_ent_t *ents = smrll->smrll_data.ents; 50 uint32_t count = BE_32(smrll->smrll_datasize) / 51 sizeof (smrt_report_logical_lun_ent_t); 52 53 VERIFY(MUTEX_HELD(&smrt->smrt_mutex)); 54 55 if (count > SMRT_MAX_LOGDRV) { 56 count = SMRT_MAX_LOGDRV; 57 } 58 59 for (unsigned i = 0; i < count; i++) { 60 smrt_volume_t *smlv; 61 char id[SCSI_MAXNAMELEN]; 62 63 DTRACE_PROBE2(read_logvol, unsigned, i, 64 smrt_report_logical_lun_ent_t *, &ents[i]); 65 66 if ((smlv = smrt_logvol_lookup_by_id(smrt, 67 ents[i].smrle_addr.VolId)) == NULL) { 68 69 /* 70 * This is a new Logical Volume, so add it the the list. 71 */ 72 if ((smlv = kmem_zalloc(sizeof (*smlv), KM_NOSLEEP)) == 73 NULL) { 74 return (ENOMEM); 75 } 76 77 list_create(&smlv->smlv_targets, 78 sizeof (smrt_target_t), 79 offsetof(smrt_target_t, smtg_link_lun)); 80 81 smlv->smlv_ctlr = smrt; 82 list_insert_tail(&smrt->smrt_volumes, smlv); 83 } 84 85 /* 86 * Always make sure that the address and the generation are up 87 * to date, regardless of where this came from. 88 */ 89 smlv->smlv_addr.LogDev = ents[i].smrle_addr; 90 smlv->smlv_gen = gen; 91 (void) snprintf(id, sizeof (id), "%x", 92 smlv->smlv_addr.LogDev.VolId); 93 if (!ddi_in_panic() && 94 scsi_hba_tgtmap_set_add(smrt->smrt_virt_tgtmap, 95 SCSI_TGT_SCSI_DEVICE, id, NULL) != DDI_SUCCESS) { 96 return (EIO); 97 } 98 } 99 100 return (0); 101 } 102 103 static int 104 smrt_read_logvols_ext(smrt_t *smrt, smrt_report_logical_lun_t *smrll, 105 uint64_t gen) 106 { 107 smrt_report_logical_lun_extent_t *extents = 108 smrll->smrll_data.extents; 109 uint32_t count = BE_32(smrll->smrll_datasize) / 110 sizeof (smrt_report_logical_lun_extent_t); 111 112 VERIFY(MUTEX_HELD(&smrt->smrt_mutex)); 113 114 if (count > SMRT_MAX_LOGDRV) { 115 count = SMRT_MAX_LOGDRV; 116 } 117 118 for (unsigned i = 0; i < count; i++) { 119 smrt_volume_t *smlv; 120 char id[SCSI_MAXNAMELEN]; 121 122 DTRACE_PROBE2(read_logvol_ext, unsigned, i, 123 smrt_report_logical_lun_extent_t *, &extents[i]); 124 125 if ((smlv = smrt_logvol_lookup_by_id(smrt, 126 extents[i].smrle_addr.VolId)) != NULL) { 127 if ((smlv->smlv_flags & SMRT_VOL_FLAG_WWN) && 128 bcmp(extents[i].smrle_wwn, smlv->smlv_wwn, 129 16) != 0) { 130 dev_err(smrt->smrt_dip, CE_PANIC, "logical " 131 "volume %u WWN changed unexpectedly", i); 132 } 133 } else { 134 /* 135 * This is a new Logical Volume, so add it the the list. 136 */ 137 if ((smlv = kmem_zalloc(sizeof (*smlv), KM_NOSLEEP)) == 138 NULL) { 139 return (ENOMEM); 140 } 141 142 bcopy(extents[i].smrle_wwn, smlv->smlv_wwn, 16); 143 smlv->smlv_flags |= SMRT_VOL_FLAG_WWN; 144 145 list_create(&smlv->smlv_targets, 146 sizeof (smrt_target_t), 147 offsetof(smrt_target_t, smtg_link_lun)); 148 149 smlv->smlv_ctlr = smrt; 150 list_insert_tail(&smrt->smrt_volumes, smlv); 151 } 152 153 /* 154 * Always make sure that the address and the generation are up 155 * to date. The address may have changed on a reset. 156 */ 157 smlv->smlv_addr.LogDev = extents[i].smrle_addr; 158 smlv->smlv_gen = gen; 159 (void) snprintf(id, sizeof (id), "%x", 160 smlv->smlv_addr.LogDev.VolId); 161 if (!ddi_in_panic() && 162 scsi_hba_tgtmap_set_add(smrt->smrt_virt_tgtmap, 163 SCSI_TGT_SCSI_DEVICE, id, NULL) != DDI_SUCCESS) { 164 return (EIO); 165 } 166 } 167 168 return (0); 169 } 170 171 /* 172 * Discover the currently visible set of Logical Volumes exposed by the 173 * controller. 174 */ 175 int 176 smrt_logvol_discover(smrt_t *smrt, uint16_t timeout, uint64_t gen) 177 { 178 smrt_command_t *smcm; 179 smrt_report_logical_lun_t *smrll; 180 smrt_report_logical_lun_req_t smrllr = { 0 }; 181 int r; 182 183 /* 184 * Allocate the command to send to the device, including buffer space 185 * for the returned list of Logical Volumes. 186 */ 187 if ((smcm = smrt_command_alloc(smrt, SMRT_CMDTYPE_INTERNAL, 188 KM_NOSLEEP)) == NULL || smrt_command_attach_internal(smrt, smcm, 189 sizeof (smrt_report_logical_lun_t), KM_NOSLEEP) != 0) { 190 r = ENOMEM; 191 mutex_enter(&smrt->smrt_mutex); 192 goto out; 193 } 194 195 smrll = smcm->smcm_internal->smcmi_va; 196 197 smrt_write_controller_lun_addr(&smcm->smcm_va_cmd->Header.LUN); 198 199 smcm->smcm_va_cmd->Request.CDBLen = sizeof (smrllr); 200 smcm->smcm_va_cmd->Request.Timeout = LE_16(timeout); 201 smcm->smcm_va_cmd->Request.Type.Type = CISS_TYPE_CMD; 202 smcm->smcm_va_cmd->Request.Type.Attribute = CISS_ATTR_SIMPLE; 203 smcm->smcm_va_cmd->Request.Type.Direction = CISS_XFER_READ; 204 205 /* 206 * The Report Logical LUNs command is essentially a vendor-specific 207 * SCSI command, which we assemble into the CDB region of the command 208 * block. 209 */ 210 bzero(&smrllr, sizeof (smrllr)); 211 smrllr.smrllr_opcode = CISS_SCMD_REPORT_LOGICAL_LUNS; 212 smrllr.smrllr_extflag = 1; 213 smrllr.smrllr_datasize = htonl(sizeof (smrt_report_logical_lun_t)); 214 bcopy(&smrllr, &smcm->smcm_va_cmd->Request.CDB[0], 215 MIN(CISS_CDBLEN, sizeof (smrllr))); 216 217 mutex_enter(&smrt->smrt_mutex); 218 219 /* 220 * Send the command to the device. 221 */ 222 smcm->smcm_status |= SMRT_CMD_STATUS_POLLED; 223 if ((r = smrt_submit(smrt, smcm)) != 0) { 224 goto out; 225 } 226 227 /* 228 * Poll for completion. 229 */ 230 smcm->smcm_expiry = gethrtime() + timeout * NANOSEC; 231 if ((r = smrt_poll_for(smrt, smcm)) != 0) { 232 VERIFY3S(r, ==, ETIMEDOUT); 233 VERIFY0(smcm->smcm_status & SMRT_CMD_STATUS_POLL_COMPLETE); 234 235 /* 236 * The command timed out; abandon it now. Remove the POLLED 237 * flag so that the periodic routine will send an abort to 238 * clean it up next time around. 239 */ 240 smcm->smcm_status |= SMRT_CMD_STATUS_ABANDONED; 241 smcm->smcm_status &= ~SMRT_CMD_STATUS_POLLED; 242 smcm = NULL; 243 goto out; 244 } 245 246 if (smcm->smcm_status & SMRT_CMD_STATUS_RESET_SENT) { 247 /* 248 * The controller was reset while we were trying to discover 249 * logical volumes. Report failure. 250 */ 251 r = EIO; 252 goto out; 253 } 254 255 if (smcm->smcm_status & SMRT_CMD_STATUS_ERROR) { 256 ErrorInfo_t *ei = smcm->smcm_va_err; 257 258 if (ei->CommandStatus != CISS_CMD_DATA_UNDERRUN) { 259 dev_err(smrt->smrt_dip, CE_WARN, "logical volume " 260 "discovery error: status 0x%x", ei->CommandStatus); 261 r = EIO; 262 goto out; 263 } 264 } 265 266 if (!ddi_in_panic() && 267 scsi_hba_tgtmap_set_begin(smrt->smrt_virt_tgtmap) != DDI_SUCCESS) { 268 dev_err(smrt->smrt_dip, CE_WARN, "failed to begin target map " 269 "observation on %s", SMRT_IPORT_VIRT); 270 r = EIO; 271 goto out; 272 } 273 274 if ((smrll->smrll_extflag & 0x1) != 0) { 275 r = smrt_read_logvols_ext(smrt, smrll, gen); 276 } else { 277 r = smrt_read_logvols(smrt, smrll, gen); 278 } 279 280 if (r == 0 && !ddi_in_panic()) { 281 if (scsi_hba_tgtmap_set_end(smrt->smrt_virt_tgtmap, 0) != 282 DDI_SUCCESS) { 283 dev_err(smrt->smrt_dip, CE_WARN, "failed to end target " 284 "map observation on %s", SMRT_IPORT_VIRT); 285 r = EIO; 286 } 287 } else if (r != 0 && !ddi_in_panic()) { 288 if (scsi_hba_tgtmap_set_flush(smrt->smrt_virt_tgtmap) != 289 DDI_SUCCESS) { 290 dev_err(smrt->smrt_dip, CE_WARN, "failed to end target " 291 "map observation on %s", SMRT_IPORT_VIRT); 292 r = EIO; 293 } 294 } 295 296 if (r == 0) { 297 /* 298 * Update the time of the last successful Logical Volume 299 * discovery: 300 */ 301 smrt->smrt_last_log_discovery = gethrtime(); 302 } 303 304 out: 305 mutex_exit(&smrt->smrt_mutex); 306 307 if (smcm != NULL) { 308 smrt_command_free(smcm); 309 } 310 return (r); 311 } 312 313 void 314 smrt_logvol_tgtmap_activate(void *arg, char *addr, scsi_tgtmap_tgt_type_t type, 315 void **privpp) 316 { 317 smrt_t *smrt = arg; 318 unsigned long volume; 319 char *eptr; 320 321 VERIFY(type == SCSI_TGT_SCSI_DEVICE); 322 VERIFY0(ddi_strtoul(addr, &eptr, 16, &volume)); 323 VERIFY3S(*eptr, ==, '\0'); 324 VERIFY3S(volume, >=, 0); 325 VERIFY3S(volume, <, SMRT_MAX_LOGDRV); 326 mutex_enter(&smrt->smrt_mutex); 327 VERIFY(smrt_logvol_lookup_by_id(smrt, volume) != NULL); 328 mutex_exit(&smrt->smrt_mutex); 329 *privpp = NULL; 330 } 331 332 boolean_t 333 smrt_logvol_tgtmap_deactivate(void *arg, char *addr, 334 scsi_tgtmap_tgt_type_t type, void *priv, scsi_tgtmap_deact_rsn_t reason) 335 { 336 smrt_t *smrt = arg; 337 smrt_volume_t *smlv; 338 unsigned long volume; 339 char *eptr; 340 341 VERIFY(type == SCSI_TGT_SCSI_DEVICE); 342 VERIFY(priv == NULL); 343 VERIFY0(ddi_strtoul(addr, &eptr, 16, &volume)); 344 VERIFY3S(*eptr, ==, '\0'); 345 VERIFY3S(volume, >=, 0); 346 VERIFY3S(volume, <, SMRT_MAX_LOGDRV); 347 348 mutex_enter(&smrt->smrt_mutex); 349 smlv = smrt_logvol_lookup_by_id(smrt, volume); 350 VERIFY(smlv != NULL); 351 352 list_remove(&smrt->smrt_volumes, smlv); 353 smrt_logvol_free(smlv); 354 mutex_exit(&smrt->smrt_mutex); 355 356 return (B_FALSE); 357 } 358 359 void 360 smrt_logvol_teardown(smrt_t *smrt) 361 { 362 smrt_volume_t *smlv; 363 364 while ((smlv = list_remove_head(&smrt->smrt_volumes)) != NULL) { 365 smrt_logvol_free(smlv); 366 } 367 } 368