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