1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  * iSCSI logical unit interfaces
26  */
27 
28 #include "iscsi.h"
29 #include <sys/fs/dv_node.h>	/* devfs_clean */
30 #include <sys/bootprops.h>
31 
32 /* tpgt bytes in string form */
33 #define	TPGT_EXT_SIZE	5
34 
35 /* logical unit number bytes in string form */
36 #define	LUN_EXT_SIZE	10
37 
38 /*
39  * Addition addr size of size of ',' + max str form of tpgt (2 bytes) +
40  * ',' + max str form of logical unit number (4 bytes).
41  */
42 #define	ADDR_EXT_SIZE	(1 + TPGT_EXT_SIZE + 1 + LUN_EXT_SIZE)
43 
44 /* internal interfaces */
45 static iscsi_status_t iscsi_lun_virt_create(iscsi_sess_t *isp,
46     uint16_t lun_num, iscsi_lun_t *ilp, struct scsi_inquiry *inq);
47 static iscsi_status_t iscsi_lun_phys_create(iscsi_sess_t *isp,
48     uint16_t lun_num, iscsi_lun_t *ilp, struct scsi_inquiry *inq);
49 
50 extern dev_info_t	*scsi_vhci_dip;
51 extern ib_boot_prop_t   *iscsiboot_prop;
52 
53 /*
54  * +--------------------------------------------------------------------+
55  * | External Connection Interfaces					|
56  * +--------------------------------------------------------------------+
57  */
58 
59 
60 /*
61  * iscsi_lun_create - This function will create a lun mapping.
62  * logic specific to MPxIO vs. NDI node creation is switched
63  * out to a helper function.
64  */
65 iscsi_status_t
66 iscsi_lun_create(iscsi_sess_t *isp, uint16_t lun_num, uint8_t lun_addr_type,
67     struct scsi_inquiry *inq, char *guid)
68 {
69 	iscsi_status_t		rtn		= ISCSI_STATUS_INTERNAL_ERROR;
70 	iscsi_hba_t		*ihp		= NULL;
71 	iscsi_lun_t		*ilp		= NULL;
72 	iscsi_lun_t		*ilp_tmp	= NULL;
73 	char			*addr		= NULL;
74 	uint16_t		boot_lun_num	= 0;
75 	uint64_t		*lun_num_ptr	= NULL;
76 
77 	ASSERT(isp != NULL);
78 	ihp = isp->sess_hba;
79 	ASSERT(ihp != NULL);
80 
81 	addr = kmem_zalloc((strlen((char *)isp->sess_name) +
82 	    ADDR_EXT_SIZE + 1), KM_SLEEP);
83 	(void) snprintf(addr,
84 	    (strlen((char *)isp->sess_name) +
85 	    ADDR_EXT_SIZE + 1),
86 	    "%02X%02X%s%04X,%d", isp->sess_isid[4],
87 	    isp->sess_isid[5], isp->sess_name,
88 	    isp->sess_tpgt_nego & 0xFFFF, lun_num);
89 
90 	/* allocate space for lun struct */
91 	ilp = kmem_zalloc(sizeof (iscsi_lun_t), KM_SLEEP);
92 	ilp->lun_sig = ISCSI_SIG_LUN;
93 	ilp->lun_state &= ISCSI_LUN_STATE_CLEAR;
94 	ilp->lun_state |= ISCSI_LUN_STATE_OFFLINE;
95 
96 	/* initialize common LU information */
97 	ilp->lun_num	    = lun_num;
98 	ilp->lun_addr_type  = lun_addr_type;
99 	ilp->lun_sess	    = isp;
100 	ilp->lun_addr	    = addr;
101 
102 	mutex_enter(&iscsi_oid_mutex);
103 	ilp->lun_oid = iscsi_oid++;
104 	mutex_exit(&iscsi_oid_mutex);
105 
106 	bcopy(inq->inq_vid, ilp->lun_vid, sizeof (inq->inq_vid));
107 	bcopy(inq->inq_pid, ilp->lun_pid, sizeof (inq->inq_pid));
108 
109 	/* store GUID if valid one exists */
110 	if (guid != NULL) {
111 		ilp->lun_guid_size = strlen(guid) + 1;
112 		ilp->lun_guid = kmem_zalloc(ilp->lun_guid_size, KM_SLEEP);
113 		(void) strcpy(ilp->lun_guid, guid);
114 	} else {
115 		ilp->lun_guid_size = 0;
116 		ilp->lun_guid = NULL;
117 	}
118 
119 	/*
120 	 * We need to add the lun to our lists now because during the
121 	 * lun creation we will get called back into multiple times
122 	 * depending on the createion type.  These callbacks will
123 	 * occur via our tran_init_lun, tran_get_name, tran_get_bus_addr,
124 	 * tran_init_pkt, tran_start.
125 	 */
126 	rw_enter(&isp->sess_lun_list_rwlock, RW_WRITER);
127 	if (isp->sess_lun_list == NULL) {
128 		isp->sess_lun_list = ilp;
129 	} else {
130 		ilp->lun_next = isp->sess_lun_list;
131 		isp->sess_lun_list = ilp;
132 	}
133 
134 	/* Attempt to create a scsi_vhci binding if GUID is available */
135 	if ((ihp->hba_mpxio_enabled == B_TRUE) &&
136 	    (guid != NULL)) {
137 		rtn = iscsi_lun_virt_create(isp, lun_num, ilp, inq);
138 	}
139 	if (!ISCSI_SUCCESS(rtn)) {
140 		/* unable to bind under scsi_vhci, failback to ndi */
141 		rtn = iscsi_lun_phys_create(isp, lun_num, ilp, inq);
142 	}
143 
144 	/*
145 	 * If NOT successful we need to remove the lun from the
146 	 * session and free any related resources.
147 	 */
148 	if (!ISCSI_SUCCESS(rtn)) {
149 		if (ilp == isp->sess_lun_list) {
150 			/* if head, set head to our next */
151 			isp->sess_lun_list = ilp->lun_next;
152 		} else {
153 			/* if not head, set prev lun's next to our next */
154 			for (ilp_tmp = isp->sess_lun_list; ilp_tmp;
155 			    ilp_tmp = ilp_tmp->lun_next) {
156 				if (ilp_tmp->lun_next == ilp) {
157 					ilp_tmp->lun_next = ilp->lun_next;
158 					break;
159 				}
160 			}
161 		}
162 
163 		kmem_free(ilp->lun_addr,
164 		    (strlen((char *)isp->sess_name) +
165 		    ADDR_EXT_SIZE + 1));
166 		ilp->lun_addr = NULL;
167 
168 		if (ilp->lun_guid != NULL) {
169 			kmem_free(ilp->lun_guid, ilp->lun_guid_size);
170 			ilp->lun_guid = NULL;
171 		}
172 		kmem_free(ilp, sizeof (iscsi_lun_t));
173 	} else {
174 		ilp->lun_state &= ISCSI_LUN_STATE_CLEAR;
175 		ilp->lun_state |= ISCSI_LUN_STATE_ONLINE;
176 		ilp->lun_time_online = ddi_get_time();
177 
178 		/* Check whether this is the required LUN for iscsi boot */
179 		if (iscsiboot_prop != NULL && isp->sess_boot == B_TRUE &&
180 		    iscsiboot_prop->boot_tgt.lun_online == 0) {
181 			lun_num_ptr =
182 			    (uint64_t *)iscsiboot_prop->boot_tgt.tgt_boot_lun;
183 			boot_lun_num = (uint16_t)(*lun_num_ptr);
184 			if (boot_lun_num == ilp->lun_num) {
185 				/*
186 				 * During iscsi boot, the boot lun has been
187 				 * online, we should set the "online flag".
188 				 */
189 				iscsiboot_prop->boot_tgt.lun_online = 1;
190 			}
191 		}
192 	}
193 	rw_exit(&isp->sess_lun_list_rwlock);
194 
195 	return (rtn);
196 }
197 
198 /*
199  * iscsi_lun_destroy - offline and remove lun
200  *
201  * This interface is called when a name service change has
202  * occured and the storage is no longer available to this
203  * initiator.  This function will offline and free the
204  * solaris node resources.  Then it will free all iscsi lun
205  * resources.
206  *
207  * This function can fail with ISCSI_STATUS_BUSY if the
208  * logical unit is in use.  The user should unmount or
209  * close the device and perform the nameservice operation
210  * again if this occurs.
211  */
212 iscsi_status_t
213 iscsi_lun_destroy(iscsi_hba_t *ihp, iscsi_lun_t *ilp)
214 {
215 	iscsi_status_t		status		= ISCSI_STATUS_SUCCESS;
216 	iscsi_sess_t		*isp		= NULL;
217 	iscsi_lun_t		*t_ilp		= NULL;
218 
219 	ASSERT(ilp != NULL);
220 	isp = ilp->lun_sess;
221 	ASSERT(isp != NULL);
222 
223 	/* attempt to offline and free solaris node */
224 	status = iscsi_lun_offline(ihp, ilp, B_TRUE);
225 
226 	/* If we successfully unplumbed the lun remove it from our lists */
227 	if (ISCSI_SUCCESS(status)) {
228 		if (isp->sess_lun_list == ilp) {
229 			/* target first item in list */
230 			isp->sess_lun_list = ilp->lun_next;
231 		} else {
232 			/*
233 			 * search session list for ilp pointing
234 			 * to lun being removed.  Then
235 			 * update that luns next pointer.
236 			 */
237 			t_ilp = isp->sess_lun_list;
238 			while (t_ilp->lun_next != NULL) {
239 				if (t_ilp->lun_next == ilp) {
240 					break;
241 				}
242 				t_ilp = t_ilp->lun_next;
243 			}
244 			if (t_ilp->lun_next == ilp) {
245 				t_ilp->lun_next = ilp->lun_next;
246 			} else {
247 				/* couldn't find session */
248 				ASSERT(FALSE);
249 			}
250 		}
251 
252 		/* release its memory */
253 		kmem_free(ilp->lun_addr, (strlen((char *)isp->sess_name) +
254 		    ADDR_EXT_SIZE + 1));
255 		ilp->lun_addr = NULL;
256 		if (ilp->lun_guid != NULL) {
257 			kmem_free(ilp->lun_guid, ilp->lun_guid_size);
258 			ilp->lun_guid = NULL;
259 		}
260 		kmem_free(ilp, sizeof (iscsi_lun_t));
261 		ilp = NULL;
262 	}
263 
264 	return (status);
265 }
266 
267 /*
268  * +--------------------------------------------------------------------+
269  * | External Logical Unit Interfaces					|
270  * +--------------------------------------------------------------------+
271  */
272 
273 /*
274  * iscsi_lun_virt_create - Creates solaris logical unit via MDI
275  */
276 static iscsi_status_t
277 iscsi_lun_virt_create(iscsi_sess_t *isp, uint16_t lun_num, iscsi_lun_t *ilp,
278     struct scsi_inquiry *inq)
279 {
280 	iscsi_status_t		rtn		= ISCSI_STATUS_INTERNAL_ERROR;
281 	int			mdi_rtn		= MDI_FAILURE;
282 	iscsi_hba_t		*ihp		= NULL;
283 	mdi_pathinfo_t		*pip		= NULL;
284 	char			*nodename	= NULL;
285 	char			**compatible	= NULL;
286 	int			ncompatible	= 0;
287 	int			circ = 0;
288 
289 	ASSERT(isp != NULL);
290 	ASSERT(ilp != NULL);
291 	ihp = isp->sess_hba;
292 	ASSERT(ihp != NULL);
293 
294 	/*
295 	 * Generate compatible property
296 	 */
297 	scsi_hba_nodename_compatible_get(inq, "vhci",
298 	    inq->inq_dtype, NULL, &nodename, &compatible, &ncompatible);
299 
300 	/* if nodename can't be determined then print a message and skip it */
301 	if (nodename == NULL) {
302 		cmn_err(CE_WARN, "iscsi driver found no compatible driver "
303 		    "for %s lun %d dtype:0x%02x", isp->sess_name, lun_num,
304 		    inq->inq_dtype);
305 		return (ISCSI_STATUS_INTERNAL_ERROR);
306 	}
307 
308 	/*
309 	 *
310 	 */
311 	ndi_devi_enter(scsi_vhci_dip, &circ);
312 	mdi_rtn = mdi_pi_alloc_compatible(ihp->hba_dip, nodename,
313 	    ilp->lun_guid, ilp->lun_addr, compatible, ncompatible,
314 	    0, &pip);
315 
316 	if (mdi_rtn == MDI_SUCCESS) {
317 		mdi_pi_set_phci_private(pip, (caddr_t)ilp);
318 
319 		if (mdi_prop_update_string(pip, MDI_GUID,
320 		    ilp->lun_guid) != DDI_SUCCESS) {
321 			cmn_err(CE_WARN, "iscsi driver unable to create "
322 			    "property for %s lun %d (MDI_GUID)",
323 			    isp->sess_name, lun_num);
324 			mdi_rtn = MDI_FAILURE;
325 			goto virt_create_done;
326 		}
327 
328 		if (mdi_prop_update_int(pip, TARGET_PROP,
329 		    isp->sess_oid) != DDI_SUCCESS) {
330 			cmn_err(CE_WARN, "iscsi driver unable to create "
331 			    "property for %s lun %d (TARGET_PROP)",
332 			    isp->sess_name, lun_num);
333 			mdi_rtn = MDI_FAILURE;
334 			goto virt_create_done;
335 		}
336 
337 		if (mdi_prop_update_int(pip, LUN_PROP,
338 		    ilp->lun_num) != DDI_SUCCESS) {
339 			cmn_err(CE_WARN, "iscsi driver unable to create "
340 			    "property for %s lun %d (LUN_PROP)",
341 			    isp->sess_name, lun_num);
342 			mdi_rtn = MDI_FAILURE;
343 			goto virt_create_done;
344 		}
345 
346 		if (mdi_prop_update_string_array(pip, "compatible",
347 		    compatible, ncompatible) !=
348 		    DDI_PROP_SUCCESS) {
349 			cmn_err(CE_WARN, "iscsi driver unable to create "
350 			    "property for %s lun %d (COMPATIBLE)",
351 			    isp->sess_name, lun_num);
352 			mdi_rtn = MDI_FAILURE;
353 			goto virt_create_done;
354 		}
355 
356 		mdi_rtn = mdi_pi_online(pip, 0);
357 		if (mdi_rtn == MDI_NOT_SUPPORTED) {
358 			mdi_rtn = MDI_FAILURE;
359 			goto virt_create_done;
360 		}
361 
362 		ilp->lun_pip = pip;
363 		ilp->lun_dip = NULL;
364 
365 virt_create_done:
366 
367 		if (pip && mdi_rtn != MDI_SUCCESS) {
368 			ilp->lun_pip = NULL;
369 			ilp->lun_dip = NULL;
370 			(void) mdi_prop_remove(pip, NULL);
371 			(void) mdi_pi_free(pip, 0);
372 		} else {
373 			rtn = ISCSI_STATUS_SUCCESS;
374 		}
375 	}
376 	ndi_devi_exit(scsi_vhci_dip, circ);
377 
378 	scsi_hba_nodename_compatible_free(nodename, compatible);
379 
380 	return (rtn);
381 }
382 
383 
384 /*
385  * iscsi_lun_phys_create - creates solaris logical unit via NDI
386  */
387 static iscsi_status_t
388 iscsi_lun_phys_create(iscsi_sess_t *isp, uint16_t lun_num,
389     iscsi_lun_t *ilp, struct scsi_inquiry *inq)
390 {
391 	iscsi_status_t		rtn		= ISCSI_STATUS_INTERNAL_ERROR;
392 	int			ndi_rtn		= NDI_FAILURE;
393 	iscsi_hba_t		*ihp		= NULL;
394 	dev_info_t		*lun_dip	= NULL;
395 	char			*nodename	= NULL;
396 	char			**compatible	= NULL;
397 	int			ncompatible	= 0;
398 	char			*scsi_binding_set = NULL;
399 	char			instance[32];
400 	int			circ		= 0;
401 
402 	ASSERT(isp != NULL);
403 	ASSERT(ilp != NULL);
404 	ihp = isp->sess_hba;
405 	ASSERT(ihp != NULL);
406 	ASSERT(inq != NULL);
407 
408 	/* get the 'scsi-binding-set' property */
409 	if (ddi_prop_lookup_string(DDI_DEV_T_ANY, isp->sess_hba->hba_dip,
410 	    DDI_PROP_NOTPROM | DDI_PROP_DONTPASS, "scsi-binding-set",
411 	    &scsi_binding_set) != DDI_PROP_SUCCESS) {
412 		scsi_binding_set = NULL;
413 	}
414 
415 	/* generate compatible property */
416 	scsi_hba_nodename_compatible_get(inq, scsi_binding_set,
417 	    inq->inq_dtype, NULL, &nodename, &compatible, &ncompatible);
418 	if (scsi_binding_set)
419 		ddi_prop_free(scsi_binding_set);
420 
421 	/* if nodename can't be determined then print a message and skip it */
422 	if (nodename == NULL) {
423 		cmn_err(CE_WARN, "iscsi driver found no compatible driver "
424 		    "for %s lun %d", isp->sess_name, lun_num);
425 		return (ISCSI_STATUS_INTERNAL_ERROR);
426 	}
427 
428 	ndi_devi_enter(ihp->hba_dip, &circ);
429 
430 	ndi_rtn = ndi_devi_alloc(ihp->hba_dip, nodename,
431 	    DEVI_SID_NODEID, &lun_dip);
432 
433 	/* if lun alloc success, set props */
434 	if (ndi_rtn == NDI_SUCCESS) {
435 
436 		if (ndi_prop_update_int(DDI_DEV_T_NONE,
437 		    lun_dip, TARGET_PROP, (int)isp->sess_oid) !=
438 		    DDI_PROP_SUCCESS) {
439 			cmn_err(CE_WARN, "iscsi driver unable to create "
440 			    "property for %s lun %d (TARGET_PROP)",
441 			    isp->sess_name, lun_num);
442 			ndi_rtn = NDI_FAILURE;
443 			goto phys_create_done;
444 		}
445 
446 		if (ndi_prop_update_int(DDI_DEV_T_NONE,
447 		    lun_dip, LUN_PROP, (int)ilp->lun_num) !=
448 		    DDI_PROP_SUCCESS) {
449 			cmn_err(CE_WARN, "iscsi driver unable to create "
450 			    "property for %s lun %d (LUN_PROP)",
451 			    isp->sess_name, lun_num);
452 			ndi_rtn = NDI_FAILURE;
453 			goto phys_create_done;
454 		}
455 
456 		if (ndi_prop_update_string_array(DDI_DEV_T_NONE,
457 		    lun_dip, "compatible", compatible, ncompatible)
458 		    != DDI_PROP_SUCCESS) {
459 			cmn_err(CE_WARN, "iscsi driver unable to create "
460 			    "property for %s lun %d (COMPATIBLE)",
461 			    isp->sess_name, lun_num);
462 			ndi_rtn = NDI_FAILURE;
463 			goto phys_create_done;
464 		}
465 
466 phys_create_done:
467 		/* If props were setup ok, online the lun */
468 		if (ndi_rtn == NDI_SUCCESS) {
469 			/* Try to online the new node */
470 			ndi_rtn = ndi_devi_online(lun_dip, 0);
471 		}
472 
473 		/* If success set rtn flag, else unwire alloc'd lun */
474 		if (ndi_rtn == NDI_SUCCESS) {
475 			rtn = ISCSI_STATUS_SUCCESS;
476 			/*
477 			 * Assign the instance number for the dev_link
478 			 * generator.  This will ensure the link name is
479 			 * unique and persistent across reboots.
480 			 */
481 			(void) snprintf(instance, 32, "%d",
482 			    ddi_get_instance(lun_dip));
483 			(void) ndi_prop_update_string(DDI_DEV_T_NONE,
484 			    lun_dip, NDI_GUID, instance);
485 		} else {
486 			cmn_err(CE_WARN, "iscsi driver unable to online "
487 			    "%s lun %d", isp->sess_name, lun_num);
488 			ndi_prop_remove_all(lun_dip);
489 			(void) ndi_devi_free(lun_dip);
490 		}
491 
492 	}
493 	ndi_devi_exit(ihp->hba_dip, circ);
494 
495 	ilp->lun_dip = lun_dip;
496 	ilp->lun_pip = NULL;
497 
498 	scsi_hba_nodename_compatible_free(nodename, compatible);
499 
500 	return (rtn);
501 }
502 
503 
504 /*
505  * iscsi_lun_online - _di_online logical unit
506  *
507  * This is called after a path has recovered it will cause
508  * an offline path to become online/active again.
509  */
510 void
511 iscsi_lun_online(iscsi_hba_t *ihp, iscsi_lun_t *ilp)
512 {
513 	int			circ		= 0;
514 	int			rval		= 0;
515 	uint64_t		*lun_num_ptr	= NULL;
516 	uint16_t		boot_lun_num	= 0;
517 	iscsi_sess_t		*isp		= NULL;
518 
519 	ASSERT(ilp != NULL);
520 	ASSERT((ilp->lun_pip != NULL) || (ilp->lun_dip != NULL));
521 
522 	if (ilp->lun_pip != NULL) {
523 		ndi_devi_enter(scsi_vhci_dip, &circ);
524 		rval =  mdi_pi_online(ilp->lun_pip, 0);
525 		ndi_devi_exit(scsi_vhci_dip, circ);
526 		if (rval == MDI_SUCCESS) {
527 			ilp->lun_state &= ISCSI_LUN_STATE_CLEAR;
528 			ilp->lun_state |= ISCSI_LUN_STATE_ONLINE;
529 			ilp->lun_time_online = ddi_get_time();
530 		}
531 
532 	} else if (ilp->lun_dip != NULL) {
533 		ndi_devi_enter(ihp->hba_dip, &circ);
534 		rval =  ndi_devi_online(ilp->lun_dip, 0);
535 		ndi_devi_exit(ihp->hba_dip, circ);
536 		if (rval == NDI_SUCCESS) {
537 			ilp->lun_state &= ISCSI_LUN_STATE_CLEAR;
538 			ilp->lun_state |= ISCSI_LUN_STATE_ONLINE;
539 			ilp->lun_time_online = ddi_get_time();
540 		}
541 	}
542 
543 	/* Check whether this is the required LUN for iscsi boot */
544 	if (iscsiboot_prop != NULL &&
545 	    iscsiboot_prop->boot_tgt.lun_online == 0) {
546 		isp = ilp->lun_sess;
547 		if (isp->sess_boot == B_TRUE) {
548 			lun_num_ptr =
549 			    (uint64_t *)iscsiboot_prop->boot_tgt.tgt_boot_lun;
550 			boot_lun_num = (uint16_t)(*lun_num_ptr);
551 			if (boot_lun_num == ilp->lun_num) {
552 				/*
553 				 * During iscsi boot, the boot lun has been
554 				 * online, we should set the "online flag".
555 				 */
556 				iscsiboot_prop->boot_tgt.lun_online = 1;
557 			}
558 		}
559 	}
560 }
561 
562 /*
563  * iscsi_lun_offline - attempt _di_offline [and optional _di_free]
564  *
565  * This function is called via two paths.  When a transport
566  * path has failed it will be called to offline the logical
567  * unit.  When nameservice access has been removed it will
568  * be called to both offline and free the logical unit.
569  * (This operates soley on the solaris node states.
570  * iscsi_lun_destroy() should be called when attempting
571  * to free all iscsi lun resources.)
572  *
573  * This function can fail with ISCSI_STATUS_BUSY if the
574  * logical unit is in use.  The user should unmount or
575  * close the device and perform the nameservice operation
576  * again if this occurs.
577  *
578  * If we fail to offline a LUN that we don't want to destroy,
579  * we will mark it with invalid state. If this LUN still
580  * exists on the target, we can have another chance to online
581  * it again when we do the LUN enumeration.
582  */
583 iscsi_status_t
584 iscsi_lun_offline(iscsi_hba_t *ihp, iscsi_lun_t *ilp, boolean_t lun_free)
585 {
586 	iscsi_status_t		status	= ISCSI_STATUS_SUCCESS;
587 	int			circ	= 0;
588 	dev_info_t		*cdip, *pdip;
589 	char			*devname;
590 	int			rval;
591 
592 	ASSERT(ilp != NULL);
593 	ASSERT((ilp->lun_pip != NULL) || (ilp->lun_dip != NULL));
594 
595 	/*
596 	 * Since we carry the logical units parent
597 	 * lock across the offline call it will not
598 	 * issue devfs_clean() and may fail with a
599 	 * devi_ref count > 0.
600 	 */
601 	if (ilp->lun_pip == NULL) {
602 		cdip = ilp->lun_dip;
603 	} else {
604 		cdip = mdi_pi_get_client(ilp->lun_pip);
605 	}
606 
607 	if ((cdip != NULL) &&
608 	    (lun_free == B_TRUE) &&
609 	    (ilp->lun_state & ISCSI_LUN_STATE_ONLINE)) {
610 		/*
611 		 * Make sure node is attached otherwise
612 		 * it won't have related cache nodes to
613 		 * clean up.  i_ddi_devi_attached is
614 		 * similiar to i_ddi_node_state(cdip) >=
615 		 * DS_ATTACHED. We should clean up only
616 		 * when lun_free is set.
617 		 */
618 		if (i_ddi_devi_attached(cdip)) {
619 
620 			/* Get parent dip */
621 			pdip = ddi_get_parent(cdip);
622 
623 			/* Get full devname */
624 			devname = kmem_alloc(MAXNAMELEN + 1, KM_SLEEP);
625 			ndi_devi_enter(pdip, &circ);
626 			(void) ddi_deviname(cdip, devname);
627 			/* Release lock before devfs_clean() */
628 			ndi_devi_exit(pdip, circ);
629 
630 			/* Clean cache */
631 			rval = devfs_clean(pdip, devname + 1,
632 			    DV_CLEAN_FORCE);
633 			kmem_free(devname, MAXNAMELEN + 1);
634 
635 			if ((rval != 0) && (ilp->lun_pip == NULL)) {
636 				return (ISCSI_STATUS_BUSY);
637 			}
638 		}
639 	}
640 
641 	/* Attempt to offline the logical units */
642 	if (ilp->lun_pip != NULL) {
643 
644 		/* virt/mdi */
645 		ndi_devi_enter(scsi_vhci_dip, &circ);
646 		if ((lun_free == B_TRUE) &&
647 		    (ilp->lun_state & ISCSI_LUN_STATE_ONLINE)) {
648 			rval = mdi_pi_offline(ilp->lun_pip,
649 			    NDI_DEVI_REMOVE);
650 		} else {
651 			rval = mdi_pi_offline(ilp->lun_pip, 0);
652 		}
653 
654 		if (rval == MDI_SUCCESS) {
655 			ilp->lun_state &= ISCSI_LUN_STATE_CLEAR;
656 			ilp->lun_state |= ISCSI_LUN_STATE_OFFLINE;
657 			if (lun_free == B_TRUE) {
658 				(void) mdi_prop_remove(ilp->lun_pip, NULL);
659 				(void) mdi_pi_free(ilp->lun_pip, 0);
660 			}
661 		} else {
662 			status = ISCSI_STATUS_INTERNAL_ERROR;
663 			if (lun_free == B_FALSE) {
664 				ilp->lun_state |= ISCSI_LUN_STATE_INVALID;
665 			}
666 		}
667 		ndi_devi_exit(scsi_vhci_dip, circ);
668 
669 	} else  {
670 
671 		/* phys/ndi */
672 		ndi_devi_enter(ihp->hba_dip, &circ);
673 		if ((lun_free == B_TRUE) &&
674 		    (ilp->lun_state & ISCSI_LUN_STATE_ONLINE)) {
675 			rval = ndi_devi_offline(
676 			    ilp->lun_dip, NDI_DEVI_REMOVE);
677 		} else {
678 			rval = ndi_devi_offline(
679 			    ilp->lun_dip, 0);
680 		}
681 		if (rval != NDI_SUCCESS) {
682 			status = ISCSI_STATUS_INTERNAL_ERROR;
683 			if (lun_free == B_FALSE) {
684 				ilp->lun_state |= ISCSI_LUN_STATE_INVALID;
685 			}
686 		} else {
687 			ilp->lun_state &= ISCSI_LUN_STATE_CLEAR;
688 			ilp->lun_state |= ISCSI_LUN_STATE_OFFLINE;
689 		}
690 		ndi_devi_exit(ihp->hba_dip, circ);
691 
692 	}
693 	return (status);
694 }
695