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
smrt_logvol_free(smrt_volume_t * smlv)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 *
smrt_logvol_lookup_by_id(smrt_t * smrt,unsigned long id)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
smrt_read_logvols(smrt_t * smrt,smrt_report_logical_lun_t * smrll,uint64_t gen)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
smrt_read_logvols_ext(smrt_t * smrt,smrt_report_logical_lun_t * smrll,uint64_t gen)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
smrt_logvol_discover(smrt_t * smrt,uint16_t timeout,uint64_t gen)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
smrt_logvol_tgtmap_activate(void * arg,char * addr,scsi_tgtmap_tgt_type_t type,void ** privpp)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
smrt_logvol_tgtmap_deactivate(void * arg,char * addr,scsi_tgtmap_tgt_type_t type,void * priv,scsi_tgtmap_deact_rsn_t reason)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
smrt_logvol_teardown(smrt_t * smrt)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