xref: /illumos-gate/usr/src/uts/common/io/scsi/adapters/smrt/smrt_logvol.c (revision b8aa3def2e2531e693fba6d1f00a74339a4a663d)
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