/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Fibre Channel SCSI ULP Mapping driver
 */

#include <sys/scsi/scsi.h>
#include <sys/types.h>
#include <sys/varargs.h>
#include <sys/devctl.h>
#include <sys/thread.h>
#include <sys/thread.h>
#include <sys/open.h>
#include <sys/file.h>
#include <sys/sunndi.h>
#include <sys/console.h>
#include <sys/proc.h>
#include <sys/time.h>
#include <sys/utsname.h>
#include <sys/scsi/impl/scsi_reset_notify.h>
#include <sys/ndi_impldefs.h>
#include <sys/byteorder.h>
#include <sys/fs/dv_node.h>
#include <sys/ctype.h>
#include <sys/sunmdi.h>

#include <sys/fibre-channel/fc.h>
#include <sys/fibre-channel/impl/fc_ulpif.h>
#include <sys/fibre-channel/ulp/fcpvar.h>

/*
 * Discovery Process
 * =================
 *
 *    The discovery process is a major function of FCP.	 In order to help
 * understand that function a flow diagram is given here.  This diagram
 * doesn't claim to cover all the cases and the events that can occur during
 * the discovery process nor the subtleties of the code.  The code paths shown
 * are simplified.  Its purpose is to help the reader (and potentially bug
 * fixer) have an overall view of the logic of the code.  For that reason the
 * diagram covers the simple case of the line coming up cleanly or of a new
 * port attaching to FCP the link being up.  The reader must keep in mind
 * that:
 *
 *	- There are special cases where bringing devices online and offline
 *	  is driven by Ioctl.
 *
 *	- The behavior of the discovery process can be modified through the
 *	  .conf file.
 *
 *	- The line can go down and come back up at any time during the
 *	  discovery process which explains some of the complexity of the code.
 *
 * ............................................................................
 *
 * STEP 1: The line comes up or a new Fibre Channel port attaches to FCP.
 *
 *
 *			+-------------------------+
 *   fp/fctl module --->|    fcp_port_attach	  |
 *			+-------------------------+
 *	   |			     |
 *	   |			     |
 *	   |			     v
 *	   |		+-------------------------+
 *	   |		| fcp_handle_port_attach  |
 *	   |		+-------------------------+
 *	   |				|
 *	   |				|
 *	   +--------------------+	|
 *				|	|
 *				v	v
 *			+-------------------------+
 *			|   fcp_statec_callback	  |
 *			+-------------------------+
 *				    |
 *				    |
 *				    v
 *			+-------------------------+
 *			|    fcp_handle_devices	  |
 *			+-------------------------+
 *				    |
 *				    |
 *				    v
 *			+-------------------------+
 *			|   fcp_handle_mapflags	  |
 *			+-------------------------+
 *				    |
 *				    |
 *				    v
 *			+-------------------------+
 *			|     fcp_send_els	  |
 *			|			  |
 *			| PLOGI or PRLI To all the|
 *			| reachable devices.	  |
 *			+-------------------------+
 *
 *
 * ............................................................................
 *
 * STEP 2: The callback functions of the PLOGI and/or PRLI requests sent during
 *	   STEP 1 are called (it is actually the same function).
 *
 *
 *			+-------------------------+
 *			|    fcp_icmd_callback	  |
 *   fp/fctl module --->|			  |
 *			| callback for PLOGI and  |
 *			| PRLI.			  |
 *			+-------------------------+
 *				     |
 *				     |
 *	    Received PLOGI Accept   /-\	  Received PRLI Accept
 *		       _ _ _ _ _ _ /   \_ _ _ _ _ _
 *		      |		   \   /	   |
 *		      |		    \-/		   |
 *		      |				   |
 *		      v				   v
 *	+-------------------------+	+-------------------------+
 *	|     fcp_send_els	  |	|     fcp_send_scsi	  |
 *	|			  |	|			  |
 *	|	  PRLI		  |	|	REPORT_LUN	  |
 *	+-------------------------+	+-------------------------+
 *
 * ............................................................................
 *
 * STEP 3: The callback functions of the SCSI commands issued by FCP are called
 *	   (It is actually the same function).
 *
 *
 *			    +-------------------------+
 *   fp/fctl module ------->|	 fcp_scsi_callback    |
 *			    +-------------------------+
 *					|
 *					|
 *					|
 *	Receive REPORT_LUN reply       /-\	Receive INQUIRY PAGE83 reply
 *		  _ _ _ _ _ _ _ _ _ _ /	  \_ _ _ _ _ _ _ _ _ _ _ _
 *		 |		      \	  /			  |
 *		 |		       \-/			  |
 *		 |			|			  |
 *		 | Receive INQUIRY reply|			  |
 *		 |			|			  |
 *		 v			v			  v
 * +------------------------+ +----------------------+ +----------------------+
 * |  fcp_handle_reportlun  | |	 fcp_handle_inquiry  | |  fcp_handle_page83   |
 * |(Called for each Target)| | (Called for each LUN)| |(Called for each LUN) |
 * +------------------------+ +----------------------+ +----------------------+
 *		 |			|			  |
 *		 |			|			  |
 *		 |			|			  |
 *		 v			v			  |
 *     +-----------------+	+-----------------+		  |
 *     |  fcp_send_scsi	 |	|  fcp_send_scsi  |		  |
 *     |		 |	|		  |		  |
 *     |     INQUIRY	 |	| INQUIRY PAGE83  |		  |
 *     |  (To each LUN)	 |	+-----------------+		  |
 *     +-----------------+					  |
 *								  |
 *								  v
 *						      +------------------------+
 *						      |	 fcp_call_finish_init  |
 *						      +------------------------+
 *								  |
 *								  v
 *						 +-----------------------------+
 *						 |  fcp_call_finish_init_held  |
 *						 +-----------------------------+
 *								  |
 *								  |
 *			   All LUNs scanned			 /-\
 *			       _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __ /   \
 *			      |					\   /
 *			      |					 \-/
 *			      v					  |
 *		     +------------------+			  |
 *		     |	fcp_finish_tgt	|			  |
 *		     +------------------+			  |
 *			      |	  Target Not Offline and	  |
 *  Target Not Offline and    |	  not marked and tgt_node_state	  |
 *  marked		     /-\  not FCP_TGT_NODE_ON_DEMAND	  |
 *		_ _ _ _ _ _ /	\_ _ _ _ _ _ _ _		  |
 *	       |	    \	/		|		  |
 *	       |	     \-/		|		  |
 *	       v				v		  |
 * +----------------------------+     +-------------------+	  |
 * |	 fcp_offline_target	|     |	 fcp_create_luns  |	  |
 * |				|     +-------------------+	  |
 * | A structure fcp_tgt_elem	|		|		  |
 * | is created and queued in	|		v		  |
 * | the FCP port list		|     +-------------------+	  |
 * | port_offline_tgts.	 It	|     |	 fcp_pass_to_hp	  |	  |
 * | will be unqueued by the	|     |			  |	  |
 * | watchdog timer.		|     | Called for each	  |	  |
 * +----------------------------+     | LUN. Dispatches	  |	  |
 *		  |		      | fcp_hp_task	  |	  |
 *		  |		      +-------------------+	  |
 *		  |				|		  |
 *		  |				|		  |
 *		  |				|		  |
 *		  |				+---------------->|
 *		  |						  |
 *		  +---------------------------------------------->|
 *								  |
 *								  |
 *		All the targets (devices) have been scanned	 /-\
 *				_ _ _ _	_ _ _ _	_ _ _ _ _ _ _ _ /   \
 *			       |				\   /
 *			       |				 \-/
 *	    +-------------------------------------+		  |
 *	    |		fcp_finish_init		  |		  |
 *	    |					  |		  |
 *	    | Signal broadcasts the condition	  |		  |
 *	    | variable port_config_cv of the FCP  |		  |
 *	    | port.  One potential code sequence  |		  |
 *	    | waiting on the condition variable	  |		  |
 *	    | the code sequence handling	  |		  |
 *	    | BUS_CONFIG_ALL and BUS_CONFIG_DRIVER|		  |
 *	    | The other is in the function	  |		  |
 *	    | fcp_reconfig_wait which is called	  |		  |
 *	    | in the transmit path preventing IOs |		  |
 *	    | from going through till the disco-  |		  |
 *	    | very process is over.		  |		  |
 *	    +-------------------------------------+		  |
 *			       |				  |
 *			       |				  |
 *			       +--------------------------------->|
 *								  |
 *								  v
 *								Return
 *
 * ............................................................................
 *
 * STEP 4: The hot plug task is called (for each fcp_hp_elem).
 *
 *
 *			+-------------------------+
 *			|      fcp_hp_task	  |
 *			+-------------------------+
 *				     |
 *				     |
 *				     v
 *			+-------------------------+
 *			|     fcp_trigger_lun	  |
 *			+-------------------------+
 *				     |
 *				     |
 *				     v
 *		   Bring offline    /-\	 Bring online
 *		  _ _ _ _ _ _ _ _ _/   \_ _ _ _ _ _ _ _ _ _
 *		 |		   \   /		   |
 *		 |		    \-/			   |
 *		 v					   v
 *    +---------------------+			+-----------------------+
 *    |	 fcp_offline_child  |			|      fcp_get_cip	|
 *    +---------------------+			|			|
 *						| Creates a dev_info_t	|
 *						| or a mdi_pathinfo_t	|
 *						| depending on whether	|
 *						| mpxio is on or off.	|
 *						+-----------------------+
 *							   |
 *							   |
 *							   v
 *						+-----------------------+
 *						|  fcp_online_child	|
 *						|			|
 *						| Set device online	|
 *						| using NDI or MDI.	|
 *						+-----------------------+
 *
 * ............................................................................
 *
 * STEP 5: The watchdog timer expires.	The watch dog timer does much more that
 *	   what is described here.  We only show the target offline path.
 *
 *
 *			 +--------------------------+
 *			 |	  fcp_watch	    |
 *			 +--------------------------+
 *				       |
 *				       |
 *				       v
 *			 +--------------------------+
 *			 |  fcp_scan_offline_tgts   |
 *			 +--------------------------+
 *				       |
 *				       |
 *				       v
 *			 +--------------------------+
 *			 |  fcp_offline_target_now  |
 *			 +--------------------------+
 *				       |
 *				       |
 *				       v
 *			 +--------------------------+
 *			 |   fcp_offline_tgt_luns   |
 *			 +--------------------------+
 *				       |
 *				       |
 *				       v
 *			 +--------------------------+
 *			 |     fcp_offline_lun	    |
 *			 +--------------------------+
 *				       |
 *				       |
 *				       v
 *		     +----------------------------------+
 *		     |	     fcp_offline_lun_now	|
 *		     |					|
 *		     | A request (or two if mpxio) is	|
 *		     | sent to the hot plug task using	|
 *		     | a fcp_hp_elem structure.		|
 *		     +----------------------------------+
 */

/*
 * Functions registered with DDI framework
 */
static int fcp_attach(dev_info_t *devi, ddi_attach_cmd_t cmd);
static int fcp_detach(dev_info_t *devi, ddi_detach_cmd_t cmd);
static int fcp_open(dev_t *devp, int flag, int otype, cred_t *credp);
static int fcp_close(dev_t dev, int flag, int otype, cred_t *credp);
static int fcp_ioctl(dev_t dev, int cmd, intptr_t data, int mode,
    cred_t *credp, int *rval);

/*
 * Functions registered with FC Transport framework
 */
static int fcp_port_attach(opaque_t ulph, fc_ulp_port_info_t *pinfo,
    fc_attach_cmd_t cmd,  uint32_t s_id);
static int fcp_port_detach(opaque_t ulph, fc_ulp_port_info_t *info,
    fc_detach_cmd_t cmd);
static int fcp_port_ioctl(opaque_t ulph, opaque_t port_handle, dev_t dev,
    int cmd, intptr_t data, int mode, cred_t *credp, int *rval,
    uint32_t claimed);
static int fcp_els_callback(opaque_t ulph, opaque_t port_handle,
    fc_unsol_buf_t *buf, uint32_t claimed);
static int fcp_data_callback(opaque_t ulph, opaque_t port_handle,
    fc_unsol_buf_t *buf, uint32_t claimed);
static void fcp_statec_callback(opaque_t ulph, opaque_t port_handle,
    uint32_t port_state, uint32_t port_top, fc_portmap_t *devlist,
    uint32_t  dev_cnt, uint32_t port_sid);

/*
 * Functions registered with SCSA framework
 */
static int fcp_phys_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip,
    scsi_hba_tran_t *hba_tran, struct scsi_device *sd);
static int fcp_scsi_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip,
    scsi_hba_tran_t *hba_tran, struct scsi_device *sd);
static void fcp_scsi_tgt_free(dev_info_t *hba_dip, dev_info_t *tgt_dip,
    scsi_hba_tran_t *hba_tran, struct scsi_device *sd);
static int fcp_scsi_start(struct scsi_address *ap, struct scsi_pkt *pkt);
static int fcp_scsi_abort(struct scsi_address *ap, struct scsi_pkt *pkt);
static int fcp_scsi_reset(struct scsi_address *ap, int level);
static int fcp_scsi_getcap(struct scsi_address *ap, char *cap, int whom);
static int fcp_scsi_setcap(struct scsi_address *ap, char *cap, int value,
    int whom);
static void fcp_pkt_teardown(struct scsi_pkt *pkt);
static int fcp_scsi_reset_notify(struct scsi_address *ap, int flag,
    void (*callback)(caddr_t), caddr_t arg);
static int fcp_scsi_bus_get_eventcookie(dev_info_t *dip, dev_info_t *rdip,
    char *name, ddi_eventcookie_t *event_cookiep);
static int fcp_scsi_bus_add_eventcall(dev_info_t *dip, dev_info_t *rdip,
    ddi_eventcookie_t eventid, void (*callback)(), void *arg,
    ddi_callback_id_t *cb_id);
static int fcp_scsi_bus_remove_eventcall(dev_info_t *devi,
    ddi_callback_id_t cb_id);
static int fcp_scsi_bus_post_event(dev_info_t *dip, dev_info_t *rdip,
    ddi_eventcookie_t eventid, void *impldata);
static int fcp_scsi_bus_config(dev_info_t *parent, uint_t flag,
    ddi_bus_config_op_t op, void *arg, dev_info_t **childp);
static int fcp_scsi_bus_unconfig(dev_info_t *parent, uint_t flag,
    ddi_bus_config_op_t op, void *arg);

/*
 * Internal functions
 */
static int fcp_setup_device_data_ioctl(int cmd, struct fcp_ioctl *data,
    int mode, int *rval);

static int fcp_setup_scsi_ioctl(struct fcp_scsi_cmd *u_fscsi,
    int mode, int *rval);
static int fcp_copyin_scsi_cmd(caddr_t base_addr,
    struct fcp_scsi_cmd *fscsi, int mode);
static int fcp_copyout_scsi_cmd(struct fcp_scsi_cmd *fscsi,
    caddr_t base_addr, int mode);
static int fcp_send_scsi_ioctl(struct fcp_scsi_cmd *fscsi);

static struct fcp_tgt *fcp_port_create_tgt(struct fcp_port *pptr,
    la_wwn_t *pwwn, int	*ret_val, int *fc_status, int *fc_pkt_state,
    int *fc_pkt_reason, int *fc_pkt_action);
static int fcp_tgt_send_plogi(struct fcp_tgt *ptgt, int *fc_status,
    int *fc_pkt_state, int *fc_pkt_reason, int *fc_pkt_action);
static int fcp_tgt_send_prli(struct fcp_tgt	*ptgt, int *fc_status,
    int *fc_pkt_state, int *fc_pkt_reason, int *fc_pkt_action);
static void fcp_ipkt_sema_init(struct fcp_ipkt *icmd);
static int fcp_ipkt_sema_wait(struct fcp_ipkt *icmd);
static void fcp_ipkt_sema_callback(struct fc_packet *fpkt);
static void fcp_ipkt_sema_cleanup(struct fcp_ipkt *icmd);

static void fcp_handle_devices(struct fcp_port *pptr,
    fc_portmap_t devlist[], uint32_t dev_cnt, int link_cnt,
    fcp_map_tag_t *map_tag, int cause);
static int fcp_handle_mapflags(struct fcp_port *pptr,
    struct fcp_tgt *ptgt, fc_portmap_t *map_entry, int link_cnt,
    int tgt_cnt, int cause);
static int fcp_send_els(struct fcp_port *pptr, struct fcp_tgt *ptgt,
    struct fcp_ipkt *icmd, uchar_t opcode, int lcount, int tcount, int cause);
static void fcp_update_state(struct fcp_port *pptr, uint32_t state,
    int cause);
static void fcp_update_tgt_state(struct fcp_tgt *ptgt, int flag,
    uint32_t state);
static struct fcp_port *fcp_get_port(opaque_t port_handle);
static void fcp_unsol_callback(fc_packet_t *fpkt);
static void fcp_unsol_resp_init(fc_packet_t *pkt, fc_unsol_buf_t *buf,
    uchar_t r_ctl, uchar_t type);
static int fcp_unsol_prli(struct fcp_port *pptr, fc_unsol_buf_t *buf);
static struct fcp_ipkt *fcp_icmd_alloc(struct fcp_port *pptr,
    struct fcp_tgt *ptgt, int cmd_len, int resp_len, int data_len,
    int nodma, int lcount, int tcount, int cause, uint32_t rscn_count);
static void fcp_icmd_free(struct fcp_port *pptr, struct fcp_ipkt *icmd);
static int fcp_alloc_dma(struct fcp_port *pptr, struct fcp_ipkt *icmd,
    int nodma, int flags);
static void fcp_free_dma(struct fcp_port *pptr, struct fcp_ipkt *icmd);
static struct fcp_tgt *fcp_lookup_target(struct fcp_port *pptr,
    uchar_t *wwn);
static struct fcp_tgt *fcp_get_target_by_did(struct fcp_port *pptr,
    uint32_t d_id);
static void fcp_icmd_callback(fc_packet_t *fpkt);
static int fcp_send_scsi(struct fcp_lun *plun, uchar_t opcode,
    int len, int lcount, int tcount, int cause, uint32_t rscn_count);
static int fcp_check_reportlun(struct fcp_rsp *rsp, fc_packet_t *fpkt);
static void fcp_scsi_callback(fc_packet_t *fpkt);
static void fcp_retry_scsi_cmd(fc_packet_t *fpkt);
static void fcp_handle_inquiry(fc_packet_t *fpkt, struct fcp_ipkt *icmd);
static void fcp_handle_reportlun(fc_packet_t *fpkt, struct fcp_ipkt *icmd);
static struct fcp_lun *fcp_get_lun(struct fcp_tgt *ptgt,
    uint16_t lun_num);
static int fcp_finish_tgt(struct fcp_port *pptr, struct fcp_tgt *ptgt,
    int link_cnt, int tgt_cnt, int cause);
static void fcp_finish_init(struct fcp_port *pptr);
static void fcp_create_luns(struct fcp_tgt *ptgt, int link_cnt,
    int tgt_cnt, int cause);
static int fcp_trigger_lun(struct fcp_lun *plun, child_info_t *cip,
    int old_mpxio, int online, int link_cnt, int tgt_cnt, int flags);
static int fcp_offline_target(struct fcp_port *pptr, struct fcp_tgt *ptgt,
    int link_cnt, int tgt_cnt, int nowait, int flags);
static void fcp_offline_target_now(struct fcp_port *pptr,
    struct fcp_tgt *ptgt, int link_cnt, int tgt_cnt, int flags);
static void fcp_offline_tgt_luns(struct fcp_tgt *ptgt, int link_cnt,
    int tgt_cnt, int flags);
static void fcp_offline_lun(struct fcp_lun *plun, int link_cnt, int tgt_cnt,
    int nowait, int flags);
static void fcp_prepare_offline_lun(struct fcp_lun *plun, int link_cnt,
    int tgt_cnt);
static void fcp_offline_lun_now(struct fcp_lun *plun, int link_cnt,
    int tgt_cnt, int flags);
static void fcp_scan_offline_luns(struct fcp_port *pptr);
static void fcp_scan_offline_tgts(struct fcp_port *pptr);
static void fcp_update_offline_flags(struct fcp_lun *plun);
static struct fcp_pkt *fcp_scan_commands(struct fcp_lun *plun);
static void fcp_abort_commands(struct fcp_pkt *head, struct
    fcp_port *pptr);
static void fcp_cmd_callback(fc_packet_t *fpkt);
static void fcp_complete_pkt(fc_packet_t *fpkt);
static int fcp_validate_fcp_response(struct fcp_rsp *rsp,
    struct fcp_port *pptr);
static int fcp_device_changed(struct fcp_port *pptr, struct fcp_tgt *ptgt,
    fc_portmap_t *map_entry, int link_cnt, int tgt_cnt, int cause);
static struct fcp_lun *fcp_alloc_lun(struct fcp_tgt *ptgt);
static void fcp_dealloc_lun(struct fcp_lun *plun);
static struct fcp_tgt *fcp_alloc_tgt(struct fcp_port *pptr,
    fc_portmap_t *map_entry, int link_cnt);
static void fcp_dealloc_tgt(struct fcp_tgt *ptgt);
static void fcp_queue_ipkt(struct fcp_port *pptr, fc_packet_t *fpkt);
static int fcp_transport(opaque_t port_handle, fc_packet_t *fpkt,
    int internal);
static void fcp_log(int level, dev_info_t *dip, const char *fmt, ...);
static int fcp_handle_port_attach(opaque_t ulph, fc_ulp_port_info_t *pinfo,
    uint32_t s_id, int instance);
static int fcp_handle_port_detach(struct fcp_port *pptr, int flag,
    int instance);
static void fcp_cleanup_port(struct fcp_port *pptr, int instance);
static int fcp_kmem_cache_constructor(struct scsi_pkt *, scsi_hba_tran_t *,
    int);
static void fcp_kmem_cache_destructor(struct  scsi_pkt *, scsi_hba_tran_t *);
static int fcp_pkt_setup(struct scsi_pkt *, int (*)(), caddr_t);
static int fcp_alloc_cmd_resp(struct fcp_port *pptr, fc_packet_t *fpkt,
    int flags);
static void fcp_free_cmd_resp(struct fcp_port *pptr, fc_packet_t *fpkt);
static int fcp_reset_target(struct scsi_address *ap, int level);
static int fcp_commoncap(struct scsi_address *ap, char *cap,
    int val, int tgtonly, int doset);
static int fcp_scsi_get_name(struct scsi_device *sd, char *name, int len);
static int fcp_scsi_get_bus_addr(struct scsi_device *sd, char *name, int len);
static int fcp_linkreset(struct fcp_port *pptr, struct scsi_address *ap,
    int sleep);
static int fcp_handle_port_resume(opaque_t ulph, fc_ulp_port_info_t *pinfo,
    uint32_t s_id, fc_attach_cmd_t cmd, int instance);
static void fcp_cp_pinfo(struct fcp_port *pptr, fc_ulp_port_info_t *pinfo);
static void fcp_process_elem(struct fcp_hp_elem *elem, int result);
static child_info_t *fcp_get_cip(struct fcp_lun *plun, child_info_t *cip,
    int lcount, int tcount);
static int fcp_is_dip_present(struct fcp_lun *plun, dev_info_t *cdip);
static int fcp_is_child_present(struct fcp_lun *plun, child_info_t *cip);
static dev_info_t *fcp_create_dip(struct fcp_lun *plun, int link_cnt,
    int tgt_cnt);
static dev_info_t *fcp_find_existing_dip(struct fcp_lun *plun,
    dev_info_t *pdip, caddr_t name);
static int fcp_online_child(struct fcp_lun *plun, child_info_t *cip,
    int lcount, int tcount, int flags, int *circ);
static int fcp_offline_child(struct fcp_lun *plun, child_info_t *cip,
    int lcount, int tcount, int flags, int *circ);
static void fcp_remove_child(struct fcp_lun *plun);
static void fcp_watch(void *arg);
static void fcp_check_reset_delay(struct fcp_port *pptr);
static void fcp_abort_all(struct fcp_port *pptr, struct fcp_tgt *ttgt,
    struct fcp_lun *rlun, int tgt_cnt);
struct fcp_port *fcp_soft_state_unlink(struct fcp_port *pptr);
static struct fcp_lun *fcp_lookup_lun(struct fcp_port *pptr,
    uchar_t *wwn, uint16_t lun);
static void fcp_prepare_pkt(struct fcp_port *pptr, struct fcp_pkt *cmd,
    struct fcp_lun *plun);
static void fcp_post_callback(struct fcp_pkt *cmd);
static int fcp_dopoll(struct fcp_port *pptr, struct fcp_pkt *cmd);
static struct fcp_port *fcp_dip2port(dev_info_t *dip);
struct fcp_lun *fcp_get_lun_from_cip(struct fcp_port *pptr,
    child_info_t *cip);
static int fcp_pass_to_hp_and_wait(struct fcp_port *pptr,
    struct fcp_lun *plun, child_info_t *cip, int what, int link_cnt,
    int tgt_cnt, int flags);
static struct fcp_hp_elem *fcp_pass_to_hp(struct fcp_port *pptr,
    struct fcp_lun *plun, child_info_t *cip, int what, int link_cnt,
    int tgt_cnt, int flags, int wait);
static void fcp_retransport_cmd(struct fcp_port *pptr,
    struct fcp_pkt *cmd);
static void fcp_fail_cmd(struct fcp_pkt *cmd, uchar_t reason,
    uint_t statistics);
static void fcp_queue_pkt(struct fcp_port *pptr, struct fcp_pkt *cmd);
static void fcp_update_targets(struct fcp_port *pptr,
    fc_portmap_t *dev_list, uint32_t count, uint32_t state, int cause);
static int fcp_call_finish_init(struct fcp_port *pptr,
    struct fcp_tgt *ptgt, int lcount, int tcount, int cause);
static int fcp_call_finish_init_held(struct fcp_port *pptr,
    struct fcp_tgt *ptgt, int lcount, int tcount, int cause);
static void fcp_reconfigure_luns(void * tgt_handle);
static void fcp_free_targets(struct fcp_port *pptr);
static void fcp_free_target(struct fcp_tgt *ptgt);
static int fcp_is_retryable(struct fcp_ipkt *icmd);
static int fcp_create_on_demand(struct fcp_port *pptr, uchar_t *pwwn);
static void fcp_ascii_to_wwn(caddr_t string, uchar_t bytes[], unsigned int);
static void fcp_wwn_to_ascii(uchar_t bytes[], char *string);
static void fcp_print_error(fc_packet_t *fpkt);
static int fcp_handle_ipkt_errors(struct fcp_port *pptr,
    struct fcp_tgt *ptgt, struct fcp_ipkt *icmd, int rval, caddr_t op);
static int fcp_outstanding_lun_cmds(struct fcp_tgt *ptgt);
static fc_portmap_t *fcp_construct_map(struct fcp_port *pptr,
    uint32_t *dev_cnt);
static void fcp_offline_all(struct fcp_port *pptr, int lcount, int cause);
static int fcp_get_statec_count(struct fcp_ioctl *data, int mode, int *rval);
static int fcp_copyin_fcp_ioctl_data(struct fcp_ioctl *, int, int *,
    struct fcp_ioctl *, struct fcp_port **);
static char *fcp_get_lun_path(struct fcp_lun *plun);
static int fcp_get_target_mappings(struct fcp_ioctl *data, int mode,
    int *rval);
static int fcp_do_ns_registry(struct fcp_port *pptr, uint32_t s_id);
static void fcp_retry_ns_registry(struct fcp_port *pptr, uint32_t s_id);
static char *fcp_get_lun_path(struct fcp_lun *plun);
static int fcp_get_target_mappings(struct fcp_ioctl *data, int mode,
    int *rval);
static void fcp_reconfig_wait(struct fcp_port *pptr);

/*
 * New functions added for mpxio support
 */
static int fcp_virt_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip,
    scsi_hba_tran_t *hba_tran, struct scsi_device *sd);
static mdi_pathinfo_t *fcp_create_pip(struct fcp_lun *plun, int lcount,
    int tcount);
static mdi_pathinfo_t *fcp_find_existing_pip(struct fcp_lun *plun,
    dev_info_t *pdip);
static int fcp_is_pip_present(struct fcp_lun *plun, mdi_pathinfo_t *pip);
static void fcp_handle_page83(fc_packet_t *, struct fcp_ipkt *, int);
static void fcp_update_mpxio_path_verifybusy(struct fcp_port *pptr);
static int fcp_copy_guid_2_lun_block(struct fcp_lun *plun, char *guidp);
static int fcp_update_mpxio_path(struct fcp_lun *plun, child_info_t *cip,
    int what);
static int fcp_is_reconfig_needed(struct fcp_tgt *ptgt,
    fc_packet_t *fpkt);
static int fcp_symmetric_device_probe(struct fcp_lun *plun);

/*
 * New functions added for lun masking support
 */
static void fcp_read_blacklist(dev_info_t *dip,
    struct fcp_black_list_entry **pplun_blacklist);
static void fcp_mask_pwwn_lun(char *curr_pwwn, char *curr_lun,
    struct fcp_black_list_entry **pplun_blacklist);
static void fcp_add_one_mask(char *curr_pwwn, uint32_t lun_id,
    struct fcp_black_list_entry **pplun_blacklist);
static int fcp_should_mask(la_wwn_t *wwn, uint32_t lun_id);
static void fcp_cleanup_blacklist(struct fcp_black_list_entry **lun_blacklist);

extern struct mod_ops	mod_driverops;
/*
 * This variable is defined in modctl.c and set to '1' after the root driver
 * and fs are loaded.  It serves as an indication that the root filesystem can
 * be used.
 */
extern int		modrootloaded;
/*
 * This table contains strings associated with the SCSI sense key codes.  It
 * is used by FCP to print a clear explanation of the code returned in the
 * sense information by a device.
 */
extern char		*sense_keys[];
/*
 * This device is created by the SCSI pseudo nexus driver (SCSI vHCI).	It is
 * under this device that the paths to a physical device are created when
 * MPxIO is used.
 */
extern dev_info_t	*scsi_vhci_dip;

/*
 * Report lun processing
 */
#define	FCP_LUN_ADDRESSING		0x80
#define	FCP_PD_ADDRESSING		0x00
#define	FCP_VOLUME_ADDRESSING		0x40

#define	FCP_SVE_THROTTLE		0x28 /* Vicom */
#define	MAX_INT_DMA			0x7fffffff
#define	FCP_MAX_SENSE_LEN		252
#define	FCP_MAX_RESPONSE_LEN		0xffffff
/*
 * Property definitions
 */
#define	NODE_WWN_PROP	(char *)fcp_node_wwn_prop
#define	PORT_WWN_PROP	(char *)fcp_port_wwn_prop
#define	TARGET_PROP	(char *)fcp_target_prop
#define	LUN_PROP	(char *)fcp_lun_prop
#define	SAM_LUN_PROP	(char *)fcp_sam_lun_prop
#define	CONF_WWN_PROP	(char *)fcp_conf_wwn_prop
#define	OBP_BOOT_WWN	(char *)fcp_obp_boot_wwn
#define	MANUAL_CFG_ONLY	(char *)fcp_manual_config_only
#define	INIT_PORT_PROP	(char *)fcp_init_port_prop
#define	TGT_PORT_PROP	(char *)fcp_tgt_port_prop
#define	LUN_BLACKLIST_PROP	(char *)fcp_lun_blacklist_prop
/*
 * Short hand macros.
 */
#define	LUN_PORT	(plun->lun_tgt->tgt_port)
#define	LUN_TGT		(plun->lun_tgt)

/*
 * Driver private macros
 */
#define	FCP_ATOB(x)	(((x) >= '0' && (x) <= '9') ? ((x) - '0') :	\
			((x) >= 'a' && (x) <= 'f') ?			\
			((x) - 'a' + 10) : ((x) - 'A' + 10))

#define	FCP_MAX(a, b)	((a) > (b) ? (a) : (b))

#define	FCP_N_NDI_EVENTS						\
	(sizeof (fcp_ndi_event_defs) / sizeof (ndi_event_definition_t))

#define	FCP_LINK_STATE_CHANGED(p, c)			\
	((p)->port_link_cnt != (c)->ipkt_link_cnt)

#define	FCP_TGT_STATE_CHANGED(t, c)			\
	((t)->tgt_change_cnt != (c)->ipkt_change_cnt)

#define	FCP_STATE_CHANGED(p, t, c)		\
	(FCP_TGT_STATE_CHANGED(t, c))

#define	FCP_MUST_RETRY(fpkt)				\
	((fpkt)->pkt_state == FC_PKT_LOCAL_BSY ||	\
	(fpkt)->pkt_state == FC_PKT_LOCAL_RJT ||	\
	(fpkt)->pkt_state == FC_PKT_TRAN_BSY ||	\
	(fpkt)->pkt_state == FC_PKT_ELS_IN_PROGRESS ||	\
	(fpkt)->pkt_state == FC_PKT_NPORT_BSY ||	\
	(fpkt)->pkt_state == FC_PKT_FABRIC_BSY ||	\
	(fpkt)->pkt_state == FC_PKT_PORT_OFFLINE ||	\
	(fpkt)->pkt_reason == FC_REASON_OFFLINE)

#define	FCP_SENSE_REPORTLUN_CHANGED(es)		\
	((es)->es_key == KEY_UNIT_ATTENTION &&	\
	(es)->es_add_code == 0x3f &&		\
	(es)->es_qual_code == 0x0e)

#define	FCP_SENSE_NO_LUN(es)			\
	((es)->es_key == KEY_ILLEGAL_REQUEST &&	\
	(es)->es_add_code == 0x25 &&		\
	(es)->es_qual_code == 0x0)

#define	FCP_VERSION		"1.187"
#define	FCP_NAME_VERSION	"SunFC FCP v" FCP_VERSION

#define	FCP_NUM_ELEMENTS(array)			\
	(sizeof (array) / sizeof ((array)[0]))

/*
 * Debugging, Error reporting, and tracing
 */
#define	FCP_LOG_SIZE		1024 * 1024

#define	FCP_LEVEL_1		0x00001		/* attach/detach PM CPR */
#define	FCP_LEVEL_2		0x00002		/* failures/Invalid data */
#define	FCP_LEVEL_3		0x00004		/* state change, discovery */
#define	FCP_LEVEL_4		0x00008		/* ULP messages */
#define	FCP_LEVEL_5		0x00010		/* ELS/SCSI cmds */
#define	FCP_LEVEL_6		0x00020		/* Transport failures */
#define	FCP_LEVEL_7		0x00040
#define	FCP_LEVEL_8		0x00080		/* I/O tracing */
#define	FCP_LEVEL_9		0x00100		/* I/O tracing */



/*
 * Log contents to system messages file
 */
#define	FCP_MSG_LEVEL_1	(FCP_LEVEL_1 | FC_TRACE_LOG_MSG)
#define	FCP_MSG_LEVEL_2	(FCP_LEVEL_2 | FC_TRACE_LOG_MSG)
#define	FCP_MSG_LEVEL_3	(FCP_LEVEL_3 | FC_TRACE_LOG_MSG)
#define	FCP_MSG_LEVEL_4	(FCP_LEVEL_4 | FC_TRACE_LOG_MSG)
#define	FCP_MSG_LEVEL_5	(FCP_LEVEL_5 | FC_TRACE_LOG_MSG)
#define	FCP_MSG_LEVEL_6	(FCP_LEVEL_6 | FC_TRACE_LOG_MSG)
#define	FCP_MSG_LEVEL_7	(FCP_LEVEL_7 | FC_TRACE_LOG_MSG)
#define	FCP_MSG_LEVEL_8	(FCP_LEVEL_8 | FC_TRACE_LOG_MSG)
#define	FCP_MSG_LEVEL_9	(FCP_LEVEL_9 | FC_TRACE_LOG_MSG)


/*
 * Log contents to trace buffer
 */
#define	FCP_BUF_LEVEL_1	(FCP_LEVEL_1 | FC_TRACE_LOG_BUF)
#define	FCP_BUF_LEVEL_2	(FCP_LEVEL_2 | FC_TRACE_LOG_BUF)
#define	FCP_BUF_LEVEL_3	(FCP_LEVEL_3 | FC_TRACE_LOG_BUF)
#define	FCP_BUF_LEVEL_4	(FCP_LEVEL_4 | FC_TRACE_LOG_BUF)
#define	FCP_BUF_LEVEL_5	(FCP_LEVEL_5 | FC_TRACE_LOG_BUF)
#define	FCP_BUF_LEVEL_6	(FCP_LEVEL_6 | FC_TRACE_LOG_BUF)
#define	FCP_BUF_LEVEL_7	(FCP_LEVEL_7 | FC_TRACE_LOG_BUF)
#define	FCP_BUF_LEVEL_8	(FCP_LEVEL_8 | FC_TRACE_LOG_BUF)
#define	FCP_BUF_LEVEL_9	(FCP_LEVEL_9 | FC_TRACE_LOG_BUF)


/*
 * Log contents to both system messages file and trace buffer
 */
#define	FCP_MSG_BUF_LEVEL_1	(FCP_LEVEL_1 | FC_TRACE_LOG_BUF |	\
				FC_TRACE_LOG_MSG)
#define	FCP_MSG_BUF_LEVEL_2	(FCP_LEVEL_2 | FC_TRACE_LOG_BUF |	\
				FC_TRACE_LOG_MSG)
#define	FCP_MSG_BUF_LEVEL_3	(FCP_LEVEL_3 | FC_TRACE_LOG_BUF |	\
				FC_TRACE_LOG_MSG)
#define	FCP_MSG_BUF_LEVEL_4	(FCP_LEVEL_4 | FC_TRACE_LOG_BUF |	\
				FC_TRACE_LOG_MSG)
#define	FCP_MSG_BUF_LEVEL_5	(FCP_LEVEL_5 | FC_TRACE_LOG_BUF |	\
				FC_TRACE_LOG_MSG)
#define	FCP_MSG_BUF_LEVEL_6	(FCP_LEVEL_6 | FC_TRACE_LOG_BUF |	\
				FC_TRACE_LOG_MSG)
#define	FCP_MSG_BUF_LEVEL_7	(FCP_LEVEL_7 | FC_TRACE_LOG_BUF |	\
				FC_TRACE_LOG_MSG)
#define	FCP_MSG_BUF_LEVEL_8	(FCP_LEVEL_8 | FC_TRACE_LOG_BUF |	\
				FC_TRACE_LOG_MSG)
#define	FCP_MSG_BUF_LEVEL_9	(FCP_LEVEL_9 | FC_TRACE_LOG_BUF |	\
				FC_TRACE_LOG_MSG)
#ifdef DEBUG
#define	FCP_DTRACE	fc_trace_debug
#else
#define	FCP_DTRACE
#endif

#define	FCP_TRACE	fc_trace_debug

static struct cb_ops fcp_cb_ops = {
	fcp_open,			/* open */
	fcp_close,			/* close */
	nodev,				/* strategy */
	nodev,				/* print */
	nodev,				/* dump */
	nodev,				/* read */
	nodev,				/* write */
	fcp_ioctl,			/* ioctl */
	nodev,				/* devmap */
	nodev,				/* mmap */
	nodev,				/* segmap */
	nochpoll,			/* chpoll */
	ddi_prop_op,			/* cb_prop_op */
	0,				/* streamtab */
	D_NEW | D_MP | D_HOTPLUG,	/* cb_flag */
	CB_REV,				/* rev */
	nodev,				/* aread */
	nodev				/* awrite */
};


static struct dev_ops fcp_ops = {
	DEVO_REV,
	0,
	ddi_getinfo_1to1,
	nulldev,		/* identify */
	nulldev,		/* probe */
	fcp_attach,		/* attach and detach are mandatory */
	fcp_detach,
	nodev,			/* reset */
	&fcp_cb_ops,		/* cb_ops */
	NULL,			/* bus_ops */
	NULL,			/* power */
};


char *fcp_version = FCP_NAME_VERSION;

static struct modldrv modldrv = {
	&mod_driverops,
	FCP_NAME_VERSION,
	&fcp_ops
};


static struct modlinkage modlinkage = {
	MODREV_1,
	&modldrv,
	NULL
};


static fc_ulp_modinfo_t fcp_modinfo = {
	&fcp_modinfo,			/* ulp_handle */
	FCTL_ULP_MODREV_4,		/* ulp_rev */
	FC4_SCSI_FCP,			/* ulp_type */
	"fcp",				/* ulp_name */
	FCP_STATEC_MASK,		/* ulp_statec_mask */
	fcp_port_attach,		/* ulp_port_attach */
	fcp_port_detach,		/* ulp_port_detach */
	fcp_port_ioctl,			/* ulp_port_ioctl */
	fcp_els_callback,		/* ulp_els_callback */
	fcp_data_callback,		/* ulp_data_callback */
	fcp_statec_callback		/* ulp_statec_callback */
};

#ifdef	DEBUG
#define	FCP_TRACE_DEFAULT	(FC_TRACE_LOG_MASK | FCP_LEVEL_1 |	\
				FCP_LEVEL_2 | FCP_LEVEL_3 |		\
				FCP_LEVEL_4 | FCP_LEVEL_5 |		\
				FCP_LEVEL_6 | FCP_LEVEL_7)
#else
#define	FCP_TRACE_DEFAULT	(FC_TRACE_LOG_MASK | FCP_LEVEL_1 |	\
				FCP_LEVEL_2 | FCP_LEVEL_3 |		\
				FCP_LEVEL_4 | FCP_LEVEL_5 |		\
				FCP_LEVEL_6 | FCP_LEVEL_7)
#endif

/* FCP global variables */
int			fcp_bus_config_debug = 0;
static int		fcp_log_size = FCP_LOG_SIZE;
static int		fcp_trace = FCP_TRACE_DEFAULT;
static fc_trace_logq_t	*fcp_logq = NULL;
static struct fcp_black_list_entry	*fcp_lun_blacklist = NULL;
/*
 * The auto-configuration is set by default.  The only way of disabling it is
 * through the property MANUAL_CFG_ONLY in the fcp.conf file.
 */
static int		fcp_enable_auto_configuration = 1;
static int		fcp_max_bus_config_retries	= 4;
static int		fcp_lun_ready_retry = 300;
/*
 * The value assigned to the following variable has changed several times due
 * to a problem with the data underruns reporting of some firmware(s).	The
 * current value of 50 gives a timeout value of 25 seconds for a max number
 * of 256 LUNs.
 */
static int		fcp_max_target_retries = 50;
/*
 * Watchdog variables
 * ------------------
 *
 * fcp_watchdog_init
 *
 *	Indicates if the watchdog timer is running or not.  This is actually
 *	a counter of the number of Fibre Channel ports that attached.  When
 *	the first port attaches the watchdog is started.  When the last port
 *	detaches the watchdog timer is stopped.
 *
 * fcp_watchdog_time
 *
 *	This is the watchdog clock counter.  It is incremented by
 *	fcp_watchdog_time each time the watchdog timer expires.
 *
 * fcp_watchdog_timeout
 *
 *	Increment value of the variable fcp_watchdog_time as well as the
 *	the timeout value of the watchdog timer.  The unit is 1 second.	 It
 *	is strange that this is not a #define	but a variable since the code
 *	never changes this value.  The reason why it can be said that the
 *	unit is 1 second is because the number of ticks for the watchdog
 *	timer is determined like this:
 *
 *	    fcp_watchdog_tick = fcp_watchdog_timeout *
 *				  drv_usectohz(1000000);
 *
 *	The value 1000000 is hard coded in the code.
 *
 * fcp_watchdog_tick
 *
 *	Watchdog timer value in ticks.
 */
static int		fcp_watchdog_init = 0;
static int		fcp_watchdog_time = 0;
static int		fcp_watchdog_timeout = 1;
static int		fcp_watchdog_tick;

/*
 * fcp_offline_delay is a global variable to enable customisation of
 * the timeout on link offlines or RSCNs. The default value is set
 * to match FCP_OFFLINE_DELAY (20sec), which is 2*RA_TOV_els as
 * specified in FCP4 Chapter 11 (see www.t10.org).
 *
 * The variable fcp_offline_delay is specified in SECONDS.
 *
 * If we made this a static var then the user would not be able to
 * change it. This variable is set in fcp_attach().
 */
unsigned int		fcp_offline_delay = FCP_OFFLINE_DELAY;

static void		*fcp_softstate = NULL; /* for soft state */
static uchar_t		fcp_oflag = FCP_IDLE; /* open flag */
static kmutex_t		fcp_global_mutex;
static kmutex_t		fcp_ioctl_mutex;
static dev_info_t	*fcp_global_dip = NULL;
static timeout_id_t	fcp_watchdog_id;
const char		*fcp_lun_prop = "lun";
const char		*fcp_sam_lun_prop = "sam-lun";
const char		*fcp_target_prop = "target";
/*
 * NOTE: consumers of "node-wwn" property include stmsboot in ON
 * consolidation.
 */
const char		*fcp_node_wwn_prop = "node-wwn";
const char		*fcp_port_wwn_prop = "port-wwn";
const char		*fcp_conf_wwn_prop = "fc-port-wwn";
const char		*fcp_obp_boot_wwn = "fc-boot-dev-portwwn";
const char		*fcp_manual_config_only = "manual_configuration_only";
const char		*fcp_init_port_prop = "initiator-port";
const char		*fcp_tgt_port_prop = "target-port";
const char		*fcp_lun_blacklist_prop = "pwwn-lun-blacklist";

static struct fcp_port	*fcp_port_head = NULL;
static ddi_eventcookie_t	fcp_insert_eid;
static ddi_eventcookie_t	fcp_remove_eid;

static ndi_event_definition_t	fcp_ndi_event_defs[] = {
	{ FCP_EVENT_TAG_INSERT, FCAL_INSERT_EVENT, EPL_KERNEL },
	{ FCP_EVENT_TAG_REMOVE, FCAL_REMOVE_EVENT, EPL_INTERRUPT }
};

/*
 * List of valid commands for the scsi_ioctl call
 */
static uint8_t scsi_ioctl_list[] = {
	SCMD_INQUIRY,
	SCMD_REPORT_LUN,
	SCMD_READ_CAPACITY
};

/*
 * this is used to dummy up a report lun response for cases
 * where the target doesn't support it
 */
static uchar_t fcp_dummy_lun[] = {
	0x00,		/* MSB length (length = no of luns * 8) */
	0x00,
	0x00,
	0x08,		/* LSB length */
	0x00,		/* MSB reserved */
	0x00,
	0x00,
	0x00,		/* LSB reserved */
	FCP_PD_ADDRESSING,
	0x00,		/* LUN is ZERO at the first level */
	0x00,
	0x00,		/* second level is zero */
	0x00,
	0x00,		/* third level is zero */
	0x00,
	0x00		/* fourth level is zero */
};

static uchar_t fcp_alpa_to_switch[] = {
	0x00, 0x7d, 0x7c, 0x00, 0x7b, 0x00, 0x00, 0x00, 0x7a, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x78, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x77, 0x76, 0x00, 0x00, 0x75, 0x00, 0x74,
	0x73, 0x72, 0x00, 0x00, 0x00, 0x71, 0x00, 0x70, 0x6f, 0x6e,
	0x00, 0x6d, 0x6c, 0x6b, 0x6a, 0x69, 0x68, 0x00, 0x00, 0x67,
	0x66, 0x65, 0x64, 0x63, 0x62, 0x00, 0x00, 0x61, 0x60, 0x00,
	0x5f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x00, 0x5d,
	0x5c, 0x5b, 0x00, 0x5a, 0x59, 0x58, 0x57, 0x56, 0x55, 0x00,
	0x00, 0x54, 0x53, 0x52, 0x51, 0x50, 0x4f, 0x00, 0x00, 0x4e,
	0x4d, 0x00, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4b,
	0x00, 0x4a, 0x49, 0x48, 0x00, 0x47, 0x46, 0x45, 0x44, 0x43,
	0x42, 0x00, 0x00, 0x41, 0x40, 0x3f, 0x3e, 0x3d, 0x3c, 0x00,
	0x00, 0x3b, 0x3a, 0x00, 0x39, 0x00, 0x00, 0x00, 0x38, 0x37,
	0x36, 0x00, 0x35, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x33, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x31, 0x30, 0x00, 0x00, 0x2f, 0x00, 0x2e, 0x2d, 0x2c,
	0x00, 0x00, 0x00, 0x2b, 0x00, 0x2a, 0x29, 0x28, 0x00, 0x27,
	0x26, 0x25, 0x24, 0x23, 0x22, 0x00, 0x00, 0x21, 0x20, 0x1f,
	0x1e, 0x1d, 0x1c, 0x00, 0x00, 0x1b, 0x1a, 0x00, 0x19, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x17, 0x16, 0x15,
	0x00, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x00, 0x00, 0x0e,
	0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x00, 0x00, 0x08, 0x07, 0x00,
	0x06, 0x00, 0x00, 0x00, 0x05, 0x04, 0x03, 0x00, 0x02, 0x00,
	0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

static caddr_t pid = "SESS01	      ";

#if	!defined(lint)

_NOTE(MUTEX_PROTECTS_DATA(fcp_global_mutex,
    fcp_port::fcp_next fcp_watchdog_id))

_NOTE(DATA_READABLE_WITHOUT_LOCK(fcp_watchdog_time))

_NOTE(SCHEME_PROTECTS_DATA("Unshared",
    fcp_insert_eid
    fcp_remove_eid
    fcp_watchdog_time))

_NOTE(SCHEME_PROTECTS_DATA("Unshared",
    fcp_cb_ops
    fcp_ops
    callb_cpr))

#endif /* lint */

/*
 * This table is used to determine whether or not it's safe to copy in
 * the target node name for a lun.  Since all luns behind the same target
 * have the same wwnn, only tagets that do not support multiple luns are
 * eligible to be enumerated under mpxio if they aren't page83 compliant.
 */

char *fcp_symmetric_disk_table[] = {
	"SEAGATE ST",
	"IBM	 DDYFT",
	"SUNW	 SUNWGS",	/* Daktari enclosure */
	"SUN	 SENA",		/* SES device */
	"SUN	 SESS01"	/* VICOM SVE box */
};

int fcp_symmetric_disk_table_size =
	sizeof (fcp_symmetric_disk_table)/sizeof (char *);

/*
 * The _init(9e) return value should be that of mod_install(9f). Under
 * some circumstances, a failure may not be related mod_install(9f) and
 * one would then require a return value to indicate the failure. Looking
 * at mod_install(9f), it is expected to return 0 for success and non-zero
 * for failure. mod_install(9f) for device drivers, further goes down the
 * calling chain and ends up in ddi_installdrv(), whose return values are
 * DDI_SUCCESS and DDI_FAILURE - There are also other functions in the
 * calling chain of mod_install(9f) which return values like EINVAL and
 * in some even return -1.
 *
 * To work around the vagaries of the mod_install() calling chain, return
 * either 0 or ENODEV depending on the success or failure of mod_install()
 */
int
_init(void)
{
	int rval;

	/*
	 * Allocate soft state and prepare to do ddi_soft_state_zalloc()
	 * before registering with the transport first.
	 */
	if (ddi_soft_state_init(&fcp_softstate,
	    sizeof (struct fcp_port), FCP_INIT_ITEMS) != 0) {
		return (EINVAL);
	}

	mutex_init(&fcp_global_mutex, NULL, MUTEX_DRIVER, NULL);
	mutex_init(&fcp_ioctl_mutex, NULL, MUTEX_DRIVER, NULL);

	if ((rval = fc_ulp_add(&fcp_modinfo)) != FC_SUCCESS) {
		cmn_err(CE_WARN, "fcp: fc_ulp_add failed");
		mutex_destroy(&fcp_global_mutex);
		mutex_destroy(&fcp_ioctl_mutex);
		ddi_soft_state_fini(&fcp_softstate);
		return (ENODEV);
	}

	fcp_logq = fc_trace_alloc_logq(fcp_log_size);

	if ((rval = mod_install(&modlinkage)) != 0) {
		fc_trace_free_logq(fcp_logq);
		(void) fc_ulp_remove(&fcp_modinfo);
		mutex_destroy(&fcp_global_mutex);
		mutex_destroy(&fcp_ioctl_mutex);
		ddi_soft_state_fini(&fcp_softstate);
		rval = ENODEV;
	}

	return (rval);
}


/*
 * the system is done with us as a driver, so clean up
 */
int
_fini(void)
{
	int rval;

	/*
	 * don't start cleaning up until we know that the module remove
	 * has worked  -- if this works, then we know that each instance
	 * has successfully been DDI_DETACHed
	 */
	if ((rval = mod_remove(&modlinkage)) != 0) {
		return (rval);
	}

	(void) fc_ulp_remove(&fcp_modinfo);

	ddi_soft_state_fini(&fcp_softstate);
	mutex_destroy(&fcp_global_mutex);
	mutex_destroy(&fcp_ioctl_mutex);
	fc_trace_free_logq(fcp_logq);

	return (rval);
}


int
_info(struct modinfo *modinfop)
{
	return (mod_info(&modlinkage, modinfop));
}


/*
 * attach the module
 */
static int
fcp_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
{
	int rval = DDI_SUCCESS;

	FCP_DTRACE(fcp_logq, "fcp", fcp_trace,
	    FCP_BUF_LEVEL_8, 0, "fcp module attach: cmd=0x%x", cmd);

	if (cmd == DDI_ATTACH) {
		/* The FCP pseudo device is created here. */
		mutex_enter(&fcp_global_mutex);
		fcp_global_dip = devi;
		mutex_exit(&fcp_global_mutex);

		if (ddi_create_minor_node(fcp_global_dip, "fcp", S_IFCHR,
		    0, DDI_PSEUDO, 0) == DDI_SUCCESS) {
			ddi_report_dev(fcp_global_dip);
		} else {
			cmn_err(CE_WARN, "FCP: Cannot create minor node");
			mutex_enter(&fcp_global_mutex);
			fcp_global_dip = NULL;
			mutex_exit(&fcp_global_mutex);

			rval = DDI_FAILURE;
		}
		/*
		 * We check the fcp_offline_delay property at this
		 * point. This variable is global for the driver,
		 * not specific to an instance.
		 *
		 * We do not recommend setting the value to less
		 * than 10 seconds (RA_TOV_els), or greater than
		 * 60 seconds.
		 */
		fcp_offline_delay = ddi_prop_get_int(DDI_DEV_T_ANY,
		    devi, DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
		    "fcp_offline_delay", FCP_OFFLINE_DELAY);
		if ((fcp_offline_delay < 10) ||
		    (fcp_offline_delay > 60)) {
			cmn_err(CE_WARN, "Setting fcp_offline_delay "
			    "to %d second(s). This is outside the "
			    "recommended range of 10..60 seconds.",
			    fcp_offline_delay);
		}
	}

	return (rval);
}


/*ARGSUSED*/
static int
fcp_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
{
	int	res = DDI_SUCCESS;

	FCP_DTRACE(fcp_logq, "fcp", fcp_trace,
	    FCP_BUF_LEVEL_8, 0,	 "module detach: cmd=0x%x", cmd);

	if (cmd == DDI_DETACH) {
		/*
		 * Check if there are active ports/threads. If there
		 * are any, we will fail, else we will succeed (there
		 * should not be much to clean up)
		 */
		mutex_enter(&fcp_global_mutex);
		FCP_DTRACE(fcp_logq, "fcp",
		    fcp_trace, FCP_BUF_LEVEL_8, 0,  "port_head=%p",
		    (void *) fcp_port_head);

		if (fcp_port_head == NULL) {
			ddi_remove_minor_node(fcp_global_dip, NULL);
			fcp_global_dip = NULL;
			mutex_exit(&fcp_global_mutex);
		} else {
			mutex_exit(&fcp_global_mutex);
			res = DDI_FAILURE;
		}
	}
	FCP_DTRACE(fcp_logq, "fcp", fcp_trace,
	    FCP_BUF_LEVEL_8, 0,	 "module detach returning %d", res);

	return (res);
}


/* ARGSUSED */
static int
fcp_open(dev_t *devp, int flag, int otype, cred_t *credp)
{
	if (otype != OTYP_CHR) {
		return (EINVAL);
	}

	/*
	 * Allow only root to talk;
	 */
	if (drv_priv(credp)) {
		return (EPERM);
	}

	mutex_enter(&fcp_global_mutex);
	if (fcp_oflag & FCP_EXCL) {
		mutex_exit(&fcp_global_mutex);
		return (EBUSY);
	}

	if (flag & FEXCL) {
		if (fcp_oflag & FCP_OPEN) {
			mutex_exit(&fcp_global_mutex);
			return (EBUSY);
		}
		fcp_oflag |= FCP_EXCL;
	}
	fcp_oflag |= FCP_OPEN;
	mutex_exit(&fcp_global_mutex);

	return (0);
}


/* ARGSUSED */
static int
fcp_close(dev_t dev, int flag, int otype, cred_t *credp)
{
	if (otype != OTYP_CHR) {
		return (EINVAL);
	}

	mutex_enter(&fcp_global_mutex);
	if (!(fcp_oflag & FCP_OPEN)) {
		mutex_exit(&fcp_global_mutex);
		return (ENODEV);
	}
	fcp_oflag = FCP_IDLE;
	mutex_exit(&fcp_global_mutex);

	return (0);
}


/*
 * fcp_ioctl
 *	Entry point for the FCP ioctls
 *
 * Input:
 *	See ioctl(9E)
 *
 * Output:
 *	See ioctl(9E)
 *
 * Returns:
 *	See ioctl(9E)
 *
 * Context:
 *	Kernel context.
 */
/* ARGSUSED */
static int
fcp_ioctl(dev_t dev, int cmd, intptr_t data, int mode, cred_t *credp,
    int *rval)
{
	int			ret = 0;

	mutex_enter(&fcp_global_mutex);
	if (!(fcp_oflag & FCP_OPEN)) {
		mutex_exit(&fcp_global_mutex);
		return (ENXIO);
	}
	mutex_exit(&fcp_global_mutex);

	switch (cmd) {
	case FCP_TGT_INQUIRY:
	case FCP_TGT_CREATE:
	case FCP_TGT_DELETE:
		ret = fcp_setup_device_data_ioctl(cmd,
		    (struct fcp_ioctl *)data, mode, rval);
		break;

	case FCP_TGT_SEND_SCSI:
		mutex_enter(&fcp_ioctl_mutex);
		ret = fcp_setup_scsi_ioctl(
		    (struct fcp_scsi_cmd *)data, mode, rval);
		mutex_exit(&fcp_ioctl_mutex);
		break;

	case FCP_STATE_COUNT:
		ret = fcp_get_statec_count((struct fcp_ioctl *)data,
		    mode, rval);
		break;
	case FCP_GET_TARGET_MAPPINGS:
		ret = fcp_get_target_mappings((struct fcp_ioctl *)data,
		    mode, rval);
		break;
	default:
		fcp_log(CE_WARN, NULL,
		    "!Invalid ioctl opcode = 0x%x", cmd);
		ret	= EINVAL;
	}

	return (ret);
}


/*
 * fcp_setup_device_data_ioctl
 *	Setup handler for the "device data" style of
 *	ioctl for FCP.	See "fcp_util.h" for data structure
 *	definition.
 *
 * Input:
 *	cmd	= FCP ioctl command
 *	data	= ioctl data
 *	mode	= See ioctl(9E)
 *
 * Output:
 *	data	= ioctl data
 *	rval	= return value - see ioctl(9E)
 *
 * Returns:
 *	See ioctl(9E)
 *
 * Context:
 *	Kernel context.
 */
/* ARGSUSED */
static int
fcp_setup_device_data_ioctl(int cmd, struct fcp_ioctl *data, int mode,
    int *rval)
{
	struct fcp_port	*pptr;
	struct	device_data	*dev_data;
	uint32_t		link_cnt;
	la_wwn_t		*wwn_ptr = NULL;
	struct fcp_tgt		*ptgt = NULL;
	struct fcp_lun		*plun = NULL;
	int			i, error;
	struct fcp_ioctl	fioctl;

#ifdef	_MULTI_DATAMODEL
	switch (ddi_model_convert_from(mode & FMODELS)) {
	case DDI_MODEL_ILP32: {
		struct fcp32_ioctl f32_ioctl;

		if (ddi_copyin((void *)data, (void *)&f32_ioctl,
		    sizeof (struct fcp32_ioctl), mode)) {
			return (EFAULT);
		}
		fioctl.fp_minor = f32_ioctl.fp_minor;
		fioctl.listlen = f32_ioctl.listlen;
		fioctl.list = (caddr_t)(long)f32_ioctl.list;
		break;
	}
	case DDI_MODEL_NONE:
		if (ddi_copyin((void *)data, (void *)&fioctl,
		    sizeof (struct fcp_ioctl), mode)) {
			return (EFAULT);
		}
		break;
	}

#else	/* _MULTI_DATAMODEL */
	if (ddi_copyin((void *)data, (void *)&fioctl,
	    sizeof (struct fcp_ioctl), mode)) {
		return (EFAULT);
	}
#endif	/* _MULTI_DATAMODEL */

	/*
	 * Right now we can assume that the minor number matches with
	 * this instance of fp. If this changes we will need to
	 * revisit this logic.
	 */
	mutex_enter(&fcp_global_mutex);
	pptr = fcp_port_head;
	while (pptr) {
		if (pptr->port_instance == (uint32_t)fioctl.fp_minor) {
			break;
		} else {
			pptr = pptr->port_next;
		}
	}
	mutex_exit(&fcp_global_mutex);
	if (pptr == NULL) {
		return (ENXIO);
	}
	mutex_enter(&pptr->port_mutex);


	if ((dev_data = kmem_zalloc((sizeof (struct device_data)) *
	    fioctl.listlen, KM_NOSLEEP)) == NULL) {
		mutex_exit(&pptr->port_mutex);
		return (ENOMEM);
	}

	if (ddi_copyin(fioctl.list, dev_data,
	    (sizeof (struct device_data)) * fioctl.listlen, mode)) {
		kmem_free(dev_data, sizeof (*dev_data) * fioctl.listlen);
		mutex_exit(&pptr->port_mutex);
		return (EFAULT);
	}
	link_cnt = pptr->port_link_cnt;

	if (cmd == FCP_TGT_INQUIRY) {
		wwn_ptr = (la_wwn_t *)&(dev_data[0].dev_pwwn);
		if (bcmp(wwn_ptr->raw_wwn, pptr->port_pwwn.raw_wwn,
		    sizeof (wwn_ptr->raw_wwn)) == 0) {
			/* This ioctl is requesting INQ info of local HBA */
			mutex_exit(&pptr->port_mutex);
			dev_data[0].dev0_type = DTYPE_UNKNOWN;
			dev_data[0].dev_status = 0;
			if (ddi_copyout(dev_data, fioctl.list,
			    (sizeof (struct device_data)) * fioctl.listlen,
			    mode)) {
				kmem_free(dev_data,
				    sizeof (*dev_data) * fioctl.listlen);
				return (EFAULT);
			}
			kmem_free(dev_data,
			    sizeof (*dev_data) * fioctl.listlen);
#ifdef	_MULTI_DATAMODEL
			switch (ddi_model_convert_from(mode & FMODELS)) {
			case DDI_MODEL_ILP32: {
				struct fcp32_ioctl f32_ioctl;
				f32_ioctl.fp_minor = fioctl.fp_minor;
				f32_ioctl.listlen = fioctl.listlen;
				f32_ioctl.list = (caddr32_t)(long)fioctl.list;
				if (ddi_copyout((void *)&f32_ioctl,
				    (void *)data,
				    sizeof (struct fcp32_ioctl), mode)) {
					return (EFAULT);
				}
				break;
			}
			case DDI_MODEL_NONE:
				if (ddi_copyout((void *)&fioctl, (void *)data,
				    sizeof (struct fcp_ioctl), mode)) {
					return (EFAULT);
				}
				break;
			}
#else	/* _MULTI_DATAMODEL */
			if (ddi_copyout((void *)&fioctl, (void *)data,
			    sizeof (struct fcp_ioctl), mode)) {
				return (EFAULT);
			}
#endif	/* _MULTI_DATAMODEL */
			return (0);
		}
	}

	if (pptr->port_state & (FCP_STATE_INIT | FCP_STATE_OFFLINE)) {
		kmem_free(dev_data, sizeof (*dev_data) * fioctl.listlen);
		mutex_exit(&pptr->port_mutex);
		return (ENXIO);
	}

	for (i = 0; (i < fioctl.listlen) && (link_cnt == pptr->port_link_cnt);
	    i++) {
		wwn_ptr = (la_wwn_t *)&(dev_data[i].dev_pwwn);

		dev_data[i].dev0_type = DTYPE_UNKNOWN;


		dev_data[i].dev_status = ENXIO;

		if ((ptgt = fcp_lookup_target(pptr,
		    (uchar_t *)wwn_ptr)) == NULL) {
			mutex_exit(&pptr->port_mutex);
			if (fc_ulp_get_remote_port(pptr->port_fp_handle,
			    wwn_ptr, &error, 0) == NULL) {
				dev_data[i].dev_status = ENODEV;
				mutex_enter(&pptr->port_mutex);
				continue;
			} else {

				dev_data[i].dev_status = EAGAIN;

				mutex_enter(&pptr->port_mutex);
				continue;
			}
		} else {
			mutex_enter(&ptgt->tgt_mutex);
			if (ptgt->tgt_state & (FCP_TGT_MARK |
			    FCP_TGT_BUSY)) {
				dev_data[i].dev_status = EAGAIN;
				mutex_exit(&ptgt->tgt_mutex);
				continue;
			}

			if (ptgt->tgt_state & FCP_TGT_OFFLINE) {
				if (ptgt->tgt_icap && !ptgt->tgt_tcap) {
					dev_data[i].dev_status = ENOTSUP;
				} else {
					dev_data[i].dev_status = ENXIO;
				}
				mutex_exit(&ptgt->tgt_mutex);
				continue;
			}

			switch (cmd) {
			case FCP_TGT_INQUIRY:
				/*
				 * The reason we give device type of
				 * lun 0 only even though in some
				 * cases(like maxstrat) lun 0 device
				 * type may be 0x3f(invalid) is that
				 * for bridge boxes target will appear
				 * as luns and the first lun could be
				 * a device that utility may not care
				 * about (like a tape device).
				 */
				dev_data[i].dev_lun_cnt = ptgt->tgt_lun_cnt;
				dev_data[i].dev_status = 0;
				mutex_exit(&ptgt->tgt_mutex);

				if ((plun = fcp_get_lun(ptgt, 0)) == NULL) {
					dev_data[i].dev0_type = DTYPE_UNKNOWN;
				} else {
					dev_data[i].dev0_type = plun->lun_type;
				}
				mutex_enter(&ptgt->tgt_mutex);
				break;

			case FCP_TGT_CREATE:
				mutex_exit(&ptgt->tgt_mutex);
				mutex_exit(&pptr->port_mutex);

				/*
				 * serialize state change call backs.
				 * only one call back will be handled
				 * at a time.
				 */
				mutex_enter(&fcp_global_mutex);
				if (fcp_oflag & FCP_BUSY) {
					mutex_exit(&fcp_global_mutex);
					if (dev_data) {
						kmem_free(dev_data,
						    sizeof (*dev_data) *
						    fioctl.listlen);
					}
					return (EBUSY);
				}
				fcp_oflag |= FCP_BUSY;
				mutex_exit(&fcp_global_mutex);

				dev_data[i].dev_status =
				    fcp_create_on_demand(pptr,
				    wwn_ptr->raw_wwn);

				if (dev_data[i].dev_status != 0) {
					char	buf[25];

					for (i = 0; i < FC_WWN_SIZE; i++) {
						(void) sprintf(&buf[i << 1],
						    "%02x",
						    wwn_ptr->raw_wwn[i]);
					}

					fcp_log(CE_WARN, pptr->port_dip,
					    "!Failed to create nodes for"
					    " pwwn=%s; error=%x", buf,
					    dev_data[i].dev_status);
				}

				/* allow state change call backs again */
				mutex_enter(&fcp_global_mutex);
				fcp_oflag &= ~FCP_BUSY;
				mutex_exit(&fcp_global_mutex);

				mutex_enter(&pptr->port_mutex);
				mutex_enter(&ptgt->tgt_mutex);

				break;

			case FCP_TGT_DELETE:
				break;

			default:
				fcp_log(CE_WARN, pptr->port_dip,
				    "!Invalid device data ioctl "
				    "opcode = 0x%x", cmd);
			}
			mutex_exit(&ptgt->tgt_mutex);
		}
	}
	mutex_exit(&pptr->port_mutex);

	if (ddi_copyout(dev_data, fioctl.list,
	    (sizeof (struct device_data)) * fioctl.listlen, mode)) {
		kmem_free(dev_data, sizeof (*dev_data) * fioctl.listlen);
		return (EFAULT);
	}
	kmem_free(dev_data, sizeof (*dev_data) * fioctl.listlen);

#ifdef	_MULTI_DATAMODEL
	switch (ddi_model_convert_from(mode & FMODELS)) {
	case DDI_MODEL_ILP32: {
		struct fcp32_ioctl f32_ioctl;

		f32_ioctl.fp_minor = fioctl.fp_minor;
		f32_ioctl.listlen = fioctl.listlen;
		f32_ioctl.list = (caddr32_t)(long)fioctl.list;
		if (ddi_copyout((void *)&f32_ioctl, (void *)data,
		    sizeof (struct fcp32_ioctl), mode)) {
			return (EFAULT);
		}
		break;
	}
	case DDI_MODEL_NONE:
		if (ddi_copyout((void *)&fioctl, (void *)data,
		    sizeof (struct fcp_ioctl), mode)) {
			return (EFAULT);
		}
		break;
	}
#else	/* _MULTI_DATAMODEL */

	if (ddi_copyout((void *)&fioctl, (void *)data,
	    sizeof (struct fcp_ioctl), mode)) {
		return (EFAULT);
	}
#endif	/* _MULTI_DATAMODEL */

	return (0);
}

/*
 * Fetch the target mappings (path, etc.) for all LUNs
 * on this port.
 */
/* ARGSUSED */
static int
fcp_get_target_mappings(struct fcp_ioctl *data,
    int mode, int *rval)
{
	struct fcp_port	    *pptr;
	fc_hba_target_mappings_t    *mappings;
	fc_hba_mapping_entry_t	    *map;
	struct fcp_tgt	    *ptgt = NULL;
	struct fcp_lun	    *plun = NULL;
	int			    i, mapIndex, mappingSize;
	int			    listlen;
	struct fcp_ioctl	    fioctl;
	char			    *path;
	fcp_ent_addr_t		    sam_lun_addr;

#ifdef	_MULTI_DATAMODEL
	switch (ddi_model_convert_from(mode & FMODELS)) {
	case DDI_MODEL_ILP32: {
		struct fcp32_ioctl f32_ioctl;

		if (ddi_copyin((void *)data, (void *)&f32_ioctl,
		    sizeof (struct fcp32_ioctl), mode)) {
			return (EFAULT);
		}
		fioctl.fp_minor = f32_ioctl.fp_minor;
		fioctl.listlen = f32_ioctl.listlen;
		fioctl.list = (caddr_t)(long)f32_ioctl.list;
		break;
	}
	case DDI_MODEL_NONE:
		if (ddi_copyin((void *)data, (void *)&fioctl,
		    sizeof (struct fcp_ioctl), mode)) {
			return (EFAULT);
		}
		break;
	}

#else	/* _MULTI_DATAMODEL */
	if (ddi_copyin((void *)data, (void *)&fioctl,
	    sizeof (struct fcp_ioctl), mode)) {
		return (EFAULT);
	}
#endif	/* _MULTI_DATAMODEL */

	/*
	 * Right now we can assume that the minor number matches with
	 * this instance of fp. If this changes we will need to
	 * revisit this logic.
	 */
	mutex_enter(&fcp_global_mutex);
	pptr = fcp_port_head;
	while (pptr) {
		if (pptr->port_instance == (uint32_t)fioctl.fp_minor) {
			break;
		} else {
			pptr = pptr->port_next;
		}
	}
	mutex_exit(&fcp_global_mutex);
	if (pptr == NULL) {
		cmn_err(CE_NOTE, "target mappings: unknown instance number: %d",
		    fioctl.fp_minor);
		return (ENXIO);
	}


	/* We use listlen to show the total buffer size */
	mappingSize = fioctl.listlen;

	/* Now calculate how many mapping entries will fit */
	listlen = fioctl.listlen + sizeof (fc_hba_mapping_entry_t)
	    - sizeof (fc_hba_target_mappings_t);
	if (listlen <= 0) {
		cmn_err(CE_NOTE, "target mappings: Insufficient buffer");
		return (ENXIO);
	}
	listlen = listlen / sizeof (fc_hba_mapping_entry_t);

	if ((mappings = kmem_zalloc(mappingSize, KM_SLEEP)) == NULL) {
		return (ENOMEM);
	}
	mappings->version = FC_HBA_TARGET_MAPPINGS_VERSION;

	/* Now get to work */
	mapIndex = 0;

	mutex_enter(&pptr->port_mutex);
	/* Loop through all targets on this port */
	for (i = 0; i < FCP_NUM_HASH; i++) {
		for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL;
		    ptgt = ptgt->tgt_next) {


			/* Loop through all LUNs on this target */
			for (plun = ptgt->tgt_lun; plun != NULL;
			    plun = plun->lun_next) {
				if (plun->lun_state & FCP_LUN_OFFLINE) {
					continue;
				}

				path = fcp_get_lun_path(plun);
				if (path == NULL) {
					continue;
				}

				if (mapIndex >= listlen) {
					mapIndex ++;
					kmem_free(path, MAXPATHLEN);
					continue;
				}
				map = &mappings->entries[mapIndex++];
				bcopy(path, map->targetDriver,
				    sizeof (map->targetDriver));
				map->d_id = ptgt->tgt_d_id;
				map->busNumber = 0;
				map->targetNumber = ptgt->tgt_d_id;
				map->osLUN = plun->lun_num;

				/*
				 * We had swapped lun when we stored it in
				 * lun_addr. We need to swap it back before
				 * returning it to user land
				 */

				sam_lun_addr.ent_addr_0 =
				    BE_16(plun->lun_addr.ent_addr_0);
				sam_lun_addr.ent_addr_1 =
				    BE_16(plun->lun_addr.ent_addr_1);
				sam_lun_addr.ent_addr_2 =
				    BE_16(plun->lun_addr.ent_addr_2);
				sam_lun_addr.ent_addr_3 =
				    BE_16(plun->lun_addr.ent_addr_3);

				bcopy(&sam_lun_addr, &map->samLUN,
				    FCP_LUN_SIZE);
				bcopy(ptgt->tgt_node_wwn.raw_wwn,
				    map->NodeWWN.raw_wwn, sizeof (la_wwn_t));
				bcopy(ptgt->tgt_port_wwn.raw_wwn,
				    map->PortWWN.raw_wwn, sizeof (la_wwn_t));

				if (plun->lun_guid) {

					/* convert ascii wwn to bytes */
					fcp_ascii_to_wwn(plun->lun_guid,
					    map->guid, sizeof (map->guid));

					if ((sizeof (map->guid)) <
					    plun->lun_guid_size / 2) {
						cmn_err(CE_WARN,
						    "fcp_get_target_mappings:"
						    "guid copy space "
						    "insufficient."
						    "Copy Truncation - "
						    "available %d; need %d",
						    (int)sizeof (map->guid),
						    (int)
						    plun->lun_guid_size / 2);
					}
				}
				kmem_free(path, MAXPATHLEN);
			}
		}
	}
	mutex_exit(&pptr->port_mutex);
	mappings->numLuns = mapIndex;

	if (ddi_copyout(mappings, fioctl.list, mappingSize, mode)) {
		kmem_free(mappings, mappingSize);
		return (EFAULT);
	}
	kmem_free(mappings, mappingSize);

#ifdef	_MULTI_DATAMODEL
	switch (ddi_model_convert_from(mode & FMODELS)) {
	case DDI_MODEL_ILP32: {
		struct fcp32_ioctl f32_ioctl;

		f32_ioctl.fp_minor = fioctl.fp_minor;
		f32_ioctl.listlen = fioctl.listlen;
		f32_ioctl.list = (caddr32_t)(long)fioctl.list;
		if (ddi_copyout((void *)&f32_ioctl, (void *)data,
		    sizeof (struct fcp32_ioctl), mode)) {
			return (EFAULT);
		}
		break;
	}
	case DDI_MODEL_NONE:
		if (ddi_copyout((void *)&fioctl, (void *)data,
		    sizeof (struct fcp_ioctl), mode)) {
			return (EFAULT);
		}
		break;
	}
#else	/* _MULTI_DATAMODEL */

	if (ddi_copyout((void *)&fioctl, (void *)data,
	    sizeof (struct fcp_ioctl), mode)) {
		return (EFAULT);
	}
#endif	/* _MULTI_DATAMODEL */

	return (0);
}

/*
 * fcp_setup_scsi_ioctl
 *	Setup handler for the "scsi passthru" style of
 *	ioctl for FCP.	See "fcp_util.h" for data structure
 *	definition.
 *
 * Input:
 *	u_fscsi	= ioctl data (user address space)
 *	mode	= See ioctl(9E)
 *
 * Output:
 *	u_fscsi	= ioctl data (user address space)
 *	rval	= return value - see ioctl(9E)
 *
 * Returns:
 *	0	= OK
 *	EAGAIN	= See errno.h
 *	EBUSY	= See errno.h
 *	EFAULT	= See errno.h
 *	EINTR	= See errno.h
 *	EINVAL	= See errno.h
 *	EIO	= See errno.h
 *	ENOMEM	= See errno.h
 *	ENXIO	= See errno.h
 *
 * Context:
 *	Kernel context.
 */
/* ARGSUSED */
static int
fcp_setup_scsi_ioctl(struct fcp_scsi_cmd *u_fscsi,
    int mode, int *rval)
{
	int			ret		= 0;
	int			temp_ret;
	caddr_t			k_cdbbufaddr	= NULL;
	caddr_t			k_bufaddr	= NULL;
	caddr_t			k_rqbufaddr	= NULL;
	caddr_t			u_cdbbufaddr;
	caddr_t			u_bufaddr;
	caddr_t			u_rqbufaddr;
	struct fcp_scsi_cmd	k_fscsi;

	/*
	 * Get fcp_scsi_cmd array element from user address space
	 */
	if ((ret = fcp_copyin_scsi_cmd((caddr_t)u_fscsi, &k_fscsi, mode))
	    != 0) {
		return (ret);
	}


	/*
	 * Even though kmem_alloc() checks the validity of the
	 * buffer length, this check is needed when the
	 * kmem_flags set and the zero buffer length is passed.
	 */
	if ((k_fscsi.scsi_cdblen <= 0) ||
	    (k_fscsi.scsi_buflen <= 0) ||
	    (k_fscsi.scsi_buflen > FCP_MAX_RESPONSE_LEN) ||
	    (k_fscsi.scsi_rqlen <= 0) ||
	    (k_fscsi.scsi_rqlen > FCP_MAX_SENSE_LEN)) {
		return (EINVAL);
	}

	/*
	 * Allocate data for fcp_scsi_cmd pointer fields
	 */
	if (ret == 0) {
		k_cdbbufaddr = kmem_alloc(k_fscsi.scsi_cdblen, KM_NOSLEEP);
		k_bufaddr    = kmem_alloc(k_fscsi.scsi_buflen, KM_NOSLEEP);
		k_rqbufaddr  = kmem_alloc(k_fscsi.scsi_rqlen,  KM_NOSLEEP);

		if (k_cdbbufaddr == NULL ||
		    k_bufaddr	 == NULL ||
		    k_rqbufaddr	 == NULL) {
			ret = ENOMEM;
		}
	}

	/*
	 * Get fcp_scsi_cmd pointer fields from user
	 * address space
	 */
	if (ret == 0) {
		u_cdbbufaddr = k_fscsi.scsi_cdbbufaddr;
		u_bufaddr    = k_fscsi.scsi_bufaddr;
		u_rqbufaddr  = k_fscsi.scsi_rqbufaddr;

		if (ddi_copyin(u_cdbbufaddr,
		    k_cdbbufaddr,
		    k_fscsi.scsi_cdblen,
		    mode)) {
			ret = EFAULT;
		} else if (ddi_copyin(u_bufaddr,
		    k_bufaddr,
		    k_fscsi.scsi_buflen,
		    mode)) {
			ret = EFAULT;
		} else if (ddi_copyin(u_rqbufaddr,
		    k_rqbufaddr,
		    k_fscsi.scsi_rqlen,
		    mode)) {
			ret = EFAULT;
		}
	}

	/*
	 * Send scsi command (blocking)
	 */
	if (ret == 0) {
		/*
		 * Prior to sending the scsi command, the
		 * fcp_scsi_cmd data structure must contain kernel,
		 * not user, addresses.
		 */
		k_fscsi.scsi_cdbbufaddr	= k_cdbbufaddr;
		k_fscsi.scsi_bufaddr	= k_bufaddr;
		k_fscsi.scsi_rqbufaddr	= k_rqbufaddr;

		ret = fcp_send_scsi_ioctl(&k_fscsi);

		/*
		 * After sending the scsi command, the
		 * fcp_scsi_cmd data structure must contain user,
		 * not kernel, addresses.
		 */
		k_fscsi.scsi_cdbbufaddr	= u_cdbbufaddr;
		k_fscsi.scsi_bufaddr	= u_bufaddr;
		k_fscsi.scsi_rqbufaddr	= u_rqbufaddr;
	}

	/*
	 * Put fcp_scsi_cmd pointer fields to user address space
	 */
	if (ret == 0) {
		if (ddi_copyout(k_cdbbufaddr,
		    u_cdbbufaddr,
		    k_fscsi.scsi_cdblen,
		    mode)) {
			ret = EFAULT;
		} else if (ddi_copyout(k_bufaddr,
		    u_bufaddr,
		    k_fscsi.scsi_buflen,
		    mode)) {
			ret = EFAULT;
		} else if (ddi_copyout(k_rqbufaddr,
		    u_rqbufaddr,
		    k_fscsi.scsi_rqlen,
		    mode)) {
			ret = EFAULT;
		}
	}

	/*
	 * Free data for fcp_scsi_cmd pointer fields
	 */
	if (k_cdbbufaddr != NULL) {
		kmem_free(k_cdbbufaddr, k_fscsi.scsi_cdblen);
	}
	if (k_bufaddr != NULL) {
		kmem_free(k_bufaddr, k_fscsi.scsi_buflen);
	}
	if (k_rqbufaddr != NULL) {
		kmem_free(k_rqbufaddr, k_fscsi.scsi_rqlen);
	}

	/*
	 * Put fcp_scsi_cmd array element to user address space
	 */
	temp_ret = fcp_copyout_scsi_cmd(&k_fscsi, (caddr_t)u_fscsi, mode);
	if (temp_ret != 0) {
		ret = temp_ret;
	}

	/*
	 * Return status
	 */
	return (ret);
}


/*
 * fcp_copyin_scsi_cmd
 *	Copy in fcp_scsi_cmd data structure from user address space.
 *	The data may be in 32 bit or 64 bit modes.
 *
 * Input:
 *	base_addr	= from address (user address space)
 *	mode		= See ioctl(9E) and ddi_copyin(9F)
 *
 * Output:
 *	fscsi		= to address (kernel address space)
 *
 * Returns:
 *	0	= OK
 *	EFAULT	= Error
 *
 * Context:
 *	Kernel context.
 */
static int
fcp_copyin_scsi_cmd(caddr_t base_addr, struct fcp_scsi_cmd *fscsi, int mode)
{
#ifdef	_MULTI_DATAMODEL
	struct fcp32_scsi_cmd	f32scsi;

	switch (ddi_model_convert_from(mode & FMODELS)) {
	case DDI_MODEL_ILP32:
		/*
		 * Copy data from user address space
		 */
		if (ddi_copyin((void *)base_addr,
		    &f32scsi,
		    sizeof (struct fcp32_scsi_cmd),
		    mode)) {
			return (EFAULT);
		}
		/*
		 * Convert from 32 bit to 64 bit
		 */
		FCP32_SCSI_CMD_TO_FCP_SCSI_CMD(&f32scsi, fscsi);
		break;
	case DDI_MODEL_NONE:
		/*
		 * Copy data from user address space
		 */
		if (ddi_copyin((void *)base_addr,
		    fscsi,
		    sizeof (struct fcp_scsi_cmd),
		    mode)) {
			return (EFAULT);
		}
		break;
	}
#else	/* _MULTI_DATAMODEL */
	/*
	 * Copy data from user address space
	 */
	if (ddi_copyin((void *)base_addr,
	    fscsi,
	    sizeof (struct fcp_scsi_cmd),
	    mode)) {
		return (EFAULT);
	}
#endif	/* _MULTI_DATAMODEL */

	return (0);
}


/*
 * fcp_copyout_scsi_cmd
 *	Copy out fcp_scsi_cmd data structure to user address space.
 *	The data may be in 32 bit or 64 bit modes.
 *
 * Input:
 *	fscsi		= to address (kernel address space)
 *	mode		= See ioctl(9E) and ddi_copyin(9F)
 *
 * Output:
 *	base_addr	= from address (user address space)
 *
 * Returns:
 *	0	= OK
 *	EFAULT	= Error
 *
 * Context:
 *	Kernel context.
 */
static int
fcp_copyout_scsi_cmd(struct fcp_scsi_cmd *fscsi, caddr_t base_addr, int mode)
{
#ifdef	_MULTI_DATAMODEL
	struct fcp32_scsi_cmd	f32scsi;

	switch (ddi_model_convert_from(mode & FMODELS)) {
	case DDI_MODEL_ILP32:
		/*
		 * Convert from 64 bit to 32 bit
		 */
		FCP_SCSI_CMD_TO_FCP32_SCSI_CMD(fscsi, &f32scsi);
		/*
		 * Copy data to user address space
		 */
		if (ddi_copyout(&f32scsi,
		    (void *)base_addr,
		    sizeof (struct fcp32_scsi_cmd),
		    mode)) {
			return (EFAULT);
		}
		break;
	case DDI_MODEL_NONE:
		/*
		 * Copy data to user address space
		 */
		if (ddi_copyout(fscsi,
		    (void *)base_addr,
		    sizeof (struct fcp_scsi_cmd),
		    mode)) {
			return (EFAULT);
		}
		break;
	}
#else	/* _MULTI_DATAMODEL */
	/*
	 * Copy data to user address space
	 */
	if (ddi_copyout(fscsi,
	    (void *)base_addr,
	    sizeof (struct fcp_scsi_cmd),
	    mode)) {
		return (EFAULT);
	}
#endif	/* _MULTI_DATAMODEL */

	return (0);
}


/*
 * fcp_send_scsi_ioctl
 *	Sends the SCSI command in blocking mode.
 *
 * Input:
 *	fscsi		= SCSI command data structure
 *
 * Output:
 *	fscsi		= SCSI command data structure
 *
 * Returns:
 *	0	= OK
 *	EAGAIN	= See errno.h
 *	EBUSY	= See errno.h
 *	EINTR	= See errno.h
 *	EINVAL	= See errno.h
 *	EIO	= See errno.h
 *	ENOMEM	= See errno.h
 *	ENXIO	= See errno.h
 *
 * Context:
 *	Kernel context.
 */
static int
fcp_send_scsi_ioctl(struct fcp_scsi_cmd *fscsi)
{
	struct fcp_lun	*plun		= NULL;
	struct fcp_port	*pptr		= NULL;
	struct fcp_tgt	*ptgt		= NULL;
	fc_packet_t		*fpkt		= NULL;
	struct fcp_ipkt	*icmd		= NULL;
	int			target_created	= FALSE;
	fc_frame_hdr_t		*hp;
	struct fcp_cmd		fcp_cmd;
	struct fcp_cmd		*fcmd;
	union scsi_cdb		*scsi_cdb;
	la_wwn_t		*wwn_ptr;
	int			nodma;
	struct fcp_rsp		*rsp;
	struct fcp_rsp_info	*rsp_info;
	caddr_t			rsp_sense;
	int			buf_len;
	int			info_len;
	int			sense_len;
	struct scsi_extended_sense	*sense_to = NULL;
	timeout_id_t		tid;
	uint8_t			reconfig_lun = FALSE;
	uint8_t			reconfig_pending = FALSE;
	uint8_t			scsi_cmd;
	int			rsp_len;
	int			cmd_index;
	int			fc_status;
	int			pkt_state;
	int			pkt_action;
	int			pkt_reason;
	int			ret, xport_retval = ~FC_SUCCESS;
	int			lcount;
	int			tcount;
	int			reconfig_status;
	int			port_busy = FALSE;
	uchar_t			*lun_string;

	/*
	 * Check valid SCSI command
	 */
	scsi_cmd = ((uint8_t *)fscsi->scsi_cdbbufaddr)[0];
	ret = EINVAL;
	for (cmd_index = 0;
	    cmd_index < FCP_NUM_ELEMENTS(scsi_ioctl_list) &&
	    ret != 0;
	    cmd_index++) {
		/*
		 * First byte of CDB is the SCSI command
		 */
		if (scsi_ioctl_list[cmd_index] == scsi_cmd) {
			ret = 0;
		}
	}

	/*
	 * Check inputs
	 */
	if (fscsi->scsi_flags != FCP_SCSI_READ) {
		ret = EINVAL;
	} else if (fscsi->scsi_cdblen > FCP_CDB_SIZE) {
		/* no larger than */
		ret = EINVAL;
	}


	/*
	 * Find FC port
	 */
	if (ret == 0) {
		/*
		 * Acquire global mutex
		 */
		mutex_enter(&fcp_global_mutex);

		pptr = fcp_port_head;
		while (pptr) {
			if (pptr->port_instance ==
			    (uint32_t)fscsi->scsi_fc_port_num) {
				break;
			} else {
				pptr = pptr->port_next;
			}
		}

		if (pptr == NULL) {
			ret = ENXIO;
		} else {
			/*
			 * fc_ulp_busy_port can raise power
			 *  so, we must not hold any mutexes involved in PM
			 */
			mutex_exit(&fcp_global_mutex);
			ret = fc_ulp_busy_port(pptr->port_fp_handle);
		}

		if (ret == 0) {

			/* remember port is busy, so we will release later */
			port_busy = TRUE;

			/*
			 * If there is a reconfiguration in progress, wait
			 * for it to complete.
			 */

			fcp_reconfig_wait(pptr);

			/* reacquire mutexes in order */
			mutex_enter(&fcp_global_mutex);
			mutex_enter(&pptr->port_mutex);

			/*
			 * Will port accept DMA?
			 */
			nodma = (pptr->port_fcp_dma == FC_NO_DVMA_SPACE)
			    ? 1 : 0;

			/*
			 * If init or offline, device not known
			 *
			 * If we are discovering (onlining), we can
			 * NOT obviously provide reliable data about
			 * devices until it is complete
			 */
			if (pptr->port_state &	  (FCP_STATE_INIT |
			    FCP_STATE_OFFLINE)) {
				ret = ENXIO;
			} else if (pptr->port_state & FCP_STATE_ONLINING) {
				ret = EBUSY;
			} else {
				/*
				 * Find target from pwwn
				 *
				 * The wwn must be put into a local
				 * variable to ensure alignment.
				 */
				wwn_ptr = (la_wwn_t *)&(fscsi->scsi_fc_pwwn);
				ptgt = fcp_lookup_target(pptr,
				    (uchar_t *)wwn_ptr);

				/*
				 * If target not found,
				 */
				if (ptgt == NULL) {
					/*
					 * Note: Still have global &
					 * port mutexes
					 */
					mutex_exit(&pptr->port_mutex);
					ptgt = fcp_port_create_tgt(pptr,
					    wwn_ptr, &ret, &fc_status,
					    &pkt_state, &pkt_action,
					    &pkt_reason);
					mutex_enter(&pptr->port_mutex);

					fscsi->scsi_fc_status  = fc_status;
					fscsi->scsi_pkt_state  =
					    (uchar_t)pkt_state;
					fscsi->scsi_pkt_reason = pkt_reason;
					fscsi->scsi_pkt_action =
					    (uchar_t)pkt_action;

					if (ptgt != NULL) {
						target_created = TRUE;
					} else if (ret == 0) {
						ret = ENOMEM;
					}
				}

				if (ret == 0) {
					/*
					 * Acquire target
					 */
					mutex_enter(&ptgt->tgt_mutex);

					/*
					 * If target is mark or busy,
					 * then target can not be used
					 */
					if (ptgt->tgt_state &
					    (FCP_TGT_MARK |
					    FCP_TGT_BUSY)) {
						ret = EBUSY;
					} else {
						/*
						 * Mark target as busy
						 */
						ptgt->tgt_state |=
						    FCP_TGT_BUSY;
					}

					/*
					 * Release target
					 */
					lcount = pptr->port_link_cnt;
					tcount = ptgt->tgt_change_cnt;
					mutex_exit(&ptgt->tgt_mutex);
				}
			}

			/*
			 * Release port
			 */
			mutex_exit(&pptr->port_mutex);
		}

		/*
		 * Release global mutex
		 */
		mutex_exit(&fcp_global_mutex);
	}

	if (ret == 0) {
		uint64_t belun = BE_64(fscsi->scsi_lun);

		/*
		 * If it's a target device, find lun from pwwn
		 * The wwn must be put into a local
		 * variable to ensure alignment.
		 */
		mutex_enter(&pptr->port_mutex);
		wwn_ptr = (la_wwn_t *)&(fscsi->scsi_fc_pwwn);
		if (!ptgt->tgt_tcap && ptgt->tgt_icap) {
			/* this is not a target */
			fscsi->scsi_fc_status = FC_DEVICE_NOT_TGT;
			ret = ENXIO;
		} else if ((belun << 16) != 0) {
			/*
			 * Since fcp only support PD and LU addressing method
			 * so far, the last 6 bytes of a valid LUN are expected
			 * to be filled with 00h.
			 */
			fscsi->scsi_fc_status = FC_INVALID_LUN;
			cmn_err(CE_WARN, "fcp: Unsupported LUN addressing"
			    " method 0x%02x with LUN number 0x%016" PRIx64,
			    (uint8_t)(belun >> 62), belun);
			ret = ENXIO;
		} else if ((plun = fcp_lookup_lun(pptr, (uchar_t *)wwn_ptr,
		    (uint16_t)((belun >> 48) & 0x3fff))) == NULL) {
			/*
			 * This is a SCSI target, but no LUN at this
			 * address.
			 *
			 * In the future, we may want to send this to
			 * the target, and let it respond
			 * appropriately
			 */
			ret = ENXIO;
		}
		mutex_exit(&pptr->port_mutex);
	}

	/*
	 * Finished grabbing external resources
	 * Allocate internal packet (icmd)
	 */
	if (ret == 0) {
		/*
		 * Calc rsp len assuming rsp info included
		 */
		rsp_len = sizeof (struct fcp_rsp) +
		    sizeof (struct fcp_rsp_info) + fscsi->scsi_rqlen;

		icmd = fcp_icmd_alloc(pptr, ptgt,
		    sizeof (struct fcp_cmd),
		    rsp_len,
		    fscsi->scsi_buflen,
		    nodma,
		    lcount,			/* ipkt_link_cnt */
		    tcount,			/* ipkt_change_cnt */
		    0,				/* cause */
		    FC_INVALID_RSCN_COUNT);	/* invalidate the count */

		if (icmd == NULL) {
			ret = ENOMEM;
		} else {
			/*
			 * Setup internal packet as sema sync
			 */
			fcp_ipkt_sema_init(icmd);
		}
	}

	if (ret == 0) {
		/*
		 * Init fpkt pointer for use.
		 */

		fpkt = icmd->ipkt_fpkt;

		fpkt->pkt_tran_flags	= FC_TRAN_CLASS3 | FC_TRAN_INTR;
		fpkt->pkt_tran_type	= FC_PKT_FCP_READ; /* only rd for now */
		fpkt->pkt_timeout	= fscsi->scsi_timeout;

		/*
		 * Init fcmd pointer for use by SCSI command
		 */

		if (nodma) {
			fcmd = (struct fcp_cmd *)fpkt->pkt_cmd;
		} else {
			fcmd = &fcp_cmd;
		}
		bzero(fcmd, sizeof (struct fcp_cmd));
		ptgt = plun->lun_tgt;

		lun_string = (uchar_t *)&fscsi->scsi_lun;

		fcmd->fcp_ent_addr.ent_addr_0 =
		    BE_16(*(uint16_t *)&(lun_string[0]));
		fcmd->fcp_ent_addr.ent_addr_1 =
		    BE_16(*(uint16_t *)&(lun_string[2]));
		fcmd->fcp_ent_addr.ent_addr_2 =
		    BE_16(*(uint16_t *)&(lun_string[4]));
		fcmd->fcp_ent_addr.ent_addr_3 =
		    BE_16(*(uint16_t *)&(lun_string[6]));

		/*
		 * Setup internal packet(icmd)
		 */
		icmd->ipkt_lun		= plun;
		icmd->ipkt_restart	= 0;
		icmd->ipkt_retries	= 0;
		icmd->ipkt_opcode	= 0;

		/*
		 * Init the frame HEADER Pointer for use
		 */
		hp = &fpkt->pkt_cmd_fhdr;

		hp->s_id	= pptr->port_id;
		hp->d_id	= ptgt->tgt_d_id;
		hp->r_ctl	= R_CTL_COMMAND;
		hp->type	= FC_TYPE_SCSI_FCP;
		hp->f_ctl	= F_CTL_SEQ_INITIATIVE | F_CTL_FIRST_SEQ;
		hp->rsvd	= 0;
		hp->seq_id	= 0;
		hp->seq_cnt	= 0;
		hp->ox_id	= 0xffff;
		hp->rx_id	= 0xffff;
		hp->ro		= 0;

		fcmd->fcp_cntl.cntl_qtype	= FCP_QTYPE_SIMPLE;
		fcmd->fcp_cntl.cntl_read_data	= 1;	/* only rd for now */
		fcmd->fcp_cntl.cntl_write_data	= 0;
		fcmd->fcp_data_len	= fscsi->scsi_buflen;

		scsi_cdb = (union scsi_cdb *)fcmd->fcp_cdb;
		bcopy((char *)fscsi->scsi_cdbbufaddr, (char *)scsi_cdb,
		    fscsi->scsi_cdblen);

		if (!nodma) {
			FCP_CP_OUT((uint8_t *)fcmd, fpkt->pkt_cmd,
			    fpkt->pkt_cmd_acc, sizeof (struct fcp_cmd));
		}

		/*
		 * Send SCSI command to FC transport
		 */

		if (ret == 0) {
			mutex_enter(&ptgt->tgt_mutex);

			if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) {
				mutex_exit(&ptgt->tgt_mutex);
				fscsi->scsi_fc_status = xport_retval =
				    fc_ulp_transport(pptr->port_fp_handle,
				    fpkt);
				if (fscsi->scsi_fc_status != FC_SUCCESS) {
					ret = EIO;
				}
			} else {
				mutex_exit(&ptgt->tgt_mutex);
				ret = EBUSY;
			}
		}
	}

	/*
	 * Wait for completion only if fc_ulp_transport was called and it
	 * returned a success. This is the only time callback will happen.
	 * Otherwise, there is no point in waiting
	 */
	if ((ret == 0) && (xport_retval == FC_SUCCESS)) {
		ret = fcp_ipkt_sema_wait(icmd);
	}

	/*
	 * Copy data to IOCTL data structures
	 */
	rsp = NULL;
	if ((ret == 0) && (xport_retval == FC_SUCCESS)) {
		rsp = (struct fcp_rsp *)fpkt->pkt_resp;

		if (fcp_validate_fcp_response(rsp, pptr) != FC_SUCCESS) {
			fcp_log(CE_WARN, pptr->port_dip,
			    "!SCSI command to d_id=0x%x lun=0x%x"
			    " failed, Bad FCP response values:"
			    " rsvd1=%x, rsvd2=%x, sts-rsvd1=%x,"
			    " sts-rsvd2=%x, rsplen=%x, senselen=%x",
			    ptgt->tgt_d_id, plun->lun_num,
			    rsp->reserved_0, rsp->reserved_1,
			    rsp->fcp_u.fcp_status.reserved_0,
			    rsp->fcp_u.fcp_status.reserved_1,
			    rsp->fcp_response_len, rsp->fcp_sense_len);

			ret = EIO;
		}
	}

	if ((ret == 0) && (rsp != NULL)) {
		/*
		 * Calc response lengths
		 */
		sense_len = 0;
		info_len = 0;

		if (rsp->fcp_u.fcp_status.rsp_len_set) {
			info_len = rsp->fcp_response_len;
		}

		rsp_info   = (struct fcp_rsp_info *)
		    ((uint8_t *)rsp + sizeof (struct fcp_rsp));

		/*
		 * Get SCSI status
		 */
		fscsi->scsi_bufstatus = rsp->fcp_u.fcp_status.scsi_status;
		/*
		 * If a lun was just added or removed and the next command
		 * comes through this interface, we need to capture the check
		 * condition so we can discover the new topology.
		 */
		if (fscsi->scsi_bufstatus != STATUS_GOOD &&
		    rsp->fcp_u.fcp_status.sense_len_set) {
			sense_len = rsp->fcp_sense_len;
			rsp_sense  = (caddr_t)((uint8_t *)rsp_info + info_len);
			sense_to = (struct scsi_extended_sense *)rsp_sense;
			if ((FCP_SENSE_REPORTLUN_CHANGED(sense_to)) ||
			    (FCP_SENSE_NO_LUN(sense_to))) {
				reconfig_lun = TRUE;
			}
		}

		if (fscsi->scsi_bufstatus == STATUS_GOOD && (ptgt != NULL) &&
		    (reconfig_lun || (scsi_cdb->scc_cmd == SCMD_REPORT_LUN))) {
			if (reconfig_lun == FALSE) {
				reconfig_status =
				    fcp_is_reconfig_needed(ptgt, fpkt);
			}

			if ((reconfig_lun == TRUE) ||
			    (reconfig_status == TRUE)) {
				mutex_enter(&ptgt->tgt_mutex);
				if (ptgt->tgt_tid == NULL) {
					/*
					 * Either we've been notified the
					 * REPORT_LUN data has changed, or
					 * we've determined on our own that
					 * we're out of date.  Kick off
					 * rediscovery.
					 */
					tid = timeout(fcp_reconfigure_luns,
					    (caddr_t)ptgt, drv_usectohz(1));

					ptgt->tgt_tid = tid;
					ptgt->tgt_state |= FCP_TGT_BUSY;
					ret = EBUSY;
					reconfig_pending = TRUE;
				}
				mutex_exit(&ptgt->tgt_mutex);
			}
		}

		/*
		 * Calc residuals and buffer lengths
		 */

		if (ret == 0) {
			buf_len = fscsi->scsi_buflen;
			fscsi->scsi_bufresid	= 0;
			if (rsp->fcp_u.fcp_status.resid_under) {
				if (rsp->fcp_resid <= fscsi->scsi_buflen) {
					fscsi->scsi_bufresid = rsp->fcp_resid;
				} else {
					cmn_err(CE_WARN, "fcp: bad residue %x "
					    "for txfer len %x", rsp->fcp_resid,
					    fscsi->scsi_buflen);
					fscsi->scsi_bufresid =
					    fscsi->scsi_buflen;
				}
				buf_len -= fscsi->scsi_bufresid;
			}
			if (rsp->fcp_u.fcp_status.resid_over) {
				fscsi->scsi_bufresid = -rsp->fcp_resid;
			}

			fscsi->scsi_rqresid	= fscsi->scsi_rqlen - sense_len;
			if (fscsi->scsi_rqlen < sense_len) {
				sense_len = fscsi->scsi_rqlen;
			}

			fscsi->scsi_fc_rspcode	= 0;
			if (rsp->fcp_u.fcp_status.rsp_len_set) {
				fscsi->scsi_fc_rspcode	= rsp_info->rsp_code;
			}
			fscsi->scsi_pkt_state	= fpkt->pkt_state;
			fscsi->scsi_pkt_action	= fpkt->pkt_action;
			fscsi->scsi_pkt_reason	= fpkt->pkt_reason;

			/*
			 * Copy data and request sense
			 *
			 * Data must be copied by using the FCP_CP_IN macro.
			 * This will ensure the proper byte order since the data
			 * is being copied directly from the memory mapped
			 * device register.
			 *
			 * The response (and request sense) will be in the
			 * correct byte order.	No special copy is necessary.
			 */

			if (buf_len) {
				FCP_CP_IN(fpkt->pkt_data,
				    fscsi->scsi_bufaddr,
				    fpkt->pkt_data_acc,
				    buf_len);
			}
			bcopy((void *)rsp_sense,
			    (void *)fscsi->scsi_rqbufaddr,
			    sense_len);
		}
	}

	/*
	 * Cleanup transport data structures if icmd was alloc-ed
	 * So, cleanup happens in the same thread that icmd was alloc-ed
	 */
	if (icmd != NULL) {
		fcp_ipkt_sema_cleanup(icmd);
	}

	/* restore pm busy/idle status */
	if (port_busy) {
		fc_ulp_idle_port(pptr->port_fp_handle);
	}

	/*
	 * Cleanup target.  if a reconfig is pending, don't clear the BUSY
	 * flag, it'll be cleared when the reconfig is complete.
	 */
	if ((ptgt != NULL) && !reconfig_pending) {
		/*
		 * If target was created,
		 */
		if (target_created) {
			mutex_enter(&ptgt->tgt_mutex);
			ptgt->tgt_state &= ~FCP_TGT_BUSY;
			mutex_exit(&ptgt->tgt_mutex);
		} else {
			/*
			 * De-mark target as busy
			 */
			mutex_enter(&ptgt->tgt_mutex);
			ptgt->tgt_state &= ~FCP_TGT_BUSY;
			mutex_exit(&ptgt->tgt_mutex);
		}
	}
	return (ret);
}


static int
fcp_is_reconfig_needed(struct fcp_tgt *ptgt,
    fc_packet_t	*fpkt)
{
	uchar_t			*lun_string;
	uint16_t		lun_num, i;
	int			num_luns;
	int			actual_luns;
	int			num_masked_luns;
	int			lun_buflen;
	struct fcp_lun	*plun	= NULL;
	struct fcp_reportlun_resp	*report_lun;
	uint8_t			reconfig_needed = FALSE;
	uint8_t			lun_exists = FALSE;

	report_lun = kmem_zalloc(fpkt->pkt_datalen, KM_SLEEP);

	FCP_CP_IN(fpkt->pkt_data, report_lun, fpkt->pkt_data_acc,
	    fpkt->pkt_datalen);

	/* get number of luns (which is supplied as LUNS * 8) */
	num_luns = BE_32(report_lun->num_lun) >> 3;

	/*
	 * Figure out exactly how many lun strings our response buffer
	 * can hold.
	 */
	lun_buflen = (fpkt->pkt_datalen -
	    2 * sizeof (uint32_t)) / sizeof (longlong_t);

	/*
	 * Is our response buffer full or not? We don't want to
	 * potentially walk beyond the number of luns we have.
	 */
	if (num_luns <= lun_buflen) {
		actual_luns = num_luns;
	} else {
		actual_luns = lun_buflen;
	}

	mutex_enter(&ptgt->tgt_mutex);

	/* Scan each lun to see if we have masked it. */
	num_masked_luns = 0;
	if (fcp_lun_blacklist != NULL) {
		for (i = 0; i < actual_luns; i++) {
			lun_string = (uchar_t *)&(report_lun->lun_string[i]);
			switch (lun_string[0] & 0xC0) {
			case FCP_LUN_ADDRESSING:
			case FCP_PD_ADDRESSING:
				lun_num = ((lun_string[0] & 0x3F) << 8)
				    | lun_string[1];
				if (fcp_should_mask(&ptgt->tgt_port_wwn,
				    lun_num) == TRUE) {
					num_masked_luns++;
				}
				break;
			default:
				break;
			}
		}
	}

	/*
	 * The quick and easy check.  If the number of LUNs reported
	 * doesn't match the number we currently know about, we need
	 * to reconfigure.
	 */
	if (num_luns && num_luns != (ptgt->tgt_lun_cnt + num_masked_luns)) {
		mutex_exit(&ptgt->tgt_mutex);
		kmem_free(report_lun, fpkt->pkt_datalen);
		return (TRUE);
	}

	/*
	 * If the quick and easy check doesn't turn up anything, we walk
	 * the list of luns from the REPORT_LUN response and look for
	 * any luns we don't know about.  If we find one, we know we need
	 * to reconfigure. We will skip LUNs that are masked because of the
	 * blacklist.
	 */
	for (i = 0; i < actual_luns; i++) {
		lun_string = (uchar_t *)&(report_lun->lun_string[i]);
		lun_exists = FALSE;
		switch (lun_string[0] & 0xC0) {
		case FCP_LUN_ADDRESSING:
		case FCP_PD_ADDRESSING:
			lun_num = ((lun_string[0] & 0x3F) << 8) | lun_string[1];

			if ((fcp_lun_blacklist != NULL) && (fcp_should_mask(
			    &ptgt->tgt_port_wwn, lun_num) == TRUE)) {
				lun_exists = TRUE;
				break;
			}

			for (plun = ptgt->tgt_lun; plun;
			    plun = plun->lun_next) {
				if (plun->lun_num == lun_num) {
					lun_exists = TRUE;
					break;
				}
			}
			break;
		default:
			break;
		}

		if (lun_exists == FALSE) {
			reconfig_needed = TRUE;
			break;
		}
	}

	mutex_exit(&ptgt->tgt_mutex);
	kmem_free(report_lun, fpkt->pkt_datalen);

	return (reconfig_needed);
}

/*
 * This function is called by fcp_handle_page83 and uses inquiry response data
 * stored in plun->lun_inq to determine whether or not a device is a member of
 * the table fcp_symmetric_disk_table_size. We return 0 if it is in the table,
 * otherwise 1.
 */
static int
fcp_symmetric_device_probe(struct fcp_lun *plun)
{
	struct scsi_inquiry	*stdinq = &plun->lun_inq;
	char			*devidptr;
	int			i, len;

	for (i = 0; i < fcp_symmetric_disk_table_size; i++) {
		devidptr = fcp_symmetric_disk_table[i];
		len = (int)strlen(devidptr);

		if (bcmp(stdinq->inq_vid, devidptr, len) == 0) {
			return (0);
		}
	}
	return (1);
}


/*
 * This function is called by fcp_ioctl for the FCP_STATE_COUNT ioctl
 * It basically returns the current count of # of state change callbacks
 * i.e the value of tgt_change_cnt.
 *
 * INPUT:
 *   fcp_ioctl.fp_minor -> The minor # of the fp port
 *   fcp_ioctl.listlen	-> 1
 *   fcp_ioctl.list	-> Pointer to a 32 bit integer
 */
/*ARGSUSED2*/
static int
fcp_get_statec_count(struct fcp_ioctl *data, int mode, int *rval)
{
	int			ret;
	uint32_t		link_cnt;
	struct fcp_ioctl	fioctl;
	struct fcp_port	*pptr = NULL;

	if ((ret = fcp_copyin_fcp_ioctl_data(data, mode, rval, &fioctl,
	    &pptr)) != 0) {
		return (ret);
	}

	ASSERT(pptr != NULL);

	if (fioctl.listlen != 1) {
		return (EINVAL);
	}

	mutex_enter(&pptr->port_mutex);
	if (pptr->port_state & FCP_STATE_OFFLINE) {
		mutex_exit(&pptr->port_mutex);
		return (ENXIO);
	}

	/*
	 * FCP_STATE_INIT is set in 2 cases (not sure why it is overloaded):
	 * When the fcp initially attaches to the port and there are nothing
	 * hanging out of the port or if there was a repeat offline state change
	 * callback (refer fcp_statec_callback() FC_STATE_OFFLINE case).
	 * In the latter case, port_tmp_cnt will be non-zero and that is how we
	 * will differentiate the 2 cases.
	 */
	if ((pptr->port_state & FCP_STATE_INIT) && pptr->port_tmp_cnt) {
		mutex_exit(&pptr->port_mutex);
		return (ENXIO);
	}

	link_cnt = pptr->port_link_cnt;
	mutex_exit(&pptr->port_mutex);

	if (ddi_copyout(&link_cnt, fioctl.list, (sizeof (uint32_t)), mode)) {
		return (EFAULT);
	}

#ifdef	_MULTI_DATAMODEL
	switch (ddi_model_convert_from(mode & FMODELS)) {
	case DDI_MODEL_ILP32: {
		struct fcp32_ioctl f32_ioctl;

		f32_ioctl.fp_minor = fioctl.fp_minor;
		f32_ioctl.listlen = fioctl.listlen;
		f32_ioctl.list = (caddr32_t)(long)fioctl.list;
		if (ddi_copyout((void *)&f32_ioctl, (void *)data,
		    sizeof (struct fcp32_ioctl), mode)) {
			return (EFAULT);
		}
		break;
	}
	case DDI_MODEL_NONE:
		if (ddi_copyout((void *)&fioctl, (void *)data,
		    sizeof (struct fcp_ioctl), mode)) {
			return (EFAULT);
		}
		break;
	}
#else	/* _MULTI_DATAMODEL */

	if (ddi_copyout((void *)&fioctl, (void *)data,
	    sizeof (struct fcp_ioctl), mode)) {
		return (EFAULT);
	}
#endif	/* _MULTI_DATAMODEL */

	return (0);
}

/*
 * This function copies the fcp_ioctl structure passed in from user land
 * into kernel land. Handles 32 bit applications.
 */
/*ARGSUSED*/
static int
fcp_copyin_fcp_ioctl_data(struct fcp_ioctl *data, int mode, int *rval,
    struct fcp_ioctl *fioctl, struct fcp_port **pptr)
{
	struct fcp_port	*t_pptr;

#ifdef	_MULTI_DATAMODEL
	switch (ddi_model_convert_from(mode & FMODELS)) {
	case DDI_MODEL_ILP32: {
		struct fcp32_ioctl f32_ioctl;

		if (ddi_copyin((void *)data, (void *)&f32_ioctl,
		    sizeof (struct fcp32_ioctl), mode)) {
			return (EFAULT);
		}
		fioctl->fp_minor = f32_ioctl.fp_minor;
		fioctl->listlen = f32_ioctl.listlen;
		fioctl->list = (caddr_t)(long)f32_ioctl.list;
		break;
	}
	case DDI_MODEL_NONE:
		if (ddi_copyin((void *)data, (void *)fioctl,
		    sizeof (struct fcp_ioctl), mode)) {
			return (EFAULT);
		}
		break;
	}

#else	/* _MULTI_DATAMODEL */
	if (ddi_copyin((void *)data, (void *)fioctl,
	    sizeof (struct fcp_ioctl), mode)) {
		return (EFAULT);
	}
#endif	/* _MULTI_DATAMODEL */

	/*
	 * Right now we can assume that the minor number matches with
	 * this instance of fp. If this changes we will need to
	 * revisit this logic.
	 */
	mutex_enter(&fcp_global_mutex);
	t_pptr = fcp_port_head;
	while (t_pptr) {
		if (t_pptr->port_instance == (uint32_t)fioctl->fp_minor) {
			break;
		} else {
			t_pptr = t_pptr->port_next;
		}
	}
	*pptr = t_pptr;
	mutex_exit(&fcp_global_mutex);
	if (t_pptr == NULL) {
		return (ENXIO);
	}

	return (0);
}

/*
 *     Function: fcp_port_create_tgt
 *
 *  Description: As the name suggest this function creates the target context
 *		 specified by the the WWN provided by the caller.  If the
 *		 creation goes well and the target is known by fp/fctl a PLOGI
 *		 followed by a PRLI are issued.
 *
 *     Argument: pptr		fcp port structure
 *		 pwwn		WWN of the target
 *		 ret_val	Address of the return code.  It could be:
 *				EIO, ENOMEM or 0.
 *		 fc_status	PLOGI or PRLI status completion
 *		 fc_pkt_state	PLOGI or PRLI state completion
 *		 fc_pkt_reason	PLOGI or PRLI reason completion
 *		 fc_pkt_action	PLOGI or PRLI action completion
 *
 * Return Value: NULL if it failed
 *		 Target structure address if it succeeds
 */
static struct fcp_tgt *
fcp_port_create_tgt(struct fcp_port *pptr, la_wwn_t *pwwn, int *ret_val,
    int *fc_status, int *fc_pkt_state, int *fc_pkt_reason, int *fc_pkt_action)
{
	struct fcp_tgt	*ptgt = NULL;
	fc_portmap_t		devlist;
	int			lcount;
	int			error;

	*ret_val = 0;

	/*
	 * Check FC port device & get port map
	 */
	if (fc_ulp_get_remote_port(pptr->port_fp_handle, pwwn,
	    &error, 1) == NULL) {
		*ret_val = EIO;
	} else {
		if (fc_ulp_pwwn_to_portmap(pptr->port_fp_handle, pwwn,
		    &devlist) != FC_SUCCESS) {
			*ret_val = EIO;
		}
	}

	/* Set port map flags */
	devlist.map_type = PORT_DEVICE_USER_CREATE;

	/* Allocate target */
	if (*ret_val == 0) {
		lcount = pptr->port_link_cnt;
		ptgt = fcp_alloc_tgt(pptr, &devlist, lcount);
		if (ptgt == NULL) {
			fcp_log(CE_WARN, pptr->port_dip,
			    "!FC target allocation failed");
			*ret_val = ENOMEM;
		} else {
			/* Setup target */
			mutex_enter(&ptgt->tgt_mutex);

			ptgt->tgt_statec_cause	= FCP_CAUSE_TGT_CHANGE;
			ptgt->tgt_tmp_cnt	= 1;
			ptgt->tgt_d_id		= devlist.map_did.port_id;
			ptgt->tgt_hard_addr	=
			    devlist.map_hard_addr.hard_addr;
			ptgt->tgt_pd_handle	= devlist.map_pd;
			ptgt->tgt_fca_dev	= NULL;

			bcopy(&devlist.map_nwwn, &ptgt->tgt_node_wwn.raw_wwn[0],
			    FC_WWN_SIZE);
			bcopy(&devlist.map_pwwn, &ptgt->tgt_port_wwn.raw_wwn[0],
			    FC_WWN_SIZE);

			mutex_exit(&ptgt->tgt_mutex);
		}
	}

	/* Release global mutex for PLOGI and PRLI */
	mutex_exit(&fcp_global_mutex);

	/* Send PLOGI (If necessary) */
	if (*ret_val == 0) {
		*ret_val = fcp_tgt_send_plogi(ptgt, fc_status,
		    fc_pkt_state, fc_pkt_reason, fc_pkt_action);
	}

	/* Send PRLI (If necessary) */
	if (*ret_val == 0) {
		*ret_val = fcp_tgt_send_prli(ptgt, fc_status,
		    fc_pkt_state, fc_pkt_reason, fc_pkt_action);
	}

	mutex_enter(&fcp_global_mutex);

	return (ptgt);
}

/*
 *     Function: fcp_tgt_send_plogi
 *
 *  Description: This function sends a PLOGI to the target specified by the
 *		 caller and waits till it completes.
 *
 *     Argument: ptgt		Target to send the plogi to.
 *		 fc_status	Status returned by fp/fctl in the PLOGI request.
 *		 fc_pkt_state	State returned by fp/fctl in the PLOGI request.
 *		 fc_pkt_reason	Reason returned by fp/fctl in the PLOGI request.
 *		 fc_pkt_action	Action returned by fp/fctl in the PLOGI request.
 *
 * Return Value: 0
 *		 ENOMEM
 *		 EIO
 *
 *	Context: User context.
 */
static int
fcp_tgt_send_plogi(struct fcp_tgt *ptgt, int *fc_status, int *fc_pkt_state,
    int *fc_pkt_reason, int *fc_pkt_action)
{
	struct fcp_port	*pptr;
	struct fcp_ipkt	*icmd;
	struct fc_packet	*fpkt;
	fc_frame_hdr_t		*hp;
	struct la_els_logi	logi;
	int			tcount;
	int			lcount;
	int			ret, login_retval = ~FC_SUCCESS;

	ret = 0;

	pptr = ptgt->tgt_port;

	lcount = pptr->port_link_cnt;
	tcount = ptgt->tgt_change_cnt;

	/* Alloc internal packet */
	icmd = fcp_icmd_alloc(pptr, ptgt, sizeof (la_els_logi_t),
	    sizeof (la_els_logi_t), 0, 0, lcount, tcount, 0,
	    FC_INVALID_RSCN_COUNT);

	if (icmd == NULL) {
		ret = ENOMEM;
	} else {
		/*
		 * Setup internal packet as sema sync
		 */
		fcp_ipkt_sema_init(icmd);

		/*
		 * Setup internal packet (icmd)
		 */
		icmd->ipkt_lun		= NULL;
		icmd->ipkt_restart	= 0;
		icmd->ipkt_retries	= 0;
		icmd->ipkt_opcode	= LA_ELS_PLOGI;

		/*
		 * Setup fc_packet
		 */
		fpkt = icmd->ipkt_fpkt;

		fpkt->pkt_tran_flags	= FC_TRAN_CLASS3 | FC_TRAN_INTR;
		fpkt->pkt_tran_type	= FC_PKT_EXCHANGE;
		fpkt->pkt_timeout	= FCP_ELS_TIMEOUT;

		/*
		 * Setup FC frame header
		 */
		hp = &fpkt->pkt_cmd_fhdr;

		hp->s_id	= pptr->port_id;	/* source ID */
		hp->d_id	= ptgt->tgt_d_id;	/* dest ID */
		hp->r_ctl	= R_CTL_ELS_REQ;
		hp->type	= FC_TYPE_EXTENDED_LS;
		hp->f_ctl	= F_CTL_SEQ_INITIATIVE | F_CTL_FIRST_SEQ;
		hp->seq_id	= 0;
		hp->rsvd	= 0;
		hp->df_ctl	= 0;
		hp->seq_cnt	= 0;
		hp->ox_id	= 0xffff;		/* i.e. none */
		hp->rx_id	= 0xffff;		/* i.e. none */
		hp->ro		= 0;

		/*
		 * Setup PLOGI
		 */
		bzero(&logi, sizeof (struct la_els_logi));
		logi.ls_code.ls_code = LA_ELS_PLOGI;

		FCP_CP_OUT((uint8_t *)&logi, fpkt->pkt_cmd,
		    fpkt->pkt_cmd_acc, sizeof (struct la_els_logi));

		/*
		 * Send PLOGI
		 */
		*fc_status = login_retval =
		    fc_ulp_login(pptr->port_fp_handle, &fpkt, 1);
		if (*fc_status != FC_SUCCESS) {
			ret = EIO;
		}
	}

	/*
	 * Wait for completion
	 */
	if ((ret == 0) && (login_retval == FC_SUCCESS)) {
		ret = fcp_ipkt_sema_wait(icmd);

		*fc_pkt_state	= fpkt->pkt_state;
		*fc_pkt_reason	= fpkt->pkt_reason;
		*fc_pkt_action	= fpkt->pkt_action;
	}

	/*
	 * Cleanup transport data structures if icmd was alloc-ed AND if there
	 * is going to be no callback (i.e if fc_ulp_login() failed).
	 * Otherwise, cleanup happens in callback routine.
	 */
	if (icmd != NULL) {
		fcp_ipkt_sema_cleanup(icmd);
	}

	return (ret);
}

/*
 *     Function: fcp_tgt_send_prli
 *
 *  Description: Does nothing as of today.
 *
 *     Argument: ptgt		Target to send the prli to.
 *		 fc_status	Status returned by fp/fctl in the PRLI request.
 *		 fc_pkt_state	State returned by fp/fctl in the PRLI request.
 *		 fc_pkt_reason	Reason returned by fp/fctl in the PRLI request.
 *		 fc_pkt_action	Action returned by fp/fctl in the PRLI request.
 *
 * Return Value: 0
 */
/*ARGSUSED*/
static int
fcp_tgt_send_prli(struct fcp_tgt *ptgt, int *fc_status, int *fc_pkt_state,
    int *fc_pkt_reason, int *fc_pkt_action)
{
	return (0);
}

/*
 *     Function: fcp_ipkt_sema_init
 *
 *  Description: Initializes the semaphore contained in the internal packet.
 *
 *     Argument: icmd	Internal packet the semaphore of which must be
 *			initialized.
 *
 * Return Value: None
 *
 *	Context: User context only.
 */
static void
fcp_ipkt_sema_init(struct fcp_ipkt *icmd)
{
	struct fc_packet	*fpkt;

	fpkt = icmd->ipkt_fpkt;

	/* Create semaphore for sync */
	sema_init(&(icmd->ipkt_sema), 0, NULL, SEMA_DRIVER, NULL);

	/* Setup the completion callback */
	fpkt->pkt_comp = fcp_ipkt_sema_callback;
}

/*
 *     Function: fcp_ipkt_sema_wait
 *
 *  Description: Wait on the semaphore embedded in the internal packet.	 The
 *		 semaphore is released in the callback.
 *
 *     Argument: icmd	Internal packet to wait on for completion.
 *
 * Return Value: 0
 *		 EIO
 *		 EBUSY
 *		 EAGAIN
 *
 *	Context: User context only.
 *
 * This function does a conversion between the field pkt_state of the fc_packet
 * embedded in the internal packet (icmd) and the code it returns.
 */
static int
fcp_ipkt_sema_wait(struct fcp_ipkt *icmd)
{
	struct fc_packet	*fpkt;
	int	ret;

	ret = EIO;
	fpkt = icmd->ipkt_fpkt;

	/*
	 * Wait on semaphore
	 */
	sema_p(&(icmd->ipkt_sema));

	/*
	 * Check the status of the FC packet
	 */
	switch (fpkt->pkt_state) {
	case FC_PKT_SUCCESS:
		ret = 0;
		break;
	case FC_PKT_LOCAL_RJT:
		switch (fpkt->pkt_reason) {
		case FC_REASON_SEQ_TIMEOUT:
		case FC_REASON_RX_BUF_TIMEOUT:
			ret = EAGAIN;
			break;
		case FC_REASON_PKT_BUSY:
			ret = EBUSY;
			break;
		}
		break;
	case FC_PKT_TIMEOUT:
		ret = EAGAIN;
		break;
	case FC_PKT_LOCAL_BSY:
	case FC_PKT_TRAN_BSY:
	case FC_PKT_NPORT_BSY:
	case FC_PKT_FABRIC_BSY:
		ret = EBUSY;
		break;
	case FC_PKT_LS_RJT:
	case FC_PKT_BA_RJT:
		switch (fpkt->pkt_reason) {
		case FC_REASON_LOGICAL_BSY:
			ret = EBUSY;
			break;
		}
		break;
	case FC_PKT_FS_RJT:
		switch (fpkt->pkt_reason) {
		case FC_REASON_FS_LOGICAL_BUSY:
			ret = EBUSY;
			break;
		}
		break;
	}

	return (ret);
}

/*
 *     Function: fcp_ipkt_sema_callback
 *
 *  Description: Registered as the completion callback function for the FC
 *		 transport when the ipkt semaphore is used for sync. This will
 *		 cleanup the used data structures, if necessary and wake up
 *		 the user thread to complete the transaction.
 *
 *     Argument: fpkt	FC packet (points to the icmd)
 *
 * Return Value: None
 *
 *	Context: User context only
 */
static void
fcp_ipkt_sema_callback(struct fc_packet *fpkt)
{
	struct fcp_ipkt	*icmd;

	icmd = (struct fcp_ipkt *)fpkt->pkt_ulp_private;

	/*
	 * Wake up user thread
	 */
	sema_v(&(icmd->ipkt_sema));
}

/*
 *     Function: fcp_ipkt_sema_cleanup
 *
 *  Description: Called to cleanup (if necessary) the data structures used
 *		 when ipkt sema is used for sync.  This function will detect
 *		 whether the caller is the last thread (via counter) and
 *		 cleanup only if necessary.
 *
 *     Argument: icmd	Internal command packet
 *
 * Return Value: None
 *
 *	Context: User context only
 */
static void
fcp_ipkt_sema_cleanup(struct fcp_ipkt *icmd)
{
	struct fcp_tgt	*ptgt;
	struct fcp_port	*pptr;

	ptgt = icmd->ipkt_tgt;
	pptr = icmd->ipkt_port;

	/*
	 * Acquire data structure
	 */
	mutex_enter(&ptgt->tgt_mutex);

	/*
	 * Destroy semaphore
	 */
	sema_destroy(&(icmd->ipkt_sema));

	/*
	 * Cleanup internal packet
	 */
	mutex_exit(&ptgt->tgt_mutex);
	fcp_icmd_free(pptr, icmd);
}

/*
 *     Function: fcp_port_attach
 *
 *  Description: Called by the transport framework to resume, suspend or
 *		 attach a new port.
 *
 *     Argument: ulph		Port handle
 *		 *pinfo		Port information
 *		 cmd		Command
 *		 s_id		Port ID
 *
 * Return Value: FC_FAILURE or FC_SUCCESS
 */
/*ARGSUSED*/
static int
fcp_port_attach(opaque_t ulph, fc_ulp_port_info_t *pinfo,
    fc_attach_cmd_t cmd, uint32_t s_id)
{
	int	instance;
	int	res = FC_FAILURE; /* default result */

	ASSERT(pinfo != NULL);

	instance = ddi_get_instance(pinfo->port_dip);

	switch (cmd) {
	case FC_CMD_ATTACH:
		/*
		 * this port instance attaching for the first time (or after
		 * being detached before)
		 */
		if (fcp_handle_port_attach(ulph, pinfo, s_id,
		    instance) == DDI_SUCCESS) {
			res = FC_SUCCESS;
		} else {
			ASSERT(ddi_get_soft_state(fcp_softstate,
			    instance) == NULL);
		}
		break;

	case FC_CMD_RESUME:
	case FC_CMD_POWER_UP:
		/*
		 * this port instance was attached and the suspended and
		 * will now be resumed
		 */
		if (fcp_handle_port_resume(ulph, pinfo, s_id, cmd,
		    instance) == DDI_SUCCESS) {
			res = FC_SUCCESS;
		}
		break;

	default:
		/* shouldn't happen */
		FCP_TRACE(fcp_logq, "fcp",
		    fcp_trace, FCP_BUF_LEVEL_2, 0,
		    "port_attach: unknown cmdcommand: %d", cmd);
		break;
	}

	/* return result */
	FCP_DTRACE(fcp_logq, "fcp", fcp_trace,
	    FCP_BUF_LEVEL_1, 0, "fcp_port_attach returning %d", res);

	return (res);
}


/*
 * detach or suspend this port instance
 *
 * acquires and releases the global mutex
 *
 * acquires and releases the mutex for this port
 *
 * acquires and releases the hotplug mutex for this port
 */
/*ARGSUSED*/
static int
fcp_port_detach(opaque_t ulph, fc_ulp_port_info_t *info,
    fc_detach_cmd_t cmd)
{
	int			flag;
	int			instance;
	struct fcp_port		*pptr;

	instance = ddi_get_instance(info->port_dip);
	pptr = ddi_get_soft_state(fcp_softstate, instance);

	switch (cmd) {
	case FC_CMD_SUSPEND:
		FCP_DTRACE(fcp_logq, "fcp",
		    fcp_trace, FCP_BUF_LEVEL_8, 0,
		    "port suspend called for port %d", instance);
		flag = FCP_STATE_SUSPENDED;
		break;

	case FC_CMD_POWER_DOWN:
		FCP_DTRACE(fcp_logq, "fcp",
		    fcp_trace, FCP_BUF_LEVEL_8, 0,
		    "port power down called for port %d", instance);
		flag = FCP_STATE_POWER_DOWN;
		break;

	case FC_CMD_DETACH:
		FCP_DTRACE(fcp_logq, "fcp",
		    fcp_trace, FCP_BUF_LEVEL_8, 0,
		    "port detach called for port %d", instance);
		flag = FCP_STATE_DETACHING;
		break;

	default:
		/* shouldn't happen */
		return (FC_FAILURE);
	}
	FCP_DTRACE(fcp_logq, "fcp", fcp_trace,
	    FCP_BUF_LEVEL_1, 0, "fcp_port_detach returning");

	return (fcp_handle_port_detach(pptr, flag, instance));
}


/*
 * called for ioctls on the transport's devctl interface, and the transport
 * has passed it to us
 *
 * this will only be called for device control ioctls (i.e. hotplugging stuff)
 *
 * return FC_SUCCESS if we decide to claim the ioctl,
 * else return FC_UNCLAIMED
 *
 * *rval is set iff we decide to claim the ioctl
 */
/*ARGSUSED*/
static int
fcp_port_ioctl(opaque_t ulph, opaque_t port_handle, dev_t dev, int cmd,
    intptr_t data, int mode, cred_t *credp, int *rval, uint32_t claimed)
{
	int			retval = FC_UNCLAIMED;	/* return value */
	struct fcp_port		*pptr = NULL;		/* our soft state */
	struct devctl_iocdata	*dcp = NULL;		/* for devctl */
	dev_info_t		*cdip;
	mdi_pathinfo_t		*pip = NULL;
	char			*ndi_nm;		/* NDI name */
	char			*ndi_addr;		/* NDI addr */
	int			is_mpxio, circ;
	int			devi_entered = 0;
	time_t			end_time;

	ASSERT(rval != NULL);

	FCP_DTRACE(fcp_logq, "fcp",
	    fcp_trace, FCP_BUF_LEVEL_8, 0,
	    "fcp_port_ioctl(cmd=0x%x, claimed=%d)", cmd, claimed);

	/* if already claimed then forget it */
	if (claimed) {
		/*
		 * for now, if this ioctl has already been claimed, then
		 * we just ignore it
		 */
		return (retval);
	}

	/* get our port info */
	if ((pptr = fcp_get_port(port_handle)) == NULL) {
		fcp_log(CE_WARN, NULL,
		    "!fcp:Invalid port handle handle in ioctl");
		*rval = ENXIO;
		return (retval);
	}
	is_mpxio = pptr->port_mpxio;

	switch (cmd) {
	case DEVCTL_BUS_GETSTATE:
	case DEVCTL_BUS_QUIESCE:
	case DEVCTL_BUS_UNQUIESCE:
	case DEVCTL_BUS_RESET:
	case DEVCTL_BUS_RESETALL:

	case DEVCTL_BUS_DEV_CREATE:
		if (ndi_dc_allochdl((void *)data, &dcp) != NDI_SUCCESS) {
			return (retval);
		}
		break;

	case DEVCTL_DEVICE_GETSTATE:
	case DEVCTL_DEVICE_OFFLINE:
	case DEVCTL_DEVICE_ONLINE:
	case DEVCTL_DEVICE_REMOVE:
	case DEVCTL_DEVICE_RESET:
		if (ndi_dc_allochdl((void *)data, &dcp) != NDI_SUCCESS) {
			return (retval);
		}

		ASSERT(dcp != NULL);

		/* ensure we have a name and address */
		if (((ndi_nm = ndi_dc_getname(dcp)) == NULL) ||
		    ((ndi_addr = ndi_dc_getaddr(dcp)) == NULL)) {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_2, 0,
			    "ioctl: can't get name (%s) or addr (%s)",
			    ndi_nm ? ndi_nm : "<null ptr>",
			    ndi_addr ? ndi_addr : "<null ptr>");
			ndi_dc_freehdl(dcp);
			return (retval);
		}


		/* get our child's DIP */
		ASSERT(pptr != NULL);
		if (is_mpxio) {
			mdi_devi_enter(pptr->port_dip, &circ);
		} else {
			ndi_devi_enter(pptr->port_dip, &circ);
		}
		devi_entered = 1;

		if ((cdip = ndi_devi_find(pptr->port_dip, ndi_nm,
		    ndi_addr)) == NULL) {
			/* Look for virtually enumerated devices. */
			pip = mdi_pi_find(pptr->port_dip, NULL, ndi_addr);
			if (pip == NULL ||
			    ((cdip = mdi_pi_get_client(pip)) == NULL)) {
				*rval = ENXIO;
				goto out;
			}
		}
		break;

	default:
		*rval = ENOTTY;
		return (retval);
	}

	/* this ioctl is ours -- process it */

	retval = FC_SUCCESS;		/* just means we claim the ioctl */

	/* we assume it will be a success; else we'll set error value */
	*rval = 0;


	FCP_DTRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_8, 0,
	    "ioctl: claiming this one");

	/* handle ioctls now */
	switch (cmd) {
	case DEVCTL_DEVICE_GETSTATE:
		ASSERT(cdip != NULL);
		ASSERT(dcp != NULL);
		if (ndi_dc_return_dev_state(cdip, dcp) != NDI_SUCCESS) {
			*rval = EFAULT;
		}
		break;

	case DEVCTL_DEVICE_REMOVE:
	case DEVCTL_DEVICE_OFFLINE: {
		int			flag = 0;
		int			lcount;
		int			tcount;
		struct fcp_pkt	*head = NULL;
		struct fcp_lun	*plun;
		child_info_t		*cip = CIP(cdip);
		int			all = 1;
		struct fcp_lun	*tplun;
		struct fcp_tgt	*ptgt;

		ASSERT(pptr != NULL);
		ASSERT(cdip != NULL);

		mutex_enter(&pptr->port_mutex);
		if (pip != NULL) {
			cip = CIP(pip);
		}
		if ((plun = fcp_get_lun_from_cip(pptr, cip)) == NULL) {
			mutex_exit(&pptr->port_mutex);
			*rval = ENXIO;
			break;
		}

		head = fcp_scan_commands(plun);
		if (head != NULL) {
			fcp_abort_commands(head, LUN_PORT);
		}
		lcount = pptr->port_link_cnt;
		tcount = plun->lun_tgt->tgt_change_cnt;
		mutex_exit(&pptr->port_mutex);

		if (cmd == DEVCTL_DEVICE_REMOVE) {
			flag = NDI_DEVI_REMOVE;
		}

		if (is_mpxio) {
			mdi_devi_exit(pptr->port_dip, circ);
		} else {
			ndi_devi_exit(pptr->port_dip, circ);
		}
		devi_entered = 0;

		*rval = fcp_pass_to_hp_and_wait(pptr, plun, cip,
		    FCP_OFFLINE, lcount, tcount, flag);

		if (*rval != NDI_SUCCESS) {
			*rval = (*rval == NDI_BUSY) ? EBUSY : EIO;
			break;
		}

		fcp_update_offline_flags(plun);

		ptgt = plun->lun_tgt;
		mutex_enter(&ptgt->tgt_mutex);
		for (tplun = ptgt->tgt_lun; tplun != NULL; tplun =
		    tplun->lun_next) {
			mutex_enter(&tplun->lun_mutex);
			if (!(tplun->lun_state & FCP_LUN_OFFLINE)) {
				all = 0;
			}
			mutex_exit(&tplun->lun_mutex);
		}

		if (all) {
			ptgt->tgt_node_state = FCP_TGT_NODE_NONE;
			/*
			 * The user is unconfiguring/offlining the device.
			 * If fabric and the auto configuration is set
			 * then make sure the user is the only one who
			 * can reconfigure the device.
			 */
			if (FC_TOP_EXTERNAL(pptr->port_topology) &&
			    fcp_enable_auto_configuration) {
				ptgt->tgt_manual_config_only = 1;
			}
		}
		mutex_exit(&ptgt->tgt_mutex);
		break;
	}

	case DEVCTL_DEVICE_ONLINE: {
		int			lcount;
		int			tcount;
		struct fcp_lun	*plun;
		child_info_t		*cip = CIP(cdip);

		ASSERT(cdip != NULL);
		ASSERT(pptr != NULL);

		mutex_enter(&pptr->port_mutex);
		if (pip != NULL) {
			cip = CIP(pip);
		}
		if ((plun = fcp_get_lun_from_cip(pptr, cip)) == NULL) {
			mutex_exit(&pptr->port_mutex);
			*rval = ENXIO;
			break;
		}
		lcount = pptr->port_link_cnt;
		tcount = plun->lun_tgt->tgt_change_cnt;
		mutex_exit(&pptr->port_mutex);

		/*
		 * The FCP_LUN_ONLINING flag is used in fcp_scsi_start()
		 * to allow the device attach to occur when the device is
		 * FCP_LUN_OFFLINE (so we don't reject the INQUIRY command
		 * from the scsi_probe()).
		 */
		mutex_enter(&LUN_TGT->tgt_mutex);
		plun->lun_state |= FCP_LUN_ONLINING;
		mutex_exit(&LUN_TGT->tgt_mutex);

		if (is_mpxio) {
			mdi_devi_exit(pptr->port_dip, circ);
		} else {
			ndi_devi_exit(pptr->port_dip, circ);
		}
		devi_entered = 0;

		*rval = fcp_pass_to_hp_and_wait(pptr, plun, cip,
		    FCP_ONLINE, lcount, tcount, 0);

		if (*rval != NDI_SUCCESS) {
			/* Reset the FCP_LUN_ONLINING bit */
			mutex_enter(&LUN_TGT->tgt_mutex);
			plun->lun_state &= ~FCP_LUN_ONLINING;
			mutex_exit(&LUN_TGT->tgt_mutex);
			*rval = EIO;
			break;
		}
		mutex_enter(&LUN_TGT->tgt_mutex);
		plun->lun_state &= ~(FCP_LUN_OFFLINE | FCP_LUN_BUSY |
		    FCP_LUN_ONLINING);
		mutex_exit(&LUN_TGT->tgt_mutex);
		break;
	}

	case DEVCTL_BUS_DEV_CREATE: {
		uchar_t			*bytes = NULL;
		uint_t			nbytes;
		struct fcp_tgt		*ptgt = NULL;
		struct fcp_lun		*plun = NULL;
		dev_info_t		*useless_dip = NULL;

		*rval = ndi_dc_devi_create(dcp, pptr->port_dip,
		    DEVCTL_CONSTRUCT, &useless_dip);
		if (*rval != 0 || useless_dip == NULL) {
			break;
		}

		if ((ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, useless_dip,
		    DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, PORT_WWN_PROP, &bytes,
		    &nbytes) != DDI_PROP_SUCCESS) || nbytes != FC_WWN_SIZE) {
			*rval = EINVAL;
			(void) ndi_devi_free(useless_dip);
			if (bytes != NULL) {
				ddi_prop_free(bytes);
			}
			break;
		}

		*rval = fcp_create_on_demand(pptr, bytes);
		if (*rval == 0) {
			mutex_enter(&pptr->port_mutex);
			ptgt = fcp_lookup_target(pptr, (uchar_t *)bytes);
			if (ptgt) {
				/*
				 * We now have a pointer to the target that
				 * was created. Lets point to the first LUN on
				 * this new target.
				 */
				mutex_enter(&ptgt->tgt_mutex);

				plun = ptgt->tgt_lun;
				/*
				 * There may be stale/offline LUN entries on
				 * this list (this is by design) and so we have
				 * to make sure we point to the first online
				 * LUN
				 */
				while (plun &&
				    plun->lun_state & FCP_LUN_OFFLINE) {
					plun = plun->lun_next;
				}

				mutex_exit(&ptgt->tgt_mutex);
			}
			mutex_exit(&pptr->port_mutex);
		}

		if (*rval == 0 && ptgt && plun) {
			mutex_enter(&plun->lun_mutex);
			/*
			 * Allow up to fcp_lun_ready_retry seconds to
			 * configure all the luns behind the target.
			 *
			 * The intent here is to allow targets with long
			 * reboot/reset-recovery times to become available
			 * while limiting the maximum wait time for an
			 * unresponsive target.
			 */
			end_time = ddi_get_lbolt() +
			    SEC_TO_TICK(fcp_lun_ready_retry);

			while (ddi_get_lbolt() < end_time) {
				retval = FC_SUCCESS;

				/*
				 * The new ndi interfaces for on-demand creation
				 * are inflexible, Do some more work to pass on
				 * a path name of some LUN (design is broken !)
				 */
				if (plun->lun_cip) {
					if (plun->lun_mpxio == 0) {
						cdip = DIP(plun->lun_cip);
					} else {
						cdip = mdi_pi_get_client(
						    PIP(plun->lun_cip));
					}
					if (cdip == NULL) {
						*rval = ENXIO;
						break;
					}

					if (!i_ddi_devi_attached(cdip)) {
						mutex_exit(&plun->lun_mutex);
						delay(drv_usectohz(1000000));
						mutex_enter(&plun->lun_mutex);
					} else {
						/*
						 * This Lun is ready, lets
						 * check the next one.
						 */
						mutex_exit(&plun->lun_mutex);
						plun = plun->lun_next;
						while (plun && (plun->lun_state
						    & FCP_LUN_OFFLINE)) {
							plun = plun->lun_next;
						}
						if (!plun) {
							break;
						}
						mutex_enter(&plun->lun_mutex);
					}
				} else {
					/*
					 * lun_cip field for a valid lun
					 * should never be NULL. Fail the
					 * command.
					 */
					*rval = ENXIO;
					break;
				}
			}
			if (plun) {
				mutex_exit(&plun->lun_mutex);
			} else {
				char devnm[MAXNAMELEN];
				int nmlen;

				nmlen = snprintf(devnm, MAXNAMELEN, "%s@%s",
				    ddi_node_name(cdip),
				    ddi_get_name_addr(cdip));

				if (copyout(&devnm, dcp->cpyout_buf, nmlen) !=
				    0) {
					*rval = EFAULT;
				}
			}
		} else {
			int	i;
			char	buf[25];

			for (i = 0; i < FC_WWN_SIZE; i++) {
				(void) sprintf(&buf[i << 1], "%02x", bytes[i]);
			}

			fcp_log(CE_WARN, pptr->port_dip,
			    "!Failed to create nodes for pwwn=%s; error=%x",
			    buf, *rval);
		}

		(void) ndi_devi_free(useless_dip);
		ddi_prop_free(bytes);
		break;
	}

	case DEVCTL_DEVICE_RESET: {
		struct fcp_lun	*plun;
		struct scsi_address	ap;
		child_info_t		*cip = CIP(cdip);

		ASSERT(cdip != NULL);
		ASSERT(pptr != NULL);
		mutex_enter(&pptr->port_mutex);
		if (pip != NULL) {
			cip = CIP(pip);
		}
		if ((plun = fcp_get_lun_from_cip(pptr, cip)) == NULL) {
			mutex_exit(&pptr->port_mutex);
			*rval = ENXIO;
			break;
		}
		mutex_exit(&pptr->port_mutex);

		mutex_enter(&plun->lun_tgt->tgt_mutex);
		if (!(plun->lun_state & FCP_SCSI_LUN_TGT_INIT)) {
			mutex_exit(&plun->lun_tgt->tgt_mutex);
			*rval = ENXIO;
			break;
		}
		ap.a_hba_tran = plun->lun_tran;
		ASSERT(pptr->port_tran != NULL);
		mutex_exit(&plun->lun_tgt->tgt_mutex);

		/*
		 * There is a chance lun_tran is NULL at this point. So check
		 * for it. If it is NULL, it basically means that the tgt has
		 * been freed. So, just return a "No such device or address"
		 * error.
		 */
		if (ap.a_hba_tran == NULL) {
			*rval = ENXIO;
			break;
		}

		/*
		 * set up ap so that fcp_reset can figure out
		 * which target to reset
		 */
		if (fcp_scsi_reset(&ap, RESET_TARGET) == FALSE) {
			*rval = EIO;
		}
		break;
	}

	case DEVCTL_BUS_GETSTATE:
		ASSERT(dcp != NULL);
		ASSERT(pptr != NULL);
		ASSERT(pptr->port_dip != NULL);
		if (ndi_dc_return_bus_state(pptr->port_dip, dcp) !=
		    NDI_SUCCESS) {
			*rval = EFAULT;
		}
		break;

	case DEVCTL_BUS_QUIESCE:
	case DEVCTL_BUS_UNQUIESCE:
		*rval = ENOTSUP;
		break;

	case DEVCTL_BUS_RESET:
	case DEVCTL_BUS_RESETALL:
		ASSERT(pptr != NULL);
		(void) fcp_linkreset(pptr, NULL,  KM_SLEEP);
		break;

	default:
		ASSERT(dcp != NULL);
		*rval = ENOTTY;
		break;
	}

	/* all done -- clean up and return */
out:	if (devi_entered) {
		if (is_mpxio) {
			mdi_devi_exit(pptr->port_dip, circ);
		} else {
			ndi_devi_exit(pptr->port_dip, circ);
		}
	}

	if (dcp != NULL) {
		ndi_dc_freehdl(dcp);
	}

	return (retval);
}


/*ARGSUSED*/
static int
fcp_els_callback(opaque_t ulph, opaque_t port_handle, fc_unsol_buf_t *buf,
    uint32_t claimed)
{
	uchar_t			r_ctl;
	uchar_t			ls_code;
	struct fcp_port	*pptr;

	if ((pptr = fcp_get_port(port_handle)) == NULL || claimed) {
		return (FC_UNCLAIMED);
	}

	mutex_enter(&pptr->port_mutex);
	if (pptr->port_state & (FCP_STATE_DETACHING |
	    FCP_STATE_SUSPENDED | FCP_STATE_POWER_DOWN)) {
		mutex_exit(&pptr->port_mutex);
		return (FC_UNCLAIMED);
	}
	mutex_exit(&pptr->port_mutex);

	r_ctl = buf->ub_frame.r_ctl;

	switch (r_ctl & R_CTL_ROUTING) {
	case R_CTL_EXTENDED_SVC:
		if (r_ctl == R_CTL_ELS_REQ) {
			ls_code = buf->ub_buffer[0];

			switch (ls_code) {
			case LA_ELS_PRLI:
				/*
				 * We really don't care if something fails.
				 * If the PRLI was not sent out, then the
				 * other end will time it out.
				 */
				if (fcp_unsol_prli(pptr, buf) == FC_SUCCESS) {
					return (FC_SUCCESS);
				}
				return (FC_UNCLAIMED);
				/* NOTREACHED */

			default:
				break;
			}
		}
		/* FALLTHROUGH */

	default:
		return (FC_UNCLAIMED);
	}
}


/*ARGSUSED*/
static int
fcp_data_callback(opaque_t ulph, opaque_t port_handle, fc_unsol_buf_t *buf,
    uint32_t claimed)
{
	return (FC_UNCLAIMED);
}

/*
 *     Function: fcp_statec_callback
 *
 *  Description: The purpose of this function is to handle a port state change.
 *		 It is called from fp/fctl and, in a few instances, internally.
 *
 *     Argument: ulph		fp/fctl port handle
 *		 port_handle	fcp_port structure
 *		 port_state	Physical state of the port
 *		 port_top	Topology
 *		 *devlist	Pointer to the first entry of a table
 *				containing the remote ports that can be
 *				reached.
 *		 dev_cnt	Number of entries pointed by devlist.
 *		 port_sid	Port ID of the local port.
 *
 * Return Value: None
 */
/*ARGSUSED*/
static void
fcp_statec_callback(opaque_t ulph, opaque_t port_handle,
    uint32_t port_state, uint32_t port_top, fc_portmap_t *devlist,
    uint32_t dev_cnt, uint32_t port_sid)
{
	uint32_t		link_count;
	int			map_len = 0;
	struct fcp_port	*pptr;
	fcp_map_tag_t		*map_tag = NULL;

	if ((pptr = fcp_get_port(port_handle)) == NULL) {
		fcp_log(CE_WARN, NULL, "!Invalid port handle in callback");
		return;			/* nothing to work with! */
	}

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_2, 0,
	    "fcp_statec_callback: port state/dev_cnt/top ="
	    "%d/%d/%d", FC_PORT_STATE_MASK(port_state),
	    dev_cnt, port_top);

	mutex_enter(&pptr->port_mutex);

	/*
	 * If a thread is in detach, don't do anything.
	 */
	if (pptr->port_state & (FCP_STATE_DETACHING |
	    FCP_STATE_SUSPENDED | FCP_STATE_POWER_DOWN)) {
		mutex_exit(&pptr->port_mutex);
		return;
	}

	/*
	 * First thing we do is set the FCP_STATE_IN_CB_DEVC flag so that if
	 * init_pkt is called, it knows whether or not the target's status
	 * (or pd) might be changing.
	 */

	if (FC_PORT_STATE_MASK(port_state) == FC_STATE_DEVICE_CHANGE) {
		pptr->port_state |= FCP_STATE_IN_CB_DEVC;
	}

	/*
	 * the transport doesn't allocate or probe unless being
	 * asked to by either the applications or ULPs
	 *
	 * in cases where the port is OFFLINE at the time of port
	 * attach callback and the link comes ONLINE later, for
	 * easier automatic node creation (i.e. without you having to
	 * go out and run the utility to perform LOGINs) the
	 * following conditional is helpful
	 */
	pptr->port_phys_state = port_state;

	if (dev_cnt) {
		mutex_exit(&pptr->port_mutex);

		map_len = sizeof (*map_tag) * dev_cnt;
		map_tag = kmem_alloc(map_len, KM_NOSLEEP);
		if (map_tag == NULL) {
			fcp_log(CE_WARN, pptr->port_dip,
			    "!fcp%d: failed to allocate for map tags; "
			    " state change will not be processed",
			    pptr->port_instance);

			mutex_enter(&pptr->port_mutex);
			pptr->port_state &= ~FCP_STATE_IN_CB_DEVC;
			mutex_exit(&pptr->port_mutex);

			return;
		}

		mutex_enter(&pptr->port_mutex);
	}

	if (pptr->port_id != port_sid) {
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_3, 0,
		    "fcp: Port S_ID=0x%x => 0x%x", pptr->port_id,
		    port_sid);
		/*
		 * The local port changed ID. It is the first time a port ID
		 * is assigned or something drastic happened.  We might have
		 * been unplugged and replugged on another loop or fabric port
		 * or somebody grabbed the AL_PA we had or somebody rezoned
		 * the fabric we were plugged into.
		 */
		pptr->port_id = port_sid;
	}

	switch (FC_PORT_STATE_MASK(port_state)) {
	case FC_STATE_OFFLINE:
	case FC_STATE_RESET_REQUESTED:
		/*
		 * link has gone from online to offline -- just update the
		 * state of this port to BUSY and MARKed to go offline
		 */
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_3, 0,
		    "link went offline");
		if ((pptr->port_state & FCP_STATE_OFFLINE) && dev_cnt) {
			/*
			 * We were offline a while ago and this one
			 * seems to indicate that the loop has gone
			 * dead forever.
			 */
			pptr->port_tmp_cnt += dev_cnt;
			pptr->port_state &= ~FCP_STATE_OFFLINE;
			pptr->port_state |= FCP_STATE_INIT;
			link_count = pptr->port_link_cnt;
			fcp_handle_devices(pptr, devlist, dev_cnt,
			    link_count, map_tag, FCP_CAUSE_LINK_DOWN);
		} else {
			pptr->port_link_cnt++;
			ASSERT(!(pptr->port_state & FCP_STATE_SUSPENDED));
			fcp_update_state(pptr, (FCP_LUN_BUSY |
			    FCP_LUN_MARK), FCP_CAUSE_LINK_DOWN);
			if (pptr->port_mpxio) {
				fcp_update_mpxio_path_verifybusy(pptr);
			}
			pptr->port_state |= FCP_STATE_OFFLINE;
			pptr->port_state &=
			    ~(FCP_STATE_ONLINING | FCP_STATE_ONLINE);
			pptr->port_tmp_cnt = 0;
		}
		mutex_exit(&pptr->port_mutex);
		break;

	case FC_STATE_ONLINE:
	case FC_STATE_LIP:
	case FC_STATE_LIP_LBIT_SET:
		/*
		 * link has gone from offline to online
		 */
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_3, 0,
		    "link went online");

		pptr->port_link_cnt++;

		while (pptr->port_ipkt_cnt) {
			mutex_exit(&pptr->port_mutex);
			delay(drv_usectohz(1000000));
			mutex_enter(&pptr->port_mutex);
		}

		pptr->port_topology = port_top;

		/*
		 * The state of the targets and luns accessible through this
		 * port is updated.
		 */
		fcp_update_state(pptr, FCP_LUN_BUSY | FCP_LUN_MARK,
		    FCP_CAUSE_LINK_CHANGE);

		pptr->port_state &= ~(FCP_STATE_INIT | FCP_STATE_OFFLINE);
		pptr->port_state |= FCP_STATE_ONLINING;
		pptr->port_tmp_cnt = dev_cnt;
		link_count = pptr->port_link_cnt;

		pptr->port_deadline = fcp_watchdog_time +
		    FCP_ICMD_DEADLINE;

		if (!dev_cnt) {
			/*
			 * We go directly to the online state if no remote
			 * ports were discovered.
			 */
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_3, 0,
			    "No remote ports discovered");

			pptr->port_state &= ~FCP_STATE_ONLINING;
			pptr->port_state |= FCP_STATE_ONLINE;
		}

		switch (port_top) {
		case FC_TOP_FABRIC:
		case FC_TOP_PUBLIC_LOOP:
		case FC_TOP_PRIVATE_LOOP:
		case FC_TOP_PT_PT:

			if (pptr->port_state & FCP_STATE_NS_REG_FAILED) {
				fcp_retry_ns_registry(pptr, port_sid);
			}

			fcp_handle_devices(pptr, devlist, dev_cnt, link_count,
			    map_tag, FCP_CAUSE_LINK_CHANGE);
			break;

		default:
			/*
			 * We got here because we were provided with an unknown
			 * topology.
			 */
			if (pptr->port_state & FCP_STATE_NS_REG_FAILED) {
				pptr->port_state &= ~FCP_STATE_NS_REG_FAILED;
			}

			pptr->port_tmp_cnt -= dev_cnt;
			fcp_log(CE_WARN, pptr->port_dip,
			    "!unknown/unsupported topology (0x%x)", port_top);
			break;
		}
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_3, 0,
		    "Notify ssd of the reset to reinstate the reservations");

		scsi_hba_reset_notify_callback(&pptr->port_mutex,
		    &pptr->port_reset_notify_listf);

		mutex_exit(&pptr->port_mutex);

		break;

	case FC_STATE_RESET:
		ASSERT(pptr->port_state & FCP_STATE_OFFLINE);
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_3, 0,
		    "RESET state, waiting for Offline/Online state_cb");
		mutex_exit(&pptr->port_mutex);
		break;

	case FC_STATE_DEVICE_CHANGE:
		/*
		 * We come here when an application has requested
		 * Dynamic node creation/deletion in Fabric connectivity.
		 */
		if (pptr->port_state & (FCP_STATE_OFFLINE |
		    FCP_STATE_INIT)) {
			/*
			 * This case can happen when the FCTL is in the
			 * process of giving us on online and the host on
			 * the other side issues a PLOGI/PLOGO. Ideally
			 * the state changes should be serialized unless
			 * they are opposite (online-offline).
			 * The transport will give us a final state change
			 * so we can ignore this for the time being.
			 */
			pptr->port_state &= ~FCP_STATE_IN_CB_DEVC;
			mutex_exit(&pptr->port_mutex);
			break;
		}

		if (pptr->port_state & FCP_STATE_NS_REG_FAILED) {
			fcp_retry_ns_registry(pptr, port_sid);
		}

		/*
		 * Extend the deadline under steady state conditions
		 * to provide more time for the device-change-commands
		 */
		if (!pptr->port_ipkt_cnt) {
			pptr->port_deadline = fcp_watchdog_time +
			    FCP_ICMD_DEADLINE;
		}

		/*
		 * There is another race condition here, where if we were
		 * in ONLINEING state and a devices in the map logs out,
		 * fp will give another state change as DEVICE_CHANGE
		 * and OLD. This will result in that target being offlined.
		 * The pd_handle is freed. If from the first statec callback
		 * we were going to fire a PLOGI/PRLI, the system will
		 * panic in fc_ulp_transport with invalid pd_handle.
		 * The fix is to check for the link_cnt before issuing
		 * any command down.
		 */
		fcp_update_targets(pptr, devlist, dev_cnt,
		    FCP_LUN_BUSY | FCP_LUN_MARK, FCP_CAUSE_TGT_CHANGE);

		link_count = pptr->port_link_cnt;

		fcp_handle_devices(pptr, devlist, dev_cnt,
		    link_count, map_tag, FCP_CAUSE_TGT_CHANGE);

		pptr->port_state &= ~FCP_STATE_IN_CB_DEVC;

		mutex_exit(&pptr->port_mutex);
		break;

	case FC_STATE_TARGET_PORT_RESET:
		if (pptr->port_state & FCP_STATE_NS_REG_FAILED) {
			fcp_retry_ns_registry(pptr, port_sid);
		}

		/* Do nothing else */
		mutex_exit(&pptr->port_mutex);
		break;

	default:
		fcp_log(CE_WARN, pptr->port_dip,
		    "!Invalid state change=0x%x", port_state);
		mutex_exit(&pptr->port_mutex);
		break;
	}

	if (map_tag) {
		kmem_free(map_tag, map_len);
	}
}

/*
 *     Function: fcp_handle_devices
 *
 *  Description: This function updates the devices currently known by
 *		 walking the list provided by the caller.  The list passed
 *		 by the caller is supposed to be the list of reachable
 *		 devices.
 *
 *     Argument: *pptr		Fcp port structure.
 *		 *devlist	Pointer to the first entry of a table
 *				containing the remote ports that can be
 *				reached.
 *		 dev_cnt	Number of entries pointed by devlist.
 *		 link_cnt	Link state count.
 *		 *map_tag	Array of fcp_map_tag_t structures.
 *		 cause		What caused this function to be called.
 *
 * Return Value: None
 *
 *	  Notes: The pptr->port_mutex must be held.
 */
static void
fcp_handle_devices(struct fcp_port *pptr, fc_portmap_t devlist[],
    uint32_t dev_cnt, int link_cnt, fcp_map_tag_t *map_tag, int cause)
{
	int			i;
	int			check_finish_init = 0;
	fc_portmap_t		*map_entry;
	struct fcp_tgt	*ptgt = NULL;

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_3, 0,
	    "fcp_handle_devices: called for %d dev(s)", dev_cnt);

	if (dev_cnt) {
		ASSERT(map_tag != NULL);
	}

	/*
	 * The following code goes through the list of remote ports that are
	 * accessible through this (pptr) local port (The list walked is the
	 * one provided by the caller which is the list of the remote ports
	 * currently reachable).  It checks if any of them was already
	 * known by looking for the corresponding target structure based on
	 * the world wide name.	 If a target is part of the list it is tagged
	 * (ptgt->tgt_aux_state = FCP_TGT_TAGGED).
	 *
	 * Old comment
	 * -----------
	 * Before we drop port mutex; we MUST get the tags updated; This
	 * two step process is somewhat slow, but more reliable.
	 */
	for (i = 0; (i < dev_cnt) && (pptr->port_link_cnt == link_cnt); i++) {
		map_entry = &(devlist[i]);

		/*
		 * get ptr to this map entry in our port's
		 * list (if any)
		 */
		ptgt = fcp_lookup_target(pptr,
		    (uchar_t *)&(map_entry->map_pwwn));

		if (ptgt) {
			map_tag[i] = ptgt->tgt_change_cnt;
			if (cause == FCP_CAUSE_LINK_CHANGE) {
				ptgt->tgt_aux_state = FCP_TGT_TAGGED;
			}
		}
	}

	/*
	 * At this point we know which devices of the new list were already
	 * known (The field tgt_aux_state of the target structure has been
	 * set to FCP_TGT_TAGGED).
	 *
	 * The following code goes through the list of targets currently known
	 * by the local port (the list is actually a hashing table).  If a
	 * target is found and is not tagged, it means the target cannot
	 * be reached anymore through the local port (pptr).  It is offlined.
	 * The offlining only occurs if the cause is FCP_CAUSE_LINK_CHANGE.
	 */
	for (i = 0; i < FCP_NUM_HASH; i++) {
		for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL;
		    ptgt = ptgt->tgt_next) {
			mutex_enter(&ptgt->tgt_mutex);
			if ((ptgt->tgt_aux_state != FCP_TGT_TAGGED) &&
			    (cause == FCP_CAUSE_LINK_CHANGE) &&
			    !(ptgt->tgt_state & FCP_TGT_OFFLINE)) {
				fcp_offline_target_now(pptr, ptgt,
				    link_cnt, ptgt->tgt_change_cnt, 0);
			}
			mutex_exit(&ptgt->tgt_mutex);
		}
	}

	/*
	 * At this point, the devices that were known but cannot be reached
	 * anymore, have most likely been offlined.
	 *
	 * The following section of code seems to go through the list of
	 * remote ports that can now be reached.  For every single one it
	 * checks if it is already known or if it is a new port.
	 */
	for (i = 0; (i < dev_cnt) && (pptr->port_link_cnt == link_cnt); i++) {

		if (check_finish_init) {
			ASSERT(i > 0);
			(void) fcp_call_finish_init_held(pptr, ptgt, link_cnt,
			    map_tag[i - 1], cause);
			check_finish_init = 0;
		}

		/* get a pointer to this map entry */
		map_entry = &(devlist[i]);

		/*
		 * Check for the duplicate map entry flag. If we have marked
		 * this entry as a duplicate we skip it since the correct
		 * (perhaps even same) state change will be encountered
		 * later in the list.
		 */
		if (map_entry->map_flags & PORT_DEVICE_DUPLICATE_MAP_ENTRY) {
			continue;
		}

		/* get ptr to this map entry in our port's list (if any) */
		ptgt = fcp_lookup_target(pptr,
		    (uchar_t *)&(map_entry->map_pwwn));

		if (ptgt) {
			/*
			 * This device was already known.  The field
			 * tgt_aux_state is reset (was probably set to
			 * FCP_TGT_TAGGED previously in this routine).
			 */
			ptgt->tgt_aux_state = 0;
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_3, 0,
			    "handle_devices: map did/state/type/flags = "
			    "0x%x/0x%x/0x%x/0x%x, tgt_d_id=0x%x, "
			    "tgt_state=%d",
			    map_entry->map_did.port_id, map_entry->map_state,
			    map_entry->map_type, map_entry->map_flags,
			    ptgt->tgt_d_id, ptgt->tgt_state);
		}

		if (map_entry->map_type == PORT_DEVICE_OLD ||
		    map_entry->map_type == PORT_DEVICE_NEW ||
		    map_entry->map_type == PORT_DEVICE_CHANGED) {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_2, 0,
			    "map_type=%x, did = %x",
			    map_entry->map_type,
			    map_entry->map_did.port_id);
		}

		switch (map_entry->map_type) {
		case PORT_DEVICE_NOCHANGE:
		case PORT_DEVICE_USER_CREATE:
		case PORT_DEVICE_USER_LOGIN:
		case PORT_DEVICE_NEW:
			FCP_TGT_TRACE(ptgt, map_tag[i], FCP_TGT_TRACE_1);

			if (fcp_handle_mapflags(pptr, ptgt, map_entry,
			    link_cnt, (ptgt) ? map_tag[i] : 0,
			    cause) == TRUE) {

				FCP_TGT_TRACE(ptgt, map_tag[i],
				    FCP_TGT_TRACE_2);
				check_finish_init++;
			}
			break;

		case PORT_DEVICE_OLD:
			if (ptgt != NULL) {
				FCP_TGT_TRACE(ptgt, map_tag[i],
				    FCP_TGT_TRACE_3);

				mutex_enter(&ptgt->tgt_mutex);
				if (!(ptgt->tgt_state & FCP_TGT_OFFLINE)) {
					/*
					 * Must do an in-line wait for I/Os
					 * to get drained
					 */
					mutex_exit(&ptgt->tgt_mutex);
					mutex_exit(&pptr->port_mutex);

					mutex_enter(&ptgt->tgt_mutex);
					while (ptgt->tgt_ipkt_cnt ||
					    fcp_outstanding_lun_cmds(ptgt)
					    == FC_SUCCESS) {
						mutex_exit(&ptgt->tgt_mutex);
						delay(drv_usectohz(1000000));
						mutex_enter(&ptgt->tgt_mutex);
					}
					mutex_exit(&ptgt->tgt_mutex);

					mutex_enter(&pptr->port_mutex);
					mutex_enter(&ptgt->tgt_mutex);

					(void) fcp_offline_target(pptr, ptgt,
					    link_cnt, map_tag[i], 0, 0);
				}
				mutex_exit(&ptgt->tgt_mutex);
			}
			check_finish_init++;
			break;

		case PORT_DEVICE_USER_DELETE:
		case PORT_DEVICE_USER_LOGOUT:
			if (ptgt != NULL) {
				FCP_TGT_TRACE(ptgt, map_tag[i],
				    FCP_TGT_TRACE_4);

				mutex_enter(&ptgt->tgt_mutex);
				if (!(ptgt->tgt_state & FCP_TGT_OFFLINE)) {
					(void) fcp_offline_target(pptr, ptgt,
					    link_cnt, map_tag[i], 1, 0);
				}
				mutex_exit(&ptgt->tgt_mutex);
			}
			check_finish_init++;
			break;

		case PORT_DEVICE_CHANGED:
			if (ptgt != NULL) {
				FCP_TGT_TRACE(ptgt, map_tag[i],
				    FCP_TGT_TRACE_5);

				if (fcp_device_changed(pptr, ptgt,
				    map_entry, link_cnt, map_tag[i],
				    cause) == TRUE) {
					check_finish_init++;
				}
			} else {
				if (fcp_handle_mapflags(pptr, ptgt,
				    map_entry, link_cnt, 0, cause) == TRUE) {
					check_finish_init++;
				}
			}
			break;

		default:
			fcp_log(CE_WARN, pptr->port_dip,
			    "!Invalid map_type=0x%x", map_entry->map_type);
			check_finish_init++;
			break;
		}
	}

	if (check_finish_init && pptr->port_link_cnt == link_cnt) {
		ASSERT(i > 0);
		(void) fcp_call_finish_init_held(pptr, ptgt, link_cnt,
		    map_tag[i-1], cause);
	} else if (dev_cnt == 0 && pptr->port_link_cnt == link_cnt) {
		fcp_offline_all(pptr, link_cnt, cause);
	}
}

/*
 *     Function: fcp_handle_mapflags
 *
 *  Description: This function creates a target structure if the ptgt passed
 *		 is NULL.  It also kicks off the PLOGI if we are not logged
 *		 into the target yet or the PRLI if we are logged into the
 *		 target already.  The rest of the treatment is done in the
 *		 callbacks of the PLOGI or PRLI.
 *
 *     Argument: *pptr		FCP Port structure.
 *		 *ptgt		Target structure.
 *		 *map_entry	Array of fc_portmap_t structures.
 *		 link_cnt	Link state count.
 *		 tgt_cnt	Target state count.
 *		 cause		What caused this function to be called.
 *
 * Return Value: TRUE	Failed
 *		 FALSE	Succeeded
 *
 *	  Notes: pptr->port_mutex must be owned.
 */
static int
fcp_handle_mapflags(struct fcp_port	*pptr, struct fcp_tgt	*ptgt,
    fc_portmap_t *map_entry, int link_cnt, int tgt_cnt, int cause)
{
	int			lcount;
	int			tcount;
	int			ret = TRUE;
	int			alloc;
	struct fcp_ipkt	*icmd;
	struct fcp_lun	*pseq_lun = NULL;
	uchar_t			opcode;
	int			valid_ptgt_was_passed = FALSE;

	ASSERT(mutex_owned(&pptr->port_mutex));

	/*
	 * This case is possible where the FCTL has come up and done discovery
	 * before FCP was loaded and attached. FCTL would have discovered the
	 * devices and later the ULP came online. In this case ULP's would get
	 * PORT_DEVICE_NOCHANGE but target would be NULL.
	 */
	if (ptgt == NULL) {
		/* don't already have a target */
		mutex_exit(&pptr->port_mutex);
		ptgt = fcp_alloc_tgt(pptr, map_entry, link_cnt);
		mutex_enter(&pptr->port_mutex);

		if (ptgt == NULL) {
			fcp_log(CE_WARN, pptr->port_dip,
			    "!FC target allocation failed");
			return (ret);
		}
		mutex_enter(&ptgt->tgt_mutex);
		ptgt->tgt_statec_cause = cause;
		ptgt->tgt_tmp_cnt = 1;
		mutex_exit(&ptgt->tgt_mutex);
	} else {
		valid_ptgt_was_passed = TRUE;
	}

	/*
	 * Copy in the target parameters
	 */
	mutex_enter(&ptgt->tgt_mutex);
	ptgt->tgt_d_id = map_entry->map_did.port_id;
	ptgt->tgt_hard_addr = map_entry->map_hard_addr.hard_addr;
	ptgt->tgt_pd_handle = map_entry->map_pd;
	ptgt->tgt_fca_dev = NULL;

	/* Copy port and node WWNs */
	bcopy(&map_entry->map_nwwn, &ptgt->tgt_node_wwn.raw_wwn[0],
	    FC_WWN_SIZE);
	bcopy(&map_entry->map_pwwn, &ptgt->tgt_port_wwn.raw_wwn[0],
	    FC_WWN_SIZE);

	if (!(map_entry->map_flags & PORT_DEVICE_NO_SKIP_DEVICE_DISCOVERY) &&
	    (map_entry->map_type == PORT_DEVICE_NOCHANGE) &&
	    (map_entry->map_state == PORT_DEVICE_LOGGED_IN) &&
	    valid_ptgt_was_passed) {
		/*
		 * determine if there are any tape LUNs on this target
		 */
		for (pseq_lun = ptgt->tgt_lun;
		    pseq_lun != NULL;
		    pseq_lun = pseq_lun->lun_next) {
			if ((pseq_lun->lun_type == DTYPE_SEQUENTIAL) &&
			    !(pseq_lun->lun_state & FCP_LUN_OFFLINE)) {
				fcp_update_tgt_state(ptgt, FCP_RESET,
				    FCP_LUN_MARK);
				mutex_exit(&ptgt->tgt_mutex);
				return (ret);
			}
		}
	}

	/*
	 * If ptgt was NULL when this function was entered, then tgt_node_state
	 * was never specifically initialized but zeroed out which means
	 * FCP_TGT_NODE_NONE.
	 */
	switch (ptgt->tgt_node_state) {
	case FCP_TGT_NODE_NONE:
	case FCP_TGT_NODE_ON_DEMAND:
		if (FC_TOP_EXTERNAL(pptr->port_topology) &&
		    !fcp_enable_auto_configuration &&
		    map_entry->map_type != PORT_DEVICE_USER_CREATE) {
			ptgt->tgt_node_state = FCP_TGT_NODE_ON_DEMAND;
		} else if (FC_TOP_EXTERNAL(pptr->port_topology) &&
		    fcp_enable_auto_configuration &&
		    (ptgt->tgt_manual_config_only == 1) &&
		    map_entry->map_type != PORT_DEVICE_USER_CREATE) {
			/*
			 * If auto configuration is set and
			 * the tgt_manual_config_only flag is set then
			 * we only want the user to be able to change
			 * the state through create_on_demand.
			 */
			ptgt->tgt_node_state = FCP_TGT_NODE_ON_DEMAND;
		} else {
			ptgt->tgt_node_state = FCP_TGT_NODE_NONE;
		}
		break;

	case FCP_TGT_NODE_PRESENT:
		break;
	}
	/*
	 * If we are booting from a fabric device, make sure we
	 * mark the node state appropriately for this target to be
	 * enumerated
	 */
	if (FC_TOP_EXTERNAL(pptr->port_topology) && pptr->port_boot_wwn[0]) {
		if (bcmp((caddr_t)pptr->port_boot_wwn,
		    (caddr_t)&ptgt->tgt_port_wwn.raw_wwn[0],
		    sizeof (ptgt->tgt_port_wwn)) == 0) {
			ptgt->tgt_node_state = FCP_TGT_NODE_NONE;
		}
	}
	mutex_exit(&ptgt->tgt_mutex);

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_3, 0,
	    "map_pd=%p, map_type=%x, did = %x, ulp_rscn_count=0x%x",
	    map_entry->map_pd, map_entry->map_type, map_entry->map_did.port_id,
	    map_entry->map_rscn_info.ulp_rscn_count);

	mutex_enter(&ptgt->tgt_mutex);

	/*
	 * Reset target OFFLINE state and mark the target BUSY
	 */
	ptgt->tgt_state &= ~FCP_TGT_OFFLINE;
	ptgt->tgt_state |= (FCP_TGT_BUSY | FCP_TGT_MARK);

	tcount = tgt_cnt ? tgt_cnt : ptgt->tgt_change_cnt;
	lcount = link_cnt;

	mutex_exit(&ptgt->tgt_mutex);
	mutex_exit(&pptr->port_mutex);

	/*
	 * if we are already logged in, then we do a PRLI, else
	 * we do a PLOGI first (to get logged in)
	 *
	 * We will not check if we are the PLOGI initiator
	 */
	opcode = (map_entry->map_state == PORT_DEVICE_LOGGED_IN &&
	    map_entry->map_pd != NULL) ? LA_ELS_PRLI : LA_ELS_PLOGI;

	alloc = FCP_MAX(sizeof (la_els_logi_t), sizeof (la_els_prli_t));

	icmd = fcp_icmd_alloc(pptr, ptgt, alloc, alloc, 0, 0, lcount, tcount,
	    cause, map_entry->map_rscn_info.ulp_rscn_count);

	if (icmd == NULL) {
		FCP_TGT_TRACE(ptgt, tgt_cnt, FCP_TGT_TRACE_29);
		/*
		 * We've exited port_mutex before calling fcp_icmd_alloc,
		 * we need to make sure we reacquire it before returning.
		 */
		mutex_enter(&pptr->port_mutex);
		return (FALSE);
	}

	/* TRUE is only returned while target is intended skipped */
	ret = FALSE;
	/* discover info about this target */
	if ((fcp_send_els(pptr, ptgt, icmd, opcode,
	    lcount, tcount, cause)) == DDI_SUCCESS) {
		FCP_TGT_TRACE(ptgt, tgt_cnt, FCP_TGT_TRACE_9);
	} else {
		fcp_icmd_free(pptr, icmd);
		ret = TRUE;
	}
	mutex_enter(&pptr->port_mutex);

	return (ret);
}

/*
 *     Function: fcp_send_els
 *
 *  Description: Sends an ELS to the target specified by the caller.  Supports
 *		 PLOGI and PRLI.
 *
 *     Argument: *pptr		Fcp port.
 *		 *ptgt		Target to send the ELS to.
 *		 *icmd		Internal packet
 *		 opcode		ELS opcode
 *		 lcount		Link state change counter
 *		 tcount		Target state change counter
 *		 cause		What caused the call
 *
 * Return Value: DDI_SUCCESS
 *		 Others
 */
static int
fcp_send_els(struct fcp_port *pptr, struct fcp_tgt *ptgt,
    struct fcp_ipkt *icmd, uchar_t opcode, int lcount, int tcount, int cause)
{
	fc_packet_t		*fpkt;
	fc_frame_hdr_t		*hp;
	int			internal = 0;
	int			alloc;
	int			cmd_len;
	int			resp_len;
	int			res = DDI_FAILURE; /* default result */
	int			rval = DDI_FAILURE;

	ASSERT(opcode == LA_ELS_PLOGI || opcode == LA_ELS_PRLI);
	ASSERT(ptgt->tgt_port == pptr);

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_5, 0,
	    "fcp_send_els: d_id=0x%x ELS 0x%x (%s)", ptgt->tgt_d_id, opcode,
	    (opcode == LA_ELS_PLOGI) ? "PLOGI" : "PRLI");

	if (opcode == LA_ELS_PLOGI) {
		cmd_len = sizeof (la_els_logi_t);
		resp_len = sizeof (la_els_logi_t);
	} else {
		ASSERT(opcode == LA_ELS_PRLI);
		cmd_len = sizeof (la_els_prli_t);
		resp_len = sizeof (la_els_prli_t);
	}

	if (icmd == NULL) {
		alloc = FCP_MAX(sizeof (la_els_logi_t),
		    sizeof (la_els_prli_t));
		icmd = fcp_icmd_alloc(pptr, ptgt, alloc, alloc, 0, 0,
		    lcount, tcount, cause, FC_INVALID_RSCN_COUNT);
		if (icmd == NULL) {
			FCP_TGT_TRACE(ptgt, tcount, FCP_TGT_TRACE_10);
			return (res);
		}
		internal++;
	}
	fpkt = icmd->ipkt_fpkt;

	fpkt->pkt_cmdlen = cmd_len;
	fpkt->pkt_rsplen = resp_len;
	fpkt->pkt_datalen = 0;
	icmd->ipkt_retries = 0;

	/* fill in fpkt info */
	fpkt->pkt_tran_flags = FC_TRAN_CLASS3 | FC_TRAN_INTR;
	fpkt->pkt_tran_type = FC_PKT_EXCHANGE;
	fpkt->pkt_timeout = FCP_ELS_TIMEOUT;

	/* get ptr to frame hdr in fpkt */
	hp = &fpkt->pkt_cmd_fhdr;

	/*
	 * fill in frame hdr
	 */
	hp->r_ctl = R_CTL_ELS_REQ;
	hp->s_id = pptr->port_id;	/* source ID */
	hp->d_id = ptgt->tgt_d_id;	/* dest ID */
	hp->type = FC_TYPE_EXTENDED_LS;
	hp->f_ctl = F_CTL_SEQ_INITIATIVE | F_CTL_FIRST_SEQ;
	hp->seq_id = 0;
	hp->rsvd = 0;
	hp->df_ctl  = 0;
	hp->seq_cnt = 0;
	hp->ox_id = 0xffff;		/* i.e. none */
	hp->rx_id = 0xffff;		/* i.e. none */
	hp->ro = 0;

	/*
	 * at this point we have a filled in cmd pkt
	 *
	 * fill in the respective info, then use the transport to send
	 * the packet
	 *
	 * for a PLOGI call fc_ulp_login(), and
	 * for a PRLI call fc_ulp_issue_els()
	 */
	switch (opcode) {
	case LA_ELS_PLOGI: {
		struct la_els_logi logi;

		bzero(&logi, sizeof (struct la_els_logi));

		hp = &fpkt->pkt_cmd_fhdr;
		hp->r_ctl = R_CTL_ELS_REQ;
		logi.ls_code.ls_code = LA_ELS_PLOGI;
		logi.ls_code.mbz = 0;

		FCP_CP_OUT((uint8_t *)&logi, fpkt->pkt_cmd,
		    fpkt->pkt_cmd_acc, sizeof (struct la_els_logi));

		icmd->ipkt_opcode = LA_ELS_PLOGI;

		mutex_enter(&pptr->port_mutex);
		if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) {

			mutex_exit(&pptr->port_mutex);

			rval = fc_ulp_login(pptr->port_fp_handle, &fpkt, 1);
			if (rval == FC_SUCCESS) {
				res = DDI_SUCCESS;
				break;
			}

			FCP_TGT_TRACE(ptgt, tcount, FCP_TGT_TRACE_11);

			res = fcp_handle_ipkt_errors(pptr, ptgt, icmd,
			    rval, "PLOGI");
		} else {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_5, 0,
			    "fcp_send_els1: state change occured"
			    " for D_ID=0x%x", ptgt->tgt_d_id);
			mutex_exit(&pptr->port_mutex);
			FCP_TGT_TRACE(ptgt, tcount, FCP_TGT_TRACE_12);
		}
		break;
	}

	case LA_ELS_PRLI: {
		struct la_els_prli	prli;
		struct fcp_prli		*fprli;

		bzero(&prli, sizeof (struct la_els_prli));

		hp = &fpkt->pkt_cmd_fhdr;
		hp->r_ctl = R_CTL_ELS_REQ;

		/* fill in PRLI cmd ELS fields */
		prli.ls_code = LA_ELS_PRLI;
		prli.page_length = 0x10;	/* huh? */
		prli.payload_length = sizeof (struct la_els_prli);

		icmd->ipkt_opcode = LA_ELS_PRLI;

		/* get ptr to PRLI service params */
		fprli = (struct fcp_prli *)prli.service_params;

		/* fill in service params */
		fprli->type = 0x08;
		fprli->resvd1 = 0;
		fprli->orig_process_assoc_valid = 0;
		fprli->resp_process_assoc_valid = 0;
		fprli->establish_image_pair = 1;
		fprli->resvd2 = 0;
		fprli->resvd3 = 0;
		fprli->obsolete_1 = 0;
		fprli->obsolete_2 = 0;
		fprli->data_overlay_allowed = 0;
		fprli->initiator_fn = 1;
		fprli->confirmed_compl_allowed = 1;

		if (fc_ulp_is_name_present("ltct") == FC_SUCCESS) {
			fprli->target_fn = 1;
		} else {
			fprli->target_fn = 0;
		}

		fprli->retry = 1;
		fprli->read_xfer_rdy_disabled = 1;
		fprli->write_xfer_rdy_disabled = 0;

		FCP_CP_OUT((uint8_t *)&prli, fpkt->pkt_cmd,
		    fpkt->pkt_cmd_acc, sizeof (struct la_els_prli));

		/* issue the PRLI request */

		mutex_enter(&pptr->port_mutex);
		if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) {

			mutex_exit(&pptr->port_mutex);

			rval = fc_ulp_issue_els(pptr->port_fp_handle, fpkt);
			if (rval == FC_SUCCESS) {
				res = DDI_SUCCESS;
				break;
			}

			FCP_TGT_TRACE(ptgt, tcount, FCP_TGT_TRACE_13);

			res = fcp_handle_ipkt_errors(pptr, ptgt, icmd,
			    rval, "PRLI");
		} else {
			mutex_exit(&pptr->port_mutex);
			FCP_TGT_TRACE(ptgt, tcount, FCP_TGT_TRACE_14);
		}
		break;
	}

	default:
		fcp_log(CE_WARN, NULL, "!invalid ELS opcode=0x%x", opcode);
		break;
	}

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_5, 0,
	    "fcp_send_els: returning %d", res);

	if (res != DDI_SUCCESS) {
		if (internal) {
			fcp_icmd_free(pptr, icmd);
		}
	}

	return (res);
}


/*
 * called internally update the state of all of the tgts and each LUN
 * for this port (i.e. each target  known to be attached to this port)
 * if they are not already offline
 *
 * must be called with the port mutex owned
 *
 * acquires and releases the target mutexes for each target attached
 * to this port
 */
void
fcp_update_state(struct fcp_port *pptr, uint32_t state, int cause)
{
	int i;
	struct fcp_tgt *ptgt;

	ASSERT(mutex_owned(&pptr->port_mutex));

	for (i = 0; i < FCP_NUM_HASH; i++) {
		for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL;
		    ptgt = ptgt->tgt_next) {
			mutex_enter(&ptgt->tgt_mutex);
			fcp_update_tgt_state(ptgt, FCP_SET, state);
			ptgt->tgt_change_cnt++;
			ptgt->tgt_statec_cause = cause;
			ptgt->tgt_tmp_cnt = 1;
			ptgt->tgt_done = 0;
			mutex_exit(&ptgt->tgt_mutex);
		}
	}
}


static void
fcp_offline_all(struct fcp_port *pptr, int lcount, int cause)
{
	int i;
	int ndevs;
	struct fcp_tgt *ptgt;

	ASSERT(mutex_owned(&pptr->port_mutex));

	for (ndevs = 0, i = 0; i < FCP_NUM_HASH; i++) {
		for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL;
		    ptgt = ptgt->tgt_next) {
			ndevs++;
		}
	}

	if (ndevs == 0) {
		return;
	}
	pptr->port_tmp_cnt = ndevs;

	for (i = 0; i < FCP_NUM_HASH; i++) {
		for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL;
		    ptgt = ptgt->tgt_next) {
			(void) fcp_call_finish_init_held(pptr, ptgt,
			    lcount, ptgt->tgt_change_cnt, cause);
		}
	}
}

/*
 *     Function: fcp_update_tgt_state
 *
 *  Description: This function updates the field tgt_state of a target.	 That
 *		 field is a bitmap and which bit can be set or reset
 *		 individually.	The action applied to the target state is also
 *		 applied to all the LUNs belonging to the target (provided the
 *		 LUN is not offline).  A side effect of applying the state
 *		 modification to the target and the LUNs is the field tgt_trace
 *		 of the target and lun_trace of the LUNs is set to zero.
 *
 *
 *     Argument: *ptgt	Target structure.
 *		 flag	Flag indication what action to apply (set/reset).
 *		 state	State bits to update.
 *
 * Return Value: None
 *
 *	Context: Interrupt, Kernel or User context.
 *		 The mutex of the target (ptgt->tgt_mutex) must be owned when
 *		 calling this function.
 */
void
fcp_update_tgt_state(struct fcp_tgt *ptgt, int flag, uint32_t state)
{
	struct fcp_lun *plun;

	ASSERT(mutex_owned(&ptgt->tgt_mutex));

	if (!(ptgt->tgt_state & FCP_TGT_OFFLINE)) {
		/* The target is not offline. */
		if (flag == FCP_SET) {
			ptgt->tgt_state |= state;
			ptgt->tgt_trace = 0;
		} else {
			ptgt->tgt_state &= ~state;
		}

		for (plun = ptgt->tgt_lun; plun != NULL;
		    plun = plun->lun_next) {
			if (!(plun->lun_state & FCP_LUN_OFFLINE)) {
				/* The LUN is not offline. */
				if (flag == FCP_SET) {
					plun->lun_state |= state;
					plun->lun_trace = 0;
				} else {
					plun->lun_state &= ~state;
				}
			}
		}
	}
}

/*
 *     Function: fcp_update_tgt_state
 *
 *  Description: This function updates the field lun_state of a LUN.  That
 *		 field is a bitmap and which bit can be set or reset
 *		 individually.
 *
 *     Argument: *plun	LUN structure.
 *		 flag	Flag indication what action to apply (set/reset).
 *		 state	State bits to update.
 *
 * Return Value: None
 *
 *	Context: Interrupt, Kernel or User context.
 *		 The mutex of the target (ptgt->tgt_mutex) must be owned when
 *		 calling this function.
 */
void
fcp_update_lun_state(struct fcp_lun *plun, int flag, uint32_t state)
{
	struct fcp_tgt	*ptgt = plun->lun_tgt;

	ASSERT(mutex_owned(&ptgt->tgt_mutex));

	if (!(plun->lun_state & FCP_TGT_OFFLINE)) {
		if (flag == FCP_SET) {
			plun->lun_state |= state;
		} else {
			plun->lun_state &= ~state;
		}
	}
}

/*
 *     Function: fcp_get_port
 *
 *  Description: This function returns the fcp_port structure from the opaque
 *		 handle passed by the caller.  That opaque handle is the handle
 *		 used by fp/fctl to identify a particular local port.  That
 *		 handle has been stored in the corresponding fcp_port
 *		 structure.  This function is going to walk the global list of
 *		 fcp_port structures till one has a port_fp_handle that matches
 *		 the handle passed by the caller.  This function enters the
 *		 mutex fcp_global_mutex while walking the global list and then
 *		 releases it.
 *
 *     Argument: port_handle	Opaque handle that fp/fctl uses to identify a
 *				particular port.
 *
 * Return Value: NULL		Not found.
 *		 Not NULL	Pointer to the fcp_port structure.
 *
 *	Context: Interrupt, Kernel or User context.
 */
static struct fcp_port *
fcp_get_port(opaque_t port_handle)
{
	struct fcp_port *pptr;

	ASSERT(port_handle != NULL);

	mutex_enter(&fcp_global_mutex);
	for (pptr = fcp_port_head; pptr != NULL; pptr = pptr->port_next) {
		if (pptr->port_fp_handle == port_handle) {
			break;
		}
	}
	mutex_exit(&fcp_global_mutex);

	return (pptr);
}


static void
fcp_unsol_callback(fc_packet_t *fpkt)
{
	struct fcp_ipkt *icmd = (struct fcp_ipkt *)fpkt->pkt_ulp_private;
	struct fcp_port *pptr = icmd->ipkt_port;

	if (fpkt->pkt_state != FC_PKT_SUCCESS) {
		caddr_t state, reason, action, expln;

		(void) fc_ulp_pkt_error(fpkt, &state, &reason,
		    &action, &expln);

		fcp_log(CE_WARN, pptr->port_dip,
		    "!couldn't post response to unsolicited request: "
		    " state=%s reason=%s rx_id=%x ox_id=%x",
		    state, reason, fpkt->pkt_cmd_fhdr.ox_id,
		    fpkt->pkt_cmd_fhdr.rx_id);
	}
	fcp_icmd_free(pptr, icmd);
}


/*
 * Perform general purpose preparation of a response to an unsolicited request
 */
static void
fcp_unsol_resp_init(fc_packet_t *pkt, fc_unsol_buf_t *buf,
    uchar_t r_ctl, uchar_t type)
{
	pkt->pkt_cmd_fhdr.r_ctl = r_ctl;
	pkt->pkt_cmd_fhdr.d_id = buf->ub_frame.s_id;
	pkt->pkt_cmd_fhdr.s_id = buf->ub_frame.d_id;
	pkt->pkt_cmd_fhdr.type = type;
	pkt->pkt_cmd_fhdr.f_ctl = F_CTL_LAST_SEQ | F_CTL_XCHG_CONTEXT;
	pkt->pkt_cmd_fhdr.seq_id = buf->ub_frame.seq_id;
	pkt->pkt_cmd_fhdr.df_ctl  = buf->ub_frame.df_ctl;
	pkt->pkt_cmd_fhdr.seq_cnt = buf->ub_frame.seq_cnt;
	pkt->pkt_cmd_fhdr.ox_id = buf->ub_frame.ox_id;
	pkt->pkt_cmd_fhdr.rx_id = buf->ub_frame.rx_id;
	pkt->pkt_cmd_fhdr.ro = 0;
	pkt->pkt_cmd_fhdr.rsvd = 0;
	pkt->pkt_comp = fcp_unsol_callback;
	pkt->pkt_pd = NULL;
}


/*ARGSUSED*/
static int
fcp_unsol_prli(struct fcp_port *pptr, fc_unsol_buf_t *buf)
{
	fc_packet_t		*fpkt;
	struct la_els_prli	prli;
	struct fcp_prli		*fprli;
	struct fcp_ipkt	*icmd;
	struct la_els_prli	*from;
	struct fcp_prli		*orig;
	struct fcp_tgt	*ptgt;
	int			tcount = 0;
	int			lcount;

	from = (struct la_els_prli *)buf->ub_buffer;
	orig = (struct fcp_prli *)from->service_params;

	if ((ptgt = fcp_get_target_by_did(pptr, buf->ub_frame.s_id)) !=
	    NULL) {
		mutex_enter(&ptgt->tgt_mutex);
		tcount = ptgt->tgt_change_cnt;
		mutex_exit(&ptgt->tgt_mutex);
	}
	mutex_enter(&pptr->port_mutex);
	lcount = pptr->port_link_cnt;
	mutex_exit(&pptr->port_mutex);

	if ((icmd = fcp_icmd_alloc(pptr, ptgt, sizeof (la_els_prli_t),
	    sizeof (la_els_prli_t), 0, 0, lcount, tcount, 0,
	    FC_INVALID_RSCN_COUNT)) == NULL) {
		return (FC_FAILURE);
	}
	fpkt = icmd->ipkt_fpkt;
	fpkt->pkt_tran_flags = FC_TRAN_CLASS3 | FC_TRAN_INTR;
	fpkt->pkt_tran_type = FC_PKT_OUTBOUND;
	fpkt->pkt_timeout = FCP_ELS_TIMEOUT;
	fpkt->pkt_cmdlen = sizeof (la_els_prli_t);
	fpkt->pkt_rsplen = 0;
	fpkt->pkt_datalen = 0;

	icmd->ipkt_opcode = LA_ELS_PRLI;

	bzero(&prli, sizeof (struct la_els_prli));
	fprli = (struct fcp_prli *)prli.service_params;
	prli.ls_code = LA_ELS_ACC;
	prli.page_length = 0x10;
	prli.payload_length = sizeof (struct la_els_prli);

	/* fill in service params */
	fprli->type = 0x08;
	fprli->resvd1 = 0;
	fprli->orig_process_assoc_valid = orig->orig_process_assoc_valid;
	fprli->orig_process_associator = orig->orig_process_associator;
	fprli->resp_process_assoc_valid = 0;
	fprli->establish_image_pair = 1;
	fprli->resvd2 = 0;
	fprli->resvd3 = 0;
	fprli->obsolete_1 = 0;
	fprli->obsolete_2 = 0;
	fprli->data_overlay_allowed = 0;
	fprli->initiator_fn = 1;
	fprli->confirmed_compl_allowed = 1;

	if (fc_ulp_is_name_present("ltct") == FC_SUCCESS) {
		fprli->target_fn = 1;
	} else {
		fprli->target_fn = 0;
	}

	fprli->retry = 1;
	fprli->read_xfer_rdy_disabled = 1;
	fprli->write_xfer_rdy_disabled = 0;

	/* save the unsol prli payload first */
	FCP_CP_OUT((uint8_t *)from, fpkt->pkt_resp,
	    fpkt->pkt_resp_acc, sizeof (struct la_els_prli));

	FCP_CP_OUT((uint8_t *)&prli, fpkt->pkt_cmd,
	    fpkt->pkt_cmd_acc, sizeof (struct la_els_prli));

	fcp_unsol_resp_init(fpkt, buf, R_CTL_ELS_RSP, FC_TYPE_EXTENDED_LS);

	mutex_enter(&pptr->port_mutex);
	if (!FCP_LINK_STATE_CHANGED(pptr, icmd)) {
		int rval;
		mutex_exit(&pptr->port_mutex);

		if ((rval = fc_ulp_issue_els(pptr->port_fp_handle, fpkt)) !=
		    FC_SUCCESS) {
			if (rval == FC_STATEC_BUSY || rval == FC_OFFLINE) {
				fcp_queue_ipkt(pptr, fpkt);
				return (FC_SUCCESS);
			}
			/* Let it timeout */
			fcp_icmd_free(pptr, icmd);
			return (FC_FAILURE);
		}
	} else {
		mutex_exit(&pptr->port_mutex);
		fcp_icmd_free(pptr, icmd);
		return (FC_FAILURE);
	}

	(void) fc_ulp_ubrelease(pptr->port_fp_handle, 1, &buf->ub_token);

	return (FC_SUCCESS);
}

/*
 *     Function: fcp_icmd_alloc
 *
 *  Description: This function allocated a fcp_ipkt structure.	The pkt_comp
 *		 field is initialized to fcp_icmd_callback.  Sometimes it is
 *		 modified by the caller (such as fcp_send_scsi).  The
 *		 structure is also tied to the state of the line and of the
 *		 target at a particular time.  That link is established by
 *		 setting the fields ipkt_link_cnt and ipkt_change_cnt to lcount
 *		 and tcount which came respectively from pptr->link_cnt and
 *		 ptgt->tgt_change_cnt.
 *
 *     Argument: *pptr		Fcp port.
 *		 *ptgt		Target (destination of the command).
 *		 cmd_len	Length of the command.
 *		 resp_len	Length of the expected response.
 *		 data_len	Length of the data.
 *		 nodma		Indicates weither the command and response.
 *				will be transfer through DMA or not.
 *		 lcount		Link state change counter.
 *		 tcount		Target state change counter.
 *		 cause		Reason that lead to this call.
 *
 * Return Value: NULL		Failed.
 *		 Not NULL	Internal packet address.
 */
static struct fcp_ipkt *
fcp_icmd_alloc(struct fcp_port *pptr, struct fcp_tgt *ptgt, int cmd_len,
    int resp_len, int data_len, int nodma, int lcount, int tcount, int cause,
    uint32_t rscn_count)
{
	int			dma_setup = 0;
	fc_packet_t		*fpkt;
	struct fcp_ipkt	*icmd = NULL;

	icmd = kmem_zalloc(sizeof (struct fcp_ipkt) +
	    pptr->port_dmacookie_sz + pptr->port_priv_pkt_len,
	    KM_NOSLEEP);
	if (icmd == NULL) {
		fcp_log(CE_WARN, pptr->port_dip,
		    "!internal packet allocation failed");
		return (NULL);
	}

	/*
	 * initialize the allocated packet
	 */
	icmd->ipkt_nodma = nodma;
	icmd->ipkt_next = icmd->ipkt_prev = NULL;
	icmd->ipkt_lun = NULL;

	icmd->ipkt_link_cnt = lcount;
	icmd->ipkt_change_cnt = tcount;
	icmd->ipkt_cause = cause;

	mutex_enter(&pptr->port_mutex);
	icmd->ipkt_port = pptr;
	mutex_exit(&pptr->port_mutex);

	/* keep track of amt of data to be sent in pkt */
	icmd->ipkt_cmdlen = cmd_len;
	icmd->ipkt_resplen = resp_len;
	icmd->ipkt_datalen = data_len;

	/* set up pkt's ptr to the fc_packet_t struct, just after the ipkt */
	icmd->ipkt_fpkt = (fc_packet_t *)(&icmd->ipkt_fc_packet);

	/* set pkt's private ptr to point to cmd pkt */
	icmd->ipkt_fpkt->pkt_ulp_private = (opaque_t)icmd;

	/* set FCA private ptr to memory just beyond */
	icmd->ipkt_fpkt->pkt_fca_private = (opaque_t)
	    ((char *)icmd + sizeof (struct fcp_ipkt) +
	    pptr->port_dmacookie_sz);

	/* get ptr to fpkt substruct and fill it in */
	fpkt = icmd->ipkt_fpkt;
	fpkt->pkt_data_cookie = (ddi_dma_cookie_t *)((caddr_t)icmd +
	    sizeof (struct fcp_ipkt));

	if (ptgt != NULL) {
		icmd->ipkt_tgt = ptgt;
		fpkt->pkt_fca_device = ptgt->tgt_fca_dev;
	}

	fpkt->pkt_comp = fcp_icmd_callback;
	fpkt->pkt_tran_flags = (FC_TRAN_CLASS3 | FC_TRAN_INTR);
	fpkt->pkt_cmdlen = cmd_len;
	fpkt->pkt_rsplen = resp_len;
	fpkt->pkt_datalen = data_len;

	/*
	 * The pkt_ulp_rscn_infop (aka pkt_ulp_rsvd1) field is used to pass the
	 * rscn_count as fcp knows down to the transport. If a valid count was
	 * passed into this function, we allocate memory to actually pass down
	 * this info.
	 *
	 * BTW, if the kmem_zalloc fails, we won't try too hard. This will
	 * basically mean that fcp will not be able to help transport
	 * distinguish if a new RSCN has come after fcp was last informed about
	 * it. In such cases, it might lead to the problem mentioned in CR/bug #
	 * 5068068 where the device might end up going offline in case of RSCN
	 * storms.
	 */
	fpkt->pkt_ulp_rscn_infop = NULL;
	if (rscn_count != FC_INVALID_RSCN_COUNT) {
		fpkt->pkt_ulp_rscn_infop = kmem_zalloc(
		    sizeof (fc_ulp_rscn_info_t), KM_NOSLEEP);
		if (fpkt->pkt_ulp_rscn_infop == NULL) {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_6, 0,
			    "Failed to alloc memory to pass rscn info");
		}
	}

	if (fpkt->pkt_ulp_rscn_infop != NULL) {
		fc_ulp_rscn_info_t	*rscnp;

		rscnp = (fc_ulp_rscn_info_t *)fpkt->pkt_ulp_rscn_infop;
		rscnp->ulp_rscn_count = rscn_count;
	}

	if (fcp_alloc_dma(pptr, icmd, nodma, KM_NOSLEEP) != FC_SUCCESS) {
		goto fail;
	}
	dma_setup++;

	/*
	 * Must hold target mutex across setting of pkt_pd and call to
	 * fc_ulp_init_packet to ensure the handle to the target doesn't go
	 * away while we're not looking.
	 */
	if (ptgt != NULL) {
		mutex_enter(&ptgt->tgt_mutex);
		fpkt->pkt_pd = ptgt->tgt_pd_handle;

		/* ask transport to do its initialization on this pkt */
		if (fc_ulp_init_packet(pptr->port_fp_handle, fpkt, KM_NOSLEEP)
		    != FC_SUCCESS) {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_6, 0,
			    "fc_ulp_init_packet failed");
			mutex_exit(&ptgt->tgt_mutex);
			goto fail;
		}
		mutex_exit(&ptgt->tgt_mutex);
	} else {
		if (fc_ulp_init_packet(pptr->port_fp_handle, fpkt, KM_NOSLEEP)
		    != FC_SUCCESS) {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_6, 0,
			    "fc_ulp_init_packet failed");
			goto fail;
		}
	}

	mutex_enter(&pptr->port_mutex);
	if (pptr->port_state & (FCP_STATE_DETACHING |
	    FCP_STATE_SUSPENDED | FCP_STATE_POWER_DOWN)) {
		int rval;

		mutex_exit(&pptr->port_mutex);

		rval = fc_ulp_uninit_packet(pptr->port_fp_handle, fpkt);
		ASSERT(rval == FC_SUCCESS);

		goto fail;
	}

	if (ptgt != NULL) {
		mutex_enter(&ptgt->tgt_mutex);
		ptgt->tgt_ipkt_cnt++;
		mutex_exit(&ptgt->tgt_mutex);
	}

	pptr->port_ipkt_cnt++;

	mutex_exit(&pptr->port_mutex);

	return (icmd);

fail:
	if (fpkt->pkt_ulp_rscn_infop != NULL) {
		kmem_free(fpkt->pkt_ulp_rscn_infop,
		    sizeof (fc_ulp_rscn_info_t));
		fpkt->pkt_ulp_rscn_infop = NULL;
	}

	if (dma_setup) {
		fcp_free_dma(pptr, icmd);
	}
	kmem_free(icmd, sizeof (struct fcp_ipkt) + pptr->port_priv_pkt_len +
	    (size_t)pptr->port_dmacookie_sz);

	return (NULL);
}

/*
 *     Function: fcp_icmd_free
 *
 *  Description: Frees the internal command passed by the caller.
 *
 *     Argument: *pptr		Fcp port.
 *		 *icmd		Internal packet to free.
 *
 * Return Value: None
 */
static void
fcp_icmd_free(struct fcp_port *pptr, struct fcp_ipkt *icmd)
{
	struct fcp_tgt	*ptgt = icmd->ipkt_tgt;

	/* Let the underlying layers do their cleanup. */
	(void) fc_ulp_uninit_packet(pptr->port_fp_handle,
	    icmd->ipkt_fpkt);

	if (icmd->ipkt_fpkt->pkt_ulp_rscn_infop) {
		kmem_free(icmd->ipkt_fpkt->pkt_ulp_rscn_infop,
		    sizeof (fc_ulp_rscn_info_t));
	}

	fcp_free_dma(pptr, icmd);

	kmem_free(icmd, sizeof (struct fcp_ipkt) + pptr->port_priv_pkt_len +
	    (size_t)pptr->port_dmacookie_sz);

	mutex_enter(&pptr->port_mutex);

	if (ptgt) {
		mutex_enter(&ptgt->tgt_mutex);
		ptgt->tgt_ipkt_cnt--;
		mutex_exit(&ptgt->tgt_mutex);
	}

	pptr->port_ipkt_cnt--;
	mutex_exit(&pptr->port_mutex);
}

/*
 *     Function: fcp_alloc_dma
 *
 *  Description: Allocated the DMA resources required for the internal
 *		 packet.
 *
 *     Argument: *pptr	FCP port.
 *		 *icmd	Internal FCP packet.
 *		 nodma	Indicates if the Cmd and Resp will be DMAed.
 *		 flags	Allocation flags (Sleep or NoSleep).
 *
 * Return Value: FC_SUCCESS
 *		 FC_NOMEM
 */
static int
fcp_alloc_dma(struct fcp_port *pptr, struct fcp_ipkt *icmd,
    int nodma, int flags)
{
	int		rval;
	size_t		real_size;
	uint_t		ccount;
	int		bound = 0;
	int		cmd_resp = 0;
	fc_packet_t	*fpkt;
	ddi_dma_cookie_t	pkt_data_cookie;
	ddi_dma_cookie_t	*cp;
	uint32_t		cnt;

	fpkt = &icmd->ipkt_fc_packet;

	ASSERT(fpkt->pkt_cmd_dma == NULL && fpkt->pkt_data_dma == NULL &&
	    fpkt->pkt_resp_dma == NULL);

	icmd->ipkt_nodma = nodma;

	if (nodma) {
		fpkt->pkt_cmd = kmem_zalloc(fpkt->pkt_cmdlen, flags);
		if (fpkt->pkt_cmd == NULL) {
			goto fail;
		}

		fpkt->pkt_resp = kmem_zalloc(fpkt->pkt_rsplen, flags);
		if (fpkt->pkt_resp == NULL) {
			goto fail;
		}
	} else {
		ASSERT(fpkt->pkt_cmdlen && fpkt->pkt_rsplen);

		rval = fcp_alloc_cmd_resp(pptr, fpkt, flags);
		if (rval == FC_FAILURE) {
			ASSERT(fpkt->pkt_cmd_dma == NULL &&
			    fpkt->pkt_resp_dma == NULL);
			goto fail;
		}
		cmd_resp++;
	}

	if (fpkt->pkt_datalen != 0) {
		/*
		 * set up DMA handle and memory for the data in this packet
		 */
		if (ddi_dma_alloc_handle(pptr->port_dip,
		    &pptr->port_data_dma_attr, DDI_DMA_DONTWAIT,
		    NULL, &fpkt->pkt_data_dma) != DDI_SUCCESS) {
			goto fail;
		}

		if (ddi_dma_mem_alloc(fpkt->pkt_data_dma, fpkt->pkt_datalen,
		    &pptr->port_dma_acc_attr, DDI_DMA_CONSISTENT,
		    DDI_DMA_DONTWAIT, NULL, &fpkt->pkt_data,
		    &real_size, &fpkt->pkt_data_acc) != DDI_SUCCESS) {
			goto fail;
		}

		/* was DMA mem size gotten < size asked for/needed ?? */
		if (real_size < fpkt->pkt_datalen) {
			goto fail;
		}

		/* bind DMA address and handle together */
		if (ddi_dma_addr_bind_handle(fpkt->pkt_data_dma,
		    NULL, fpkt->pkt_data, real_size, DDI_DMA_READ |
		    DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL,
		    &pkt_data_cookie, &ccount) != DDI_DMA_MAPPED) {
			goto fail;
		}
		bound++;

		if (ccount > pptr->port_data_dma_attr.dma_attr_sgllen) {
			goto fail;
		}

		fpkt->pkt_data_cookie_cnt = ccount;

		cp = fpkt->pkt_data_cookie;
		*cp = pkt_data_cookie;
		cp++;

		for (cnt = 1; cnt < ccount; cnt++, cp++) {
			ddi_dma_nextcookie(fpkt->pkt_data_dma,
			    &pkt_data_cookie);
			*cp = pkt_data_cookie;
		}

	}

	return (FC_SUCCESS);

fail:
	if (bound) {
		(void) ddi_dma_unbind_handle(fpkt->pkt_data_dma);
	}

	if (fpkt->pkt_data_dma) {
		if (fpkt->pkt_data) {
			ddi_dma_mem_free(&fpkt->pkt_data_acc);
		}
		ddi_dma_free_handle(&fpkt->pkt_data_dma);
	}

	if (nodma) {
		if (fpkt->pkt_cmd) {
			kmem_free(fpkt->pkt_cmd, fpkt->pkt_cmdlen);
		}
		if (fpkt->pkt_resp) {
			kmem_free(fpkt->pkt_resp, fpkt->pkt_rsplen);
		}
	} else {
		if (cmd_resp) {
			fcp_free_cmd_resp(pptr, fpkt);
		}
	}

	return (FC_NOMEM);
}


static void
fcp_free_dma(struct fcp_port *pptr, struct fcp_ipkt *icmd)
{
	fc_packet_t *fpkt = icmd->ipkt_fpkt;

	if (fpkt->pkt_data_dma) {
		(void) ddi_dma_unbind_handle(fpkt->pkt_data_dma);
		if (fpkt->pkt_data) {
			ddi_dma_mem_free(&fpkt->pkt_data_acc);
		}
		ddi_dma_free_handle(&fpkt->pkt_data_dma);
	}

	if (icmd->ipkt_nodma) {
		if (fpkt->pkt_cmd) {
			kmem_free(fpkt->pkt_cmd, icmd->ipkt_cmdlen);
		}
		if (fpkt->pkt_resp) {
			kmem_free(fpkt->pkt_resp, icmd->ipkt_resplen);
		}
	} else {
		ASSERT(fpkt->pkt_resp_dma != NULL && fpkt->pkt_cmd_dma != NULL);

		fcp_free_cmd_resp(pptr, fpkt);
	}
}

/*
 *     Function: fcp_lookup_target
 *
 *  Description: Finds a target given a WWN.
 *
 *     Argument: *pptr	FCP port.
 *		 *wwn	World Wide Name of the device to look for.
 *
 * Return Value: NULL		No target found
 *		 Not NULL	Target structure
 *
 *	Context: Interrupt context.
 *		 The mutex pptr->port_mutex must be owned.
 */
/* ARGSUSED */
static struct fcp_tgt *
fcp_lookup_target(struct fcp_port *pptr, uchar_t *wwn)
{
	int			hash;
	struct fcp_tgt	*ptgt;

	ASSERT(mutex_owned(&pptr->port_mutex));

	hash = FCP_HASH(wwn);

	for (ptgt = pptr->port_tgt_hash_table[hash]; ptgt != NULL;
	    ptgt = ptgt->tgt_next) {
		if (!(ptgt->tgt_state & FCP_TGT_ORPHAN) &&
		    bcmp((caddr_t)wwn, (caddr_t)&ptgt->tgt_port_wwn.raw_wwn[0],
		    sizeof (ptgt->tgt_port_wwn)) == 0) {
			break;
		}
	}

	return (ptgt);
}


/*
 * Find target structure given a port identifier
 */
static struct fcp_tgt *
fcp_get_target_by_did(struct fcp_port *pptr, uint32_t d_id)
{
	fc_portid_t		port_id;
	la_wwn_t		pwwn;
	struct fcp_tgt	*ptgt = NULL;

	port_id.priv_lilp_posit = 0;
	port_id.port_id = d_id;
	if (fc_ulp_get_pwwn_by_did(pptr->port_fp_handle, port_id,
	    &pwwn) == FC_SUCCESS) {
		mutex_enter(&pptr->port_mutex);
		ptgt = fcp_lookup_target(pptr, pwwn.raw_wwn);
		mutex_exit(&pptr->port_mutex);
	}

	return (ptgt);
}


/*
 * the packet completion callback routine for info cmd pkts
 *
 * this means fpkt pts to a response to either a PLOGI or a PRLI
 *
 * if there is an error an attempt is made to call a routine to resend
 * the command that failed
 */
static void
fcp_icmd_callback(fc_packet_t *fpkt)
{
	struct fcp_ipkt	*icmd;
	struct fcp_port	*pptr;
	struct fcp_tgt	*ptgt;
	struct la_els_prli	*prli;
	struct la_els_prli	prli_s;
	struct fcp_prli		*fprli;
	struct fcp_lun	*plun;
	int		free_pkt = 1;
	int		rval;
	ls_code_t	resp;
	uchar_t		prli_acc = 0;
	uint32_t	rscn_count = FC_INVALID_RSCN_COUNT;
	int		lun0_newalloc;

	icmd = (struct fcp_ipkt *)fpkt->pkt_ulp_private;

	/* get ptrs to the port and target structs for the cmd */
	pptr = icmd->ipkt_port;
	ptgt = icmd->ipkt_tgt;

	FCP_CP_IN(fpkt->pkt_resp, &resp, fpkt->pkt_resp_acc, sizeof (resp));

	if (icmd->ipkt_opcode == LA_ELS_PRLI) {
		FCP_CP_IN(fpkt->pkt_cmd, &prli_s, fpkt->pkt_cmd_acc,
		    sizeof (prli_s));
		prli_acc = (prli_s.ls_code == LA_ELS_ACC);
	}

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_2, 0,
	    "ELS (%x) callback state=0x%x reason=0x%x for %x",
	    icmd->ipkt_opcode, fpkt->pkt_state, fpkt->pkt_reason,
	    ptgt->tgt_d_id);

	if ((fpkt->pkt_state == FC_PKT_SUCCESS) &&
	    ((resp.ls_code == LA_ELS_ACC) || prli_acc)) {

		mutex_enter(&ptgt->tgt_mutex);
		if (ptgt->tgt_pd_handle == NULL) {
			/*
			 * in a fabric environment the port device handles
			 * get created only after successful LOGIN into the
			 * transport, so the transport makes this port
			 * device (pd) handle available in this packet, so
			 * save it now
			 */
			ASSERT(fpkt->pkt_pd != NULL);
			ptgt->tgt_pd_handle = fpkt->pkt_pd;
		}
		mutex_exit(&ptgt->tgt_mutex);

		/* which ELS cmd is this response for ?? */
		switch (icmd->ipkt_opcode) {
		case LA_ELS_PLOGI:
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_5, 0,
			    "PLOGI to d_id=0x%x succeeded, wwn=%08x%08x",
			    ptgt->tgt_d_id,
			    *((int *)&ptgt->tgt_port_wwn.raw_wwn[0]),
			    *((int *)&ptgt->tgt_port_wwn.raw_wwn[4]));

			FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt,
			    FCP_TGT_TRACE_15);

			/* Note that we are not allocating a new icmd */
			if (fcp_send_els(pptr, ptgt, icmd, LA_ELS_PRLI,
			    icmd->ipkt_link_cnt, icmd->ipkt_change_cnt,
			    icmd->ipkt_cause) != DDI_SUCCESS) {
				FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt,
				    FCP_TGT_TRACE_16);
				goto fail;
			}
			break;

		case LA_ELS_PRLI:
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_5, 0,
			    "PRLI to d_id=0x%x succeeded", ptgt->tgt_d_id);

			FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt,
			    FCP_TGT_TRACE_17);

			prli = &prli_s;

			FCP_CP_IN(fpkt->pkt_resp, prli, fpkt->pkt_resp_acc,
			    sizeof (prli_s));

			fprli = (struct fcp_prli *)prli->service_params;

			mutex_enter(&ptgt->tgt_mutex);
			ptgt->tgt_icap = fprli->initiator_fn;
			ptgt->tgt_tcap = fprli->target_fn;
			mutex_exit(&ptgt->tgt_mutex);

			if ((fprli->type != 0x08) || (fprli->target_fn != 1)) {
				/*
				 * this FCP device does not support target mode
				 */
				FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt,
				    FCP_TGT_TRACE_18);
				goto fail;
			}
			if (fprli->retry == 1) {
				fc_ulp_disable_relogin(pptr->port_fp_handle,
				    &ptgt->tgt_port_wwn);
			}

			/* target is no longer offline */
			mutex_enter(&pptr->port_mutex);
			mutex_enter(&ptgt->tgt_mutex);
			if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) {
				ptgt->tgt_state &= ~(FCP_TGT_OFFLINE |
				    FCP_TGT_MARK);
			} else {
				FCP_TRACE(fcp_logq, pptr->port_instbuf,
				    fcp_trace, FCP_BUF_LEVEL_2, 0,
				    "fcp_icmd_callback,1: state change "
				    " occured for D_ID=0x%x", ptgt->tgt_d_id);
				mutex_exit(&ptgt->tgt_mutex);
				mutex_exit(&pptr->port_mutex);
				goto fail;
			}
			mutex_exit(&ptgt->tgt_mutex);
			mutex_exit(&pptr->port_mutex);

			/*
			 * lun 0 should always respond to inquiry, so
			 * get the LUN struct for LUN 0
			 *
			 * Currently we deal with first level of addressing.
			 * If / when we start supporting 0x device types
			 * (DTYPE_ARRAY_CTRL, i.e. array controllers)
			 * this logic will need revisiting.
			 */
			lun0_newalloc = 0;
			if ((plun = fcp_get_lun(ptgt, 0)) == NULL) {
				/*
				 * no LUN struct for LUN 0 yet exists,
				 * so create one
				 */
				plun = fcp_alloc_lun(ptgt);
				if (plun == NULL) {
					fcp_log(CE_WARN, pptr->port_dip,
					    "!Failed to allocate lun 0 for"
					    " D_ID=%x", ptgt->tgt_d_id);
					goto fail;
				}
				lun0_newalloc = 1;
			}

			/* fill in LUN info */
			mutex_enter(&ptgt->tgt_mutex);
			/*
			 * consider lun 0 as device not connected if it is
			 * offlined or newly allocated
			 */
			if ((plun->lun_state & FCP_LUN_OFFLINE) ||
			    lun0_newalloc) {
				plun->lun_state |= FCP_LUN_DEVICE_NOT_CONNECTED;
			}
			plun->lun_state |= (FCP_LUN_BUSY | FCP_LUN_MARK);
			plun->lun_state &= ~FCP_LUN_OFFLINE;
			ptgt->tgt_lun_cnt = 1;
			ptgt->tgt_report_lun_cnt = 0;
			mutex_exit(&ptgt->tgt_mutex);

			/* Retrieve the rscn count (if a valid one exists) */
			if (icmd->ipkt_fpkt->pkt_ulp_rscn_infop != NULL) {
				rscn_count = ((fc_ulp_rscn_info_t *)
				    (icmd->ipkt_fpkt->pkt_ulp_rscn_infop))
				    ->ulp_rscn_count;
			} else {
				rscn_count = FC_INVALID_RSCN_COUNT;
			}

			/* send Report Lun request to target */
			if (fcp_send_scsi(plun, SCMD_REPORT_LUN,
			    sizeof (struct fcp_reportlun_resp),
			    icmd->ipkt_link_cnt, icmd->ipkt_change_cnt,
			    icmd->ipkt_cause, rscn_count) != DDI_SUCCESS) {
				mutex_enter(&pptr->port_mutex);
				if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) {
					fcp_log(CE_WARN, pptr->port_dip,
					    "!Failed to send REPORT LUN to"
					    "  D_ID=%x", ptgt->tgt_d_id);
				} else {
					FCP_TRACE(fcp_logq,
					    pptr->port_instbuf, fcp_trace,
					    FCP_BUF_LEVEL_5, 0,
					    "fcp_icmd_callback,2:state change"
					    " occured for D_ID=0x%x",
					    ptgt->tgt_d_id);
				}
				mutex_exit(&pptr->port_mutex);

				FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt,
				    FCP_TGT_TRACE_19);

				goto fail;
			} else {
				free_pkt = 0;
				fcp_icmd_free(pptr, icmd);
			}
			break;

		default:
			fcp_log(CE_WARN, pptr->port_dip,
			    "!fcp_icmd_callback Invalid opcode");
			goto fail;
		}

		return;
	}


	/*
	 * Other PLOGI failures are not retried as the
	 * transport does it already
	 */
	if (icmd->ipkt_opcode != LA_ELS_PLOGI) {
		if (fcp_is_retryable(icmd) &&
		    icmd->ipkt_retries++ < FCP_MAX_RETRIES) {

			if (FCP_MUST_RETRY(fpkt)) {
				fcp_queue_ipkt(pptr, fpkt);
				return;
			}

			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_2, 0,
			    "ELS PRLI is retried for d_id=0x%x, state=%x,"
			    " reason= %x", ptgt->tgt_d_id, fpkt->pkt_state,
			    fpkt->pkt_reason);

			/*
			 * Retry by recalling the routine that
			 * originally queued this packet
			 */
			mutex_enter(&pptr->port_mutex);
			if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) {
				caddr_t msg;

				mutex_exit(&pptr->port_mutex);

				ASSERT(icmd->ipkt_opcode != LA_ELS_PLOGI);

				if (fpkt->pkt_state == FC_PKT_TIMEOUT) {
					fpkt->pkt_timeout +=
					    FCP_TIMEOUT_DELTA;
				}

				rval = fc_ulp_issue_els(pptr->port_fp_handle,
				    fpkt);
				if (rval == FC_SUCCESS) {
					return;
				}

				if (rval == FC_STATEC_BUSY ||
				    rval == FC_OFFLINE) {
					fcp_queue_ipkt(pptr, fpkt);
					return;
				}
				(void) fc_ulp_error(rval, &msg);

				fcp_log(CE_NOTE, pptr->port_dip,
				    "!ELS 0x%x failed to d_id=0x%x;"
				    " %s", icmd->ipkt_opcode,
				    ptgt->tgt_d_id, msg);
			} else {
				FCP_TRACE(fcp_logq, pptr->port_instbuf,
				    fcp_trace, FCP_BUF_LEVEL_2, 0,
				    "fcp_icmd_callback,3: state change "
				    " occured for D_ID=0x%x", ptgt->tgt_d_id);
				mutex_exit(&pptr->port_mutex);
			}
		}
	} else {
		if (fcp_is_retryable(icmd) &&
		    icmd->ipkt_retries++ < FCP_MAX_RETRIES) {
			if (FCP_MUST_RETRY(fpkt)) {
				fcp_queue_ipkt(pptr, fpkt);
				return;
			}
		}
		mutex_enter(&pptr->port_mutex);
		if (!FCP_TGT_STATE_CHANGED(ptgt, icmd) &&
		    fpkt->pkt_state != FC_PKT_PORT_OFFLINE) {
			mutex_exit(&pptr->port_mutex);
			fcp_print_error(fpkt);
		} else {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_2, 0,
			    "fcp_icmd_callback,4: state change occured"
			    " for D_ID=0x%x", ptgt->tgt_d_id);
			mutex_exit(&pptr->port_mutex);
		}
	}

fail:
	if (free_pkt) {
		(void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt,
		    icmd->ipkt_change_cnt, icmd->ipkt_cause);
		fcp_icmd_free(pptr, icmd);
	}
}


/*
 * called internally to send an info cmd using the transport
 *
 * sends either an INQ or a REPORT_LUN
 *
 * when the packet is completed fcp_scsi_callback is called
 */
static int
fcp_send_scsi(struct fcp_lun *plun, uchar_t opcode, int alloc_len,
    int lcount, int tcount, int cause, uint32_t rscn_count)
{
	int			nodma;
	struct fcp_ipkt		*icmd;
	struct fcp_tgt		*ptgt;
	struct fcp_port		*pptr;
	fc_frame_hdr_t		*hp;
	fc_packet_t		*fpkt;
	struct fcp_cmd		fcp_cmd;
	struct fcp_cmd		*fcmd;
	union scsi_cdb		*scsi_cdb;

	ASSERT(plun != NULL);

	ptgt = plun->lun_tgt;
	ASSERT(ptgt != NULL);

	pptr = ptgt->tgt_port;
	ASSERT(pptr != NULL);

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_5, 0,
	    "fcp_send_scsi: d_id=0x%x opcode=0x%x", ptgt->tgt_d_id, opcode);

	nodma = (pptr->port_fcp_dma == FC_NO_DVMA_SPACE) ? 1 : 0;

	icmd = fcp_icmd_alloc(pptr, ptgt, sizeof (struct fcp_cmd),
	    FCP_MAX_RSP_IU_SIZE, alloc_len, nodma, lcount, tcount, cause,
	    rscn_count);

	if (icmd == NULL) {
		return (DDI_FAILURE);
	}

	fpkt = icmd->ipkt_fpkt;
	fpkt->pkt_tran_flags = FC_TRAN_CLASS3 | FC_TRAN_INTR;
	icmd->ipkt_retries = 0;
	icmd->ipkt_opcode = opcode;
	icmd->ipkt_lun = plun;

	if (nodma) {
		fcmd = (struct fcp_cmd *)fpkt->pkt_cmd;
	} else {
		fcmd = &fcp_cmd;
	}
	bzero(fcmd, sizeof (struct fcp_cmd));

	fpkt->pkt_timeout = FCP_SCSI_CMD_TIMEOUT;

	hp = &fpkt->pkt_cmd_fhdr;

	hp->s_id = pptr->port_id;
	hp->d_id = ptgt->tgt_d_id;
	hp->r_ctl = R_CTL_COMMAND;
	hp->type = FC_TYPE_SCSI_FCP;
	hp->f_ctl = F_CTL_SEQ_INITIATIVE | F_CTL_FIRST_SEQ;
	hp->rsvd = 0;
	hp->seq_id = 0;
	hp->seq_cnt = 0;
	hp->ox_id = 0xffff;
	hp->rx_id = 0xffff;
	hp->ro = 0;

	bcopy(&(plun->lun_addr), &(fcmd->fcp_ent_addr), FCP_LUN_SIZE);

	/*
	 * Request SCSI target for expedited processing
	 */

	/*
	 * Set up for untagged queuing because we do not
	 * know if the fibre device supports queuing.
	 */
	fcmd->fcp_cntl.cntl_reserved_0 = 0;
	fcmd->fcp_cntl.cntl_reserved_1 = 0;
	fcmd->fcp_cntl.cntl_reserved_2 = 0;
	fcmd->fcp_cntl.cntl_reserved_3 = 0;
	fcmd->fcp_cntl.cntl_reserved_4 = 0;
	fcmd->fcp_cntl.cntl_qtype = FCP_QTYPE_UNTAGGED;
	scsi_cdb = (union scsi_cdb *)fcmd->fcp_cdb;

	switch (opcode) {
	case SCMD_INQUIRY_PAGE83:
		/*
		 * Prepare to get the Inquiry VPD page 83 information
		 */
		fcmd->fcp_cntl.cntl_read_data = 1;
		fcmd->fcp_cntl.cntl_write_data = 0;
		fcmd->fcp_data_len = alloc_len;

		fpkt->pkt_tran_type = FC_PKT_FCP_READ;
		fpkt->pkt_comp = fcp_scsi_callback;

		scsi_cdb->scc_cmd = SCMD_INQUIRY;
		scsi_cdb->g0_addr2 = 0x01;
		scsi_cdb->g0_addr1 = 0x83;
		scsi_cdb->g0_count0 = (uchar_t)alloc_len;
		break;

	case SCMD_INQUIRY:
		fcmd->fcp_cntl.cntl_read_data = 1;
		fcmd->fcp_cntl.cntl_write_data = 0;
		fcmd->fcp_data_len = alloc_len;

		fpkt->pkt_tran_type = FC_PKT_FCP_READ;
		fpkt->pkt_comp = fcp_scsi_callback;

		scsi_cdb->scc_cmd = SCMD_INQUIRY;
		scsi_cdb->g0_count0 = SUN_INQSIZE;
		break;

	case SCMD_REPORT_LUN: {
		fc_portid_t	d_id;
		opaque_t	fca_dev;

		ASSERT(alloc_len >= 16);

		d_id.priv_lilp_posit = 0;
		d_id.port_id = ptgt->tgt_d_id;

		fca_dev = fc_ulp_get_fca_device(pptr->port_fp_handle, d_id);

		mutex_enter(&ptgt->tgt_mutex);
		ptgt->tgt_fca_dev = fca_dev;
		mutex_exit(&ptgt->tgt_mutex);

		fcmd->fcp_cntl.cntl_read_data = 1;
		fcmd->fcp_cntl.cntl_write_data = 0;
		fcmd->fcp_data_len = alloc_len;

		fpkt->pkt_tran_type = FC_PKT_FCP_READ;
		fpkt->pkt_comp = fcp_scsi_callback;

		scsi_cdb->scc_cmd = SCMD_REPORT_LUN;
		scsi_cdb->scc5_count0 = alloc_len & 0xff;
		scsi_cdb->scc5_count1 = (alloc_len >> 8) & 0xff;
		scsi_cdb->scc5_count2 = (alloc_len >> 16) & 0xff;
		scsi_cdb->scc5_count3 = (alloc_len >> 24) & 0xff;
		break;
	}

	default:
		fcp_log(CE_WARN, pptr->port_dip,
		    "!fcp_send_scsi Invalid opcode");
		break;
	}

	if (!nodma) {
		FCP_CP_OUT((uint8_t *)fcmd, fpkt->pkt_cmd,
		    fpkt->pkt_cmd_acc, sizeof (struct fcp_cmd));
	}

	mutex_enter(&pptr->port_mutex);
	if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) {

		mutex_exit(&pptr->port_mutex);
		if (fcp_transport(pptr->port_fp_handle, fpkt, 1) !=
		    FC_SUCCESS) {
			fcp_icmd_free(pptr, icmd);
			return (DDI_FAILURE);
		}
		return (DDI_SUCCESS);
	} else {
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_2, 0,
		    "fcp_send_scsi,1: state change occured"
		    " for D_ID=0x%x", ptgt->tgt_d_id);
		mutex_exit(&pptr->port_mutex);
		fcp_icmd_free(pptr, icmd);
		return (DDI_FAILURE);
	}
}


/*
 * called by fcp_scsi_callback to check to handle the case where
 * REPORT_LUN returns ILLEGAL REQUEST or a UNIT ATTENTION
 */
static int
fcp_check_reportlun(struct fcp_rsp *rsp, fc_packet_t *fpkt)
{
	uchar_t				rqlen;
	int				rval = DDI_FAILURE;
	struct scsi_extended_sense	sense_info, *sense;
	struct fcp_ipkt		*icmd = (struct fcp_ipkt *)
	    fpkt->pkt_ulp_private;
	struct fcp_tgt		*ptgt = icmd->ipkt_tgt;
	struct fcp_port		*pptr = ptgt->tgt_port;

	ASSERT(icmd->ipkt_opcode == SCMD_REPORT_LUN);

	if (rsp->fcp_u.fcp_status.scsi_status == STATUS_RESERVATION_CONFLICT) {
		/*
		 * SCSI-II Reserve Release support. Some older FC drives return
		 * Reservation conflict for Report Luns command.
		 */
		if (icmd->ipkt_nodma) {
			rsp->fcp_u.fcp_status.rsp_len_set = 0;
			rsp->fcp_u.fcp_status.sense_len_set = 0;
			rsp->fcp_u.fcp_status.scsi_status = STATUS_GOOD;
		} else {
			fcp_rsp_t	new_resp;

			FCP_CP_IN(fpkt->pkt_resp, &new_resp,
			    fpkt->pkt_resp_acc, sizeof (new_resp));

			new_resp.fcp_u.fcp_status.rsp_len_set = 0;
			new_resp.fcp_u.fcp_status.sense_len_set = 0;
			new_resp.fcp_u.fcp_status.scsi_status = STATUS_GOOD;

			FCP_CP_OUT(&new_resp, fpkt->pkt_resp,
			    fpkt->pkt_resp_acc, sizeof (new_resp));
		}

		FCP_CP_OUT(fcp_dummy_lun, fpkt->pkt_data,
		    fpkt->pkt_data_acc, sizeof (fcp_dummy_lun));

		return (DDI_SUCCESS);
	}

	sense = &sense_info;
	if (!rsp->fcp_u.fcp_status.sense_len_set) {
		/* no need to continue if sense length is not set */
		return (rval);
	}

	/* casting 64-bit integer to 8-bit */
	rqlen = (uchar_t)min(rsp->fcp_sense_len,
	    sizeof (struct scsi_extended_sense));

	if (rqlen < 14) {
		/* no need to continue if request length isn't long enough */
		return (rval);
	}

	if (icmd->ipkt_nodma) {
		/*
		 * We can safely use fcp_response_len here since the
		 * only path that calls fcp_check_reportlun,
		 * fcp_scsi_callback, has already called
		 * fcp_validate_fcp_response.
		 */
		sense = (struct scsi_extended_sense *)(fpkt->pkt_resp +
		    sizeof (struct fcp_rsp) + rsp->fcp_response_len);
	} else {
		FCP_CP_IN(fpkt->pkt_resp + sizeof (struct fcp_rsp) +
		    rsp->fcp_response_len, sense, fpkt->pkt_resp_acc,
		    sizeof (struct scsi_extended_sense));
	}

	if (!FCP_SENSE_NO_LUN(sense)) {
		mutex_enter(&ptgt->tgt_mutex);
		/* clear the flag if any */
		ptgt->tgt_state &= ~FCP_TGT_ILLREQ;
		mutex_exit(&ptgt->tgt_mutex);
	}

	if ((sense->es_key == KEY_ILLEGAL_REQUEST) &&
	    (sense->es_add_code == 0x20)) {
		if (icmd->ipkt_nodma) {
			rsp->fcp_u.fcp_status.rsp_len_set = 0;
			rsp->fcp_u.fcp_status.sense_len_set = 0;
			rsp->fcp_u.fcp_status.scsi_status = STATUS_GOOD;
		} else {
			fcp_rsp_t	new_resp;

			FCP_CP_IN(fpkt->pkt_resp, &new_resp,
			    fpkt->pkt_resp_acc, sizeof (new_resp));

			new_resp.fcp_u.fcp_status.rsp_len_set = 0;
			new_resp.fcp_u.fcp_status.sense_len_set = 0;
			new_resp.fcp_u.fcp_status.scsi_status = STATUS_GOOD;

			FCP_CP_OUT(&new_resp, fpkt->pkt_resp,
			    fpkt->pkt_resp_acc, sizeof (new_resp));
		}

		FCP_CP_OUT(fcp_dummy_lun, fpkt->pkt_data,
		    fpkt->pkt_data_acc, sizeof (fcp_dummy_lun));

		return (DDI_SUCCESS);
	}

	/*
	 * This is for the STK library which returns a check condition,
	 * to indicate device is not ready, manual assistance needed.
	 * This is to a report lun command when the door is open.
	 */
	if ((sense->es_key == KEY_NOT_READY) && (sense->es_add_code == 0x04)) {
		if (icmd->ipkt_nodma) {
			rsp->fcp_u.fcp_status.rsp_len_set = 0;
			rsp->fcp_u.fcp_status.sense_len_set = 0;
			rsp->fcp_u.fcp_status.scsi_status = STATUS_GOOD;
		} else {
			fcp_rsp_t	new_resp;

			FCP_CP_IN(fpkt->pkt_resp, &new_resp,
			    fpkt->pkt_resp_acc, sizeof (new_resp));

			new_resp.fcp_u.fcp_status.rsp_len_set = 0;
			new_resp.fcp_u.fcp_status.sense_len_set = 0;
			new_resp.fcp_u.fcp_status.scsi_status = STATUS_GOOD;

			FCP_CP_OUT(&new_resp, fpkt->pkt_resp,
			    fpkt->pkt_resp_acc, sizeof (new_resp));
		}

		FCP_CP_OUT(fcp_dummy_lun, fpkt->pkt_data,
		    fpkt->pkt_data_acc, sizeof (fcp_dummy_lun));

		return (DDI_SUCCESS);
	}

	if ((FCP_SENSE_REPORTLUN_CHANGED(sense)) ||
	    (FCP_SENSE_NO_LUN(sense))) {
		mutex_enter(&ptgt->tgt_mutex);
		if ((FCP_SENSE_NO_LUN(sense)) &&
		    (ptgt->tgt_state & FCP_TGT_ILLREQ)) {
			ptgt->tgt_state &= ~FCP_TGT_ILLREQ;
			mutex_exit(&ptgt->tgt_mutex);
			/*
			 * reconfig was triggred by ILLEGAL REQUEST but
			 * got ILLEGAL REQUEST again
			 */
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_3, 0,
			    "!FCP: Unable to obtain Report Lun data"
			    " target=%x", ptgt->tgt_d_id);
		} else {
			if (ptgt->tgt_tid == NULL) {
				timeout_id_t	tid;
				/*
				 * REPORT LUN data has changed.	 Kick off
				 * rediscovery
				 */
				tid = timeout(fcp_reconfigure_luns,
				    (caddr_t)ptgt, (clock_t)drv_usectohz(1));

				ptgt->tgt_tid = tid;
				ptgt->tgt_state |= FCP_TGT_BUSY;
			}
			if (FCP_SENSE_NO_LUN(sense)) {
				ptgt->tgt_state |= FCP_TGT_ILLREQ;
			}
			mutex_exit(&ptgt->tgt_mutex);
			if (FCP_SENSE_REPORTLUN_CHANGED(sense)) {
				FCP_TRACE(fcp_logq, pptr->port_instbuf,
				    fcp_trace, FCP_BUF_LEVEL_3, 0,
				    "!FCP:Report Lun Has Changed"
				    " target=%x", ptgt->tgt_d_id);
			} else if (FCP_SENSE_NO_LUN(sense)) {
				FCP_TRACE(fcp_logq, pptr->port_instbuf,
				    fcp_trace, FCP_BUF_LEVEL_3, 0,
				    "!FCP:LU Not Supported"
				    " target=%x", ptgt->tgt_d_id);
			}
		}
		rval = DDI_SUCCESS;
	}

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_5, 0,
	    "D_ID=%x, sense=%x, status=%x",
	    fpkt->pkt_cmd_fhdr.d_id, sense->es_key,
	    rsp->fcp_u.fcp_status.scsi_status);

	return (rval);
}

/*
 *     Function: fcp_scsi_callback
 *
 *  Description: This is the callback routine set by fcp_send_scsi() after
 *		 it calls fcp_icmd_alloc().  The SCSI command completed here
 *		 and autogenerated by FCP are:	REPORT_LUN, INQUIRY and
 *		 INQUIRY_PAGE83.
 *
 *     Argument: *fpkt	 FC packet used to convey the command
 *
 * Return Value: None
 */
static void
fcp_scsi_callback(fc_packet_t *fpkt)
{
	struct fcp_ipkt	*icmd = (struct fcp_ipkt *)
	    fpkt->pkt_ulp_private;
	struct fcp_rsp_info	fcp_rsp_err, *bep;
	struct fcp_port	*pptr;
	struct fcp_tgt	*ptgt;
	struct fcp_lun	*plun;
	struct fcp_rsp		response, *rsp;

	if (icmd->ipkt_nodma) {
		rsp = (struct fcp_rsp *)fpkt->pkt_resp;
	} else {
		rsp = &response;
		FCP_CP_IN(fpkt->pkt_resp, rsp, fpkt->pkt_resp_acc,
		    sizeof (struct fcp_rsp));
	}

	ptgt = icmd->ipkt_tgt;
	pptr = ptgt->tgt_port;
	plun = icmd->ipkt_lun;

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_2, 0,
	    "SCSI callback state=0x%x for %x, op_code=0x%x, "
	    "status=%x, lun num=%x",
	    fpkt->pkt_state, ptgt->tgt_d_id, icmd->ipkt_opcode,
	    rsp->fcp_u.fcp_status.scsi_status, plun->lun_num);

	/*
	 * Pre-init LUN GUID with NWWN if it is not a device that
	 * supports multiple luns and we know it's not page83
	 * compliant.  Although using a NWWN is not lun unique,
	 * we will be fine since there is only one lun behind the taget
	 * in this case.
	 */
	if ((plun->lun_guid_size == 0) &&
	    (icmd->ipkt_opcode == SCMD_INQUIRY_PAGE83) &&
	    (fcp_symmetric_device_probe(plun) == 0)) {

		char ascii_wwn[FC_WWN_SIZE*2+1];
		fcp_wwn_to_ascii(&ptgt->tgt_node_wwn.raw_wwn[0], ascii_wwn);
		(void) fcp_copy_guid_2_lun_block(plun, ascii_wwn);
	}

	/*
	 * Some old FC tapes and FC <-> SCSI bridge devices return overrun
	 * when thay have more data than what is asked in CDB. An overrun
	 * is really when FCP_DL is smaller than the data length in CDB.
	 * In the case here we know that REPORT LUN command we formed within
	 * this binary has correct FCP_DL. So this OVERRUN is due to bad device
	 * behavior. In reality this is FC_SUCCESS.
	 */
	if ((fpkt->pkt_state != FC_PKT_SUCCESS) &&
	    (fpkt->pkt_reason == FC_REASON_OVERRUN) &&
	    (icmd->ipkt_opcode == SCMD_REPORT_LUN)) {
		fpkt->pkt_state = FC_PKT_SUCCESS;
	}

	if (fpkt->pkt_state != FC_PKT_SUCCESS) {
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_2, 0,
		    "icmd failed with state=0x%x for %x", fpkt->pkt_state,
		    ptgt->tgt_d_id);

		if (fpkt->pkt_reason == FC_REASON_CRC_ERROR) {
			/*
			 * Inquiry VPD page command on A5K SES devices would
			 * result in data CRC errors.
			 */
			if (icmd->ipkt_opcode == SCMD_INQUIRY_PAGE83) {
				(void) fcp_handle_page83(fpkt, icmd, 1);
				return;
			}
		}
		if (fpkt->pkt_state == FC_PKT_TIMEOUT ||
		    FCP_MUST_RETRY(fpkt)) {
			fpkt->pkt_timeout += FCP_TIMEOUT_DELTA;
			fcp_retry_scsi_cmd(fpkt);
			return;
		}

		FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt,
		    FCP_TGT_TRACE_20);

		mutex_enter(&pptr->port_mutex);
		mutex_enter(&ptgt->tgt_mutex);
		if (!FCP_STATE_CHANGED(pptr, ptgt, icmd)) {
			mutex_exit(&ptgt->tgt_mutex);
			mutex_exit(&pptr->port_mutex);
			fcp_print_error(fpkt);
		} else {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_2, 0,
			    "fcp_scsi_callback,1: state change occured"
			    " for D_ID=0x%x", ptgt->tgt_d_id);
			mutex_exit(&ptgt->tgt_mutex);
			mutex_exit(&pptr->port_mutex);
		}
		(void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt,
		    icmd->ipkt_change_cnt, icmd->ipkt_cause);
		fcp_icmd_free(pptr, icmd);
		return;
	}

	FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt, FCP_TGT_TRACE_21);

	mutex_enter(&pptr->port_mutex);
	mutex_enter(&ptgt->tgt_mutex);
	if (FCP_STATE_CHANGED(pptr, ptgt, icmd)) {
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_2, 0,
		    "fcp_scsi_callback,2: state change occured"
		    " for D_ID=0x%x", ptgt->tgt_d_id);
		mutex_exit(&ptgt->tgt_mutex);
		mutex_exit(&pptr->port_mutex);
		(void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt,
		    icmd->ipkt_change_cnt, icmd->ipkt_cause);
		fcp_icmd_free(pptr, icmd);
		return;
	}
	ASSERT((ptgt->tgt_state & FCP_TGT_MARK) == 0);

	mutex_exit(&ptgt->tgt_mutex);
	mutex_exit(&pptr->port_mutex);

	if (icmd->ipkt_nodma) {
		bep = (struct fcp_rsp_info *)(fpkt->pkt_resp +
		    sizeof (struct fcp_rsp));
	} else {
		bep = &fcp_rsp_err;
		FCP_CP_IN(fpkt->pkt_resp + sizeof (struct fcp_rsp), bep,
		    fpkt->pkt_resp_acc, sizeof (struct fcp_rsp_info));
	}

	if (fcp_validate_fcp_response(rsp, pptr) != FC_SUCCESS) {
		fcp_retry_scsi_cmd(fpkt);
		return;
	}

	if (rsp->fcp_u.fcp_status.rsp_len_set && bep->rsp_code !=
	    FCP_NO_FAILURE) {
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_2, 0,
		    "rsp_code=0x%x, rsp_len_set=0x%x",
		    bep->rsp_code, rsp->fcp_u.fcp_status.rsp_len_set);
		fcp_retry_scsi_cmd(fpkt);
		return;
	}

	if (rsp->fcp_u.fcp_status.scsi_status == STATUS_QFULL ||
	    rsp->fcp_u.fcp_status.scsi_status == STATUS_BUSY) {
		fcp_queue_ipkt(pptr, fpkt);
		return;
	}

	/*
	 * Devices that do not support INQUIRY_PAGE83, return check condition
	 * with illegal request as per SCSI spec.
	 * Crossbridge is one such device and Daktari's SES node is another.
	 * We want to ideally enumerate these devices as a non-mpxio devices.
	 * SES nodes (Daktari only currently) are an exception to this.
	 */
	if ((icmd->ipkt_opcode == SCMD_INQUIRY_PAGE83) &&
	    (rsp->fcp_u.fcp_status.scsi_status & STATUS_CHECK)) {

		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_3, 0,
		    "INQUIRY_PAGE83 for d_id %x (dtype:0x%x) failed with "
		    "check condition. May enumerate as non-mpxio device",
		    ptgt->tgt_d_id, plun->lun_type);

		/*
		 * If we let Daktari's SES be enumerated as a non-mpxio
		 * device, there will be a discrepency in that the other
		 * internal FC disks will get enumerated as mpxio devices.
		 * Applications like luxadm expect this to be consistent.
		 *
		 * So, we put in a hack here to check if this is an SES device
		 * and handle it here.
		 */
		if (plun->lun_type == DTYPE_ESI) {
			/*
			 * Since, pkt_state is actually FC_PKT_SUCCESS
			 * at this stage, we fake a failure here so that
			 * fcp_handle_page83 will create a device path using
			 * the WWN instead of the GUID which is not there anyway
			 */
			fpkt->pkt_state = FC_PKT_LOCAL_RJT;
			(void) fcp_handle_page83(fpkt, icmd, 1);
			return;
		}

		mutex_enter(&ptgt->tgt_mutex);
		plun->lun_state &= ~(FCP_LUN_OFFLINE |
		    FCP_LUN_MARK | FCP_LUN_BUSY);
		mutex_exit(&ptgt->tgt_mutex);

		(void) fcp_call_finish_init(pptr, ptgt,
		    icmd->ipkt_link_cnt, icmd->ipkt_change_cnt,
		    icmd->ipkt_cause);
		fcp_icmd_free(pptr, icmd);
		return;
	}

	if (rsp->fcp_u.fcp_status.scsi_status != STATUS_GOOD) {
		int rval = DDI_FAILURE;

		/*
		 * handle cases where report lun isn't supported
		 * by faking up our own REPORT_LUN response or
		 * UNIT ATTENTION
		 */
		if (icmd->ipkt_opcode == SCMD_REPORT_LUN) {
			rval = fcp_check_reportlun(rsp, fpkt);

			/*
			 * fcp_check_reportlun might have modified the
			 * FCP response. Copy it in again to get an updated
			 * FCP response
			 */
			if (rval == DDI_SUCCESS && icmd->ipkt_nodma == 0) {
				rsp = &response;

				FCP_CP_IN(fpkt->pkt_resp, rsp,
				    fpkt->pkt_resp_acc,
				    sizeof (struct fcp_rsp));
			}
		}

		if (rsp->fcp_u.fcp_status.scsi_status != STATUS_GOOD) {
			if (rval == DDI_SUCCESS) {
				(void) fcp_call_finish_init(pptr, ptgt,
				    icmd->ipkt_link_cnt, icmd->ipkt_change_cnt,
				    icmd->ipkt_cause);
				fcp_icmd_free(pptr, icmd);
			} else {
				fcp_retry_scsi_cmd(fpkt);
			}

			return;
		}
	} else {
		if (icmd->ipkt_opcode == SCMD_REPORT_LUN) {
			mutex_enter(&ptgt->tgt_mutex);
			ptgt->tgt_state &= ~FCP_TGT_ILLREQ;
			mutex_exit(&ptgt->tgt_mutex);
		}
	}

	ASSERT(rsp->fcp_u.fcp_status.scsi_status == STATUS_GOOD);

	(void) ddi_dma_sync(fpkt->pkt_data_dma, 0, 0, DDI_DMA_SYNC_FORCPU);

	switch (icmd->ipkt_opcode) {
	case SCMD_INQUIRY:
		FCP_LUN_TRACE(plun, FCP_LUN_TRACE_1);
		fcp_handle_inquiry(fpkt, icmd);
		break;

	case SCMD_REPORT_LUN:
		FCP_TGT_TRACE(ptgt, icmd->ipkt_change_cnt,
		    FCP_TGT_TRACE_22);
		fcp_handle_reportlun(fpkt, icmd);
		break;

	case SCMD_INQUIRY_PAGE83:
		FCP_LUN_TRACE(plun, FCP_LUN_TRACE_2);
		(void) fcp_handle_page83(fpkt, icmd, 0);
		break;

	default:
		fcp_log(CE_WARN, NULL, "!Invalid SCSI opcode");
		(void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt,
		    icmd->ipkt_change_cnt, icmd->ipkt_cause);
		fcp_icmd_free(pptr, icmd);
		break;
	}
}


static void
fcp_retry_scsi_cmd(fc_packet_t *fpkt)
{
	struct fcp_ipkt	*icmd = (struct fcp_ipkt *)
	    fpkt->pkt_ulp_private;
	struct fcp_tgt	*ptgt = icmd->ipkt_tgt;
	struct fcp_port	*pptr = ptgt->tgt_port;

	if (icmd->ipkt_retries < FCP_MAX_RETRIES &&
	    fcp_is_retryable(icmd)) {
		mutex_enter(&pptr->port_mutex);
		if (!FCP_TGT_STATE_CHANGED(ptgt, icmd)) {
			mutex_exit(&pptr->port_mutex);
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_3, 0,
			    "Retrying %s to %x; state=%x, reason=%x",
			    (icmd->ipkt_opcode == SCMD_REPORT_LUN) ?
			    "Report LUN" : "INQUIRY", ptgt->tgt_d_id,
			    fpkt->pkt_state, fpkt->pkt_reason);

			fcp_queue_ipkt(pptr, fpkt);
		} else {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_3, 0,
			    "fcp_retry_scsi_cmd,1: state change occured"
			    " for D_ID=0x%x", ptgt->tgt_d_id);
			mutex_exit(&pptr->port_mutex);
			(void) fcp_call_finish_init(pptr, ptgt,
			    icmd->ipkt_link_cnt, icmd->ipkt_change_cnt,
			    icmd->ipkt_cause);
			fcp_icmd_free(pptr, icmd);
		}
	} else {
		fcp_print_error(fpkt);
		(void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt,
		    icmd->ipkt_change_cnt, icmd->ipkt_cause);
		fcp_icmd_free(pptr, icmd);
	}
}

/*
 *     Function: fcp_handle_page83
 *
 *  Description: Treats the response to INQUIRY_PAGE83.
 *
 *     Argument: *fpkt	FC packet used to convey the command.
 *		 *icmd	Original fcp_ipkt structure.
 *		 ignore_page83_data
 *			if it's 1, that means it's a special devices's
 *			page83 response, it should be enumerated under mpxio
 *
 * Return Value: None
 */
static void
fcp_handle_page83(fc_packet_t *fpkt, struct fcp_ipkt *icmd,
    int ignore_page83_data)
{
	struct fcp_port	*pptr;
	struct fcp_lun	*plun;
	struct fcp_tgt	*ptgt;
	uchar_t			dev_id_page[SCMD_MAX_INQUIRY_PAGE83_SIZE];
	int			fail = 0;
	ddi_devid_t		devid;
	char			*guid = NULL;
	int			ret;

	ASSERT(icmd != NULL && fpkt != NULL);

	pptr = icmd->ipkt_port;
	ptgt = icmd->ipkt_tgt;
	plun = icmd->ipkt_lun;

	if (fpkt->pkt_state == FC_PKT_SUCCESS) {
		FCP_LUN_TRACE(plun, FCP_LUN_TRACE_7);

		FCP_CP_IN(fpkt->pkt_data, dev_id_page, fpkt->pkt_data_acc,
		    SCMD_MAX_INQUIRY_PAGE83_SIZE);

		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_5, 0,
		    "fcp_handle_page83: port=%d, tgt D_ID=0x%x, "
		    "dtype=0x%x, lun num=%x",
		    pptr->port_instance, ptgt->tgt_d_id,
		    dev_id_page[0], plun->lun_num);

		ret = ddi_devid_scsi_encode(
		    DEVID_SCSI_ENCODE_VERSION_LATEST,
		    NULL,		/* driver name */
		    (unsigned char *) &plun->lun_inq, /* standard inquiry */
		    sizeof (plun->lun_inq), /* size of standard inquiry */
		    NULL,		/* page 80 data */
		    0,		/* page 80 len */
		    dev_id_page,	/* page 83 data */
		    SCMD_MAX_INQUIRY_PAGE83_SIZE, /* page 83 data len */
		    &devid);

		if (ret == DDI_SUCCESS) {

			guid = ddi_devid_to_guid(devid);

			if (guid) {
				/*
				 * Check our current guid.  If it's non null
				 * and it has changed, we need to copy it into
				 * lun_old_guid since we might still need it.
				 */
				if (plun->lun_guid &&
				    strcmp(guid, plun->lun_guid)) {
					unsigned int len;

					/*
					 * If the guid of the LUN changes,
					 * reconfiguration should be triggered
					 * to reflect the changes.
					 * i.e. we should offline the LUN with
					 * the old guid, and online the LUN with
					 * the new guid.
					 */
					plun->lun_state |= FCP_LUN_CHANGED;

					if (plun->lun_old_guid) {
						kmem_free(plun->lun_old_guid,
						    plun->lun_old_guid_size);
					}

					len = plun->lun_guid_size;
					plun->lun_old_guid_size = len;

					plun->lun_old_guid = kmem_zalloc(len,
					    KM_NOSLEEP);

					if (plun->lun_old_guid) {
						/*
						 * The alloc was successful then
						 * let's do the copy.
						 */
						bcopy(plun->lun_guid,
						    plun->lun_old_guid, len);
					} else {
						fail = 1;
						plun->lun_old_guid_size = 0;
					}
				}
				if (!fail) {
					if (fcp_copy_guid_2_lun_block(
					    plun, guid)) {
						fail = 1;
					}
				}
				ddi_devid_free_guid(guid);

			} else {
				FCP_TRACE(fcp_logq, pptr->port_instbuf,
				    fcp_trace, FCP_BUF_LEVEL_2, 0,
				    "fcp_handle_page83: unable to create "
				    "GUID");

				/* couldn't create good guid from devid */
				fail = 1;
			}
			ddi_devid_free(devid);

		} else if (ret == DDI_NOT_WELL_FORMED) {
			/* NULL filled data for page 83 */
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_2, 0,
			    "fcp_handle_page83: retry GUID");

			icmd->ipkt_retries = 0;
			fcp_retry_scsi_cmd(fpkt);
			return;
		} else {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_2, 0,
			    "fcp_handle_page83: bad ddi_devid_scsi_encode %x",
			    ret);
			/*
			 * Since the page83 validation
			 * introduced late, we are being
			 * tolerant to the existing devices
			 * that already found to be working
			 * under mpxio, like A5200's SES device,
			 * its page83 response will not be standard-compliant,
			 * but we still want it to be enumerated under mpxio.
			 */
			if (fcp_symmetric_device_probe(plun) != 0) {
				fail = 1;
			}
		}

	} else {
		/* bad packet state */
		FCP_LUN_TRACE(plun, FCP_LUN_TRACE_8);

		/*
		 * For some special devices (A5K SES and Daktari's SES devices),
		 * they should be enumerated under mpxio
		 * or "luxadm dis" will fail
		 */
		if (ignore_page83_data) {
			fail = 0;
		} else {
			fail = 1;
		}
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_2, 0,
		    "!Devid page cmd failed. "
		    "fpkt_state: %x fpkt_reason: %x",
		    "ignore_page83: %d",
		    fpkt->pkt_state, fpkt->pkt_reason,
		    ignore_page83_data);
	}

	mutex_enter(&pptr->port_mutex);
	mutex_enter(&plun->lun_mutex);
	/*
	 * If lun_cip is not NULL, then we needn't update lun_mpxio to avoid
	 * mismatch between lun_cip and lun_mpxio.
	 */
	if (plun->lun_cip == NULL) {
		/*
		 * If we don't have a guid for this lun it's because we were
		 * unable to glean one from the page 83 response.  Set the
		 * control flag to 0 here to make sure that we don't attempt to
		 * enumerate it under mpxio.
		 */
		if (fail || pptr->port_mpxio == 0) {
			plun->lun_mpxio = 0;
		} else {
			plun->lun_mpxio = 1;
		}
	}
	mutex_exit(&plun->lun_mutex);
	mutex_exit(&pptr->port_mutex);

	mutex_enter(&ptgt->tgt_mutex);
	plun->lun_state &=
	    ~(FCP_LUN_OFFLINE | FCP_LUN_MARK | FCP_LUN_BUSY);
	mutex_exit(&ptgt->tgt_mutex);

	(void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt,
	    icmd->ipkt_change_cnt, icmd->ipkt_cause);

	fcp_icmd_free(pptr, icmd);
}

/*
 *     Function: fcp_handle_inquiry
 *
 *  Description: Called by fcp_scsi_callback to handle the response to an
 *		 INQUIRY request.
 *
 *     Argument: *fpkt	FC packet used to convey the command.
 *		 *icmd	Original fcp_ipkt structure.
 *
 * Return Value: None
 */
static void
fcp_handle_inquiry(fc_packet_t *fpkt, struct fcp_ipkt *icmd)
{
	struct fcp_port	*pptr;
	struct fcp_lun	*plun;
	struct fcp_tgt	*ptgt;
	uchar_t		dtype;
	uchar_t		pqual;
	uint32_t	rscn_count = FC_INVALID_RSCN_COUNT;

	ASSERT(icmd != NULL && fpkt != NULL);

	pptr = icmd->ipkt_port;
	ptgt = icmd->ipkt_tgt;
	plun = icmd->ipkt_lun;

	FCP_CP_IN(fpkt->pkt_data, &plun->lun_inq, fpkt->pkt_data_acc,
	    sizeof (struct scsi_inquiry));

	dtype = plun->lun_inq.inq_dtype & DTYPE_MASK;
	pqual = plun->lun_inq.inq_dtype >> 5;

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_5, 0,
	    "fcp_handle_inquiry: port=%d, tgt D_ID=0x%x, lun=0x%x, "
	    "dtype=0x%x pqual: 0x%x", pptr->port_instance, ptgt->tgt_d_id,
	    plun->lun_num, dtype, pqual);

	if (pqual != 0) {
		/*
		 * Non-zero peripheral qualifier
		 */
		fcp_log(CE_CONT, pptr->port_dip,
		    "!Target 0x%x lun 0x%x: Nonzero peripheral qualifier: "
		    "Device type=0x%x Peripheral qual=0x%x\n",
		    ptgt->tgt_d_id, plun->lun_num, dtype, pqual);

		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_5, 0,
		    "!Target 0x%x lun 0x%x: Nonzero peripheral qualifier: "
		    "Device type=0x%x Peripheral qual=0x%x\n",
		    ptgt->tgt_d_id, plun->lun_num, dtype, pqual);

		FCP_LUN_TRACE(plun, FCP_LUN_TRACE_3);

		(void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt,
		    icmd->ipkt_change_cnt, icmd->ipkt_cause);
		fcp_icmd_free(pptr, icmd);
		return;
	}

	/*
	 * If the device is already initialized, check the dtype
	 * for a change. If it has changed then update the flags
	 * so the create_luns will offline the old device and
	 * create the new device. Refer to bug: 4764752
	 */
	if ((plun->lun_state & FCP_LUN_INIT) && dtype != plun->lun_type) {
		plun->lun_state |= FCP_LUN_CHANGED;
	}
	plun->lun_type = plun->lun_inq.inq_dtype;

	/*
	 * This code is setting/initializing the throttling in the FCA
	 * driver.
	 */
	mutex_enter(&pptr->port_mutex);
	if (!pptr->port_notify) {
		if (bcmp(plun->lun_inq.inq_pid, pid, strlen(pid)) == 0) {
			uint32_t cmd = 0;
			cmd = ((cmd & 0xFF | FC_NOTIFY_THROTTLE) |
			    ((cmd & 0xFFFFFF00 >> 8) |
			    FCP_SVE_THROTTLE << 8));
			pptr->port_notify = 1;
			mutex_exit(&pptr->port_mutex);
			(void) fc_ulp_port_notify(pptr->port_fp_handle, cmd);
			mutex_enter(&pptr->port_mutex);
		}
	}

	if (FCP_TGT_STATE_CHANGED(ptgt, icmd)) {
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_2, 0,
		    "fcp_handle_inquiry,1:state change occured"
		    " for D_ID=0x%x", ptgt->tgt_d_id);
		mutex_exit(&pptr->port_mutex);

		FCP_LUN_TRACE(plun, FCP_LUN_TRACE_5);
		(void) fcp_call_finish_init(pptr, ptgt,
		    icmd->ipkt_link_cnt, icmd->ipkt_change_cnt,
		    icmd->ipkt_cause);
		fcp_icmd_free(pptr, icmd);
		return;
	}
	ASSERT((ptgt->tgt_state & FCP_TGT_MARK) == 0);
	mutex_exit(&pptr->port_mutex);

	/* Retrieve the rscn count (if a valid one exists) */
	if (icmd->ipkt_fpkt->pkt_ulp_rscn_infop != NULL) {
		rscn_count = ((fc_ulp_rscn_info_t *)
		    (icmd->ipkt_fpkt->pkt_ulp_rscn_infop))->ulp_rscn_count;
	} else {
		rscn_count = FC_INVALID_RSCN_COUNT;
	}

	if (fcp_send_scsi(plun, SCMD_INQUIRY_PAGE83,
	    SCMD_MAX_INQUIRY_PAGE83_SIZE,
	    icmd->ipkt_link_cnt, icmd->ipkt_change_cnt,
	    icmd->ipkt_cause, rscn_count) != DDI_SUCCESS) {
		fcp_log(CE_WARN, NULL, "!failed to send page 83");
		FCP_LUN_TRACE(plun, FCP_LUN_TRACE_6);
		(void) fcp_call_finish_init(pptr, ptgt,
		    icmd->ipkt_link_cnt, icmd->ipkt_change_cnt,
		    icmd->ipkt_cause);
	}

	/*
	 * Read Inquiry VPD Page 0x83 to uniquely
	 * identify this logical unit.
	 */
	fcp_icmd_free(pptr, icmd);
}

/*
 *     Function: fcp_handle_reportlun
 *
 *  Description: Called by fcp_scsi_callback to handle the response to a
 *		 REPORT_LUN request.
 *
 *     Argument: *fpkt	FC packet used to convey the command.
 *		 *icmd	Original fcp_ipkt structure.
 *
 * Return Value: None
 */
static void
fcp_handle_reportlun(fc_packet_t *fpkt, struct fcp_ipkt *icmd)
{
	int				i;
	int				nluns_claimed;
	int				nluns_bufmax;
	int				len;
	uint16_t			lun_num;
	uint32_t			rscn_count = FC_INVALID_RSCN_COUNT;
	struct fcp_port			*pptr;
	struct fcp_tgt			*ptgt;
	struct fcp_lun			*plun;
	struct fcp_reportlun_resp	*report_lun;

	pptr = icmd->ipkt_port;
	ptgt = icmd->ipkt_tgt;
	len = fpkt->pkt_datalen;

	if ((len < FCP_LUN_HEADER) ||
	    ((report_lun = kmem_zalloc(len, KM_NOSLEEP)) == NULL)) {
		(void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt,
		    icmd->ipkt_change_cnt, icmd->ipkt_cause);
		fcp_icmd_free(pptr, icmd);
		return;
	}

	FCP_CP_IN(fpkt->pkt_data, report_lun, fpkt->pkt_data_acc,
	    fpkt->pkt_datalen);

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_5, 0,
	    "fcp_handle_reportlun: port=%d, tgt D_ID=0x%x",
	    pptr->port_instance, ptgt->tgt_d_id);

	/*
	 * Get the number of luns (which is supplied as LUNS * 8) the
	 * device claims it has.
	 */
	nluns_claimed = BE_32(report_lun->num_lun) >> 3;

	/*
	 * Get the maximum number of luns the buffer submitted can hold.
	 */
	nluns_bufmax = (fpkt->pkt_datalen - FCP_LUN_HEADER) / FCP_LUN_SIZE;

	/*
	 * Due to limitations of certain hardware, we support only 16 bit LUNs
	 */
	if (nluns_claimed > FCP_MAX_LUNS_SUPPORTED) {
		kmem_free(report_lun, len);

		fcp_log(CE_NOTE, pptr->port_dip, "!Can not support"
		    " 0x%x number of LUNs for target=%x", nluns_claimed,
		    ptgt->tgt_d_id);

		(void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt,
		    icmd->ipkt_change_cnt, icmd->ipkt_cause);
		fcp_icmd_free(pptr, icmd);
		return;
	}

	/*
	 * If there are more LUNs than we have allocated memory for,
	 * allocate more space and send down yet another report lun if
	 * the maximum number of attempts hasn't been reached.
	 */
	mutex_enter(&ptgt->tgt_mutex);

	if ((nluns_claimed > nluns_bufmax) &&
	    (ptgt->tgt_report_lun_cnt < FCP_MAX_REPORTLUNS_ATTEMPTS)) {

		struct fcp_lun *plun;

		ptgt->tgt_report_lun_cnt++;
		plun = ptgt->tgt_lun;
		ASSERT(plun != NULL);
		mutex_exit(&ptgt->tgt_mutex);

		kmem_free(report_lun, len);

		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_5, 0,
		    "!Dynamically discovered %d LUNs for D_ID=%x",
		    nluns_claimed, ptgt->tgt_d_id);

		/* Retrieve the rscn count (if a valid one exists) */
		if (icmd->ipkt_fpkt->pkt_ulp_rscn_infop != NULL) {
			rscn_count = ((fc_ulp_rscn_info_t *)
			    (icmd->ipkt_fpkt->pkt_ulp_rscn_infop))->
			    ulp_rscn_count;
		} else {
			rscn_count = FC_INVALID_RSCN_COUNT;
		}

		if (fcp_send_scsi(icmd->ipkt_lun, SCMD_REPORT_LUN,
		    FCP_LUN_HEADER + (nluns_claimed * FCP_LUN_SIZE),
		    icmd->ipkt_link_cnt, icmd->ipkt_change_cnt,
		    icmd->ipkt_cause, rscn_count) != DDI_SUCCESS) {
			(void) fcp_call_finish_init(pptr, ptgt,
			    icmd->ipkt_link_cnt, icmd->ipkt_change_cnt,
			    icmd->ipkt_cause);
		}

		fcp_icmd_free(pptr, icmd);
		return;
	}

	if (nluns_claimed > nluns_bufmax) {
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_5, 0,
		    "Target=%x:%x:%x:%x:%x:%x:%x:%x"
		    "	 Number of LUNs lost=%x",
		    ptgt->tgt_port_wwn.raw_wwn[0],
		    ptgt->tgt_port_wwn.raw_wwn[1],
		    ptgt->tgt_port_wwn.raw_wwn[2],
		    ptgt->tgt_port_wwn.raw_wwn[3],
		    ptgt->tgt_port_wwn.raw_wwn[4],
		    ptgt->tgt_port_wwn.raw_wwn[5],
		    ptgt->tgt_port_wwn.raw_wwn[6],
		    ptgt->tgt_port_wwn.raw_wwn[7],
		    nluns_claimed - nluns_bufmax);

		nluns_claimed = nluns_bufmax;
	}
	ptgt->tgt_lun_cnt = nluns_claimed;

	/*
	 * Identify missing LUNs and print warning messages
	 */
	for (plun = ptgt->tgt_lun; plun; plun = plun->lun_next) {
		int offline;
		int exists = 0;

		offline = (plun->lun_state & FCP_LUN_OFFLINE) ? 1 : 0;

		for (i = 0; i < nluns_claimed && exists == 0; i++) {
			uchar_t		*lun_string;

			lun_string = (uchar_t *)&(report_lun->lun_string[i]);

			switch (lun_string[0] & 0xC0) {
			case FCP_LUN_ADDRESSING:
			case FCP_PD_ADDRESSING:
				lun_num = ((lun_string[0] & 0x3F) << 8) |
				    lun_string[1];
				if (plun->lun_num == lun_num) {
					exists++;
					break;
				}
				break;

			default:
				break;
			}
		}

		if (!exists && !offline) {
			mutex_exit(&ptgt->tgt_mutex);

			mutex_enter(&pptr->port_mutex);
			mutex_enter(&ptgt->tgt_mutex);
			if (!FCP_STATE_CHANGED(pptr, ptgt, icmd)) {
				/*
				 * set disappear flag when device was connected
				 */
				if (!(plun->lun_state &
				    FCP_LUN_DEVICE_NOT_CONNECTED)) {
					plun->lun_state |= FCP_LUN_DISAPPEARED;
				}
				mutex_exit(&ptgt->tgt_mutex);
				mutex_exit(&pptr->port_mutex);
				if (!(plun->lun_state &
				    FCP_LUN_DEVICE_NOT_CONNECTED)) {
					fcp_log(CE_NOTE, pptr->port_dip,
					    "!Lun=%x for target=%x disappeared",
					    plun->lun_num, ptgt->tgt_d_id);
				}
				mutex_enter(&ptgt->tgt_mutex);
			} else {
				FCP_TRACE(fcp_logq, pptr->port_instbuf,
				    fcp_trace, FCP_BUF_LEVEL_5, 0,
				    "fcp_handle_reportlun,1: state change"
				    " occured for D_ID=0x%x", ptgt->tgt_d_id);
				mutex_exit(&ptgt->tgt_mutex);
				mutex_exit(&pptr->port_mutex);
				kmem_free(report_lun, len);
				(void) fcp_call_finish_init(pptr, ptgt,
				    icmd->ipkt_link_cnt, icmd->ipkt_change_cnt,
				    icmd->ipkt_cause);
				fcp_icmd_free(pptr, icmd);
				return;
			}
		} else if (exists) {
			/*
			 * clear FCP_LUN_DEVICE_NOT_CONNECTED when lun 0
			 * actually exists in REPORT_LUN response
			 */
			if (plun->lun_state & FCP_LUN_DEVICE_NOT_CONNECTED) {
				plun->lun_state &=
				    ~FCP_LUN_DEVICE_NOT_CONNECTED;
			}
			if (offline || plun->lun_num == 0) {
				if (plun->lun_state & FCP_LUN_DISAPPEARED)  {
					plun->lun_state &= ~FCP_LUN_DISAPPEARED;
					mutex_exit(&ptgt->tgt_mutex);
					fcp_log(CE_NOTE, pptr->port_dip,
					    "!Lun=%x for target=%x reappeared",
					    plun->lun_num, ptgt->tgt_d_id);
					mutex_enter(&ptgt->tgt_mutex);
				}
			}
		}
	}

	ptgt->tgt_tmp_cnt = nluns_claimed ? nluns_claimed : 1;
	mutex_exit(&ptgt->tgt_mutex);

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_5, 0,
	    "fcp_handle_reportlun: port=%d, tgt D_ID=0x%x, %d LUN(s)",
	    pptr->port_instance, ptgt->tgt_d_id, nluns_claimed);

	/* scan each lun */
	for (i = 0; i < nluns_claimed; i++) {
		uchar_t	*lun_string;

		lun_string = (uchar_t *)&(report_lun->lun_string[i]);

		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_5, 0,
		    "handle_reportlun: d_id=%x, LUN ind=%d, LUN=%d,"
		    " addr=0x%x", ptgt->tgt_d_id, i, lun_string[1],
		    lun_string[0]);

		switch (lun_string[0] & 0xC0) {
		case FCP_LUN_ADDRESSING:
		case FCP_PD_ADDRESSING:
			lun_num = ((lun_string[0] & 0x3F) << 8) | lun_string[1];

			/* We will skip masked LUNs because of the blacklist. */
			if (fcp_lun_blacklist != NULL) {
				mutex_enter(&ptgt->tgt_mutex);
				if (fcp_should_mask(&ptgt->tgt_port_wwn,
				    lun_num) == TRUE) {
					ptgt->tgt_lun_cnt--;
					mutex_exit(&ptgt->tgt_mutex);
					break;
				}
				mutex_exit(&ptgt->tgt_mutex);
			}

			/* see if this LUN is already allocated */
			if ((plun = fcp_get_lun(ptgt, lun_num)) == NULL) {
				plun = fcp_alloc_lun(ptgt);
				if (plun == NULL) {
					fcp_log(CE_NOTE, pptr->port_dip,
					    "!Lun allocation failed"
					    " target=%x lun=%x",
					    ptgt->tgt_d_id, lun_num);
					break;
				}
			}

			mutex_enter(&plun->lun_tgt->tgt_mutex);
			/* convert to LUN */
			plun->lun_addr.ent_addr_0 =
			    BE_16(*(uint16_t *)&(lun_string[0]));
			plun->lun_addr.ent_addr_1 =
			    BE_16(*(uint16_t *)&(lun_string[2]));
			plun->lun_addr.ent_addr_2 =
			    BE_16(*(uint16_t *)&(lun_string[4]));
			plun->lun_addr.ent_addr_3 =
			    BE_16(*(uint16_t *)&(lun_string[6]));

			plun->lun_num = lun_num;
			plun->lun_state |= FCP_LUN_BUSY | FCP_LUN_MARK;
			plun->lun_state &= ~FCP_LUN_OFFLINE;
			mutex_exit(&plun->lun_tgt->tgt_mutex);

			/* Retrieve the rscn count (if a valid one exists) */
			if (icmd->ipkt_fpkt->pkt_ulp_rscn_infop != NULL) {
				rscn_count = ((fc_ulp_rscn_info_t *)
				    (icmd->ipkt_fpkt->pkt_ulp_rscn_infop))->
				    ulp_rscn_count;
			} else {
				rscn_count = FC_INVALID_RSCN_COUNT;
			}

			if (fcp_send_scsi(plun, SCMD_INQUIRY, SUN_INQSIZE,
			    icmd->ipkt_link_cnt, icmd->ipkt_change_cnt,
			    icmd->ipkt_cause, rscn_count) != DDI_SUCCESS) {
				mutex_enter(&pptr->port_mutex);
				mutex_enter(&plun->lun_tgt->tgt_mutex);
				if (!FCP_STATE_CHANGED(pptr, ptgt, icmd)) {
					fcp_log(CE_NOTE, pptr->port_dip,
					    "!failed to send INQUIRY"
					    " target=%x lun=%x",
					    ptgt->tgt_d_id, plun->lun_num);
				} else {
					FCP_TRACE(fcp_logq,
					    pptr->port_instbuf, fcp_trace,
					    FCP_BUF_LEVEL_5, 0,
					    "fcp_handle_reportlun,2: state"
					    " change occured for D_ID=0x%x",
					    ptgt->tgt_d_id);
				}
				mutex_exit(&plun->lun_tgt->tgt_mutex);
				mutex_exit(&pptr->port_mutex);
			} else {
				continue;
			}
			break;

		case FCP_VOLUME_ADDRESSING:
			/* FALLTHROUGH */
		default:
			fcp_log(CE_WARN, NULL,
			    "!Unsupported LUN Addressing method %x "
			    "in response to REPORT_LUN", lun_string[0]);
			break;
		}

		/*
		 * each time through this loop we should decrement
		 * the tmp_cnt by one -- since we go through this loop
		 * one time for each LUN, the tmp_cnt should never be <=0
		 */
		(void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt,
		    icmd->ipkt_change_cnt, icmd->ipkt_cause);
	}

	if (i == 0) {
		fcp_log(CE_WARN, pptr->port_dip,
		    "!FCP: target=%x reported NO Luns", ptgt->tgt_d_id);
		(void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt,
		    icmd->ipkt_change_cnt, icmd->ipkt_cause);
	}

	kmem_free(report_lun, len);
	fcp_icmd_free(pptr, icmd);
}


/*
 * called internally to return a LUN given a target and a LUN number
 */
static struct fcp_lun *
fcp_get_lun(struct fcp_tgt *ptgt, uint16_t lun_num)
{
	struct fcp_lun	*plun;

	mutex_enter(&ptgt->tgt_mutex);
	for (plun = ptgt->tgt_lun; plun != NULL; plun = plun->lun_next) {
		if (plun->lun_num == lun_num) {
			mutex_exit(&ptgt->tgt_mutex);
			return (plun);
		}
	}
	mutex_exit(&ptgt->tgt_mutex);

	return (NULL);
}


/*
 * handle finishing one target for fcp_finish_init
 *
 * return true (non-zero) if we want finish_init to continue with the
 * next target
 *
 * called with the port mutex held
 */
/*ARGSUSED*/
static int
fcp_finish_tgt(struct fcp_port *pptr, struct fcp_tgt *ptgt,
    int link_cnt, int tgt_cnt, int cause)
{
	int	rval = 1;
	ASSERT(pptr != NULL);
	ASSERT(ptgt != NULL);

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_5, 0,
	    "finish_tgt: D_ID/state = 0x%x/0x%x", ptgt->tgt_d_id,
	    ptgt->tgt_state);

	ASSERT(mutex_owned(&pptr->port_mutex));

	if ((pptr->port_link_cnt != link_cnt) ||
	    (tgt_cnt && ptgt->tgt_change_cnt != tgt_cnt)) {
		/*
		 * oh oh -- another link reset or target change
		 * must have occurred while we are in here
		 */
		FCP_TGT_TRACE(ptgt, tgt_cnt, FCP_TGT_TRACE_23);

		return (0);
	} else {
		FCP_TGT_TRACE(ptgt, tgt_cnt, FCP_TGT_TRACE_24);
	}

	mutex_enter(&ptgt->tgt_mutex);

	if (!(ptgt->tgt_state & FCP_TGT_OFFLINE)) {
		/*
		 * tgt is not offline -- is it marked (i.e. needs
		 * to be offlined) ??
		 */
		if (ptgt->tgt_state & FCP_TGT_MARK) {
			/*
			 * this target not offline *and*
			 * marked
			 */
			ptgt->tgt_state &= ~FCP_TGT_MARK;
			rval = fcp_offline_target(pptr, ptgt, link_cnt,
			    tgt_cnt, 0, 0);
		} else {
			ptgt->tgt_state &= ~FCP_TGT_BUSY;

			/* create the LUNs */
			if (ptgt->tgt_node_state != FCP_TGT_NODE_ON_DEMAND) {
				ptgt->tgt_node_state = FCP_TGT_NODE_PRESENT;
				fcp_create_luns(ptgt, link_cnt, tgt_cnt,
				    cause);
				ptgt->tgt_device_created = 1;
			} else {
				fcp_update_tgt_state(ptgt, FCP_RESET,
				    FCP_LUN_BUSY);
			}
		}
	}

	mutex_exit(&ptgt->tgt_mutex);

	return (rval);
}


/*
 * this routine is called to finish port initialization
 *
 * Each port has a "temp" counter -- when a state change happens (e.g.
 * port online), the temp count is set to the number of devices in the map.
 * Then, as each device gets "discovered", the temp counter is decremented
 * by one.  When this count reaches zero we know that all of the devices
 * in the map have been discovered (or an error has occurred), so we can
 * then finish initialization -- which is done by this routine (well, this
 * and fcp-finish_tgt())
 *
 * acquires and releases the global mutex
 *
 * called with the port mutex owned
 */
static void
fcp_finish_init(struct fcp_port *pptr)
{
#ifdef	DEBUG
	bzero(pptr->port_finish_stack, sizeof (pptr->port_finish_stack));
	pptr->port_finish_depth = getpcstack(pptr->port_finish_stack,
	    FCP_STACK_DEPTH);
#endif /* DEBUG */

	ASSERT(mutex_owned(&pptr->port_mutex));

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_2, 0, "finish_init:"
	    " entering; ipkt count=%d", pptr->port_ipkt_cnt);

	if ((pptr->port_state & FCP_STATE_ONLINING) &&
	    !(pptr->port_state & (FCP_STATE_SUSPENDED |
	    FCP_STATE_DETACHING | FCP_STATE_POWER_DOWN))) {
		pptr->port_state &= ~FCP_STATE_ONLINING;
		pptr->port_state |= FCP_STATE_ONLINE;
	}

	/* Wake up threads waiting on config done */
	cv_broadcast(&pptr->port_config_cv);
}


/*
 * called from fcp_finish_init to create the LUNs for a target
 *
 * called with the port mutex owned
 */
static void
fcp_create_luns(struct fcp_tgt *ptgt, int link_cnt, int tgt_cnt, int cause)
{
	struct fcp_lun	*plun;
	struct fcp_port	*pptr;
	child_info_t		*cip = NULL;

	ASSERT(ptgt != NULL);
	ASSERT(mutex_owned(&ptgt->tgt_mutex));

	pptr = ptgt->tgt_port;

	ASSERT(pptr != NULL);

	/* scan all LUNs for this target */
	for (plun = ptgt->tgt_lun; plun != NULL; plun = plun->lun_next) {
		if (plun->lun_state & FCP_LUN_OFFLINE) {
			continue;
		}

		if (plun->lun_state & FCP_LUN_MARK) {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_2, 0,
			    "fcp_create_luns: offlining marked LUN!");
			fcp_offline_lun(plun, link_cnt, tgt_cnt, 1, 0);
			continue;
		}

		plun->lun_state &= ~FCP_LUN_BUSY;

		/*
		 * There are conditions in which FCP_LUN_INIT flag is cleared
		 * but we have a valid plun->lun_cip. To cover this case also
		 * CLEAR_BUSY whenever we have a valid lun_cip.
		 */
		if (plun->lun_mpxio && plun->lun_cip &&
		    (!fcp_pass_to_hp(pptr, plun, plun->lun_cip,
		    FCP_MPXIO_PATH_CLEAR_BUSY, link_cnt, tgt_cnt,
		    0, 0))) {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_2, 0,
			    "fcp_create_luns: enable lun %p failed!",
			    plun);
		}

		if (plun->lun_state & FCP_LUN_INIT &&
		    !(plun->lun_state & FCP_LUN_CHANGED)) {
			continue;
		}

		if (cause == FCP_CAUSE_USER_CREATE) {
			continue;
		}

		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_6, 0,
		    "create_luns: passing ONLINE elem to HP thread");

		/*
		 * If lun has changed, prepare for offlining the old path.
		 * Do not offline the old path right now, since it may be
		 * still opened.
		 */
		if (plun->lun_cip && (plun->lun_state & FCP_LUN_CHANGED)) {
			fcp_prepare_offline_lun(plun, link_cnt, tgt_cnt);
		}

		/* pass an ONLINE element to the hotplug thread */
		if (!fcp_pass_to_hp(pptr, plun, cip, FCP_ONLINE,
		    link_cnt, tgt_cnt, NDI_ONLINE_ATTACH, 0)) {

			/*
			 * We can not synchronous attach (i.e pass
			 * NDI_ONLINE_ATTACH) here as we might be
			 * coming from an interrupt or callback
			 * thread.
			 */
			if (!fcp_pass_to_hp(pptr, plun, cip, FCP_ONLINE,
			    link_cnt, tgt_cnt, 0, 0)) {
				fcp_log(CE_CONT, pptr->port_dip,
				    "Can not ONLINE LUN; D_ID=%x, LUN=%x\n",
				    plun->lun_tgt->tgt_d_id, plun->lun_num);
			}
		}
	}
}


/*
 * function to online/offline devices
 */
static int
fcp_trigger_lun(struct fcp_lun *plun, child_info_t *cip, int old_mpxio,
    int online, int lcount, int tcount, int flags)
{
	int			rval = NDI_FAILURE;
	int			circ;
	child_info_t		*ccip;
	struct fcp_port		*pptr = plun->lun_tgt->tgt_port;
	int			is_mpxio = pptr->port_mpxio;
	dev_info_t		*cdip, *pdip;
	char			*devname;

	if ((old_mpxio != 0) && (plun->lun_mpxio != old_mpxio)) {
		/*
		 * When this event gets serviced, lun_cip and lun_mpxio
		 * has changed, so it should be invalidated now.
		 */
		FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace,
		    FCP_BUF_LEVEL_2, 0, "fcp_trigger_lun: lun_mpxio changed: "
		    "plun: %p, cip: %p, what:%d", plun, cip, online);
		return (rval);
	}

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_2, 0,
	    "fcp_trigger_lun: plun=%p target=%x lun=%d cip=%p what=%x "
	    "flags=%x mpxio=%x\n",
	    plun, LUN_TGT->tgt_d_id, plun->lun_num, cip, online, flags,
	    plun->lun_mpxio);

	/*
	 * lun_mpxio needs checking here because we can end up in a race
	 * condition where this task has been dispatched while lun_mpxio is
	 * set, but an earlier FCP_ONLINE task for the same LUN tried to
	 * enable MPXIO for the LUN, but was unable to, and hence cleared
	 * the flag. We rely on the serialization of the tasks here. We return
	 * NDI_SUCCESS so any callers continue without reporting spurious
	 * errors, and the still think we're an MPXIO LUN.
	 */

	if (online == FCP_MPXIO_PATH_CLEAR_BUSY ||
	    online == FCP_MPXIO_PATH_SET_BUSY) {
		if (plun->lun_mpxio) {
			rval = fcp_update_mpxio_path(plun, cip, online);
		} else {
			rval = NDI_SUCCESS;
		}
		return (rval);
	}

	/*
	 * Explicit devfs_clean() due to ndi_devi_offline() not
	 * executing devfs_clean() if parent lock is held.
	 */
	ASSERT(!servicing_interrupt());
	if (online == FCP_OFFLINE) {
		if (plun->lun_mpxio == 0) {
			if (plun->lun_cip == cip) {
				cdip = DIP(plun->lun_cip);
			} else {
				cdip = DIP(cip);
			}
		} else if ((plun->lun_cip == cip) && plun->lun_cip) {
			cdip = mdi_pi_get_client(PIP(plun->lun_cip));
		} else if ((plun->lun_cip != cip) && cip) {
			/*
			 * This means a DTYPE/GUID change, we shall get the
			 * dip of the old cip instead of the current lun_cip.
			 */
			cdip = mdi_pi_get_client(PIP(cip));
		}
		if (cdip) {
			if (i_ddi_devi_attached(cdip)) {
				pdip = ddi_get_parent(cdip);
				devname = kmem_alloc(MAXNAMELEN + 1, KM_SLEEP);
				ndi_devi_enter(pdip, &circ);
				(void) ddi_deviname(cdip, devname);
				ndi_devi_exit(pdip, circ);
				/*
				 * Release parent lock before calling
				 * devfs_clean().
				 */
				rval = devfs_clean(pdip, devname + 1,
				    DV_CLEAN_FORCE);
				kmem_free(devname, MAXNAMELEN + 1);
				/*
				 * Return if devfs_clean() fails for
				 * non-MPXIO case.
				 * For MPXIO case, another path could be
				 * offlined.
				 */
				if (rval && plun->lun_mpxio == 0) {
					FCP_TRACE(fcp_logq, pptr->port_instbuf,
					    fcp_trace, FCP_BUF_LEVEL_3, 0,
					    "fcp_trigger_lun: devfs_clean "
					    "failed rval=%x  dip=%p",
					    rval, pdip);
					return (NDI_FAILURE);
				}
			}
		}
	}

	if (fc_ulp_busy_port(pptr->port_fp_handle) != 0) {
		return (NDI_FAILURE);
	}

	if (is_mpxio) {
		mdi_devi_enter(pptr->port_dip, &circ);
	} else {
		ndi_devi_enter(pptr->port_dip, &circ);
	}

	mutex_enter(&pptr->port_mutex);
	mutex_enter(&plun->lun_mutex);

	if (online == FCP_ONLINE) {
		ccip = fcp_get_cip(plun, cip, lcount, tcount);
		if (ccip == NULL) {
			goto fail;
		}
	} else {
		if (fcp_is_child_present(plun, cip) != FC_SUCCESS) {
			goto fail;
		}
		ccip = cip;
	}

	if (online == FCP_ONLINE) {
		rval = fcp_online_child(plun, ccip, lcount, tcount, flags,
		    &circ);
		fc_ulp_log_device_event(pptr->port_fp_handle,
		    FC_ULP_DEVICE_ONLINE);
	} else {
		rval = fcp_offline_child(plun, ccip, lcount, tcount, flags,
		    &circ);
		fc_ulp_log_device_event(pptr->port_fp_handle,
		    FC_ULP_DEVICE_OFFLINE);
	}

fail:	mutex_exit(&plun->lun_mutex);
	mutex_exit(&pptr->port_mutex);

	if (is_mpxio) {
		mdi_devi_exit(pptr->port_dip, circ);
	} else {
		ndi_devi_exit(pptr->port_dip, circ);
	}

	fc_ulp_idle_port(pptr->port_fp_handle);

	return (rval);
}


/*
 * take a target offline by taking all of its LUNs offline
 */
/*ARGSUSED*/
static int
fcp_offline_target(struct fcp_port *pptr, struct fcp_tgt *ptgt,
    int link_cnt, int tgt_cnt, int nowait, int flags)
{
	struct fcp_tgt_elem	*elem;

	ASSERT(mutex_owned(&pptr->port_mutex));
	ASSERT(mutex_owned(&ptgt->tgt_mutex));

	ASSERT(!(ptgt->tgt_state & FCP_TGT_OFFLINE));

	if (link_cnt != pptr->port_link_cnt || (tgt_cnt && tgt_cnt !=
	    ptgt->tgt_change_cnt)) {
		mutex_exit(&ptgt->tgt_mutex);
		FCP_TGT_TRACE(ptgt, tgt_cnt, FCP_TGT_TRACE_25);
		mutex_enter(&ptgt->tgt_mutex);

		return (0);
	}

	ptgt->tgt_pd_handle = NULL;
	mutex_exit(&ptgt->tgt_mutex);
	FCP_TGT_TRACE(ptgt, tgt_cnt, FCP_TGT_TRACE_26);
	mutex_enter(&ptgt->tgt_mutex);

	tgt_cnt = tgt_cnt ? tgt_cnt : ptgt->tgt_change_cnt;

	if (ptgt->tgt_tcap &&
	    (elem = kmem_zalloc(sizeof (*elem), KM_NOSLEEP)) != NULL) {
		elem->flags = flags;
		elem->time = fcp_watchdog_time;
		if (nowait == 0) {
			elem->time += fcp_offline_delay;
		}
		elem->ptgt = ptgt;
		elem->link_cnt = link_cnt;
		elem->tgt_cnt = tgt_cnt;
		elem->next = pptr->port_offline_tgts;
		pptr->port_offline_tgts = elem;
	} else {
		fcp_offline_target_now(pptr, ptgt, link_cnt, tgt_cnt, flags);
	}

	return (1);
}


static void
fcp_offline_target_now(struct fcp_port *pptr, struct fcp_tgt *ptgt,
    int link_cnt, int tgt_cnt, int flags)
{
	ASSERT(mutex_owned(&pptr->port_mutex));
	ASSERT(mutex_owned(&ptgt->tgt_mutex));

	fc_ulp_enable_relogin(pptr->port_fp_handle, &ptgt->tgt_port_wwn);
	ptgt->tgt_state = FCP_TGT_OFFLINE;
	ptgt->tgt_pd_handle = NULL;
	fcp_offline_tgt_luns(ptgt, link_cnt, tgt_cnt, flags);
}


static void
fcp_offline_tgt_luns(struct fcp_tgt *ptgt, int link_cnt, int tgt_cnt,
    int flags)
{
	struct	fcp_lun	*plun;

	ASSERT(mutex_owned(&ptgt->tgt_port->port_mutex));
	ASSERT(mutex_owned(&ptgt->tgt_mutex));

	for (plun = ptgt->tgt_lun; plun != NULL; plun = plun->lun_next) {
		if (!(plun->lun_state & FCP_LUN_OFFLINE)) {
			fcp_offline_lun(plun, link_cnt, tgt_cnt, 1, flags);
		}
	}
}


/*
 * take a LUN offline
 *
 * enters and leaves with the target mutex held, releasing it in the process
 *
 * allocates memory in non-sleep mode
 */
static void
fcp_offline_lun(struct fcp_lun *plun, int link_cnt, int tgt_cnt,
    int nowait, int flags)
{
	struct fcp_port	*pptr = plun->lun_tgt->tgt_port;
	struct fcp_lun_elem	*elem;

	ASSERT(plun != NULL);
	ASSERT(mutex_owned(&LUN_TGT->tgt_mutex));

	if (nowait) {
		fcp_offline_lun_now(plun, link_cnt, tgt_cnt, flags);
		return;
	}

	if ((elem = kmem_zalloc(sizeof (*elem), KM_NOSLEEP)) != NULL) {
		elem->flags = flags;
		elem->time = fcp_watchdog_time;
		if (nowait == 0) {
			elem->time += fcp_offline_delay;
		}
		elem->plun = plun;
		elem->link_cnt = link_cnt;
		elem->tgt_cnt = plun->lun_tgt->tgt_change_cnt;
		elem->next = pptr->port_offline_luns;
		pptr->port_offline_luns = elem;
	} else {
		fcp_offline_lun_now(plun, link_cnt, tgt_cnt, flags);
	}
}


static void
fcp_prepare_offline_lun(struct fcp_lun *plun, int link_cnt, int tgt_cnt)
{
	struct fcp_pkt	*head = NULL;

	ASSERT(mutex_owned(&LUN_TGT->tgt_mutex));

	mutex_exit(&LUN_TGT->tgt_mutex);

	head = fcp_scan_commands(plun);
	if (head != NULL) {
		fcp_abort_commands(head, LUN_PORT);
	}

	mutex_enter(&LUN_TGT->tgt_mutex);

	if (plun->lun_cip && plun->lun_mpxio) {
		/*
		 * Intimate MPxIO lun busy is cleared
		 */
		if (!fcp_pass_to_hp(LUN_PORT, plun, plun->lun_cip,
		    FCP_MPXIO_PATH_CLEAR_BUSY, link_cnt, tgt_cnt,
		    0, 0)) {
			fcp_log(CE_NOTE, LUN_PORT->port_dip,
			    "Can not ENABLE LUN; D_ID=%x, LUN=%x",
			    LUN_TGT->tgt_d_id, plun->lun_num);
		}
		/*
		 * Intimate MPxIO that the lun is now marked for offline
		 */
		mutex_exit(&LUN_TGT->tgt_mutex);
		(void) mdi_pi_disable_path(PIP(plun->lun_cip), DRIVER_DISABLE);
		mutex_enter(&LUN_TGT->tgt_mutex);
	}
}

static void
fcp_offline_lun_now(struct fcp_lun *plun, int link_cnt, int tgt_cnt,
    int flags)
{
	ASSERT(mutex_owned(&LUN_TGT->tgt_mutex));

	mutex_exit(&LUN_TGT->tgt_mutex);
	fcp_update_offline_flags(plun);
	mutex_enter(&LUN_TGT->tgt_mutex);

	fcp_prepare_offline_lun(plun, link_cnt, tgt_cnt);

	FCP_TRACE(fcp_logq, LUN_PORT->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_4, 0,
	    "offline_lun: passing OFFLINE elem to HP thread");

	if (plun->lun_cip) {
		fcp_log(CE_NOTE, LUN_PORT->port_dip,
		    "!offlining lun=%x (trace=%x), target=%x (trace=%x)",
		    plun->lun_num, plun->lun_trace, LUN_TGT->tgt_d_id,
		    LUN_TGT->tgt_trace);

		if (!fcp_pass_to_hp(LUN_PORT, plun, plun->lun_cip, FCP_OFFLINE,
		    link_cnt, tgt_cnt, flags, 0)) {
			fcp_log(CE_CONT, LUN_PORT->port_dip,
			    "Can not OFFLINE LUN; D_ID=%x, LUN=%x\n",
			    LUN_TGT->tgt_d_id, plun->lun_num);
		}
	}
}

static void
fcp_scan_offline_luns(struct fcp_port *pptr)
{
	struct fcp_lun_elem	*elem;
	struct fcp_lun_elem	*prev;
	struct fcp_lun_elem	*next;

	ASSERT(MUTEX_HELD(&pptr->port_mutex));

	prev = NULL;
	elem = pptr->port_offline_luns;
	while (elem) {
		next = elem->next;
		if (elem->time <= fcp_watchdog_time) {
			int			changed = 1;
			struct fcp_tgt	*ptgt = elem->plun->lun_tgt;

			mutex_enter(&ptgt->tgt_mutex);
			if (pptr->port_link_cnt == elem->link_cnt &&
			    ptgt->tgt_change_cnt == elem->tgt_cnt) {
				changed = 0;
			}

			if (!changed &&
			    !(elem->plun->lun_state & FCP_TGT_OFFLINE)) {
				fcp_offline_lun_now(elem->plun,
				    elem->link_cnt, elem->tgt_cnt, elem->flags);
			}
			mutex_exit(&ptgt->tgt_mutex);

			kmem_free(elem, sizeof (*elem));

			if (prev) {
				prev->next = next;
			} else {
				pptr->port_offline_luns = next;
			}
		} else {
			prev = elem;
		}
		elem = next;
	}
}


static void
fcp_scan_offline_tgts(struct fcp_port *pptr)
{
	struct fcp_tgt_elem	*elem;
	struct fcp_tgt_elem	*prev;
	struct fcp_tgt_elem	*next;

	ASSERT(MUTEX_HELD(&pptr->port_mutex));

	prev = NULL;
	elem = pptr->port_offline_tgts;
	while (elem) {
		next = elem->next;
		if (elem->time <= fcp_watchdog_time) {
			int			changed = 1;
			struct fcp_tgt	*ptgt = elem->ptgt;

			if (ptgt->tgt_change_cnt == elem->tgt_cnt) {
				changed = 0;
			}

			mutex_enter(&ptgt->tgt_mutex);
			if (!changed && !(ptgt->tgt_state &
			    FCP_TGT_OFFLINE)) {
				fcp_offline_target_now(pptr,
				    ptgt, elem->link_cnt, elem->tgt_cnt,
				    elem->flags);
			}
			mutex_exit(&ptgt->tgt_mutex);

			kmem_free(elem, sizeof (*elem));

			if (prev) {
				prev->next = next;
			} else {
				pptr->port_offline_tgts = next;
			}
		} else {
			prev = elem;
		}
		elem = next;
	}
}


static void
fcp_update_offline_flags(struct fcp_lun *plun)
{
	struct fcp_port	*pptr = LUN_PORT;
	ASSERT(plun != NULL);

	mutex_enter(&LUN_TGT->tgt_mutex);
	plun->lun_state |= FCP_LUN_OFFLINE;
	plun->lun_state &= ~(FCP_LUN_INIT | FCP_LUN_BUSY | FCP_LUN_MARK);

	mutex_enter(&plun->lun_mutex);
	if (plun->lun_cip && plun->lun_state & FCP_SCSI_LUN_TGT_INIT) {
		dev_info_t *cdip = NULL;

		mutex_exit(&LUN_TGT->tgt_mutex);

		if (plun->lun_mpxio == 0) {
			cdip = DIP(plun->lun_cip);
		} else if (plun->lun_cip) {
			cdip = mdi_pi_get_client(PIP(plun->lun_cip));
		}

		mutex_exit(&plun->lun_mutex);
		if (cdip) {
			(void) ndi_event_retrieve_cookie(
			    pptr->port_ndi_event_hdl, cdip, FCAL_REMOVE_EVENT,
			    &fcp_remove_eid, NDI_EVENT_NOPASS);
			(void) ndi_event_run_callbacks(
			    pptr->port_ndi_event_hdl, cdip,
			    fcp_remove_eid, NULL);
		}
	} else {
		mutex_exit(&plun->lun_mutex);
		mutex_exit(&LUN_TGT->tgt_mutex);
	}
}


/*
 * Scan all of the command pkts for this port, moving pkts that
 * match our LUN onto our own list (headed by "head")
 */
static struct fcp_pkt *
fcp_scan_commands(struct fcp_lun *plun)
{
	struct fcp_port	*pptr = LUN_PORT;

	struct fcp_pkt	*cmd = NULL;	/* pkt cmd ptr */
	struct fcp_pkt	*ncmd = NULL;	/* next pkt ptr */
	struct fcp_pkt	*pcmd = NULL;	/* the previous command */

	struct fcp_pkt	*head = NULL;	/* head of our list */
	struct fcp_pkt	*tail = NULL;	/* tail of our list */

	int			cmds_found = 0;

	mutex_enter(&pptr->port_pkt_mutex);
	for (cmd = pptr->port_pkt_head; cmd != NULL; cmd = ncmd) {
		struct fcp_lun *tlun =
		    ADDR2LUN(&cmd->cmd_pkt->pkt_address);

		ncmd = cmd->cmd_next;	/* set next command */

		/*
		 * if this pkt is for a different LUN  or the
		 * command is sent down, skip it.
		 */
		if (tlun != plun || cmd->cmd_state == FCP_PKT_ISSUED ||
		    (cmd->cmd_pkt->pkt_flags & FLAG_NOINTR)) {
			pcmd = cmd;
			continue;
		}
		cmds_found++;
		if (pcmd != NULL) {
			ASSERT(pptr->port_pkt_head != cmd);
			pcmd->cmd_next = cmd->cmd_next;
		} else {
			ASSERT(cmd == pptr->port_pkt_head);
			pptr->port_pkt_head = cmd->cmd_next;
		}

		if (cmd == pptr->port_pkt_tail) {
			pptr->port_pkt_tail = pcmd;
			if (pcmd) {
				pcmd->cmd_next = NULL;
			}
		}

		if (head == NULL) {
			head = tail = cmd;
		} else {
			ASSERT(tail != NULL);

			tail->cmd_next = cmd;
			tail = cmd;
		}
		cmd->cmd_next = NULL;
	}
	mutex_exit(&pptr->port_pkt_mutex);

	FCP_DTRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_8, 0,
	    "scan commands: %d cmd(s) found", cmds_found);

	return (head);
}


/*
 * Abort all the commands in the command queue
 */
static void
fcp_abort_commands(struct fcp_pkt *head, struct fcp_port *pptr)
{
	struct fcp_pkt	*cmd = NULL;	/* pkt cmd ptr */
	struct	fcp_pkt	*ncmd = NULL;	/* next pkt ptr */

	ASSERT(mutex_owned(&pptr->port_mutex));

	/* scan through the pkts and invalid them */
	for (cmd = head; cmd != NULL; cmd = ncmd) {
		struct scsi_pkt *pkt = cmd->cmd_pkt;

		ncmd = cmd->cmd_next;
		ASSERT(pkt != NULL);

		/*
		 * The lun is going to be marked offline. Indicate
		 * the target driver not to requeue or retry this command
		 * as the device is going to be offlined pretty soon.
		 */
		pkt->pkt_reason = CMD_DEV_GONE;
		pkt->pkt_statistics = 0;
		pkt->pkt_state = 0;

		/* reset cmd flags/state */
		cmd->cmd_flags &= ~CFLAG_IN_QUEUE;
		cmd->cmd_state = FCP_PKT_IDLE;

		/*
		 * ensure we have a packet completion routine,
		 * then call it.
		 */
		ASSERT(pkt->pkt_comp != NULL);

		mutex_exit(&pptr->port_mutex);
		fcp_post_callback(cmd);
		mutex_enter(&pptr->port_mutex);
	}
}


/*
 * the pkt_comp callback for command packets
 */
static void
fcp_cmd_callback(fc_packet_t *fpkt)
{
	struct fcp_pkt *cmd = (struct fcp_pkt *)fpkt->pkt_ulp_private;
	struct scsi_pkt *pkt = cmd->cmd_pkt;
	struct fcp_port *pptr = ADDR2FCP(&pkt->pkt_address);

	ASSERT(cmd->cmd_state != FCP_PKT_IDLE);

	if (cmd->cmd_state == FCP_PKT_IDLE) {
		cmn_err(CE_PANIC, "Packet already completed %p",
		    (void *)cmd);
	}

	/*
	 * Watch thread should be freeing the packet, ignore the pkt.
	 */
	if (cmd->cmd_state == FCP_PKT_ABORTING) {
		fcp_log(CE_CONT, pptr->port_dip,
		    "!FCP: Pkt completed while aborting\n");
		return;
	}
	cmd->cmd_state = FCP_PKT_IDLE;

	fcp_complete_pkt(fpkt);

#ifdef	DEBUG
	mutex_enter(&pptr->port_pkt_mutex);
	pptr->port_npkts--;
	mutex_exit(&pptr->port_pkt_mutex);
#endif /* DEBUG */

	fcp_post_callback(cmd);
}


static void
fcp_complete_pkt(fc_packet_t *fpkt)
{
	int			error = 0;
	struct fcp_pkt	*cmd = (struct fcp_pkt *)
	    fpkt->pkt_ulp_private;
	struct scsi_pkt		*pkt = cmd->cmd_pkt;
	struct fcp_port		*pptr = ADDR2FCP(&pkt->pkt_address);
	struct fcp_lun	*plun;
	struct fcp_tgt	*ptgt;
	struct fcp_rsp		*rsp;
	struct scsi_address	save;

#ifdef	DEBUG
	save = pkt->pkt_address;
#endif /* DEBUG */

	rsp = (struct fcp_rsp *)cmd->cmd_fcp_rsp;

	if (fpkt->pkt_state == FC_PKT_SUCCESS) {
		if (pptr->port_fcp_dma != FC_NO_DVMA_SPACE) {
			FCP_CP_IN(fpkt->pkt_resp, rsp, fpkt->pkt_resp_acc,
			    sizeof (struct fcp_rsp));
		}

		pkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
		    STATE_SENT_CMD | STATE_GOT_STATUS;

		pkt->pkt_resid = 0;

		if (cmd->cmd_pkt->pkt_numcookies) {
			pkt->pkt_state |= STATE_XFERRED_DATA;
			if (fpkt->pkt_data_resid) {
				error++;
			}
		}

		if ((pkt->pkt_scbp != NULL) && ((*(pkt->pkt_scbp) =
		    rsp->fcp_u.fcp_status.scsi_status) != STATUS_GOOD)) {
			/*
			 * The next two checks make sure that if there
			 * is no sense data or a valid response and
			 * the command came back with check condition,
			 * the command should be retried.
			 */
			if (!rsp->fcp_u.fcp_status.rsp_len_set &&
			    !rsp->fcp_u.fcp_status.sense_len_set) {
				pkt->pkt_state &= ~STATE_XFERRED_DATA;
				pkt->pkt_resid = cmd->cmd_dmacount;
			}
		}

		if ((error | rsp->fcp_u.i_fcp_status | rsp->fcp_resid) == 0) {
			return;
		}

		plun = ADDR2LUN(&pkt->pkt_address);
		ptgt = plun->lun_tgt;
		ASSERT(ptgt != NULL);

		/*
		 * Update the transfer resid, if appropriate
		 */
		if (rsp->fcp_u.fcp_status.resid_over ||
		    rsp->fcp_u.fcp_status.resid_under) {
			pkt->pkt_resid = rsp->fcp_resid;
		}

		/*
		 * First see if we got a FCP protocol error.
		 */
		if (rsp->fcp_u.fcp_status.rsp_len_set) {
			struct fcp_rsp_info	*bep;
			bep = (struct fcp_rsp_info *)(cmd->cmd_fcp_rsp +
			    sizeof (struct fcp_rsp));

			if (fcp_validate_fcp_response(rsp, pptr) !=
			    FC_SUCCESS) {
				pkt->pkt_reason = CMD_CMPLT;
				*(pkt->pkt_scbp) = STATUS_CHECK;

				fcp_log(CE_WARN, pptr->port_dip,
				    "!SCSI command to d_id=0x%x lun=0x%x"
				    " failed, Bad FCP response values:"
				    " rsvd1=%x, rsvd2=%x, sts-rsvd1=%x,"
				    " sts-rsvd2=%x, rsplen=%x, senselen=%x",
				    ptgt->tgt_d_id, plun->lun_num,
				    rsp->reserved_0, rsp->reserved_1,
				    rsp->fcp_u.fcp_status.reserved_0,
				    rsp->fcp_u.fcp_status.reserved_1,
				    rsp->fcp_response_len, rsp->fcp_sense_len);

				return;
			}

			if (pptr->port_fcp_dma != FC_NO_DVMA_SPACE) {
				FCP_CP_IN(fpkt->pkt_resp +
				    sizeof (struct fcp_rsp), bep,
				    fpkt->pkt_resp_acc,
				    sizeof (struct fcp_rsp_info));
			}

			if (bep->rsp_code != FCP_NO_FAILURE) {
				child_info_t	*cip;

				pkt->pkt_reason = CMD_TRAN_ERR;

				mutex_enter(&plun->lun_mutex);
				cip = plun->lun_cip;
				mutex_exit(&plun->lun_mutex);

				FCP_TRACE(fcp_logq, pptr->port_instbuf,
				    fcp_trace, FCP_BUF_LEVEL_2, 0,
				    "FCP response error on cmd=%p"
				    " target=0x%x, cip=%p", cmd,
				    ptgt->tgt_d_id, cip);
			}
		}

		/*
		 * See if we got a SCSI error with sense data
		 */
		if (rsp->fcp_u.fcp_status.sense_len_set) {
			uchar_t				rqlen;
			caddr_t				sense_from;
			child_info_t			*cip;
			timeout_id_t			tid;
			struct scsi_arq_status		*arq;
			struct scsi_extended_sense	*sense_to;

			arq = (struct scsi_arq_status *)pkt->pkt_scbp;
			sense_to = &arq->sts_sensedata;

			rqlen = (uchar_t)min(rsp->fcp_sense_len,
			    sizeof (struct scsi_extended_sense));

			sense_from = (caddr_t)fpkt->pkt_resp +
			    sizeof (struct fcp_rsp) + rsp->fcp_response_len;

			if (fcp_validate_fcp_response(rsp, pptr) !=
			    FC_SUCCESS) {
				pkt->pkt_reason = CMD_CMPLT;
				*(pkt->pkt_scbp) = STATUS_CHECK;

				fcp_log(CE_WARN, pptr->port_dip,
				    "!SCSI command to d_id=0x%x lun=0x%x"
				    " failed, Bad FCP response values:"
				    " rsvd1=%x, rsvd2=%x, sts-rsvd1=%x,"
				    " sts-rsvd2=%x, rsplen=%x, senselen=%x",
				    ptgt->tgt_d_id, plun->lun_num,
				    rsp->reserved_0, rsp->reserved_1,
				    rsp->fcp_u.fcp_status.reserved_0,
				    rsp->fcp_u.fcp_status.reserved_1,
				    rsp->fcp_response_len, rsp->fcp_sense_len);

				return;
			}

			/*
			 * copy in sense information
			 */
			if (pptr->port_fcp_dma != FC_NO_DVMA_SPACE) {
				FCP_CP_IN(sense_from, sense_to,
				    fpkt->pkt_resp_acc, rqlen);
			} else {
				bcopy(sense_from, sense_to, rqlen);
			}

			if ((FCP_SENSE_REPORTLUN_CHANGED(sense_to)) ||
			    (FCP_SENSE_NO_LUN(sense_to))) {
				mutex_enter(&ptgt->tgt_mutex);
				if (ptgt->tgt_tid == NULL) {
					/*
					 * Kick off rediscovery
					 */
					tid = timeout(fcp_reconfigure_luns,
					    (caddr_t)ptgt, drv_usectohz(1));

					ptgt->tgt_tid = tid;
					ptgt->tgt_state |= FCP_TGT_BUSY;
				}
				mutex_exit(&ptgt->tgt_mutex);
				if (FCP_SENSE_REPORTLUN_CHANGED(sense_to)) {
					FCP_TRACE(fcp_logq, pptr->port_instbuf,
					    fcp_trace, FCP_BUF_LEVEL_3, 0,
					    "!FCP: Report Lun Has Changed"
					    " target=%x", ptgt->tgt_d_id);
				} else if (FCP_SENSE_NO_LUN(sense_to)) {
					FCP_TRACE(fcp_logq, pptr->port_instbuf,
					    fcp_trace, FCP_BUF_LEVEL_3, 0,
					    "!FCP: LU Not Supported"
					    " target=%x", ptgt->tgt_d_id);
				}
			}
			ASSERT(pkt->pkt_scbp != NULL);

			pkt->pkt_state |= STATE_ARQ_DONE;

			arq->sts_rqpkt_resid = SENSE_LENGTH - rqlen;

			*((uchar_t *)&arq->sts_rqpkt_status) = STATUS_GOOD;
			arq->sts_rqpkt_reason = 0;
			arq->sts_rqpkt_statistics = 0;

			arq->sts_rqpkt_state = STATE_GOT_BUS |
			    STATE_GOT_TARGET | STATE_SENT_CMD |
			    STATE_GOT_STATUS | STATE_ARQ_DONE |
			    STATE_XFERRED_DATA;

			mutex_enter(&plun->lun_mutex);
			cip = plun->lun_cip;
			mutex_exit(&plun->lun_mutex);

			FCP_DTRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_8, 0,
			    "SCSI Check condition on cmd=%p target=0x%x"
			    " LUN=%p, cmd=%x SCSI status=%x, es key=%x"
			    " ASC=%x ASCQ=%x", cmd, ptgt->tgt_d_id, cip,
			    cmd->cmd_fcp_cmd.fcp_cdb[0],
			    rsp->fcp_u.fcp_status.scsi_status,
			    sense_to->es_key, sense_to->es_add_code,
			    sense_to->es_qual_code);
		}
	} else {
		plun = ADDR2LUN(&pkt->pkt_address);
		ptgt = plun->lun_tgt;
		ASSERT(ptgt != NULL);

		/*
		 * Work harder to translate errors into target driver
		 * understandable ones. Note with despair that the target
		 * drivers don't decode pkt_state and pkt_reason exhaustively
		 * They resort to using the big hammer most often, which
		 * may not get fixed in the life time of this driver.
		 */
		pkt->pkt_state = 0;
		pkt->pkt_statistics = 0;

		switch (fpkt->pkt_state) {
		case FC_PKT_TRAN_ERROR:
			switch (fpkt->pkt_reason) {
			case FC_REASON_OVERRUN:
				pkt->pkt_reason = CMD_CMD_OVR;
				pkt->pkt_statistics |= STAT_ABORTED;
				break;

			case FC_REASON_XCHG_BSY: {
				caddr_t ptr;

				pkt->pkt_reason = CMD_CMPLT;	/* Lie */

				ptr = (caddr_t)pkt->pkt_scbp;
				if (ptr) {
					*ptr = STATUS_BUSY;
				}
				break;
			}

			case FC_REASON_ABORTED:
				pkt->pkt_reason = CMD_TRAN_ERR;
				pkt->pkt_statistics |= STAT_ABORTED;
				break;

			case FC_REASON_ABORT_FAILED:
				pkt->pkt_reason = CMD_ABORT_FAIL;
				break;

			case FC_REASON_NO_SEQ_INIT:
			case FC_REASON_CRC_ERROR:
				pkt->pkt_reason = CMD_TRAN_ERR;
				pkt->pkt_statistics |= STAT_ABORTED;
				break;
			default:
				pkt->pkt_reason = CMD_TRAN_ERR;
				break;
			}
			break;

		case FC_PKT_PORT_OFFLINE: {
			dev_info_t	*cdip = NULL;
			caddr_t		ptr;

			if (fpkt->pkt_reason == FC_REASON_LOGIN_REQUIRED) {
				FCP_DTRACE(fcp_logq, pptr->port_instbuf,
				    fcp_trace, FCP_BUF_LEVEL_8, 0,
				    "SCSI cmd; LOGIN REQUIRED from FCA for %x",
				    ptgt->tgt_d_id);
			}

			mutex_enter(&plun->lun_mutex);
			if (plun->lun_mpxio == 0) {
				cdip = DIP(plun->lun_cip);
			} else if (plun->lun_cip) {
				cdip = mdi_pi_get_client(PIP(plun->lun_cip));
			}

			mutex_exit(&plun->lun_mutex);

			if (cdip) {
				(void) ndi_event_retrieve_cookie(
				    pptr->port_ndi_event_hdl, cdip,
				    FCAL_REMOVE_EVENT, &fcp_remove_eid,
				    NDI_EVENT_NOPASS);
				(void) ndi_event_run_callbacks(
				    pptr->port_ndi_event_hdl, cdip,
				    fcp_remove_eid, NULL);
			}

			/*
			 * If the link goes off-line for a lip,
			 * this will cause a error to the ST SG
			 * SGEN drivers. By setting BUSY we will
			 * give the drivers the chance to retry
			 * before it blows of the job. ST will
			 * remember how many times it has retried.
			 */

			if ((plun->lun_type == DTYPE_SEQUENTIAL) ||
			    (plun->lun_type == DTYPE_CHANGER)) {
				pkt->pkt_reason = CMD_CMPLT;	/* Lie */
				ptr = (caddr_t)pkt->pkt_scbp;
				if (ptr) {
					*ptr = STATUS_BUSY;
				}
			} else {
				pkt->pkt_reason = CMD_TRAN_ERR;
				pkt->pkt_statistics |= STAT_BUS_RESET;
			}
			break;
		}

		case FC_PKT_TRAN_BSY:
			/*
			 * Use the ssd Qfull handling here.
			 */
			*pkt->pkt_scbp = STATUS_INTERMEDIATE;
			pkt->pkt_state = STATE_GOT_BUS;
			break;

		case FC_PKT_TIMEOUT:
			pkt->pkt_reason = CMD_TIMEOUT;
			if (fpkt->pkt_reason == FC_REASON_ABORT_FAILED) {
				pkt->pkt_statistics |= STAT_TIMEOUT;
			} else {
				pkt->pkt_statistics |= STAT_ABORTED;
			}
			break;

		case FC_PKT_LOCAL_RJT:
			switch (fpkt->pkt_reason) {
			case FC_REASON_OFFLINE: {
				dev_info_t	*cdip = NULL;

				mutex_enter(&plun->lun_mutex);
				if (plun->lun_mpxio == 0) {
					cdip = DIP(plun->lun_cip);
				} else if (plun->lun_cip) {
					cdip = mdi_pi_get_client(
					    PIP(plun->lun_cip));
				}
				mutex_exit(&plun->lun_mutex);

				if (cdip) {
					(void) ndi_event_retrieve_cookie(
					    pptr->port_ndi_event_hdl, cdip,
					    FCAL_REMOVE_EVENT,
					    &fcp_remove_eid,
					    NDI_EVENT_NOPASS);
					(void) ndi_event_run_callbacks(
					    pptr->port_ndi_event_hdl,
					    cdip, fcp_remove_eid, NULL);
				}

				pkt->pkt_reason = CMD_TRAN_ERR;
				pkt->pkt_statistics |= STAT_BUS_RESET;

				break;
			}

			case FC_REASON_NOMEM:
			case FC_REASON_QFULL: {
				caddr_t ptr;

				pkt->pkt_reason = CMD_CMPLT;	/* Lie */
				ptr = (caddr_t)pkt->pkt_scbp;
				if (ptr) {
					*ptr = STATUS_BUSY;
				}
				break;
			}

			case FC_REASON_DMA_ERROR:
				pkt->pkt_reason = CMD_DMA_DERR;
				pkt->pkt_statistics |= STAT_ABORTED;
				break;

			case FC_REASON_CRC_ERROR:
			case FC_REASON_UNDERRUN: {
				uchar_t		status;
				/*
				 * Work around for Bugid: 4240945.
				 * IB on A5k doesn't set the Underrun bit
				 * in the fcp status, when it is transferring
				 * less than requested amount of data. Work
				 * around the ses problem to keep luxadm
				 * happy till ibfirmware is fixed.
				 */
				if (pptr->port_fcp_dma != FC_NO_DVMA_SPACE) {
					FCP_CP_IN(fpkt->pkt_resp, rsp,
					    fpkt->pkt_resp_acc,
					    sizeof (struct fcp_rsp));
				}
				status = rsp->fcp_u.fcp_status.scsi_status;
				if (((plun->lun_type & DTYPE_MASK) ==
				    DTYPE_ESI) && (status == STATUS_GOOD)) {
					pkt->pkt_reason = CMD_CMPLT;
					*pkt->pkt_scbp = status;
					pkt->pkt_resid = 0;
				} else {
					pkt->pkt_reason = CMD_TRAN_ERR;
					pkt->pkt_statistics |= STAT_ABORTED;
				}
				break;
			}

			case FC_REASON_NO_CONNECTION:
			case FC_REASON_UNSUPPORTED:
			case FC_REASON_ILLEGAL_REQ:
			case FC_REASON_BAD_SID:
			case FC_REASON_DIAG_BUSY:
			case FC_REASON_FCAL_OPN_FAIL:
			case FC_REASON_BAD_XID:
			default:
				pkt->pkt_reason = CMD_TRAN_ERR;
				pkt->pkt_statistics |= STAT_ABORTED;
				break;

			}
			break;

		case FC_PKT_NPORT_RJT:
		case FC_PKT_FABRIC_RJT:
		case FC_PKT_NPORT_BSY:
		case FC_PKT_FABRIC_BSY:
		default:
			FCP_DTRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_8, 0,
			    "FC Status 0x%x, reason 0x%x",
			    fpkt->pkt_state, fpkt->pkt_reason);
			pkt->pkt_reason = CMD_TRAN_ERR;
			pkt->pkt_statistics |= STAT_ABORTED;
			break;
		}

		FCP_DTRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_9, 0,
		    "!FC error on cmd=%p target=0x%x: pkt state=0x%x "
		    " pkt reason=0x%x", cmd, ptgt->tgt_d_id, fpkt->pkt_state,
		    fpkt->pkt_reason);
	}

	ASSERT(save.a_hba_tran == pkt->pkt_address.a_hba_tran);
}


static int
fcp_validate_fcp_response(struct fcp_rsp *rsp, struct fcp_port *pptr)
{
	if (rsp->reserved_0 || rsp->reserved_1 ||
	    rsp->fcp_u.fcp_status.reserved_0 ||
	    rsp->fcp_u.fcp_status.reserved_1) {
		/*
		 * These reserved fields should ideally be zero. FCP-2 does say
		 * that the recipient need not check for reserved fields to be
		 * zero. If they are not zero, we will not make a fuss about it
		 * - just log it (in debug to both trace buffer and messages
		 * file and to trace buffer only in non-debug) and move on.
		 *
		 * Non-zero reserved fields were seen with minnows.
		 *
		 * qlc takes care of some of this but we cannot assume that all
		 * FCAs will do so.
		 */
		FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace,
		    FCP_BUF_LEVEL_5, 0,
		    "Got fcp response packet with non-zero reserved fields "
		    "rsp->reserved_0:0x%x, rsp_reserved_1:0x%x, "
		    "status.reserved_0:0x%x, status.reserved_1:0x%x",
		    rsp->reserved_0, rsp->reserved_1,
		    rsp->fcp_u.fcp_status.reserved_0,
		    rsp->fcp_u.fcp_status.reserved_1);
	}

	if (rsp->fcp_u.fcp_status.rsp_len_set && (rsp->fcp_response_len >
	    (FCP_MAX_RSP_IU_SIZE - sizeof (struct fcp_rsp)))) {
		return (FC_FAILURE);
	}

	if (rsp->fcp_u.fcp_status.sense_len_set && rsp->fcp_sense_len >
	    (FCP_MAX_RSP_IU_SIZE - rsp->fcp_response_len -
	    sizeof (struct fcp_rsp))) {
		return (FC_FAILURE);
	}

	return (FC_SUCCESS);
}


/*
 * This is called when there is a change the in device state. The case we're
 * handling here is, if the d_id s does not match, offline this tgt and online
 * a new tgt with the new d_id.	 called from fcp_handle_devices with
 * port_mutex held.
 */
static int
fcp_device_changed(struct fcp_port *pptr, struct fcp_tgt *ptgt,
    fc_portmap_t *map_entry, int link_cnt, int tgt_cnt, int cause)
{
	ASSERT(mutex_owned(&pptr->port_mutex));

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_3, 0,
	    "Starting fcp_device_changed...");

	/*
	 * The two cases where the port_device_changed is called is
	 * either it changes it's d_id or it's hard address.
	 */
	if ((ptgt->tgt_d_id != map_entry->map_did.port_id) ||
	    (FC_TOP_EXTERNAL(pptr->port_topology) &&
	    (ptgt->tgt_hard_addr != map_entry->map_hard_addr.hard_addr))) {

		/* offline this target */
		mutex_enter(&ptgt->tgt_mutex);
		if (!(ptgt->tgt_state & FCP_TGT_OFFLINE)) {
			(void) fcp_offline_target(pptr, ptgt, link_cnt,
			    0, 1, NDI_DEVI_REMOVE);
		}
		mutex_exit(&ptgt->tgt_mutex);

		fcp_log(CE_NOTE, pptr->port_dip,
		    "Change in target properties: Old D_ID=%x New D_ID=%x"
		    " Old HA=%x New HA=%x", ptgt->tgt_d_id,
		    map_entry->map_did.port_id, ptgt->tgt_hard_addr,
		    map_entry->map_hard_addr.hard_addr);
	}

	return (fcp_handle_mapflags(pptr, ptgt, map_entry,
	    link_cnt, tgt_cnt, cause));
}

/*
 *     Function: fcp_alloc_lun
 *
 *  Description: Creates a new lun structure and adds it to the list
 *		 of luns of the target.
 *
 *     Argument: ptgt		Target the lun will belong to.
 *
 * Return Value: NULL		Failed
 *		 Not NULL	Succeeded
 *
 *	Context: Kernel context
 */
static struct fcp_lun *
fcp_alloc_lun(struct fcp_tgt *ptgt)
{
	struct fcp_lun *plun;

	plun = kmem_zalloc(sizeof (struct fcp_lun), KM_NOSLEEP);
	if (plun != NULL) {
		/*
		 * Initialize the mutex before putting in the target list
		 * especially before releasing the target mutex.
		 */
		mutex_init(&plun->lun_mutex, NULL, MUTEX_DRIVER, NULL);
		plun->lun_tgt = ptgt;

		mutex_enter(&ptgt->tgt_mutex);
		plun->lun_next = ptgt->tgt_lun;
		ptgt->tgt_lun = plun;
		plun->lun_old_guid = NULL;
		plun->lun_old_guid_size = 0;
		mutex_exit(&ptgt->tgt_mutex);
	}

	return (plun);
}

/*
 *     Function: fcp_dealloc_lun
 *
 *  Description: Frees the LUN structure passed by the caller.
 *
 *     Argument: plun		LUN structure to free.
 *
 * Return Value: None
 *
 *	Context: Kernel context.
 */
static void
fcp_dealloc_lun(struct fcp_lun *plun)
{
	mutex_enter(&plun->lun_mutex);
	if (plun->lun_cip) {
		fcp_remove_child(plun);
	}
	mutex_exit(&plun->lun_mutex);

	mutex_destroy(&plun->lun_mutex);
	if (plun->lun_guid) {
		kmem_free(plun->lun_guid, plun->lun_guid_size);
	}
	if (plun->lun_old_guid) {
		kmem_free(plun->lun_old_guid, plun->lun_old_guid_size);
	}
	kmem_free(plun, sizeof (*plun));
}

/*
 *     Function: fcp_alloc_tgt
 *
 *  Description: Creates a new target structure and adds it to the port
 *		 hash list.
 *
 *     Argument: pptr		fcp port structure
 *		 *map_entry	entry describing the target to create
 *		 link_cnt	Link state change counter
 *
 * Return Value: NULL		Failed
 *		 Not NULL	Succeeded
 *
 *	Context: Kernel context.
 */
static struct fcp_tgt *
fcp_alloc_tgt(struct fcp_port *pptr, fc_portmap_t *map_entry, int link_cnt)
{
	int			hash;
	uchar_t			*wwn;
	struct fcp_tgt	*ptgt;

	ptgt = kmem_zalloc(sizeof (*ptgt), KM_NOSLEEP);
	if (ptgt != NULL) {
		mutex_enter(&pptr->port_mutex);
		if (link_cnt != pptr->port_link_cnt) {
			/*
			 * oh oh -- another link reset
			 * in progress -- give up
			 */
			mutex_exit(&pptr->port_mutex);
			kmem_free(ptgt, sizeof (*ptgt));
			ptgt = NULL;
		} else {
			/*
			 * initialize the mutex before putting in the port
			 * wwn list, especially before releasing the port
			 * mutex.
			 */
			mutex_init(&ptgt->tgt_mutex, NULL, MUTEX_DRIVER, NULL);

			/* add new target entry to the port's hash list */
			wwn = (uchar_t *)&map_entry->map_pwwn;
			hash = FCP_HASH(wwn);

			ptgt->tgt_next = pptr->port_tgt_hash_table[hash];
			pptr->port_tgt_hash_table[hash] = ptgt;

			/* save cross-ptr */
			ptgt->tgt_port = pptr;

			ptgt->tgt_change_cnt = 1;

			/* initialize the target manual_config_only flag */
			if (fcp_enable_auto_configuration) {
				ptgt->tgt_manual_config_only = 0;
			} else {
				ptgt->tgt_manual_config_only = 1;
			}

			mutex_exit(&pptr->port_mutex);
		}
	}

	return (ptgt);
}

/*
 *     Function: fcp_dealloc_tgt
 *
 *  Description: Frees the target structure passed by the caller.
 *
 *     Argument: ptgt		Target structure to free.
 *
 * Return Value: None
 *
 *	Context: Kernel context.
 */
static void
fcp_dealloc_tgt(struct fcp_tgt *ptgt)
{
	mutex_destroy(&ptgt->tgt_mutex);
	kmem_free(ptgt, sizeof (*ptgt));
}


/*
 * Handle STATUS_QFULL and STATUS_BUSY by performing delayed retry
 *
 *	Device discovery commands will not be retried for-ever as
 *	this will have repercussions on other devices that need to
 *	be submitted to the hotplug thread. After a quick glance
 *	at the SCSI-3 spec, it was found that the spec doesn't
 *	mandate a forever retry, rather recommends a delayed retry.
 *
 *	Since Photon IB is single threaded, STATUS_BUSY is common
 *	in a 4+initiator environment. Make sure the total time
 *	spent on retries (including command timeout) does not
 *	60 seconds
 */
static void
fcp_queue_ipkt(struct fcp_port *pptr, fc_packet_t *fpkt)
{
	struct fcp_ipkt *icmd = (struct fcp_ipkt *)fpkt->pkt_ulp_private;
	struct fcp_tgt *ptgt = icmd->ipkt_tgt;

	mutex_enter(&pptr->port_mutex);
	mutex_enter(&ptgt->tgt_mutex);
	if (FCP_STATE_CHANGED(pptr, ptgt, icmd)) {
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_2, 0,
		    "fcp_queue_ipkt,1:state change occured"
		    " for D_ID=0x%x", ptgt->tgt_d_id);
		mutex_exit(&ptgt->tgt_mutex);
		mutex_exit(&pptr->port_mutex);
		(void) fcp_call_finish_init(pptr, ptgt, icmd->ipkt_link_cnt,
		    icmd->ipkt_change_cnt, icmd->ipkt_cause);
		fcp_icmd_free(pptr, icmd);
		return;
	}
	mutex_exit(&ptgt->tgt_mutex);

	icmd->ipkt_restart = fcp_watchdog_time + icmd->ipkt_retries++;

	if (pptr->port_ipkt_list != NULL) {
		/* add pkt to front of doubly-linked list */
		pptr->port_ipkt_list->ipkt_prev = icmd;
		icmd->ipkt_next = pptr->port_ipkt_list;
		pptr->port_ipkt_list = icmd;
		icmd->ipkt_prev = NULL;
	} else {
		/* this is the first/only pkt on the list */
		pptr->port_ipkt_list = icmd;
		icmd->ipkt_next = NULL;
		icmd->ipkt_prev = NULL;
	}
	mutex_exit(&pptr->port_mutex);
}

/*
 *     Function: fcp_transport
 *
 *  Description: This function submits the Fibre Channel packet to the transort
 *		 layer by calling fc_ulp_transport().  If fc_ulp_transport()
 *		 fails the submission, the treatment depends on the value of
 *		 the variable internal.
 *
 *     Argument: port_handle	fp/fctl port handle.
 *		 *fpkt		Packet to submit to the transport layer.
 *		 internal	Not zero when it's an internal packet.
 *
 * Return Value: FC_TRAN_BUSY
 *		 FC_STATEC_BUSY
 *		 FC_OFFLINE
 *		 FC_LOGINREQ
 *		 FC_DEVICE_BUSY
 *		 FC_SUCCESS
 */
static int
fcp_transport(opaque_t port_handle, fc_packet_t *fpkt, int internal)
{
	int	rval;

	rval = fc_ulp_transport(port_handle, fpkt);
	if (rval == FC_SUCCESS) {
		return (rval);
	}

	/*
	 * LUN isn't marked BUSY or OFFLINE, so we got here to transport
	 * a command, if the underlying modules see that there is a state
	 * change, or if a port is OFFLINE, that means, that state change
	 * hasn't reached FCP yet, so re-queue the command for deferred
	 * submission.
	 */
	if ((rval == FC_STATEC_BUSY) || (rval == FC_OFFLINE) ||
	    (rval == FC_LOGINREQ) || (rval == FC_DEVICE_BUSY) ||
	    (rval == FC_DEVICE_BUSY_NEW_RSCN) || (rval == FC_TRAN_BUSY)) {
		/*
		 * Defer packet re-submission. Life hang is possible on
		 * internal commands if the port driver sends FC_STATEC_BUSY
		 * for ever, but that shouldn't happen in a good environment.
		 * Limiting re-transport for internal commands is probably a
		 * good idea..
		 * A race condition can happen when a port sees barrage of
		 * link transitions offline to online. If the FCTL has
		 * returned FC_STATEC_BUSY or FC_OFFLINE then none of the
		 * internal commands should be queued to do the discovery.
		 * The race condition is when an online comes and FCP starts
		 * its internal discovery and the link goes offline. It is
		 * possible that the statec_callback has not reached FCP
		 * and FCP is carrying on with its internal discovery.
		 * FC_STATEC_BUSY or FC_OFFLINE will be the first indication
		 * that the link has gone offline. At this point FCP should
		 * drop all the internal commands and wait for the
		 * statec_callback. It will be facilitated by incrementing
		 * port_link_cnt.
		 *
		 * For external commands, the (FC)pkt_timeout is decremented
		 * by the QUEUE Delay added by our driver, Care is taken to
		 * ensure that it doesn't become zero (zero means no timeout)
		 * If the time expires right inside driver queue itself,
		 * the watch thread will return it to the original caller
		 * indicating that the command has timed-out.
		 */
		if (internal) {
			char			*op;
			struct fcp_ipkt	*icmd;

			icmd = (struct fcp_ipkt *)fpkt->pkt_ulp_private;
			switch (icmd->ipkt_opcode) {
			case SCMD_REPORT_LUN:
				op = "REPORT LUN";
				break;

			case SCMD_INQUIRY:
				op = "INQUIRY";
				break;

			case SCMD_INQUIRY_PAGE83:
				op = "INQUIRY-83";
				break;

			default:
				op = "Internal SCSI COMMAND";
				break;
			}

			if (fcp_handle_ipkt_errors(icmd->ipkt_port,
			    icmd->ipkt_tgt, icmd, rval, op) == DDI_SUCCESS) {
				rval = FC_SUCCESS;
			}
		} else {
			struct fcp_pkt *cmd;
			struct fcp_port *pptr;

			cmd = (struct fcp_pkt *)fpkt->pkt_ulp_private;
			cmd->cmd_state = FCP_PKT_IDLE;
			pptr = ADDR2FCP(&cmd->cmd_pkt->pkt_address);

			if (cmd->cmd_pkt->pkt_flags & FLAG_NOQUEUE) {
				FCP_DTRACE(fcp_logq, pptr->port_instbuf,
				    fcp_trace, FCP_BUF_LEVEL_9, 0,
				    "fcp_transport: xport busy for pkt %p",
				    cmd->cmd_pkt);
				rval = FC_TRAN_BUSY;
			} else {
				fcp_queue_pkt(pptr, cmd);
				rval = FC_SUCCESS;
			}
		}
	}

	return (rval);
}

/*VARARGS3*/
static void
fcp_log(int level, dev_info_t *dip, const char *fmt, ...)
{
	char		buf[256];
	va_list		ap;

	if (dip == NULL) {
		dip = fcp_global_dip;
	}

	va_start(ap, fmt);
	(void) vsprintf(buf, fmt, ap);
	va_end(ap);

	scsi_log(dip, "fcp", level, buf);
}

/*
 * This function retries NS registry of FC4 type.
 * It assumes that fcp_mutex is held.
 * The function does nothing if topology is not fabric
 * So, the topology has to be set before this function can be called
 */
static void
fcp_retry_ns_registry(struct fcp_port *pptr, uint32_t s_id)
{
	int	rval;

	ASSERT(MUTEX_HELD(&pptr->port_mutex));

	if (((pptr->port_state & FCP_STATE_NS_REG_FAILED) == 0) ||
	    ((pptr->port_topology != FC_TOP_FABRIC) &&
	    (pptr->port_topology != FC_TOP_PUBLIC_LOOP))) {
		if (pptr->port_state & FCP_STATE_NS_REG_FAILED) {
			pptr->port_state &= ~FCP_STATE_NS_REG_FAILED;
		}
		return;
	}
	mutex_exit(&pptr->port_mutex);
	rval = fcp_do_ns_registry(pptr, s_id);
	mutex_enter(&pptr->port_mutex);

	if (rval == 0) {
		/* Registry successful. Reset flag */
		pptr->port_state &= ~(FCP_STATE_NS_REG_FAILED);
	}
}

/*
 * This function registers the ULP with the switch by calling transport i/f
 */
static int
fcp_do_ns_registry(struct fcp_port *pptr, uint32_t s_id)
{
	fc_ns_cmd_t		ns_cmd;
	ns_rfc_type_t		rfc;
	uint32_t		types[8];

	/*
	 * Prepare the Name server structure to
	 * register with the transport in case of
	 * Fabric configuration.
	 */
	bzero(&rfc, sizeof (rfc));
	bzero(types, sizeof (types));

	types[FC4_TYPE_WORD_POS(FC_TYPE_SCSI_FCP)] =
	    (1 << FC4_TYPE_BIT_POS(FC_TYPE_SCSI_FCP));

	rfc.rfc_port_id.port_id = s_id;
	bcopy(types, rfc.rfc_types, sizeof (types));

	ns_cmd.ns_flags = 0;
	ns_cmd.ns_cmd = NS_RFT_ID;
	ns_cmd.ns_req_len = sizeof (rfc);
	ns_cmd.ns_req_payload = (caddr_t)&rfc;
	ns_cmd.ns_resp_len = 0;
	ns_cmd.ns_resp_payload = NULL;

	/*
	 * Perform the Name Server Registration for SCSI_FCP FC4 Type.
	 */
	if (fc_ulp_port_ns(pptr->port_fp_handle, NULL, &ns_cmd)) {
		fcp_log(CE_WARN, pptr->port_dip,
		    "!ns_registry: failed name server registration");
		return (1);
	}

	return (0);
}

/*
 *     Function: fcp_handle_port_attach
 *
 *  Description: This function is called from fcp_port_attach() to attach a
 *		 new port. This routine does the following:
 *
 *		1) Allocates an fcp_port structure and initializes it.
 *		2) Tries to register the new FC-4 (FCP) capablity with the name
 *		   server.
 *		3) Kicks off the enumeration of the targets/luns visible
 *		   through this new port.  That is done by calling
 *		   fcp_statec_callback() if the port is online.
 *
 *     Argument: ulph		fp/fctl port handle.
 *		 *pinfo		Port information.
 *		 s_id		Port ID.
 *		 instance	Device instance number for the local port
 *				(returned by ddi_get_instance()).
 *
 * Return Value: DDI_SUCCESS
 *		 DDI_FAILURE
 *
 *	Context: User and Kernel context.
 */
/*ARGSUSED*/
int
fcp_handle_port_attach(opaque_t ulph, fc_ulp_port_info_t *pinfo,
    uint32_t s_id, int instance)
{
	int			res = DDI_FAILURE;
	scsi_hba_tran_t		*tran;
	int			mutex_initted = FALSE;
	int			hba_attached = FALSE;
	int			soft_state_linked = FALSE;
	int			event_bind = FALSE;
	struct fcp_port		*pptr;
	fc_portmap_t		*tmp_list = NULL;
	uint32_t		max_cnt, alloc_cnt;
	uchar_t			*boot_wwn = NULL;
	uint_t			nbytes;
	int			manual_cfg;

	/*
	 * this port instance attaching for the first time (or after
	 * being detached before)
	 */
	FCP_TRACE(fcp_logq, "fcp", fcp_trace,
	    FCP_BUF_LEVEL_3, 0, "port attach: for port %d", instance);

	if (ddi_soft_state_zalloc(fcp_softstate, instance) != DDI_SUCCESS) {
		cmn_err(CE_WARN, "fcp: Softstate struct alloc failed"
		    "parent dip: %p; instance: %d", (void *)pinfo->port_dip,
		    instance);
		return (res);
	}

	if ((pptr = ddi_get_soft_state(fcp_softstate, instance)) == NULL) {
		/* this shouldn't happen */
		ddi_soft_state_free(fcp_softstate, instance);
		cmn_err(CE_WARN, "fcp: bad soft state");
		return (res);
	}

	(void) sprintf(pptr->port_instbuf, "fcp(%d)", instance);

	/*
	 * Make a copy of ulp_port_info as fctl allocates
	 * a temp struct.
	 */
	(void) fcp_cp_pinfo(pptr, pinfo);

	/*
	 * Check for manual_configuration_only property.
	 * Enable manual configurtion if the property is
	 * set to 1, otherwise disable manual configuration.
	 */
	if ((manual_cfg = ddi_prop_get_int(DDI_DEV_T_ANY, pptr->port_dip,
	    DDI_PROP_NOTPROM | DDI_PROP_DONTPASS,
	    MANUAL_CFG_ONLY,
	    -1)) != -1) {
		if (manual_cfg == 1) {
			char	*pathname;
			pathname = kmem_zalloc(MAXPATHLEN, KM_SLEEP);
			(void) ddi_pathname(pptr->port_dip, pathname);
			cmn_err(CE_NOTE,
			    "%s (%s%d) %s is enabled via %s.conf.",
			    pathname,
			    ddi_driver_name(pptr->port_dip),
			    ddi_get_instance(pptr->port_dip),
			    MANUAL_CFG_ONLY,
			    ddi_driver_name(pptr->port_dip));
			fcp_enable_auto_configuration = 0;
			kmem_free(pathname, MAXPATHLEN);
		}
	}
	_NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(pptr->port_link_cnt))
	pptr->port_link_cnt = 1;
	_NOTE(NOW_VISIBLE_TO_OTHER_THREADS(pptr->port_link_cnt))
	pptr->port_id = s_id;
	pptr->port_instance = instance;
	_NOTE(NOW_INVISIBLE_TO_OTHER_THREADS(pptr->port_state))
	pptr->port_state = FCP_STATE_INIT;
	_NOTE(NOW_VISIBLE_TO_OTHER_THREADS(pptr->port_state))

	pptr->port_dmacookie_sz = (pptr->port_data_dma_attr.dma_attr_sgllen *
	    sizeof (ddi_dma_cookie_t));

	/*
	 * The two mutexes of fcp_port are initialized.	 The variable
	 * mutex_initted is incremented to remember that fact.	That variable
	 * is checked when the routine fails and the mutexes have to be
	 * destroyed.
	 */
	mutex_init(&pptr->port_mutex, NULL, MUTEX_DRIVER, NULL);
	mutex_init(&pptr->port_pkt_mutex, NULL, MUTEX_DRIVER, NULL);
	mutex_initted++;

	/*
	 * The SCSI tran structure is allocate and initialized now.
	 */
	if ((tran = scsi_hba_tran_alloc(pptr->port_dip, 0)) == NULL) {
		fcp_log(CE_WARN, pptr->port_dip,
		    "!fcp%d: scsi_hba_tran_alloc failed", instance);
		goto fail;
	}

	/* link in the transport structure then fill it in */
	pptr->port_tran = tran;
	tran->tran_hba_private		= pptr;
	tran->tran_tgt_init		= fcp_scsi_tgt_init;
	tran->tran_tgt_probe		= NULL;
	tran->tran_tgt_free		= fcp_scsi_tgt_free;
	tran->tran_start		= fcp_scsi_start;
	tran->tran_reset		= fcp_scsi_reset;
	tran->tran_abort		= fcp_scsi_abort;
	tran->tran_getcap		= fcp_scsi_getcap;
	tran->tran_setcap		= fcp_scsi_setcap;
	tran->tran_init_pkt		= NULL;
	tran->tran_destroy_pkt		= NULL;
	tran->tran_dmafree		= NULL;
	tran->tran_sync_pkt		= NULL;
	tran->tran_reset_notify		= fcp_scsi_reset_notify;
	tran->tran_get_bus_addr		= fcp_scsi_get_bus_addr;
	tran->tran_get_name		= fcp_scsi_get_name;
	tran->tran_clear_aca		= NULL;
	tran->tran_clear_task_set	= NULL;
	tran->tran_terminate_task	= NULL;
	tran->tran_get_eventcookie	= fcp_scsi_bus_get_eventcookie;
	tran->tran_add_eventcall	= fcp_scsi_bus_add_eventcall;
	tran->tran_remove_eventcall	= fcp_scsi_bus_remove_eventcall;
	tran->tran_post_event		= fcp_scsi_bus_post_event;
	tran->tran_quiesce		= NULL;
	tran->tran_unquiesce		= NULL;
	tran->tran_bus_reset		= NULL;
	tran->tran_bus_config		= fcp_scsi_bus_config;
	tran->tran_bus_unconfig		= fcp_scsi_bus_unconfig;
	tran->tran_bus_power		= NULL;
	tran->tran_interconnect_type	= INTERCONNECT_FABRIC;

	tran->tran_pkt_constructor	= fcp_kmem_cache_constructor;
	tran->tran_pkt_destructor	= fcp_kmem_cache_destructor;
	tran->tran_setup_pkt		= fcp_pkt_setup;
	tran->tran_teardown_pkt		= fcp_pkt_teardown;
	tran->tran_hba_len		= pptr->port_priv_pkt_len +
	    sizeof (struct fcp_pkt) + pptr->port_dmacookie_sz;

	/*
	 * Allocate an ndi event handle
	 */
	pptr->port_ndi_event_defs = (ndi_event_definition_t *)
	    kmem_zalloc(sizeof (fcp_ndi_event_defs), KM_SLEEP);

	bcopy(fcp_ndi_event_defs, pptr->port_ndi_event_defs,
	    sizeof (fcp_ndi_event_defs));

	(void) ndi_event_alloc_hdl(pptr->port_dip, NULL,
	    &pptr->port_ndi_event_hdl, NDI_SLEEP);

	pptr->port_ndi_events.ndi_events_version = NDI_EVENTS_REV1;
	pptr->port_ndi_events.ndi_n_events = FCP_N_NDI_EVENTS;
	pptr->port_ndi_events.ndi_event_defs = pptr->port_ndi_event_defs;

	if (DEVI_IS_ATTACHING(pptr->port_dip) &&
	    (ndi_event_bind_set(pptr->port_ndi_event_hdl,
	    &pptr->port_ndi_events, NDI_SLEEP) != NDI_SUCCESS)) {
		goto fail;
	}
	event_bind++;	/* Checked in fail case */

	if (scsi_hba_attach_setup(pptr->port_dip, &pptr->port_data_dma_attr,
	    tran, SCSI_HBA_ADDR_COMPLEX | SCSI_HBA_TRAN_SCB)
	    != DDI_SUCCESS) {
		fcp_log(CE_WARN, pptr->port_dip,
		    "!fcp%d: scsi_hba_attach_setup failed", instance);
		goto fail;
	}
	hba_attached++;	/* Checked in fail case */

	pptr->port_mpxio = 0;
	if (mdi_phci_register(MDI_HCI_CLASS_SCSI, pptr->port_dip, 0) ==
	    MDI_SUCCESS) {
		pptr->port_mpxio++;
	}

	/*
	 * The following code is putting the new port structure in the global
	 * list of ports and, if it is the first port to attach, it start the
	 * fcp_watchdog_tick.
	 *
	 * Why put this new port in the global before we are done attaching it?
	 * We are actually making the structure globally known before we are
	 * done attaching it.  The reason for that is: because of the code that
	 * follows.  At this point the resources to handle the port are
	 * allocated.  This function is now going to do the following:
	 *
	 *   1) It is going to try to register with the name server advertizing
	 *	the new FCP capability of the port.
	 *   2) It is going to play the role of the fp/fctl layer by building
	 *	a list of worlwide names reachable through this port and call
	 *	itself on fcp_statec_callback().  That requires the port to
	 *	be part of the global list.
	 */
	mutex_enter(&fcp_global_mutex);
	if (fcp_port_head == NULL) {
		fcp_read_blacklist(pinfo->port_dip, &fcp_lun_blacklist);
	}
	pptr->port_next = fcp_port_head;
	fcp_port_head = pptr;
	soft_state_linked++;

	if (fcp_watchdog_init++ == 0) {
		fcp_watchdog_tick = fcp_watchdog_timeout *
		    drv_usectohz(1000000);
		fcp_watchdog_id = timeout(fcp_watch, NULL,
		    fcp_watchdog_tick);
	}
	mutex_exit(&fcp_global_mutex);

	/*
	 * Here an attempt is made to register with the name server, the new
	 * FCP capability.  That is done using an RTF_ID to the name server.
	 * It is done synchronously.  The function fcp_do_ns_registry()
	 * doesn't return till the name server responded.
	 * On failures, just ignore it for now and it will get retried during
	 * state change callbacks. We'll set a flag to show this failure
	 */
	if (fcp_do_ns_registry(pptr, s_id)) {
		mutex_enter(&pptr->port_mutex);
		pptr->port_state |= FCP_STATE_NS_REG_FAILED;
		mutex_exit(&pptr->port_mutex);
	} else {
		mutex_enter(&pptr->port_mutex);
		pptr->port_state &= ~(FCP_STATE_NS_REG_FAILED);
		mutex_exit(&pptr->port_mutex);
	}

	/*
	 * Lookup for boot WWN property
	 */
	if (modrootloaded != 1) {
		if ((ddi_prop_lookup_byte_array(DDI_DEV_T_ANY,
		    ddi_get_parent(pinfo->port_dip),
		    DDI_PROP_DONTPASS, OBP_BOOT_WWN,
		    &boot_wwn, &nbytes) == DDI_PROP_SUCCESS) &&
		    (nbytes == FC_WWN_SIZE)) {
			bcopy(boot_wwn, pptr->port_boot_wwn, FC_WWN_SIZE);
		}
		if (boot_wwn) {
			ddi_prop_free(boot_wwn);
		}
	}

	/*
	 * Handle various topologies and link states.
	 */
	switch (FC_PORT_STATE_MASK(pptr->port_phys_state)) {
	case FC_STATE_OFFLINE:

		/*
		 * we're attaching a port where the link is offline
		 *
		 * Wait for ONLINE, at which time a state
		 * change will cause a statec_callback
		 *
		 * in the mean time, do not do anything
		 */
		res = DDI_SUCCESS;
		pptr->port_state |= FCP_STATE_OFFLINE;
		break;

	case FC_STATE_ONLINE: {
		if (pptr->port_topology == FC_TOP_UNKNOWN) {
			(void) fcp_linkreset(pptr, NULL, KM_NOSLEEP);
			res = DDI_SUCCESS;
			break;
		}
		/*
		 * discover devices and create nodes (a private
		 * loop or point-to-point)
		 */
		ASSERT(pptr->port_topology != FC_TOP_UNKNOWN);

		/*
		 * At this point we are going to build a list of all the ports
		 * that	can be reached through this local port.	 It looks like
		 * we cannot handle more than FCP_MAX_DEVICES per local port
		 * (128).
		 */
		if ((tmp_list = (fc_portmap_t *)kmem_zalloc(
		    sizeof (fc_portmap_t) * FCP_MAX_DEVICES,
		    KM_NOSLEEP)) == NULL) {
			fcp_log(CE_WARN, pptr->port_dip,
			    "!fcp%d: failed to allocate portmap",
			    instance);
			goto fail;
		}

		/*
		 * fc_ulp_getportmap() is going to provide us with the list of
		 * remote ports in the buffer we just allocated.  The way the
		 * list is going to be retrieved depends on the topology.
		 * However, if we are connected to a Fabric, a name server
		 * request may be sent to get the list of FCP capable ports.
		 * It should be noted that is the case the request is
		 * synchronous.	 This means we are stuck here till the name
		 * server replies.  A lot of things can change during that time
		 * and including, may be, being called on
		 * fcp_statec_callback() for different reasons. I'm not sure
		 * the code can handle that.
		 */
		max_cnt = FCP_MAX_DEVICES;
		alloc_cnt = FCP_MAX_DEVICES;
		if ((res = fc_ulp_getportmap(pptr->port_fp_handle,
		    &tmp_list, &max_cnt, FC_ULP_PLOGI_PRESERVE)) !=
		    FC_SUCCESS) {
			caddr_t msg;

			(void) fc_ulp_error(res, &msg);

			/*
			 * this	 just means the transport is
			 * busy perhaps building a portmap so,
			 * for now, succeed this port attach
			 * when the transport has a new map,
			 * it'll send us a state change then
			 */
			fcp_log(CE_WARN, pptr->port_dip,
			    "!failed to get port map : %s", msg);

			res = DDI_SUCCESS;
			break;	/* go return result */
		}
		if (max_cnt > alloc_cnt) {
			alloc_cnt = max_cnt;
		}

		/*
		 * We are now going to call fcp_statec_callback() ourselves.
		 * By issuing this call we are trying to kick off the enumera-
		 * tion process.
		 */
		/*
		 * let the state change callback do the SCSI device
		 * discovery and create the devinfos
		 */
		fcp_statec_callback(ulph, pptr->port_fp_handle,
		    pptr->port_phys_state, pptr->port_topology, tmp_list,
		    max_cnt, pptr->port_id);

		res = DDI_SUCCESS;
		break;
	}

	default:
		/* unknown port state */
		fcp_log(CE_WARN, pptr->port_dip,
		    "!fcp%d: invalid port state at attach=0x%x",
		    instance, pptr->port_phys_state);

		mutex_enter(&pptr->port_mutex);
		pptr->port_phys_state = FCP_STATE_OFFLINE;
		mutex_exit(&pptr->port_mutex);

		res = DDI_SUCCESS;
		break;
	}

	/* free temp list if used */
	if (tmp_list != NULL) {
		kmem_free(tmp_list, sizeof (fc_portmap_t) * alloc_cnt);
	}

	/* note the attach time */
	pptr->port_attach_time = lbolt64;

	/* all done */
	return (res);

	/* a failure we have to clean up after */
fail:
	fcp_log(CE_WARN, pptr->port_dip, "!failed to attach to port");

	if (soft_state_linked) {
		/* remove this fcp_port from the linked list */
		(void) fcp_soft_state_unlink(pptr);
	}

	/* unbind and free event set */
	if (pptr->port_ndi_event_hdl) {
		if (event_bind) {
			(void) ndi_event_unbind_set(pptr->port_ndi_event_hdl,
			    &pptr->port_ndi_events, NDI_SLEEP);
		}
		(void) ndi_event_free_hdl(pptr->port_ndi_event_hdl);
	}

	if (pptr->port_ndi_event_defs) {
		(void) kmem_free(pptr->port_ndi_event_defs,
		    sizeof (fcp_ndi_event_defs));
	}

	/*
	 * Clean up mpxio stuff
	 */
	if (pptr->port_mpxio) {
		(void) mdi_phci_unregister(pptr->port_dip, 0);
		pptr->port_mpxio--;
	}

	/* undo SCSI HBA setup */
	if (hba_attached) {
		(void) scsi_hba_detach(pptr->port_dip);
	}
	if (pptr->port_tran != NULL) {
		scsi_hba_tran_free(pptr->port_tran);
	}

	mutex_enter(&fcp_global_mutex);

	/*
	 * We check soft_state_linked, because it is incremented right before
	 * we call increment fcp_watchdog_init.	 Therefore, we know if
	 * soft_state_linked is still FALSE, we do not want to decrement
	 * fcp_watchdog_init or possibly call untimeout.
	 */

	if (soft_state_linked) {
		if (--fcp_watchdog_init == 0) {
			timeout_id_t	tid = fcp_watchdog_id;

			mutex_exit(&fcp_global_mutex);
			(void) untimeout(tid);
		} else {
			mutex_exit(&fcp_global_mutex);
		}
	} else {
		mutex_exit(&fcp_global_mutex);
	}

	if (mutex_initted) {
		mutex_destroy(&pptr->port_mutex);
		mutex_destroy(&pptr->port_pkt_mutex);
	}

	if (tmp_list != NULL) {
		kmem_free(tmp_list, sizeof (fc_portmap_t) * alloc_cnt);
	}

	/* this makes pptr invalid */
	ddi_soft_state_free(fcp_softstate, instance);

	return (DDI_FAILURE);
}


static int
fcp_handle_port_detach(struct fcp_port *pptr, int flag, int instance)
{
	int count = 0;

	mutex_enter(&pptr->port_mutex);

	/*
	 * if the port is powered down or suspended, nothing else
	 * to do; just return.
	 */
	if (flag != FCP_STATE_DETACHING) {
		if (pptr->port_state & (FCP_STATE_POWER_DOWN |
		    FCP_STATE_SUSPENDED)) {
			pptr->port_state |= flag;
			mutex_exit(&pptr->port_mutex);
			return (FC_SUCCESS);
		}
	}

	if (pptr->port_state & FCP_STATE_IN_MDI) {
		mutex_exit(&pptr->port_mutex);
		return (FC_FAILURE);
	}

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_2, 0,
	    "fcp_handle_port_detach: port is detaching");

	pptr->port_state |= flag;

	/*
	 * Wait for any ongoing reconfig/ipkt to complete, that
	 * ensures the freeing to targets/luns is safe.
	 * No more ref to this port should happen from statec/ioctl
	 * after that as it was removed from the global port list.
	 */
	while (pptr->port_tmp_cnt || pptr->port_ipkt_cnt ||
	    (pptr->port_state & FCP_STATE_IN_WATCHDOG)) {
		/*
		 * Let's give sufficient time for reconfig/ipkt
		 * to complete.
		 */
		if (count++ >= FCP_ICMD_DEADLINE) {
			break;
		}
		mutex_exit(&pptr->port_mutex);
		delay(drv_usectohz(1000000));
		mutex_enter(&pptr->port_mutex);
	}

	/*
	 * if the driver is still busy then fail to
	 * suspend/power down.
	 */
	if (pptr->port_tmp_cnt || pptr->port_ipkt_cnt ||
	    (pptr->port_state & FCP_STATE_IN_WATCHDOG)) {
		pptr->port_state &= ~flag;
		mutex_exit(&pptr->port_mutex);
		return (FC_FAILURE);
	}

	if (flag == FCP_STATE_DETACHING) {
		pptr = fcp_soft_state_unlink(pptr);
		ASSERT(pptr != NULL);
	}

	pptr->port_link_cnt++;
	pptr->port_state |= FCP_STATE_OFFLINE;
	pptr->port_state &= ~(FCP_STATE_ONLINING | FCP_STATE_ONLINE);

	fcp_update_state(pptr, (FCP_LUN_BUSY | FCP_LUN_MARK),
	    FCP_CAUSE_LINK_DOWN);
	mutex_exit(&pptr->port_mutex);

	/* kill watch dog timer if we're the last */
	mutex_enter(&fcp_global_mutex);
	if (--fcp_watchdog_init == 0) {
		timeout_id_t	tid = fcp_watchdog_id;
		mutex_exit(&fcp_global_mutex);
		(void) untimeout(tid);
	} else {
		mutex_exit(&fcp_global_mutex);
	}

	/* clean up the port structures */
	if (flag == FCP_STATE_DETACHING) {
		fcp_cleanup_port(pptr, instance);
	}

	return (FC_SUCCESS);
}


static void
fcp_cleanup_port(struct fcp_port *pptr, int instance)
{
	ASSERT(pptr != NULL);

	/* unbind and free event set */
	if (pptr->port_ndi_event_hdl) {
		(void) ndi_event_unbind_set(pptr->port_ndi_event_hdl,
		    &pptr->port_ndi_events, NDI_SLEEP);
		(void) ndi_event_free_hdl(pptr->port_ndi_event_hdl);
	}

	if (pptr->port_ndi_event_defs) {
		(void) kmem_free(pptr->port_ndi_event_defs,
		    sizeof (fcp_ndi_event_defs));
	}

	/* free the lun/target structures and devinfos */
	fcp_free_targets(pptr);

	/*
	 * Clean up mpxio stuff
	 */
	if (pptr->port_mpxio) {
		(void) mdi_phci_unregister(pptr->port_dip, 0);
		pptr->port_mpxio--;
	}

	/* clean up SCSA stuff */
	(void) scsi_hba_detach(pptr->port_dip);
	if (pptr->port_tran != NULL) {
		scsi_hba_tran_free(pptr->port_tran);
	}

#ifdef	KSTATS_CODE
	/* clean up kstats */
	if (pptr->fcp_ksp != NULL) {
		kstat_delete(pptr->fcp_ksp);
	}
#endif

	/* clean up soft state mutexes/condition variables */
	mutex_destroy(&pptr->port_mutex);
	mutex_destroy(&pptr->port_pkt_mutex);

	/* all done with soft state */
	ddi_soft_state_free(fcp_softstate, instance);
}

/*
 *     Function: fcp_kmem_cache_constructor
 *
 *  Description: This function allocates and initializes the resources required
 *		 to build a scsi_pkt structure the target driver.  The result
 *		 of the allocation and initialization will be cached in the
 *		 memory cache.	As DMA resources may be allocated here, that
 *		 means DMA resources will be tied up in the cache manager.
 *		 This is a tradeoff that has been made for performance reasons.
 *
 *     Argument: *buf		Memory to preinitialize.
 *		 *arg		FCP port structure (fcp_port).
 *		 kmflags	Value passed to kmem_cache_alloc() and
 *				propagated to the constructor.
 *
 * Return Value: 0	Allocation/Initialization was successful.
 *		 -1	Allocation or Initialization failed.
 *
 *
 * If the returned value is 0, the buffer is initialized like this:
 *
 *		    +================================+
 *	     +----> |	      struct scsi_pkt	     |
 *	     |	    |				     |
 *	     | +--- | pkt_ha_private		     |
 *	     | |    |				     |
 *	     | |    +================================+
 *	     | |
 *	     | |    +================================+
 *	     | +--> |	    struct fcp_pkt	     | <---------+
 *	     |	    |				     |		 |
 *	     +----- | cmd_pkt			     |		 |
 *		    |			  cmd_fp_pkt | ---+	 |
 *	  +-------->| cmd_fcp_rsp[]		     |	  |	 |
 *	  |    +--->| cmd_fcp_cmd[]		     |	  |	 |
 *	  |    |    |--------------------------------|	  |	 |
 *	  |    |    |	      struct fc_packet	     | <--+	 |
 *	  |    |    |				     |		 |
 *	  |    |    |		     pkt_ulp_private | ----------+
 *	  |    |    |		     pkt_fca_private | -----+
 *	  |    |    |		     pkt_data_cookie | ---+ |
 *	  |    |    | pkt_cmdlen		     |	  | |
 *	  |    |(a) | pkt_rsplen		     |	  | |
 *	  |    +----| .......... pkt_cmd ........... | ---|-|---------------+
 *	  |	(b) |		      pkt_cmd_cookie | ---|-|----------+    |
 *	  +---------| .......... pkt_resp .......... | ---|-|------+   |    |
 *		    |		     pkt_resp_cookie | ---|-|--+   |   |    |
 *		    | pkt_cmd_dma		     |	  | |  |   |   |    |
 *		    | pkt_cmd_acc		     |	  | |  |   |   |    |
 *		    +================================+	  | |  |   |   |    |
 *		    |	      dma_cookies	     | <--+ |  |   |   |    |
 *		    |				     |	    |  |   |   |    |
 *		    +================================+	    |  |   |   |    |
 *		    |	      fca_private	     | <----+  |   |   |    |
 *		    |				     |	       |   |   |    |
 *		    +================================+	       |   |   |    |
 *							       |   |   |    |
 *							       |   |   |    |
 *		    +================================+	 (d)   |   |   |    |
 *		    |	     fcp_resp cookies	     | <-------+   |   |    |
 *		    |				     |		   |   |    |
 *		    +================================+		   |   |    |
 *								   |   |    |
 *		    +================================+	 (d)	   |   |    |
 *		    |		fcp_resp	     | <-----------+   |    |
 *		    |	(DMA resources associated)   |		       |    |
 *		    +================================+		       |    |
 *								       |    |
 *								       |    |
 *								       |    |
 *		    +================================+	 (c)	       |    |
 *		    |	     fcp_cmd cookies	     | <---------------+    |
 *		    |				     |			    |
 *		    +================================+			    |
 *									    |
 *		    +================================+	 (c)		    |
 *		    |		 fcp_cmd	     | <--------------------+
 *		    |	(DMA resources associated)   |
 *		    +================================+
 *
 * (a) Only if DMA is NOT used for the FCP_CMD buffer.
 * (b) Only if DMA is NOT used for the FCP_RESP buffer
 * (c) Only if DMA is used for the FCP_CMD buffer.
 * (d) Only if DMA is used for the FCP_RESP buffer
 */
static int
fcp_kmem_cache_constructor(struct scsi_pkt *pkt, scsi_hba_tran_t *tran,
    int kmflags)
{
	struct fcp_pkt	*cmd;
	struct fcp_port	*pptr;
	fc_packet_t	*fpkt;

	pptr = (struct fcp_port *)tran->tran_hba_private;
	cmd = (struct fcp_pkt *)pkt->pkt_ha_private;
	bzero(cmd, tran->tran_hba_len);

	cmd->cmd_pkt = pkt;
	pkt->pkt_cdbp = cmd->cmd_fcp_cmd.fcp_cdb;
	fpkt = (fc_packet_t *)&cmd->cmd_fc_packet;
	cmd->cmd_fp_pkt = fpkt;

	cmd->cmd_pkt->pkt_ha_private = (opaque_t)cmd;
	cmd->cmd_fp_pkt->pkt_ulp_private = (opaque_t)cmd;
	cmd->cmd_fp_pkt->pkt_fca_private = (opaque_t)((caddr_t)cmd +
	    sizeof (struct fcp_pkt) + pptr->port_dmacookie_sz);

	fpkt->pkt_data_cookie = (ddi_dma_cookie_t *)((caddr_t)cmd +
	    sizeof (struct fcp_pkt));

	fpkt->pkt_cmdlen = sizeof (struct fcp_cmd);
	fpkt->pkt_rsplen = FCP_MAX_RSP_IU_SIZE;

	if (pptr->port_fcp_dma == FC_NO_DVMA_SPACE) {
		/*
		 * The underlying HBA doesn't want to DMA the fcp_cmd or
		 * fcp_resp.  The transfer of information will be done by
		 * bcopy.
		 * The naming of the flags (that is actually a value) is
		 * unfortunate.	 FC_NO_DVMA_SPACE doesn't mean "NO VIRTUAL
		 * DMA" but instead "NO DMA".
		 */
		fpkt->pkt_resp_acc = fpkt->pkt_cmd_acc = NULL;
		fpkt->pkt_cmd = (caddr_t)&cmd->cmd_fcp_cmd;
		fpkt->pkt_resp = cmd->cmd_fcp_rsp;
	} else {
		/*
		 * The underlying HBA will dma the fcp_cmd buffer and fcp_resp
		 * buffer.  A buffer is allocated for each one the ddi_dma_*
		 * interfaces.
		 */
		if (fcp_alloc_cmd_resp(pptr, fpkt, kmflags) != FC_SUCCESS) {
			return (-1);
		}
	}

	return (0);
}

/*
 *     Function: fcp_kmem_cache_destructor
 *
 *  Description: Called by the destructor of the cache managed by SCSA.
 *		 All the resources pre-allocated in fcp_pkt_constructor
 *		 and the data also pre-initialized in fcp_pkt_constructor
 *		 are freed and uninitialized here.
 *
 *     Argument: *buf		Memory to uninitialize.
 *		 *arg		FCP port structure (fcp_port).
 *
 * Return Value: None
 *
 *	Context: kernel
 */
static void
fcp_kmem_cache_destructor(struct scsi_pkt *pkt, scsi_hba_tran_t *tran)
{
	struct fcp_pkt	*cmd;
	struct fcp_port	*pptr;

	pptr = (struct fcp_port *)(tran->tran_hba_private);
	cmd = pkt->pkt_ha_private;

	if (pptr->port_fcp_dma != FC_NO_DVMA_SPACE) {
		/*
		 * If DMA was used to transfer the FCP_CMD and FCP_RESP, the
		 * buffer and DMA resources allocated to do so are released.
		 */
		fcp_free_cmd_resp(pptr, cmd->cmd_fp_pkt);
	}
}

/*
 *     Function: fcp_alloc_cmd_resp
 *
 *  Description: This function allocated an FCP_CMD and FCP_RESP buffer that
 *		 will be DMAed by the HBA.  The buffer is allocated applying
 *		 the DMA requirements for the HBA.  The buffers allocated will
 *		 also be bound.	 DMA resources are allocated in the process.
 *		 They will be released by fcp_free_cmd_resp().
 *
 *     Argument: *pptr	FCP port.
 *		 *fpkt	fc packet for which the cmd and resp packet should be
 *			allocated.
 *		 flags	Allocation flags.
 *
 * Return Value: FC_FAILURE
 *		 FC_SUCCESS
 *
 *	Context: User or Kernel context only if flags == KM_SLEEP.
 *		 Interrupt context if the KM_SLEEP is not specified.
 */
static int
fcp_alloc_cmd_resp(struct fcp_port *pptr, fc_packet_t *fpkt, int flags)
{
	int			rval;
	int			cmd_len;
	int			resp_len;
	ulong_t			real_len;
	int			(*cb) (caddr_t);
	ddi_dma_cookie_t	pkt_cookie;
	ddi_dma_cookie_t	*cp;
	uint32_t		cnt;

	cb = (flags == KM_SLEEP) ? DDI_DMA_SLEEP : DDI_DMA_DONTWAIT;

	cmd_len = fpkt->pkt_cmdlen;
	resp_len = fpkt->pkt_rsplen;

	ASSERT(fpkt->pkt_cmd_dma == NULL);

	/* Allocation of a DMA handle used in subsequent calls. */
	if (ddi_dma_alloc_handle(pptr->port_dip, &pptr->port_cmd_dma_attr,
	    cb, NULL, &fpkt->pkt_cmd_dma) != DDI_SUCCESS) {
		return (FC_FAILURE);
	}

	/* A buffer is allocated that satisfies the DMA requirements. */
	rval = ddi_dma_mem_alloc(fpkt->pkt_cmd_dma, cmd_len,
	    &pptr->port_dma_acc_attr, DDI_DMA_CONSISTENT, cb, NULL,
	    (caddr_t *)&fpkt->pkt_cmd, &real_len, &fpkt->pkt_cmd_acc);

	if (rval != DDI_SUCCESS) {
		ddi_dma_free_handle(&fpkt->pkt_cmd_dma);
		return (FC_FAILURE);
	}

	if (real_len < cmd_len) {
		ddi_dma_mem_free(&fpkt->pkt_cmd_acc);
		ddi_dma_free_handle(&fpkt->pkt_cmd_dma);
		return (FC_FAILURE);
	}

	/* The buffer allocated is DMA bound. */
	rval = ddi_dma_addr_bind_handle(fpkt->pkt_cmd_dma, NULL,
	    fpkt->pkt_cmd, real_len, DDI_DMA_WRITE | DDI_DMA_CONSISTENT,
	    cb, NULL, &pkt_cookie, &fpkt->pkt_cmd_cookie_cnt);

	if (rval != DDI_DMA_MAPPED) {
		ddi_dma_mem_free(&fpkt->pkt_cmd_acc);
		ddi_dma_free_handle(&fpkt->pkt_cmd_dma);
		return (FC_FAILURE);
	}

	if (fpkt->pkt_cmd_cookie_cnt >
	    pptr->port_cmd_dma_attr.dma_attr_sgllen) {
		(void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma);
		ddi_dma_mem_free(&fpkt->pkt_cmd_acc);
		ddi_dma_free_handle(&fpkt->pkt_cmd_dma);
		return (FC_FAILURE);
	}

	ASSERT(fpkt->pkt_cmd_cookie_cnt != 0);

	/*
	 * The buffer where the scatter/gather list is going to be built is
	 * allocated.
	 */
	cp = fpkt->pkt_cmd_cookie = (ddi_dma_cookie_t *)kmem_alloc(
	    fpkt->pkt_cmd_cookie_cnt * sizeof (pkt_cookie),
	    KM_NOSLEEP);

	if (cp == NULL) {
		(void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma);
		ddi_dma_mem_free(&fpkt->pkt_cmd_acc);
		ddi_dma_free_handle(&fpkt->pkt_cmd_dma);
		return (FC_FAILURE);
	}

	/*
	 * The scatter/gather list for the buffer we just allocated is built
	 * here.
	 */
	*cp = pkt_cookie;
	cp++;

	for (cnt = 1; cnt < fpkt->pkt_cmd_cookie_cnt; cnt++, cp++) {
		ddi_dma_nextcookie(fpkt->pkt_cmd_dma,
		    &pkt_cookie);
		*cp = pkt_cookie;
	}

	ASSERT(fpkt->pkt_resp_dma == NULL);
	if (ddi_dma_alloc_handle(pptr->port_dip, &pptr->port_resp_dma_attr,
	    cb, NULL, &fpkt->pkt_resp_dma) != DDI_SUCCESS) {
		(void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma);
		ddi_dma_mem_free(&fpkt->pkt_cmd_acc);
		ddi_dma_free_handle(&fpkt->pkt_cmd_dma);
		return (FC_FAILURE);
	}

	rval = ddi_dma_mem_alloc(fpkt->pkt_resp_dma, resp_len,
	    &pptr->port_dma_acc_attr, DDI_DMA_CONSISTENT, cb, NULL,
	    (caddr_t *)&fpkt->pkt_resp, &real_len,
	    &fpkt->pkt_resp_acc);

	if (rval != DDI_SUCCESS) {
		ddi_dma_free_handle(&fpkt->pkt_resp_dma);
		(void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma);
		ddi_dma_mem_free(&fpkt->pkt_cmd_acc);
		ddi_dma_free_handle(&fpkt->pkt_cmd_dma);
		kmem_free(fpkt->pkt_cmd_cookie,
		    fpkt->pkt_cmd_cookie_cnt * sizeof (pkt_cookie));
		return (FC_FAILURE);
	}

	if (real_len < resp_len) {
		ddi_dma_mem_free(&fpkt->pkt_resp_acc);
		ddi_dma_free_handle(&fpkt->pkt_resp_dma);
		(void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma);
		ddi_dma_mem_free(&fpkt->pkt_cmd_acc);
		ddi_dma_free_handle(&fpkt->pkt_cmd_dma);
		kmem_free(fpkt->pkt_cmd_cookie,
		    fpkt->pkt_cmd_cookie_cnt * sizeof (pkt_cookie));
		return (FC_FAILURE);
	}

	rval = ddi_dma_addr_bind_handle(fpkt->pkt_resp_dma, NULL,
	    fpkt->pkt_resp, real_len, DDI_DMA_READ | DDI_DMA_CONSISTENT,
	    cb, NULL, &pkt_cookie, &fpkt->pkt_resp_cookie_cnt);

	if (rval != DDI_DMA_MAPPED) {
		ddi_dma_mem_free(&fpkt->pkt_resp_acc);
		ddi_dma_free_handle(&fpkt->pkt_resp_dma);
		(void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma);
		ddi_dma_mem_free(&fpkt->pkt_cmd_acc);
		ddi_dma_free_handle(&fpkt->pkt_cmd_dma);
		kmem_free(fpkt->pkt_cmd_cookie,
		    fpkt->pkt_cmd_cookie_cnt * sizeof (pkt_cookie));
		return (FC_FAILURE);
	}

	if (fpkt->pkt_resp_cookie_cnt >
	    pptr->port_resp_dma_attr.dma_attr_sgllen) {
		ddi_dma_mem_free(&fpkt->pkt_resp_acc);
		ddi_dma_free_handle(&fpkt->pkt_resp_dma);
		(void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma);
		ddi_dma_mem_free(&fpkt->pkt_cmd_acc);
		ddi_dma_free_handle(&fpkt->pkt_cmd_dma);
		kmem_free(fpkt->pkt_cmd_cookie,
		    fpkt->pkt_cmd_cookie_cnt * sizeof (pkt_cookie));
		return (FC_FAILURE);
	}

	ASSERT(fpkt->pkt_resp_cookie_cnt != 0);

	cp = fpkt->pkt_resp_cookie = (ddi_dma_cookie_t *)kmem_alloc(
	    fpkt->pkt_resp_cookie_cnt * sizeof (pkt_cookie),
	    KM_NOSLEEP);

	if (cp == NULL) {
		ddi_dma_mem_free(&fpkt->pkt_resp_acc);
		ddi_dma_free_handle(&fpkt->pkt_resp_dma);
		(void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma);
		ddi_dma_mem_free(&fpkt->pkt_cmd_acc);
		ddi_dma_free_handle(&fpkt->pkt_cmd_dma);
		kmem_free(fpkt->pkt_cmd_cookie,
		    fpkt->pkt_cmd_cookie_cnt * sizeof (pkt_cookie));
		return (FC_FAILURE);
	}

	*cp = pkt_cookie;
	cp++;

	for (cnt = 1; cnt < fpkt->pkt_resp_cookie_cnt; cnt++, cp++) {
		ddi_dma_nextcookie(fpkt->pkt_resp_dma,
		    &pkt_cookie);
		*cp = pkt_cookie;
	}

	return (FC_SUCCESS);
}

/*
 *     Function: fcp_free_cmd_resp
 *
 *  Description: This function releases the FCP_CMD and FCP_RESP buffer
 *		 allocated by fcp_alloc_cmd_resp() and all the resources
 *		 associated with them.	That includes the DMA resources and the
 *		 buffer allocated for the cookies of each one of them.
 *
 *     Argument: *pptr		FCP port context.
 *		 *fpkt		fc packet containing the cmd and resp packet
 *				to be released.
 *
 * Return Value: None
 *
 *	Context: Interrupt, User and Kernel context.
 */
/* ARGSUSED */
static void
fcp_free_cmd_resp(struct fcp_port *pptr, fc_packet_t *fpkt)
{
	ASSERT(fpkt->pkt_resp_dma != NULL && fpkt->pkt_cmd_dma != NULL);

	if (fpkt->pkt_resp_dma) {
		(void) ddi_dma_unbind_handle(fpkt->pkt_resp_dma);
		ddi_dma_mem_free(&fpkt->pkt_resp_acc);
		ddi_dma_free_handle(&fpkt->pkt_resp_dma);
	}

	if (fpkt->pkt_resp_cookie) {
		kmem_free(fpkt->pkt_resp_cookie,
		    fpkt->pkt_resp_cookie_cnt * sizeof (ddi_dma_cookie_t));
		fpkt->pkt_resp_cookie = NULL;
	}

	if (fpkt->pkt_cmd_dma) {
		(void) ddi_dma_unbind_handle(fpkt->pkt_cmd_dma);
		ddi_dma_mem_free(&fpkt->pkt_cmd_acc);
		ddi_dma_free_handle(&fpkt->pkt_cmd_dma);
	}

	if (fpkt->pkt_cmd_cookie) {
		kmem_free(fpkt->pkt_cmd_cookie,
		    fpkt->pkt_cmd_cookie_cnt * sizeof (ddi_dma_cookie_t));
		fpkt->pkt_cmd_cookie = NULL;
	}
}


/*
 * called by the transport to do our own target initialization
 *
 * can acquire and release the global mutex
 */
/* ARGSUSED */
static int
fcp_phys_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip,
    scsi_hba_tran_t *hba_tran, struct scsi_device *sd)
{
	int			*words;
	uchar_t			*bytes;
	uint_t			nbytes;
	uint_t			nwords;
	struct fcp_tgt	*ptgt;
	struct fcp_lun	*plun;
	struct fcp_port	*pptr = (struct fcp_port *)
	    hba_tran->tran_hba_private;

	ASSERT(pptr != NULL);

	FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace,
	    FCP_BUF_LEVEL_8, 0,
	    "fcp_phys_tgt_init: called for %s (instance %d)",
	    ddi_get_name(tgt_dip), ddi_get_instance(tgt_dip));

	/* get our port WWN property */
	bytes = NULL;
	if ((ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, tgt_dip,
	    DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, PORT_WWN_PROP, &bytes,
	    &nbytes) != DDI_PROP_SUCCESS) || nbytes != FC_WWN_SIZE) {
		/* no port WWN property */
		FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace,
		    FCP_BUF_LEVEL_8, 0,
		    "fcp_phys_tgt_init: Returning DDI_NOT_WELL_FORMED"
		    " for %s (instance %d): bytes=%p nbytes=%x",
		    ddi_get_name(tgt_dip), ddi_get_instance(tgt_dip), bytes,
		    nbytes);

		if (bytes != NULL) {
			ddi_prop_free(bytes);
		}

		return (DDI_NOT_WELL_FORMED);
	}

	words = NULL;
	if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, tgt_dip,
	    DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
	    LUN_PROP, &words, &nwords) != DDI_PROP_SUCCESS) {
		ASSERT(bytes != NULL);

		FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace,
		    FCP_BUF_LEVEL_8, 0,
		    "fcp_phys_tgt_init: Returning DDI_FAILURE:lun"
		    " for %s (instance %d)", ddi_get_name(tgt_dip),
		    ddi_get_instance(tgt_dip));

		ddi_prop_free(bytes);

		return (DDI_NOT_WELL_FORMED);
	}

	if (nwords == 0) {
		ddi_prop_free(bytes);
		ddi_prop_free(words);
		return (DDI_NOT_WELL_FORMED);
	}

	ASSERT(bytes != NULL && words != NULL);

	mutex_enter(&pptr->port_mutex);
	if ((plun = fcp_lookup_lun(pptr, bytes, *words)) == NULL) {
		mutex_exit(&pptr->port_mutex);
		FCP_DTRACE(fcp_logq, pptr->port_instbuf, fcp_trace,
		    FCP_BUF_LEVEL_8, 0,
		    "fcp_phys_tgt_init: Returning DDI_FAILURE: No Lun"
		    " for %s (instance %d)", ddi_get_name(tgt_dip),
		    ddi_get_instance(tgt_dip));

		ddi_prop_free(bytes);
		ddi_prop_free(words);

		return (DDI_FAILURE);
	}

	ASSERT(bcmp(plun->lun_tgt->tgt_port_wwn.raw_wwn, bytes,
	    FC_WWN_SIZE) == 0);
	ASSERT(plun->lun_num == (uint16_t)*words);

	ddi_prop_free(bytes);
	ddi_prop_free(words);

	ptgt = plun->lun_tgt;

	mutex_enter(&ptgt->tgt_mutex);
	plun->lun_tgt_count++;
	scsi_device_hba_private_set(sd, plun);
	plun->lun_state |= FCP_SCSI_LUN_TGT_INIT;
	plun->lun_tran = hba_tran;
	mutex_exit(&ptgt->tgt_mutex);
	mutex_exit(&pptr->port_mutex);

	return (DDI_SUCCESS);
}

/*ARGSUSED*/
static int
fcp_virt_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip,
    scsi_hba_tran_t *hba_tran, struct scsi_device *sd)
{
	int			words;
	uchar_t			*bytes;
	uint_t			nbytes;
	struct fcp_tgt	*ptgt;
	struct fcp_lun	*plun;
	struct fcp_port	*pptr = (struct fcp_port *)
	    hba_tran->tran_hba_private;
	child_info_t		*cip;

	ASSERT(pptr != NULL);

	FCP_DTRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_8, 0,
	    "fcp_virt_tgt_init: called for %s (instance %d) (hba_dip %p),"
	    " (tgt_dip %p)", ddi_get_name(tgt_dip),
	    ddi_get_instance(tgt_dip), hba_dip, tgt_dip);

	cip = (child_info_t *)sd->sd_pathinfo;
	if (cip == NULL) {
		FCP_DTRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_8, 0,
		    "fcp_virt_tgt_init: Returning DDI_NOT_WELL_FORMED"
		    " for %s (instance %d)", ddi_get_name(tgt_dip),
		    ddi_get_instance(tgt_dip));

		return (DDI_NOT_WELL_FORMED);
	}

	/* get our port WWN property */
	bytes = NULL;
	if ((mdi_prop_lookup_byte_array(PIP(cip), PORT_WWN_PROP, &bytes,
	    &nbytes) != DDI_PROP_SUCCESS) || nbytes != FC_WWN_SIZE) {
		if (bytes) {
			(void) mdi_prop_free(bytes);
		}
		return (DDI_NOT_WELL_FORMED);
	}

	words = 0;
	if (mdi_prop_lookup_int(PIP(cip), LUN_PROP, &words) !=
	    DDI_PROP_SUCCESS) {
		ASSERT(bytes != NULL);

		FCP_DTRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_8, 0,
		    "fcp_virt_tgt_init: Returning DDI_FAILURE:lun"
		    " for %s (instance %d)", ddi_get_name(tgt_dip),
		    ddi_get_instance(tgt_dip));

		(void) mdi_prop_free(bytes);
		return (DDI_NOT_WELL_FORMED);
	}

	ASSERT(bytes != NULL);

	mutex_enter(&pptr->port_mutex);
	if ((plun = fcp_lookup_lun(pptr, bytes, words)) == NULL) {
		mutex_exit(&pptr->port_mutex);
		FCP_DTRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_8, 0,
		    "fcp_virt_tgt_init: Returning DDI_FAILURE: No Lun"
		    " for %s (instance %d)", ddi_get_name(tgt_dip),
		    ddi_get_instance(tgt_dip));

		(void) mdi_prop_free(bytes);
		(void) mdi_prop_free(&words);

		return (DDI_FAILURE);
	}

	ASSERT(bcmp(plun->lun_tgt->tgt_port_wwn.raw_wwn, bytes,
	    FC_WWN_SIZE) == 0);
	ASSERT(plun->lun_num == (uint16_t)words);

	(void) mdi_prop_free(bytes);
	(void) mdi_prop_free(&words);

	ptgt = plun->lun_tgt;

	mutex_enter(&ptgt->tgt_mutex);
	plun->lun_tgt_count++;
	scsi_device_hba_private_set(sd, plun);
	plun->lun_state |= FCP_SCSI_LUN_TGT_INIT;
	plun->lun_tran = hba_tran;
	mutex_exit(&ptgt->tgt_mutex);
	mutex_exit(&pptr->port_mutex);

	return (DDI_SUCCESS);
}


/*
 * called by the transport to do our own target initialization
 *
 * can acquire and release the global mutex
 */
/* ARGSUSED */
static int
fcp_scsi_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip,
    scsi_hba_tran_t *hba_tran, struct scsi_device *sd)
{
	struct fcp_port	*pptr = (struct fcp_port *)
	    hba_tran->tran_hba_private;
	int			rval;

	ASSERT(pptr != NULL);

	/*
	 * Child node is getting initialized.  Look at the mpxio component
	 * type on the child device to see if this device is mpxio managed
	 * or not.
	 */
	if (mdi_component_is_client(tgt_dip, NULL) == MDI_SUCCESS) {
		rval = fcp_virt_tgt_init(hba_dip, tgt_dip, hba_tran, sd);
	} else {
		rval = fcp_phys_tgt_init(hba_dip, tgt_dip, hba_tran, sd);
	}

	return (rval);
}


/* ARGSUSED */
static void
fcp_scsi_tgt_free(dev_info_t *hba_dip, dev_info_t *tgt_dip,
    scsi_hba_tran_t *hba_tran, struct scsi_device *sd)
{
	struct fcp_lun	*plun = scsi_device_hba_private_get(sd);
	struct fcp_tgt	*ptgt;

	FCP_DTRACE(fcp_logq, LUN_PORT->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_8, 0,
	    "fcp_scsi_tgt_free: called for tran %s%d, dev %s%d",
	    ddi_get_name(hba_dip), ddi_get_instance(hba_dip),
	    ddi_get_name(tgt_dip), ddi_get_instance(tgt_dip));

	if (plun == NULL) {
		return;
	}
	ptgt = plun->lun_tgt;

	ASSERT(ptgt != NULL);

	mutex_enter(&ptgt->tgt_mutex);
	ASSERT(plun->lun_tgt_count > 0);

	if (--plun->lun_tgt_count == 0) {
		plun->lun_state &= ~FCP_SCSI_LUN_TGT_INIT;
	}
	plun->lun_tran = NULL;
	mutex_exit(&ptgt->tgt_mutex);
}

/*
 *     Function: fcp_scsi_start
 *
 *  Description: This function is called by the target driver to request a
 *		 command to be sent.
 *
 *     Argument: *ap		SCSI address of the device.
 *		 *pkt		SCSI packet containing the cmd to send.
 *
 * Return Value: TRAN_ACCEPT
 *		 TRAN_BUSY
 *		 TRAN_BADPKT
 *		 TRAN_FATAL_ERROR
 */
static int
fcp_scsi_start(struct scsi_address *ap, struct scsi_pkt *pkt)
{
	struct fcp_port	*pptr = ADDR2FCP(ap);
	struct fcp_lun	*plun = ADDR2LUN(ap);
	struct fcp_pkt	*cmd = PKT2CMD(pkt);
	struct fcp_tgt	*ptgt = plun->lun_tgt;
	int			rval;

	/* ensure command isn't already issued */
	ASSERT(cmd->cmd_state != FCP_PKT_ISSUED);

	FCP_DTRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_9, 0,
	    "fcp_transport Invoked for %x", plun->lun_tgt->tgt_d_id);

	/*
	 * It is strange that we enter the fcp_port mutex and the target
	 * mutex to check the lun state (which has a mutex of its own).
	 */
	mutex_enter(&pptr->port_mutex);
	mutex_enter(&ptgt->tgt_mutex);

	/*
	 * If the device is offline and is not in the process of coming
	 * online, fail the request.
	 */

	if ((plun->lun_state & FCP_LUN_OFFLINE) &&
	    !(plun->lun_state & FCP_LUN_ONLINING)) {
		mutex_exit(&ptgt->tgt_mutex);
		mutex_exit(&pptr->port_mutex);

		if (cmd->cmd_fp_pkt->pkt_pd == NULL) {
			pkt->pkt_reason = CMD_DEV_GONE;
		}

		return (TRAN_FATAL_ERROR);
	}
	cmd->cmd_fp_pkt->pkt_timeout = pkt->pkt_time;

	/*
	 * If we are suspended, kernel is trying to dump, so don't
	 * block, fail or defer requests - send them down right away.
	 * NOTE: If we are in panic (i.e. trying to dump), we can't
	 * assume we have been suspended.  There is hardware such as
	 * the v880 that doesn't do PM.	 Thus, the check for
	 * ddi_in_panic.
	 *
	 * If FCP_STATE_IN_CB_DEVC is set, devices are in the process
	 * of changing.	 So, if we can queue the packet, do it.	 Eventually,
	 * either the device will have gone away or changed and we can fail
	 * the request, or we can proceed if the device didn't change.
	 *
	 * If the pd in the target or the packet is NULL it's probably
	 * because the device has gone away, we allow the request to be
	 * put on the internal queue here in case the device comes back within
	 * the offline timeout. fctl will fix up the pd's if the tgt_pd_handle
	 * has gone NULL, while fcp deals cases where pkt_pd is NULL. pkt_pd
	 * could be NULL because the device was disappearing during or since
	 * packet initialization.
	 */

	if (((plun->lun_state & FCP_LUN_BUSY) && (!(pptr->port_state &
	    FCP_STATE_SUSPENDED)) && !ddi_in_panic()) ||
	    (pptr->port_state & (FCP_STATE_ONLINING | FCP_STATE_IN_CB_DEVC)) ||
	    (ptgt->tgt_pd_handle == NULL) ||
	    (cmd->cmd_fp_pkt->pkt_pd == NULL)) {
		/*
		 * If ((LUN is busy AND
		 *	LUN not suspended AND
		 *	The system is not in panic state) OR
		 *	(The port is coming up))
		 *
		 * We check to see if the any of the flags FLAG_NOINTR or
		 * FLAG_NOQUEUE is set.	 If one of them is set the value
		 * returned will be TRAN_BUSY.	If not, the request is queued.
		 */
		mutex_exit(&ptgt->tgt_mutex);
		mutex_exit(&pptr->port_mutex);

		/* see if using interrupts is allowed (so queueing'll work) */
		if (pkt->pkt_flags & FLAG_NOINTR) {
			pkt->pkt_resid = 0;
			return (TRAN_BUSY);
		}
		if (pkt->pkt_flags & FLAG_NOQUEUE) {
			FCP_DTRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_9, 0,
			    "fcp_scsi_start: lun busy for pkt %p", pkt);
			return (TRAN_BUSY);
		}
#ifdef	DEBUG
		mutex_enter(&pptr->port_pkt_mutex);
		pptr->port_npkts++;
		mutex_exit(&pptr->port_pkt_mutex);
#endif /* DEBUG */

		/* got queue up the pkt for later */
		fcp_queue_pkt(pptr, cmd);
		return (TRAN_ACCEPT);
	}
	cmd->cmd_state = FCP_PKT_ISSUED;

	mutex_exit(&ptgt->tgt_mutex);
	mutex_exit(&pptr->port_mutex);

	/*
	 * Now that we released the mutexes, what was protected by them can
	 * change.
	 */

	/*
	 * If there is a reconfiguration in progress, wait for it to complete.
	 */
	fcp_reconfig_wait(pptr);

	cmd->cmd_timeout = pkt->pkt_time ? fcp_watchdog_time +
	    pkt->pkt_time : 0;

	/* prepare the packet */

	fcp_prepare_pkt(pptr, cmd, plun);

	if (cmd->cmd_pkt->pkt_time) {
		cmd->cmd_fp_pkt->pkt_timeout = cmd->cmd_pkt->pkt_time;
	} else {
		cmd->cmd_fp_pkt->pkt_timeout = 5 * 60 * 60;
	}

	/*
	 * if interrupts aren't allowed (e.g. at dump time) then we'll
	 * have to do polled I/O
	 */
	if (pkt->pkt_flags & FLAG_NOINTR) {
		cmd->cmd_state &= ~FCP_PKT_ISSUED;
		return (fcp_dopoll(pptr, cmd));
	}

#ifdef	DEBUG
	mutex_enter(&pptr->port_pkt_mutex);
	pptr->port_npkts++;
	mutex_exit(&pptr->port_pkt_mutex);
#endif /* DEBUG */

	rval = fcp_transport(pptr->port_fp_handle, cmd->cmd_fp_pkt, 0);
	if (rval == FC_SUCCESS) {
		FCP_DTRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_9, 0,
		    "fcp_transport success for %x", plun->lun_tgt->tgt_d_id);
		return (TRAN_ACCEPT);
	}

	cmd->cmd_state = FCP_PKT_IDLE;

#ifdef	DEBUG
	mutex_enter(&pptr->port_pkt_mutex);
	pptr->port_npkts--;
	mutex_exit(&pptr->port_pkt_mutex);
#endif /* DEBUG */

	/*
	 * For lack of clearer definitions, choose
	 * between TRAN_BUSY and TRAN_FATAL_ERROR.
	 */

	if (rval == FC_TRAN_BUSY) {
		pkt->pkt_resid = 0;
		rval = TRAN_BUSY;
	} else {
		mutex_enter(&ptgt->tgt_mutex);
		if (plun->lun_state & FCP_LUN_OFFLINE) {
			child_info_t	*cip;

			mutex_enter(&plun->lun_mutex);
			cip = plun->lun_cip;
			mutex_exit(&plun->lun_mutex);

			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_6, 0,
			    "fcp_transport failed 2 for %x: %x; dip=%p",
			    plun->lun_tgt->tgt_d_id, rval, cip);

			rval = TRAN_FATAL_ERROR;
		} else {
			if (pkt->pkt_flags & FLAG_NOQUEUE) {
				FCP_DTRACE(fcp_logq, pptr->port_instbuf,
				    fcp_trace, FCP_BUF_LEVEL_9, 0,
				    "fcp_scsi_start: FC_BUSY for pkt %p",
				    pkt);
				rval = TRAN_BUSY;
			} else {
				rval = TRAN_ACCEPT;
				fcp_queue_pkt(pptr, cmd);
			}
		}
		mutex_exit(&ptgt->tgt_mutex);
	}

	return (rval);
}

/*
 * called by the transport to abort a packet
 */
/*ARGSUSED*/
static int
fcp_scsi_abort(struct scsi_address *ap, struct scsi_pkt *pkt)
{
	int tgt_cnt;
	struct fcp_port		*pptr = ADDR2FCP(ap);
	struct fcp_lun	*plun = ADDR2LUN(ap);
	struct fcp_tgt	*ptgt = plun->lun_tgt;

	if (pkt == NULL) {
		if (ptgt) {
			mutex_enter(&ptgt->tgt_mutex);
			tgt_cnt = ptgt->tgt_change_cnt;
			mutex_exit(&ptgt->tgt_mutex);
			fcp_abort_all(pptr, ptgt, plun, tgt_cnt);
			return (TRUE);
		}
	}
	return (FALSE);
}


/*
 * Perform reset
 */
int
fcp_scsi_reset(struct scsi_address *ap, int level)
{
	int			rval = 0;
	struct fcp_port		*pptr = ADDR2FCP(ap);
	struct fcp_lun	*plun = ADDR2LUN(ap);
	struct fcp_tgt	*ptgt = plun->lun_tgt;

	if (level == RESET_ALL) {
		if (fcp_linkreset(pptr, ap, KM_NOSLEEP) == FC_SUCCESS) {
			rval = 1;
		}
	} else if (level == RESET_TARGET || level == RESET_LUN) {
		/*
		 * If we are in the middle of discovery, return
		 * SUCCESS as this target will be rediscovered
		 * anyway
		 */
		mutex_enter(&ptgt->tgt_mutex);
		if (ptgt->tgt_state & (FCP_TGT_OFFLINE | FCP_TGT_BUSY)) {
			mutex_exit(&ptgt->tgt_mutex);
			return (1);
		}
		mutex_exit(&ptgt->tgt_mutex);

		if (fcp_reset_target(ap, level) == FC_SUCCESS) {
			rval = 1;
		}
	}
	return (rval);
}


/*
 * called by the framework to get a SCSI capability
 */
static int
fcp_scsi_getcap(struct scsi_address *ap, char *cap, int whom)
{
	return (fcp_commoncap(ap, cap, 0, whom, 0));
}


/*
 * called by the framework to set a SCSI capability
 */
static int
fcp_scsi_setcap(struct scsi_address *ap, char *cap, int value, int whom)
{
	return (fcp_commoncap(ap, cap, value, whom, 1));
}

/*
 *     Function: fcp_pkt_setup
 *
 *  Description: This function sets up the scsi_pkt structure passed by the
 *		 caller. This function assumes fcp_pkt_constructor has been
 *		 called previously for the packet passed by the caller.	 If
 *		 successful this call will have the following results:
 *
 *		   - The resources needed that will be constant through out
 *		     the whole transaction are allocated.
 *		   - The fields that will be constant through out the whole
 *		     transaction are initialized.
 *		   - The scsi packet will be linked to the LUN structure
 *		     addressed by the transaction.
 *
 *     Argument:
 *		 *pkt		Pointer to a scsi_pkt structure.
 *		 callback
 *		 arg
 *
 * Return Value: 0	Success
 *		 !0	Failure
 *
 *	Context: Kernel context or interrupt context
 */
/* ARGSUSED */
static int
fcp_pkt_setup(struct scsi_pkt *pkt,
    int (*callback)(caddr_t arg),
    caddr_t arg)
{
	struct fcp_pkt	*cmd;
	struct fcp_port	*pptr;
	struct fcp_lun	*plun;
	struct fcp_tgt	*ptgt;
	int		kf;
	fc_packet_t	*fpkt;
	fc_frame_hdr_t	*hp;

	pptr = ADDR2FCP(&pkt->pkt_address);
	plun = ADDR2LUN(&pkt->pkt_address);
	ptgt = plun->lun_tgt;

	cmd = (struct fcp_pkt *)pkt->pkt_ha_private;
	fpkt = cmd->cmd_fp_pkt;

	/*
	 * this request is for dma allocation only
	 */
	/*
	 * First step of fcp_scsi_init_pkt: pkt allocation
	 * We determine if the caller is willing to wait for the
	 * resources.
	 */
	kf = (callback == SLEEP_FUNC) ? KM_SLEEP: KM_NOSLEEP;

	/*
	 * Selective zeroing of the pkt.
	 */
	cmd->cmd_back = NULL;
	cmd->cmd_next = NULL;

	/*
	 * Zero out fcp command
	 */
	bzero(&cmd->cmd_fcp_cmd, sizeof (cmd->cmd_fcp_cmd));

	cmd->cmd_state = FCP_PKT_IDLE;

	fpkt = cmd->cmd_fp_pkt;
	fpkt->pkt_data_acc = NULL;

	mutex_enter(&ptgt->tgt_mutex);
	fpkt->pkt_pd = ptgt->tgt_pd_handle;

	if (fc_ulp_init_packet(pptr->port_fp_handle, fpkt, kf)
	    != FC_SUCCESS) {
		mutex_exit(&ptgt->tgt_mutex);
		return (-1);
	}

	mutex_exit(&ptgt->tgt_mutex);

	/* Fill in the Fabric Channel Header */
	hp = &fpkt->pkt_cmd_fhdr;
	hp->r_ctl = R_CTL_COMMAND;
	hp->rsvd = 0;
	hp->type = FC_TYPE_SCSI_FCP;
	hp->f_ctl = F_CTL_SEQ_INITIATIVE | F_CTL_FIRST_SEQ;
	hp->seq_id = 0;
	hp->df_ctl  = 0;
	hp->seq_cnt = 0;
	hp->ox_id = 0xffff;
	hp->rx_id = 0xffff;
	hp->ro = 0;

	/*
	 * A doubly linked list (cmd_forw, cmd_back) is built
	 * out of every allocated packet on a per-lun basis
	 *
	 * The packets are maintained in the list so as to satisfy
	 * scsi_abort() requests. At present (which is unlikely to
	 * change in the future) nobody performs a real scsi_abort
	 * in the SCSI target drivers (as they don't keep the packets
	 * after doing scsi_transport - so they don't know how to
	 * abort a packet other than sending a NULL to abort all
	 * outstanding packets)
	 */
	mutex_enter(&plun->lun_mutex);
	if ((cmd->cmd_forw = plun->lun_pkt_head) != NULL) {
		plun->lun_pkt_head->cmd_back = cmd;
	} else {
		plun->lun_pkt_tail = cmd;
	}
	plun->lun_pkt_head = cmd;
	mutex_exit(&plun->lun_mutex);
	return (0);
}

/*
 *     Function: fcp_pkt_teardown
 *
 *  Description: This function releases a scsi_pkt structure and all the
 *		 resources attached to it.
 *
 *     Argument: *pkt		Pointer to a scsi_pkt structure.
 *
 * Return Value: None
 *
 *	Context: User, Kernel or Interrupt context.
 */
static void
fcp_pkt_teardown(struct scsi_pkt *pkt)
{
	struct fcp_port	*pptr = ADDR2FCP(&pkt->pkt_address);
	struct fcp_lun	*plun = ADDR2LUN(&pkt->pkt_address);
	struct fcp_pkt	*cmd = (struct fcp_pkt *)pkt->pkt_ha_private;

	/*
	 * Remove the packet from the per-lun list
	 */
	mutex_enter(&plun->lun_mutex);
	if (cmd->cmd_back) {
		ASSERT(cmd != plun->lun_pkt_head);
		cmd->cmd_back->cmd_forw = cmd->cmd_forw;
	} else {
		ASSERT(cmd == plun->lun_pkt_head);
		plun->lun_pkt_head = cmd->cmd_forw;
	}

	if (cmd->cmd_forw) {
		cmd->cmd_forw->cmd_back = cmd->cmd_back;
	} else {
		ASSERT(cmd == plun->lun_pkt_tail);
		plun->lun_pkt_tail = cmd->cmd_back;
	}

	mutex_exit(&plun->lun_mutex);

	(void) fc_ulp_uninit_packet(pptr->port_fp_handle, cmd->cmd_fp_pkt);
}

/*
 * Routine for reset notification setup, to register or cancel.
 * This function is called by SCSA
 */
/*ARGSUSED*/
static int
fcp_scsi_reset_notify(struct scsi_address *ap, int flag,
    void (*callback)(caddr_t), caddr_t arg)
{
	struct fcp_port *pptr = ADDR2FCP(ap);

	return (scsi_hba_reset_notify_setup(ap, flag, callback, arg,
	    &pptr->port_mutex, &pptr->port_reset_notify_listf));
}


static int
fcp_scsi_bus_get_eventcookie(dev_info_t *dip, dev_info_t *rdip, char *name,
    ddi_eventcookie_t *event_cookiep)
{
	struct fcp_port *pptr = fcp_dip2port(dip);

	if (pptr == NULL) {
		return (DDI_FAILURE);
	}

	return (ndi_event_retrieve_cookie(pptr->port_ndi_event_hdl, rdip, name,
	    event_cookiep, NDI_EVENT_NOPASS));
}


static int
fcp_scsi_bus_add_eventcall(dev_info_t *dip, dev_info_t *rdip,
    ddi_eventcookie_t eventid, void (*callback)(), void *arg,
    ddi_callback_id_t *cb_id)
{
	struct fcp_port *pptr = fcp_dip2port(dip);

	if (pptr == NULL) {
		return (DDI_FAILURE);
	}

	return (ndi_event_add_callback(pptr->port_ndi_event_hdl, rdip,
	    eventid, callback, arg, NDI_SLEEP, cb_id));
}


static int
fcp_scsi_bus_remove_eventcall(dev_info_t *dip, ddi_callback_id_t cb_id)
{

	struct fcp_port *pptr = fcp_dip2port(dip);

	if (pptr == NULL) {
		return (DDI_FAILURE);
	}
	return (ndi_event_remove_callback(pptr->port_ndi_event_hdl, cb_id));
}


/*
 * called by the transport to post an event
 */
static int
fcp_scsi_bus_post_event(dev_info_t *dip, dev_info_t *rdip,
    ddi_eventcookie_t eventid, void *impldata)
{
	struct fcp_port *pptr = fcp_dip2port(dip);

	if (pptr == NULL) {
		return (DDI_FAILURE);
	}

	return (ndi_event_run_callbacks(pptr->port_ndi_event_hdl, rdip,
	    eventid, impldata));
}


/*
 * A target in in many cases in Fibre Channel has a one to one relation
 * with a port identifier (which is also known as D_ID and also as AL_PA
 * in private Loop) On Fibre Channel-to-SCSI bridge boxes a target reset
 * will most likely result in resetting all LUNs (which means a reset will
 * occur on all the SCSI devices connected at the other end of the bridge)
 * That is the latest favorite topic for discussion, for, one can debate as
 * hot as one likes and come up with arguably a best solution to one's
 * satisfaction
 *
 * To stay on track and not digress much, here are the problems stated
 * briefly:
 *
 *	SCSA doesn't define RESET_LUN, It defines RESET_TARGET, but the
 *	target drivers use RESET_TARGET even if their instance is on a
 *	LUN. Doesn't that sound a bit broken ?
 *
 *	FCP SCSI (the current spec) only defines RESET TARGET in the
 *	control fields of an FCP_CMND structure. It should have been
 *	fixed right there, giving flexibility to the initiators to
 *	minimize havoc that could be caused by resetting a target.
 */
static int
fcp_reset_target(struct scsi_address *ap, int level)
{
	int			rval = FC_FAILURE;
	char			lun_id[25];
	struct fcp_port		*pptr = ADDR2FCP(ap);
	struct fcp_lun	*plun = ADDR2LUN(ap);
	struct fcp_tgt	*ptgt = plun->lun_tgt;
	struct scsi_pkt		*pkt;
	struct fcp_pkt	*cmd;
	struct fcp_rsp		*rsp;
	uint32_t		tgt_cnt;
	struct fcp_rsp_info	*rsp_info;
	struct fcp_reset_elem	*p;
	int			bval;

	if ((p = kmem_alloc(sizeof (struct fcp_reset_elem),
	    KM_NOSLEEP)) == NULL) {
		return (rval);
	}

	mutex_enter(&ptgt->tgt_mutex);
	if (level == RESET_TARGET) {
		if (ptgt->tgt_state & (FCP_TGT_OFFLINE | FCP_TGT_BUSY)) {
			mutex_exit(&ptgt->tgt_mutex);
			kmem_free(p, sizeof (struct fcp_reset_elem));
			return (rval);
		}
		fcp_update_tgt_state(ptgt, FCP_SET, FCP_LUN_BUSY);
		(void) strcpy(lun_id, " ");
	} else {
		if (plun->lun_state & (FCP_LUN_OFFLINE | FCP_LUN_BUSY)) {
			mutex_exit(&ptgt->tgt_mutex);
			kmem_free(p, sizeof (struct fcp_reset_elem));
			return (rval);
		}
		fcp_update_lun_state(plun, FCP_SET, FCP_LUN_BUSY);

		(void) sprintf(lun_id, ", LUN=%d", plun->lun_num);
	}
	tgt_cnt = ptgt->tgt_change_cnt;

	mutex_exit(&ptgt->tgt_mutex);

	if ((pkt = scsi_init_pkt(ap, NULL, NULL, 0, 0,
	    0, 0, NULL, 0)) == NULL) {
		kmem_free(p, sizeof (struct fcp_reset_elem));
		mutex_enter(&ptgt->tgt_mutex);
		fcp_update_tgt_state(ptgt, FCP_RESET, FCP_LUN_BUSY);
		mutex_exit(&ptgt->tgt_mutex);
		return (rval);
	}
	pkt->pkt_time = FCP_POLL_TIMEOUT;

	/* fill in cmd part of packet */
	cmd = PKT2CMD(pkt);
	if (level == RESET_TARGET) {
		cmd->cmd_fcp_cmd.fcp_cntl.cntl_reset_tgt = 1;
	} else {
		cmd->cmd_fcp_cmd.fcp_cntl.cntl_reset_lun = 1;
	}
	cmd->cmd_fp_pkt->pkt_comp = NULL;
	cmd->cmd_pkt->pkt_flags |= FLAG_NOINTR;

	/* prepare a packet for transport */
	fcp_prepare_pkt(pptr, cmd, plun);

	if (cmd->cmd_pkt->pkt_time) {
		cmd->cmd_fp_pkt->pkt_timeout = cmd->cmd_pkt->pkt_time;
	} else {
		cmd->cmd_fp_pkt->pkt_timeout = 5 * 60 * 60;
	}

	(void) fc_ulp_busy_port(pptr->port_fp_handle);
	bval = fcp_dopoll(pptr, cmd);
	fc_ulp_idle_port(pptr->port_fp_handle);

	/* submit the packet */
	if (bval == TRAN_ACCEPT) {
		int error = 3;

		rsp = (struct fcp_rsp *)cmd->cmd_fcp_rsp;
		rsp_info = (struct fcp_rsp_info *)(cmd->cmd_fcp_rsp +
		    sizeof (struct fcp_rsp));

		if (rsp->fcp_u.fcp_status.rsp_len_set) {
			if (fcp_validate_fcp_response(rsp, pptr) ==
			    FC_SUCCESS) {
				if (pptr->port_fcp_dma != FC_NO_DVMA_SPACE) {
					FCP_CP_IN(cmd->cmd_fp_pkt->pkt_resp +
					    sizeof (struct fcp_rsp), rsp_info,
					    cmd->cmd_fp_pkt->pkt_resp_acc,
					    sizeof (struct fcp_rsp_info));
				}
				if (rsp_info->rsp_code == FCP_NO_FAILURE) {
					rval = FC_SUCCESS;
					error = 0;
				} else {
					error = 1;
				}
			} else {
				error = 2;
			}
		}

		switch (error) {
		case 0:
			fcp_log(CE_WARN, pptr->port_dip,
			    "!FCP: WWN 0x%08x%08x %s reset successfully",
			    *((int *)&ptgt->tgt_port_wwn.raw_wwn[0]),
			    *((int *)&ptgt->tgt_port_wwn.raw_wwn[4]), lun_id);
			break;

		case 1:
			fcp_log(CE_WARN, pptr->port_dip,
			    "!FCP: Reset to WWN	 0x%08x%08x %s failed,"
			    " response code=%x",
			    *((int *)&ptgt->tgt_port_wwn.raw_wwn[0]),
			    *((int *)&ptgt->tgt_port_wwn.raw_wwn[4]), lun_id,
			    rsp_info->rsp_code);
			break;

		case 2:
			fcp_log(CE_WARN, pptr->port_dip,
			    "!FCP: Reset to WWN 0x%08x%08x %s failed,"
			    " Bad FCP response values: rsvd1=%x,"
			    " rsvd2=%x, sts-rsvd1=%x, sts-rsvd2=%x,"
			    " rsplen=%x, senselen=%x",
			    *((int *)&ptgt->tgt_port_wwn.raw_wwn[0]),
			    *((int *)&ptgt->tgt_port_wwn.raw_wwn[4]), lun_id,
			    rsp->reserved_0, rsp->reserved_1,
			    rsp->fcp_u.fcp_status.reserved_0,
			    rsp->fcp_u.fcp_status.reserved_1,
			    rsp->fcp_response_len, rsp->fcp_sense_len);
			break;

		default:
			fcp_log(CE_WARN, pptr->port_dip,
			    "!FCP: Reset to WWN	 0x%08x%08x %s failed",
			    *((int *)&ptgt->tgt_port_wwn.raw_wwn[0]),
			    *((int *)&ptgt->tgt_port_wwn.raw_wwn[4]), lun_id);
			break;
		}
	}
	scsi_destroy_pkt(pkt);

	if (rval == FC_FAILURE) {
		mutex_enter(&ptgt->tgt_mutex);
		if (level == RESET_TARGET) {
			fcp_update_tgt_state(ptgt, FCP_RESET, FCP_LUN_BUSY);
		} else {
			fcp_update_lun_state(plun, FCP_RESET, FCP_LUN_BUSY);
		}
		mutex_exit(&ptgt->tgt_mutex);
		kmem_free(p, sizeof (struct fcp_reset_elem));
		return (rval);
	}

	mutex_enter(&pptr->port_mutex);
	if (level == RESET_TARGET) {
		p->tgt = ptgt;
		p->lun = NULL;
	} else {
		p->tgt = NULL;
		p->lun = plun;
	}
	p->tgt = ptgt;
	p->tgt_cnt = tgt_cnt;
	p->timeout = fcp_watchdog_time + FCP_RESET_DELAY;
	p->next = pptr->port_reset_list;
	pptr->port_reset_list = p;

	FCP_TRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_3, 0,
	    "Notify ssd of the reset to reinstate the reservations");

	scsi_hba_reset_notify_callback(&pptr->port_mutex,
	    &pptr->port_reset_notify_listf);

	mutex_exit(&pptr->port_mutex);

	return (rval);
}


/*
 * called by fcp_getcap and fcp_setcap to get and set (respectively)
 * SCSI capabilities
 */
/* ARGSUSED */
static int
fcp_commoncap(struct scsi_address *ap, char *cap,
    int val, int tgtonly, int doset)
{
	struct fcp_port		*pptr = ADDR2FCP(ap);
	struct fcp_lun	*plun = ADDR2LUN(ap);
	struct fcp_tgt	*ptgt = plun->lun_tgt;
	int			cidx;
	int			rval = FALSE;

	if (cap == (char *)0) {
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_3, 0,
		    "fcp_commoncap: invalid arg");
		return (rval);
	}

	if ((cidx = scsi_hba_lookup_capstr(cap)) == -1) {
		return (UNDEFINED);
	}

	/*
	 * Process setcap request.
	 */
	if (doset) {
		/*
		 * At present, we can only set binary (0/1) values
		 */
		switch (cidx) {
		case SCSI_CAP_ARQ:
			if (val == 0) {
				rval = FALSE;
			} else {
				rval = TRUE;
			}
			break;

		case SCSI_CAP_LUN_RESET:
			if (val) {
				plun->lun_cap |= FCP_LUN_CAP_RESET;
			} else {
				plun->lun_cap &= ~FCP_LUN_CAP_RESET;
			}
			rval = TRUE;
			break;

		case SCSI_CAP_SECTOR_SIZE:
			rval = TRUE;
			break;
		default:
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_4, 0,
			    "fcp_setcap: unsupported %d", cidx);
			rval = UNDEFINED;
			break;
		}

		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_5, 0,
		    "set cap: cap=%s, val/tgtonly/doset/rval = "
		    "0x%x/0x%x/0x%x/%d",
		    cap, val, tgtonly, doset, rval);

	} else {
		/*
		 * Process getcap request.
		 */
		switch (cidx) {
		case SCSI_CAP_DMA_MAX:
			rval = (int)pptr->port_data_dma_attr.dma_attr_maxxfer;

			/*
			 * Need to make an adjustment qlc is uint_t 64
			 * st is int, so we will make the adjustment here
			 * being as nobody wants to touch this.
			 * It still leaves the max single block length
			 * of 2 gig. This should last .
			 */

			if (rval == -1) {
				rval = MAX_INT_DMA;
			}

			break;

		case SCSI_CAP_INITIATOR_ID:
			rval = pptr->port_id;
			break;

		case SCSI_CAP_ARQ:
		case SCSI_CAP_RESET_NOTIFICATION:
		case SCSI_CAP_TAGGED_QING:
			rval = TRUE;
			break;

		case SCSI_CAP_SCSI_VERSION:
			rval = 3;
			break;

		case SCSI_CAP_INTERCONNECT_TYPE:
			if (FC_TOP_EXTERNAL(pptr->port_topology) ||
			    (ptgt->tgt_hard_addr == 0)) {
				rval = INTERCONNECT_FABRIC;
			} else {
				rval = INTERCONNECT_FIBRE;
			}
			break;

		case SCSI_CAP_LUN_RESET:
			rval = ((plun->lun_cap & FCP_LUN_CAP_RESET) != 0) ?
			    TRUE : FALSE;
			break;

		default:
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_4, 0,
			    "fcp_getcap: unsupported %d", cidx);
			rval = UNDEFINED;
			break;
		}

		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_8, 0,
		    "get cap: cap=%s, val/tgtonly/doset/rval = "
		    "0x%x/0x%x/0x%x/%d",
		    cap, val, tgtonly, doset, rval);
	}

	return (rval);
}

/*
 * called by the transport to get the port-wwn and lun
 * properties of this device, and to create a "name" based on them
 *
 * these properties don't exist on sun4m
 *
 * return 1 for success else return 0
 */
/* ARGSUSED */
static int
fcp_scsi_get_name(struct scsi_device *sd, char *name, int len)
{
	int			i;
	int			*lun;
	int			numChars;
	uint_t			nlun;
	uint_t			count;
	uint_t			nbytes;
	uchar_t			*bytes;
	uint16_t		lun_num;
	uint32_t		tgt_id;
	char			**conf_wwn;
	char			tbuf[(FC_WWN_SIZE << 1) + 1];
	uchar_t			barray[FC_WWN_SIZE];
	dev_info_t		*tgt_dip;
	struct fcp_tgt	*ptgt;
	struct fcp_port	*pptr;
	struct fcp_lun	*plun;

	ASSERT(sd != NULL);
	ASSERT(name != NULL);

	tgt_dip = sd->sd_dev;
	pptr = ddi_get_soft_state(fcp_softstate,
	    ddi_get_instance(ddi_get_parent(tgt_dip)));
	if (pptr == NULL) {
		return (0);
	}

	ASSERT(tgt_dip != NULL);

	if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, sd->sd_dev,
	    DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
	    LUN_PROP, &lun, &nlun) != DDI_SUCCESS) {
		name[0] = '\0';
		return (0);
	}

	if (nlun == 0) {
		ddi_prop_free(lun);
		return (0);
	}

	lun_num = lun[0];
	ddi_prop_free(lun);

	/*
	 * Lookup for .conf WWN property
	 */
	if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, tgt_dip,
	    DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, CONF_WWN_PROP,
	    &conf_wwn, &count) == DDI_PROP_SUCCESS) {
		ASSERT(count >= 1);

		fcp_ascii_to_wwn(conf_wwn[0], barray, FC_WWN_SIZE);
		ddi_prop_free(conf_wwn);
		mutex_enter(&pptr->port_mutex);
		if ((plun = fcp_lookup_lun(pptr, barray, lun_num)) == NULL) {
			mutex_exit(&pptr->port_mutex);
			return (0);
		}
		ptgt = plun->lun_tgt;
		mutex_exit(&pptr->port_mutex);

		(void) ndi_prop_update_byte_array(DDI_DEV_T_NONE,
		    tgt_dip, PORT_WWN_PROP, barray, FC_WWN_SIZE);

		if (!FC_TOP_EXTERNAL(pptr->port_topology) &&
		    ptgt->tgt_hard_addr != 0) {
			tgt_id = (uint32_t)fcp_alpa_to_switch[
			    ptgt->tgt_hard_addr];
		} else {
			tgt_id = ptgt->tgt_d_id;
		}

		(void) ndi_prop_update_int(DDI_DEV_T_NONE, tgt_dip,
		    TARGET_PROP, tgt_id);
	}

	/* get the our port-wwn property */
	bytes = NULL;
	if ((ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, tgt_dip,
	    DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, PORT_WWN_PROP, &bytes,
	    &nbytes) != DDI_PROP_SUCCESS) || nbytes != FC_WWN_SIZE) {
		if (bytes != NULL) {
			ddi_prop_free(bytes);
		}
		return (0);
	}

	for (i = 0; i < FC_WWN_SIZE; i++) {
		(void) sprintf(&tbuf[i << 1], "%02x", *(bytes + i));
	}

	/* Stick in the address of the form "wWWN,LUN" */
	numChars = snprintf(name, len, "w%s,%x", tbuf, lun_num);

	ASSERT(numChars < len);
	if (numChars >= len) {
		fcp_log(CE_WARN, pptr->port_dip,
		    "!fcp_scsi_get_name: "
		    "name parameter length too small, it needs to be %d",
		    numChars+1);
	}

	ddi_prop_free(bytes);

	return (1);
}


/*
 * called by the transport to get the SCSI target id value, returning
 * it in "name"
 *
 * this isn't needed/used on sun4m
 *
 * return 1 for success else return 0
 */
/* ARGSUSED */
static int
fcp_scsi_get_bus_addr(struct scsi_device *sd, char *name, int len)
{
	struct fcp_lun	*plun = ADDR2LUN(&sd->sd_address);
	struct fcp_tgt	*ptgt;
	int    numChars;

	if (plun == NULL) {
		return (0);
	}

	if ((ptgt = plun->lun_tgt) == NULL) {
		return (0);
	}

	numChars = snprintf(name, len, "%x", ptgt->tgt_d_id);

	ASSERT(numChars < len);
	if (numChars >= len) {
		fcp_log(CE_WARN, NULL,
		    "!fcp_scsi_get_bus_addr: "
		    "name parameter length too small, it needs to be %d",
		    numChars+1);
	}

	return (1);
}


/*
 * called internally to reset the link where the specified port lives
 */
static int
fcp_linkreset(struct fcp_port *pptr, struct scsi_address *ap, int sleep)
{
	la_wwn_t		wwn;
	struct fcp_lun	*plun;
	struct fcp_tgt	*ptgt;

	/* disable restart of lip if we're suspended */
	mutex_enter(&pptr->port_mutex);

	if (pptr->port_state & (FCP_STATE_SUSPENDED |
	    FCP_STATE_POWER_DOWN)) {
		mutex_exit(&pptr->port_mutex);
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_2, 0,
		    "fcp_linkreset, fcp%d: link reset "
		    "disabled due to DDI_SUSPEND",
		    ddi_get_instance(pptr->port_dip));
		return (FC_FAILURE);
	}

	if (pptr->port_state & (FCP_STATE_OFFLINE | FCP_STATE_ONLINING)) {
		mutex_exit(&pptr->port_mutex);
		return (FC_SUCCESS);
	}

	FCP_DTRACE(fcp_logq, pptr->port_instbuf,
	    fcp_trace, FCP_BUF_LEVEL_8, 0, "Forcing link reset");

	/*
	 * If ap == NULL assume local link reset.
	 */
	if (FC_TOP_EXTERNAL(pptr->port_topology) && (ap != NULL)) {
		plun = ADDR2LUN(ap);
		ptgt = plun->lun_tgt;
		bcopy(&ptgt->tgt_port_wwn.raw_wwn[0], &wwn, sizeof (wwn));
	} else {
		bzero((caddr_t)&wwn, sizeof (wwn));
	}
	mutex_exit(&pptr->port_mutex);

	return (fc_ulp_linkreset(pptr->port_fp_handle, &wwn, sleep));
}


/*
 * called from fcp_port_attach() to resume a port
 * return DDI_* success/failure status
 * acquires and releases the global mutex
 * acquires and releases the port mutex
 */
/*ARGSUSED*/

static int
fcp_handle_port_resume(opaque_t ulph, fc_ulp_port_info_t *pinfo,
    uint32_t s_id, fc_attach_cmd_t cmd, int instance)
{
	int			res = DDI_FAILURE; /* default result */
	struct fcp_port	*pptr;		/* port state ptr */
	uint32_t		alloc_cnt;
	uint32_t		max_cnt;
	fc_portmap_t		*tmp_list = NULL;

	FCP_DTRACE(fcp_logq, "fcp", fcp_trace,
	    FCP_BUF_LEVEL_8, 0, "port resume: for port %d",
	    instance);

	if ((pptr = ddi_get_soft_state(fcp_softstate, instance)) == NULL) {
		cmn_err(CE_WARN, "fcp: bad soft state");
		return (res);
	}

	mutex_enter(&pptr->port_mutex);
	switch (cmd) {
	case FC_CMD_RESUME:
		ASSERT((pptr->port_state & FCP_STATE_POWER_DOWN) == 0);
		pptr->port_state &= ~FCP_STATE_SUSPENDED;
		break;

	case FC_CMD_POWER_UP:
		/*
		 * If the port is DDI_SUSPENded, defer rediscovery
		 * until DDI_RESUME occurs
		 */
		if (pptr->port_state & FCP_STATE_SUSPENDED) {
			pptr->port_state &= ~FCP_STATE_POWER_DOWN;
			mutex_exit(&pptr->port_mutex);
			return (DDI_SUCCESS);
		}
		pptr->port_state &= ~FCP_STATE_POWER_DOWN;
	}
	pptr->port_id = s_id;
	pptr->port_state = FCP_STATE_INIT;
	mutex_exit(&pptr->port_mutex);

	/*
	 * Make a copy of ulp_port_info as fctl allocates
	 * a temp struct.
	 */
	(void) fcp_cp_pinfo(pptr, pinfo);

	mutex_enter(&fcp_global_mutex);
	if (fcp_watchdog_init++ == 0) {
		fcp_watchdog_tick = fcp_watchdog_timeout *
		    drv_usectohz(1000000);
		fcp_watchdog_id = timeout(fcp_watch,
		    NULL, fcp_watchdog_tick);
	}
	mutex_exit(&fcp_global_mutex);

	/*
	 * Handle various topologies and link states.
	 */
	switch (FC_PORT_STATE_MASK(pptr->port_phys_state)) {
	case FC_STATE_OFFLINE:
		/*
		 * Wait for ONLINE, at which time a state
		 * change will cause a statec_callback
		 */
		res = DDI_SUCCESS;
		break;

	case FC_STATE_ONLINE:

		if (pptr->port_topology == FC_TOP_UNKNOWN) {
			(void) fcp_linkreset(pptr, NULL, KM_NOSLEEP);
			res = DDI_SUCCESS;
			break;
		}

		if (FC_TOP_EXTERNAL(pptr->port_topology) &&
		    !fcp_enable_auto_configuration) {
			tmp_list = fcp_construct_map(pptr, &alloc_cnt);
			if (tmp_list == NULL) {
				if (!alloc_cnt) {
					res = DDI_SUCCESS;
				}
				break;
			}
			max_cnt = alloc_cnt;
		} else {
			ASSERT(pptr->port_topology != FC_TOP_UNKNOWN);

			alloc_cnt = FCP_MAX_DEVICES;

			if ((tmp_list = (fc_portmap_t *)kmem_zalloc(
			    (sizeof (fc_portmap_t)) * alloc_cnt,
			    KM_NOSLEEP)) == NULL) {
				fcp_log(CE_WARN, pptr->port_dip,
				    "!fcp%d: failed to allocate portmap",
				    instance);
				break;
			}

			max_cnt = alloc_cnt;
			if ((res = fc_ulp_getportmap(pptr->port_fp_handle,
			    &tmp_list, &max_cnt, FC_ULP_PLOGI_PRESERVE)) !=
			    FC_SUCCESS) {
				caddr_t msg;

				(void) fc_ulp_error(res, &msg);

				FCP_TRACE(fcp_logq, pptr->port_instbuf,
				    fcp_trace, FCP_BUF_LEVEL_2, 0,
				    "resume failed getportmap: reason=0x%x",
				    res);

				fcp_log(CE_WARN, pptr->port_dip,
				    "!failed to get port map : %s", msg);
				break;
			}
			if (max_cnt > alloc_cnt) {
				alloc_cnt = max_cnt;
			}
		}

		/*
		 * do the SCSI device discovery and create
		 * the devinfos
		 */
		fcp_statec_callback(ulph, pptr->port_fp_handle,
		    pptr->port_phys_state, pptr->port_topology, tmp_list,
		    max_cnt, pptr->port_id);

		res = DDI_SUCCESS;
		break;

	default:
		fcp_log(CE_WARN, pptr->port_dip,
		    "!fcp%d: invalid port state at attach=0x%x",
		    instance, pptr->port_phys_state);

		mutex_enter(&pptr->port_mutex);
		pptr->port_phys_state = FCP_STATE_OFFLINE;
		mutex_exit(&pptr->port_mutex);
		res = DDI_SUCCESS;

		break;
	}

	if (tmp_list != NULL) {
		kmem_free(tmp_list, sizeof (fc_portmap_t) * alloc_cnt);
	}

	return (res);
}


static void
fcp_cp_pinfo(struct fcp_port *pptr, fc_ulp_port_info_t *pinfo)
{
	pptr->port_fp_modlinkage = *pinfo->port_linkage;
	pptr->port_dip = pinfo->port_dip;
	pptr->port_fp_handle = pinfo->port_handle;
	pptr->port_data_dma_attr = *pinfo->port_data_dma_attr;
	pptr->port_cmd_dma_attr = *pinfo->port_cmd_dma_attr;
	pptr->port_resp_dma_attr = *pinfo->port_resp_dma_attr;
	pptr->port_dma_acc_attr = *pinfo->port_acc_attr;
	pptr->port_priv_pkt_len = pinfo->port_fca_pkt_size;
	pptr->port_max_exch = pinfo->port_fca_max_exch;
	pptr->port_phys_state = pinfo->port_state;
	pptr->port_topology = pinfo->port_flags;
	pptr->port_reset_action = pinfo->port_reset_action;
	pptr->port_cmds_dma_flags = pinfo->port_dma_behavior;
	pptr->port_fcp_dma = pinfo->port_fcp_dma;
	bcopy(&pinfo->port_nwwn, &pptr->port_nwwn, sizeof (la_wwn_t));
	bcopy(&pinfo->port_pwwn, &pptr->port_pwwn, sizeof (la_wwn_t));
}

/*
 * If the elements wait field is set to 1 then
 * another thread is waiting for the operation to complete. Once
 * it is complete, the waiting thread is signaled and the element is
 * freed by the waiting thread. If the elements wait field is set to 0
 * the element is freed.
 */
static void
fcp_process_elem(struct fcp_hp_elem *elem, int result)
{
	ASSERT(elem != NULL);
	mutex_enter(&elem->mutex);
	elem->result = result;
	if (elem->wait) {
		elem->wait = 0;
		cv_signal(&elem->cv);
		mutex_exit(&elem->mutex);
	} else {
		mutex_exit(&elem->mutex);
		cv_destroy(&elem->cv);
		mutex_destroy(&elem->mutex);
		kmem_free(elem, sizeof (struct fcp_hp_elem));
	}
}

/*
 * This function is invoked from the taskq thread to allocate
 * devinfo nodes and to online/offline them.
 */
static void
fcp_hp_task(void *arg)
{
	struct fcp_hp_elem	*elem = (struct fcp_hp_elem *)arg;
	struct fcp_lun	*plun = elem->lun;
	struct fcp_port		*pptr = elem->port;
	int			result;

	ASSERT(elem->what == FCP_ONLINE ||
	    elem->what == FCP_OFFLINE ||
	    elem->what == FCP_MPXIO_PATH_CLEAR_BUSY ||
	    elem->what == FCP_MPXIO_PATH_SET_BUSY);

	mutex_enter(&pptr->port_mutex);
	mutex_enter(&plun->lun_mutex);
	if (((elem->what == FCP_ONLINE || elem->what == FCP_OFFLINE) &&
	    plun->lun_event_count != elem->event_cnt) ||
	    pptr->port_state & (FCP_STATE_SUSPENDED |
	    FCP_STATE_DETACHING | FCP_STATE_POWER_DOWN)) {
		mutex_exit(&plun->lun_mutex);
		mutex_exit(&pptr->port_mutex);
		fcp_process_elem(elem, NDI_FAILURE);
		return;
	}
	mutex_exit(&plun->lun_mutex);
	mutex_exit(&pptr->port_mutex);

	result = fcp_trigger_lun(plun, elem->cip, elem->old_lun_mpxio,
	    elem->what, elem->link_cnt, elem->tgt_cnt, elem->flags);
	fcp_process_elem(elem, result);
}


static child_info_t *
fcp_get_cip(struct fcp_lun *plun, child_info_t *cip, int lcount,
    int tcount)
{
	ASSERT(MUTEX_HELD(&plun->lun_mutex));

	if (fcp_is_child_present(plun, cip) == FC_FAILURE) {
		struct fcp_port *pptr = plun->lun_tgt->tgt_port;

		ASSERT(MUTEX_HELD(&pptr->port_mutex));
		/*
		 * Child has not been created yet. Create the child device
		 * based on the per-Lun flags.
		 */
		if (pptr->port_mpxio == 0 || plun->lun_mpxio == 0) {
			plun->lun_cip =
			    CIP(fcp_create_dip(plun, lcount, tcount));
			plun->lun_mpxio = 0;
		} else {
			plun->lun_cip =
			    CIP(fcp_create_pip(plun, lcount, tcount));
			plun->lun_mpxio = 1;
		}
	} else {
		plun->lun_cip = cip;
	}

	return (plun->lun_cip);
}


static int
fcp_is_dip_present(struct fcp_lun *plun, dev_info_t *cdip)
{
	int		rval = FC_FAILURE;
	dev_info_t	*pdip;
	struct dev_info	*dip;
	int		circular;

	ASSERT(MUTEX_HELD(&plun->lun_mutex));

	pdip = plun->lun_tgt->tgt_port->port_dip;

	if (plun->lun_cip == NULL) {
		FCP_TRACE(fcp_logq, LUN_PORT->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_3, 0,
		    "fcp_is_dip_present: plun->lun_cip is NULL: "
		    "plun: %p lun state: %x num: %d target state: %x",
		    plun, plun->lun_state, plun->lun_num,
		    plun->lun_tgt->tgt_port->port_state);
		return (rval);
	}
	ndi_devi_enter(pdip, &circular);
	dip = DEVI(pdip)->devi_child;
	while (dip) {
		if (dip == DEVI(cdip)) {
			rval = FC_SUCCESS;
			break;
		}
		dip = dip->devi_sibling;
	}
	ndi_devi_exit(pdip, circular);
	return (rval);
}

static int
fcp_is_child_present(struct fcp_lun *plun, child_info_t *cip)
{
	int		rval = FC_FAILURE;

	ASSERT(plun != NULL);
	ASSERT(MUTEX_HELD(&plun->lun_mutex));

	if (plun->lun_mpxio == 0) {
		rval = fcp_is_dip_present(plun, DIP(cip));
	} else {
		rval = fcp_is_pip_present(plun, PIP(cip));
	}

	return (rval);
}

/*
 *     Function: fcp_create_dip
 *
 *  Description: Creates a dev_info_t structure for the LUN specified by the
 *		 caller.
 *
 *     Argument: plun		Lun structure
 *		 link_cnt	Link state count.
 *		 tgt_cnt	Target state change count.
 *
 * Return Value: NULL if it failed
 *		 dev_info_t structure address if it succeeded
 *
 *	Context: Kernel context
 */
static dev_info_t *
fcp_create_dip(struct fcp_lun *plun, int link_cnt, int tgt_cnt)
{
	int			failure = 0;
	uint32_t		tgt_id;
	uint64_t		sam_lun;
	struct fcp_tgt	*ptgt = plun->lun_tgt;
	struct fcp_port	*pptr = ptgt->tgt_port;
	dev_info_t		*pdip = pptr->port_dip;
	dev_info_t		*cdip = NULL;
	dev_info_t		*old_dip = DIP(plun->lun_cip);
	char			*nname = NULL;
	char			**compatible = NULL;
	int			ncompatible;
	char			*scsi_binding_set;
	char			t_pwwn[17];

	ASSERT(MUTEX_HELD(&plun->lun_mutex));
	ASSERT(MUTEX_HELD(&pptr->port_mutex));

	/* get the 'scsi-binding-set' property */
	if (ddi_prop_lookup_string(DDI_DEV_T_ANY, pdip,
	    DDI_PROP_NOTPROM | DDI_PROP_DONTPASS, "scsi-binding-set",
	    &scsi_binding_set) != DDI_PROP_SUCCESS) {
		scsi_binding_set = NULL;
	}

	/* determine the node name and compatible */
	scsi_hba_nodename_compatible_get(&plun->lun_inq, scsi_binding_set,
	    plun->lun_inq.inq_dtype, NULL, &nname, &compatible, &ncompatible);
	if (scsi_binding_set) {
		ddi_prop_free(scsi_binding_set);
	}

	if (nname == NULL) {
#ifdef	DEBUG
		cmn_err(CE_WARN, "%s%d: no driver for "
		    "device @w%02x%02x%02x%02x%02x%02x%02x%02x,%d:"
		    "	 compatible: %s",
		    ddi_driver_name(pdip), ddi_get_instance(pdip),
		    ptgt->tgt_port_wwn.raw_wwn[0],
		    ptgt->tgt_port_wwn.raw_wwn[1],
		    ptgt->tgt_port_wwn.raw_wwn[2],
		    ptgt->tgt_port_wwn.raw_wwn[3],
		    ptgt->tgt_port_wwn.raw_wwn[4],
		    ptgt->tgt_port_wwn.raw_wwn[5],
		    ptgt->tgt_port_wwn.raw_wwn[6],
		    ptgt->tgt_port_wwn.raw_wwn[7], plun->lun_num,
		    *compatible);
#endif	/* DEBUG */
		failure++;
		goto end_of_fcp_create_dip;
	}

	cdip = fcp_find_existing_dip(plun, pdip, nname);

	/*
	 * if the old_dip does not match the cdip, that means there is
	 * some property change. since we'll be using the cdip, we need
	 * to offline the old_dip. If the state contains FCP_LUN_CHANGED
	 * then the dtype for the device has been updated. Offline the
	 * the old device and create a new device with the new device type
	 * Refer to bug: 4764752
	 */
	if (old_dip && (cdip != old_dip ||
	    plun->lun_state & FCP_LUN_CHANGED)) {
		plun->lun_state &= ~(FCP_LUN_INIT);
		mutex_exit(&plun->lun_mutex);
		mutex_exit(&pptr->port_mutex);

		mutex_enter(&ptgt->tgt_mutex);
		(void) fcp_pass_to_hp(pptr, plun, CIP(old_dip), FCP_OFFLINE,
		    link_cnt, tgt_cnt, NDI_DEVI_REMOVE, 0);
		mutex_exit(&ptgt->tgt_mutex);

#ifdef DEBUG
		if (cdip != NULL) {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_2, 0,
			    "Old dip=%p; New dip=%p don't match", old_dip,
			    cdip);
		} else {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_2, 0,
			    "Old dip=%p; New dip=NULL don't match", old_dip);
		}
#endif

		mutex_enter(&pptr->port_mutex);
		mutex_enter(&plun->lun_mutex);
	}

	if (cdip == NULL || plun->lun_state & FCP_LUN_CHANGED) {
		plun->lun_state &= ~(FCP_LUN_CHANGED);
		if (ndi_devi_alloc(pptr->port_dip, nname,
		    DEVI_SID_NODEID, &cdip) != NDI_SUCCESS) {
			failure++;
			goto end_of_fcp_create_dip;
		}
	}

	/*
	 * Previously all the properties for the devinfo were destroyed here
	 * with a call to ndi_prop_remove_all(). Since this may cause loss of
	 * the devid property (and other properties established by the target
	 * driver or framework) which the code does not always recreate, this
	 * call was removed.
	 * This opens a theoretical possibility that we may return with a
	 * stale devid on the node if the scsi entity behind the fibre channel
	 * lun has changed.
	 */

	/* decorate the node with compatible */
	if (ndi_prop_update_string_array(DDI_DEV_T_NONE, cdip,
	    "compatible", compatible, ncompatible) != DDI_PROP_SUCCESS) {
		failure++;
		goto end_of_fcp_create_dip;
	}

	if (ndi_prop_update_byte_array(DDI_DEV_T_NONE, cdip, NODE_WWN_PROP,
	    ptgt->tgt_node_wwn.raw_wwn, FC_WWN_SIZE) != DDI_PROP_SUCCESS) {
		failure++;
		goto end_of_fcp_create_dip;
	}

	if (ndi_prop_update_byte_array(DDI_DEV_T_NONE, cdip, PORT_WWN_PROP,
	    ptgt->tgt_port_wwn.raw_wwn, FC_WWN_SIZE) != DDI_PROP_SUCCESS) {
		failure++;
		goto end_of_fcp_create_dip;
	}

	fcp_wwn_to_ascii(ptgt->tgt_port_wwn.raw_wwn, t_pwwn);
	t_pwwn[16] = '\0';
	if (ndi_prop_update_string(DDI_DEV_T_NONE, cdip, TGT_PORT_PROP, t_pwwn)
	    != DDI_PROP_SUCCESS) {
		failure++;
		goto end_of_fcp_create_dip;
	}

	/*
	 * If there is no hard address - We might have to deal with
	 * that by using WWN - Having said that it is important to
	 * recognize this problem early so ssd can be informed of
	 * the right interconnect type.
	 */
	if (!FC_TOP_EXTERNAL(pptr->port_topology) && ptgt->tgt_hard_addr != 0) {
		tgt_id = (uint32_t)fcp_alpa_to_switch[ptgt->tgt_hard_addr];
	} else {
		tgt_id = ptgt->tgt_d_id;
	}

	if (ndi_prop_update_int(DDI_DEV_T_NONE, cdip, TARGET_PROP,
	    tgt_id) != DDI_PROP_SUCCESS) {
		failure++;
		goto end_of_fcp_create_dip;
	}

	if (ndi_prop_update_int(DDI_DEV_T_NONE, cdip, LUN_PROP,
	    (int)plun->lun_num) != DDI_PROP_SUCCESS) {
		failure++;
		goto end_of_fcp_create_dip;
	}
	bcopy(&plun->lun_addr, &sam_lun, FCP_LUN_SIZE);
	if (ndi_prop_update_int64(DDI_DEV_T_NONE, cdip, SAM_LUN_PROP,
	    sam_lun) != DDI_PROP_SUCCESS) {
		failure++;
		goto end_of_fcp_create_dip;
	}

end_of_fcp_create_dip:
	scsi_hba_nodename_compatible_free(nname, compatible);

	if (cdip != NULL && failure) {
		(void) ndi_prop_remove_all(cdip);
		(void) ndi_devi_free(cdip);
		cdip = NULL;
	}

	return (cdip);
}

/*
 *     Function: fcp_create_pip
 *
 *  Description: Creates a Path Id for the LUN specified by the caller.
 *
 *     Argument: plun		Lun structure
 *		 link_cnt	Link state count.
 *		 tgt_cnt	Target state count.
 *
 * Return Value: NULL if it failed
 *		 mdi_pathinfo_t structure address if it succeeded
 *
 *	Context: Kernel context
 */
static mdi_pathinfo_t *
fcp_create_pip(struct fcp_lun *plun, int lcount, int tcount)
{
	int			i;
	char			buf[MAXNAMELEN];
	char			uaddr[MAXNAMELEN];
	int			failure = 0;
	uint32_t		tgt_id;
	uint64_t		sam_lun;
	struct fcp_tgt	*ptgt = plun->lun_tgt;
	struct fcp_port	*pptr = ptgt->tgt_port;
	dev_info_t		*pdip = pptr->port_dip;
	mdi_pathinfo_t		*pip = NULL;
	mdi_pathinfo_t		*old_pip = PIP(plun->lun_cip);
	char			*nname = NULL;
	char			**compatible = NULL;
	int			ncompatible;
	char			*scsi_binding_set;
	char			t_pwwn[17];

	ASSERT(MUTEX_HELD(&plun->lun_mutex));
	ASSERT(MUTEX_HELD(&pptr->port_mutex));

	scsi_binding_set = "vhci";

	/* determine the node name and compatible */
	scsi_hba_nodename_compatible_get(&plun->lun_inq, scsi_binding_set,
	    plun->lun_inq.inq_dtype, NULL, &nname, &compatible, &ncompatible);

	if (nname == NULL) {
#ifdef	DEBUG
		cmn_err(CE_WARN, "fcp_create_dip: %s%d: no driver for "
		    "device @w%02x%02x%02x%02x%02x%02x%02x%02x,%d:"
		    "	 compatible: %s",
		    ddi_driver_name(pdip), ddi_get_instance(pdip),
		    ptgt->tgt_port_wwn.raw_wwn[0],
		    ptgt->tgt_port_wwn.raw_wwn[1],
		    ptgt->tgt_port_wwn.raw_wwn[2],
		    ptgt->tgt_port_wwn.raw_wwn[3],
		    ptgt->tgt_port_wwn.raw_wwn[4],
		    ptgt->tgt_port_wwn.raw_wwn[5],
		    ptgt->tgt_port_wwn.raw_wwn[6],
		    ptgt->tgt_port_wwn.raw_wwn[7], plun->lun_num,
		    *compatible);
#endif	/* DEBUG */
		failure++;
		goto end_of_fcp_create_pip;
	}

	pip = fcp_find_existing_pip(plun, pdip);

	/*
	 * if the old_dip does not match the cdip, that means there is
	 * some property change. since we'll be using the cdip, we need
	 * to offline the old_dip. If the state contains FCP_LUN_CHANGED
	 * then the dtype for the device has been updated. Offline the
	 * the old device and create a new device with the new device type
	 * Refer to bug: 4764752
	 */
	if (old_pip && (pip != old_pip ||
	    plun->lun_state & FCP_LUN_CHANGED)) {
		plun->lun_state &= ~(FCP_LUN_INIT);
		mutex_exit(&plun->lun_mutex);
		mutex_exit(&pptr->port_mutex);

		mutex_enter(&ptgt->tgt_mutex);
		(void) fcp_pass_to_hp(pptr, plun, CIP(old_pip),
		    FCP_OFFLINE, lcount, tcount,
		    NDI_DEVI_REMOVE, 0);
		mutex_exit(&ptgt->tgt_mutex);

		if (pip != NULL) {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_2, 0,
			    "Old pip=%p; New pip=%p don't match",
			    old_pip, pip);
		} else {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_2, 0,
			    "Old pip=%p; New pip=NULL don't match",
			    old_pip);
		}

		mutex_enter(&pptr->port_mutex);
		mutex_enter(&plun->lun_mutex);
	}

	/*
	 * Since FC_WWN_SIZE is 8 bytes and its not like the
	 * lun_guid_size which is dependent on the target, I don't
	 * believe the same trancation happens here UNLESS the standards
	 * change the FC_WWN_SIZE value to something larger than
	 * MAXNAMELEN(currently 255 bytes).
	 */

	for (i = 0; i < FC_WWN_SIZE; i++) {
		(void) sprintf(&buf[i << 1], "%02x",
		    ptgt->tgt_port_wwn.raw_wwn[i]);
	}

	(void) snprintf(uaddr, MAXNAMELEN, "w%s,%x",
	    buf, plun->lun_num);

	if (pip == NULL || plun->lun_state & FCP_LUN_CHANGED) {
		/*
		 * Release the locks before calling into
		 * mdi_pi_alloc_compatible() since this can result in a
		 * callback into fcp which can result in a deadlock
		 * (see bug # 4870272).
		 *
		 * Basically, what we are trying to avoid is the scenario where
		 * one thread does ndi_devi_enter() and tries to grab
		 * fcp_mutex and another does it the other way round.
		 *
		 * But before we do that, make sure that nobody releases the
		 * port in the meantime. We can do this by setting a flag.
		 */
		plun->lun_state &= ~(FCP_LUN_CHANGED);
		pptr->port_state |= FCP_STATE_IN_MDI;
		mutex_exit(&plun->lun_mutex);
		mutex_exit(&pptr->port_mutex);
		if (mdi_pi_alloc_compatible(pdip, nname, plun->lun_guid,
		    uaddr, compatible, ncompatible, 0, &pip) != MDI_SUCCESS) {
			fcp_log(CE_WARN, pptr->port_dip,
			    "!path alloc failed:0x%x", plun);
			mutex_enter(&pptr->port_mutex);
			mutex_enter(&plun->lun_mutex);
			pptr->port_state &= ~FCP_STATE_IN_MDI;
			failure++;
			goto end_of_fcp_create_pip;
		}
		mutex_enter(&pptr->port_mutex);
		mutex_enter(&plun->lun_mutex);
		pptr->port_state &= ~FCP_STATE_IN_MDI;
	} else {
		(void) mdi_prop_remove(pip, NULL);
	}

	mdi_pi_set_phci_private(pip, (caddr_t)plun);

	if (mdi_prop_update_byte_array(pip, NODE_WWN_PROP,
	    ptgt->tgt_node_wwn.raw_wwn, FC_WWN_SIZE)
	    != DDI_PROP_SUCCESS) {
		failure++;
		goto end_of_fcp_create_pip;
	}

	if (mdi_prop_update_byte_array(pip, PORT_WWN_PROP,
	    ptgt->tgt_port_wwn.raw_wwn, FC_WWN_SIZE)
	    != DDI_PROP_SUCCESS) {
		failure++;
		goto end_of_fcp_create_pip;
	}

	fcp_wwn_to_ascii(ptgt->tgt_port_wwn.raw_wwn, t_pwwn);
	t_pwwn[16] = '\0';
	if (mdi_prop_update_string(pip, TGT_PORT_PROP, t_pwwn)
	    != DDI_PROP_SUCCESS) {
		failure++;
		goto end_of_fcp_create_pip;
	}

	/*
	 * If there is no hard address - We might have to deal with
	 * that by using WWN - Having said that it is important to
	 * recognize this problem early so ssd can be informed of
	 * the right interconnect type.
	 */
	if (!FC_TOP_EXTERNAL(pptr->port_topology) &&
	    ptgt->tgt_hard_addr != 0) {
		tgt_id = (uint32_t)
		    fcp_alpa_to_switch[ptgt->tgt_hard_addr];
	} else {
		tgt_id = ptgt->tgt_d_id;
	}

	if (mdi_prop_update_int(pip, TARGET_PROP, tgt_id)
	    != DDI_PROP_SUCCESS) {
		failure++;
		goto end_of_fcp_create_pip;
	}

	if (mdi_prop_update_int(pip, LUN_PROP, (int)plun->lun_num)
	    != DDI_PROP_SUCCESS) {
		failure++;
		goto end_of_fcp_create_pip;
	}
	bcopy(&plun->lun_addr, &sam_lun, FCP_LUN_SIZE);
	if (mdi_prop_update_int64(pip, SAM_LUN_PROP, sam_lun)
	    != DDI_PROP_SUCCESS) {
		failure++;
		goto end_of_fcp_create_pip;
	}

end_of_fcp_create_pip:
	scsi_hba_nodename_compatible_free(nname, compatible);

	if (pip != NULL && failure) {
		(void) mdi_prop_remove(pip, NULL);
		mutex_exit(&plun->lun_mutex);
		mutex_exit(&pptr->port_mutex);
		(void) mdi_pi_free(pip, 0);
		mutex_enter(&pptr->port_mutex);
		mutex_enter(&plun->lun_mutex);
		pip = NULL;
	}

	return (pip);
}

static dev_info_t *
fcp_find_existing_dip(struct fcp_lun *plun, dev_info_t *pdip, caddr_t name)
{
	uint_t			nbytes;
	uchar_t			*bytes;
	uint_t			nwords;
	uint32_t		tgt_id;
	int			*words;
	dev_info_t		*cdip;
	dev_info_t		*ndip;
	struct fcp_tgt	*ptgt = plun->lun_tgt;
	struct fcp_port	*pptr = ptgt->tgt_port;
	int			circular;

	ndi_devi_enter(pdip, &circular);

	ndip = (dev_info_t *)DEVI(pdip)->devi_child;
	while ((cdip = ndip) != NULL) {
		ndip = (dev_info_t *)DEVI(cdip)->devi_sibling;

		if (strcmp(DEVI(cdip)->devi_node_name, name)) {
			continue;
		}

		if (ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, cdip,
		    DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, NODE_WWN_PROP, &bytes,
		    &nbytes) != DDI_PROP_SUCCESS) {
			continue;
		}

		if (nbytes != FC_WWN_SIZE || bytes == NULL) {
			if (bytes != NULL) {
				ddi_prop_free(bytes);
			}
			continue;
		}
		ASSERT(bytes != NULL);

		if (bcmp(bytes, ptgt->tgt_node_wwn.raw_wwn, nbytes) != 0) {
			ddi_prop_free(bytes);
			continue;
		}

		ddi_prop_free(bytes);

		if (ddi_prop_lookup_byte_array(DDI_DEV_T_ANY, cdip,
		    DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, PORT_WWN_PROP, &bytes,
		    &nbytes) != DDI_PROP_SUCCESS) {
			continue;
		}

		if (nbytes != FC_WWN_SIZE || bytes == NULL) {
			if (bytes != NULL) {
				ddi_prop_free(bytes);
			}
			continue;
		}
		ASSERT(bytes != NULL);

		if (bcmp(bytes, ptgt->tgt_port_wwn.raw_wwn, nbytes) != 0) {
			ddi_prop_free(bytes);
			continue;
		}

		ddi_prop_free(bytes);

		if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, cdip,
		    DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, TARGET_PROP, &words,
		    &nwords) != DDI_PROP_SUCCESS) {
			continue;
		}

		if (nwords != 1 || words == NULL) {
			if (words != NULL) {
				ddi_prop_free(words);
			}
			continue;
		}
		ASSERT(words != NULL);

		/*
		 * If there is no hard address - We might have to deal with
		 * that by using WWN - Having said that it is important to
		 * recognize this problem early so ssd can be informed of
		 * the right interconnect type.
		 */
		if (!FC_TOP_EXTERNAL(pptr->port_topology) &&
		    ptgt->tgt_hard_addr != 0) {
			tgt_id =
			    (uint32_t)fcp_alpa_to_switch[ptgt->tgt_hard_addr];
		} else {
			tgt_id = ptgt->tgt_d_id;
		}

		if (tgt_id != (uint32_t)*words) {
			ddi_prop_free(words);
			continue;
		}
		ddi_prop_free(words);

		if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, cdip,
		    DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, LUN_PROP, &words,
		    &nwords) != DDI_PROP_SUCCESS) {
			continue;
		}

		if (nwords != 1 || words == NULL) {
			if (words != NULL) {
				ddi_prop_free(words);
			}
			continue;
		}
		ASSERT(words != NULL);

		if (plun->lun_num == (uint16_t)*words) {
			ddi_prop_free(words);
			break;
		}
		ddi_prop_free(words);
	}
	ndi_devi_exit(pdip, circular);

	return (cdip);
}


static int
fcp_is_pip_present(struct fcp_lun *plun, mdi_pathinfo_t *pip)
{
	dev_info_t	*pdip;
	char		buf[MAXNAMELEN];
	char		uaddr[MAXNAMELEN];
	int		rval = FC_FAILURE;

	ASSERT(MUTEX_HELD(&plun->lun_mutex));

	pdip = plun->lun_tgt->tgt_port->port_dip;

	/*
	 * Check if pip (and not plun->lun_cip) is NULL. plun->lun_cip can be
	 * non-NULL even when the LUN is not there as in the case when a LUN is
	 * configured and then deleted on the device end (for T3/T4 case). In
	 * such cases, pip will be NULL.
	 *
	 * If the device generates an RSCN, it will end up getting offlined when
	 * it disappeared and a new LUN will get created when it is rediscovered
	 * on the device. If we check for lun_cip here, the LUN will not end
	 * up getting onlined since this function will end up returning a
	 * FC_SUCCESS.
	 *
	 * The behavior is different on other devices. For instance, on a HDS,
	 * there was no RSCN generated by the device but the next I/O generated
	 * a check condition and rediscovery got triggered that way. So, in
	 * such cases, this path will not be exercised
	 */
	if (pip == NULL) {
		FCP_TRACE(fcp_logq, LUN_PORT->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_4, 0,
		    "fcp_is_pip_present: plun->lun_cip is NULL: "
		    "plun: %p lun state: %x num: %d target state: %x",
		    plun, plun->lun_state, plun->lun_num,
		    plun->lun_tgt->tgt_port->port_state);
		return (rval);
	}

	fcp_wwn_to_ascii(plun->lun_tgt->tgt_port_wwn.raw_wwn, buf);

	(void) snprintf(uaddr, MAXNAMELEN, "w%s,%x", buf, plun->lun_num);

	if (plun->lun_old_guid) {
		if (mdi_pi_find(pdip, plun->lun_old_guid, uaddr) == pip) {
			rval = FC_SUCCESS;
		}
	} else {
		if (mdi_pi_find(pdip, plun->lun_guid, uaddr) == pip) {
			rval = FC_SUCCESS;
		}
	}
	return (rval);
}

static mdi_pathinfo_t *
fcp_find_existing_pip(struct fcp_lun *plun, dev_info_t *pdip)
{
	char			buf[MAXNAMELEN];
	char			uaddr[MAXNAMELEN];
	mdi_pathinfo_t		*pip;
	struct fcp_tgt	*ptgt = plun->lun_tgt;
	struct fcp_port	*pptr = ptgt->tgt_port;

	ASSERT(MUTEX_HELD(&pptr->port_mutex));

	fcp_wwn_to_ascii(ptgt->tgt_port_wwn.raw_wwn, buf);
	(void) snprintf(uaddr, MAXNAMELEN, "w%s,%x", buf, plun->lun_num);

	pip = mdi_pi_find(pdip, plun->lun_guid, uaddr);

	return (pip);
}


static int
fcp_online_child(struct fcp_lun *plun, child_info_t *cip, int lcount,
    int tcount, int flags, int *circ)
{
	int			rval;
	struct fcp_port		*pptr = plun->lun_tgt->tgt_port;
	struct fcp_tgt	*ptgt = plun->lun_tgt;
	dev_info_t		*cdip = NULL;

	ASSERT(MUTEX_HELD(&pptr->port_mutex));
	ASSERT(MUTEX_HELD(&plun->lun_mutex));

	if (plun->lun_cip == NULL) {
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_3, 0,
		    "fcp_online_child: plun->lun_cip is NULL: "
		    "plun: %p state: %x num: %d target state: %x",
		    plun, plun->lun_state, plun->lun_num,
		    plun->lun_tgt->tgt_port->port_state);
		return (NDI_FAILURE);
	}
again:
	if (plun->lun_mpxio == 0) {
		cdip = DIP(cip);
		mutex_exit(&plun->lun_mutex);
		mutex_exit(&pptr->port_mutex);

		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_3, 0,
		    "!Invoking ndi_devi_online for %s: target=%x lun=%x",
		    ddi_get_name(cdip), ptgt->tgt_d_id, plun->lun_num);

		/*
		 * We could check for FCP_LUN_INIT here but chances
		 * of getting here when it's already in FCP_LUN_INIT
		 * is rare and a duplicate ndi_devi_online wouldn't
		 * hurt either (as the node would already have been
		 * in CF2)
		 */
		if (!i_ddi_devi_attached(ddi_get_parent(cdip))) {
			rval = ndi_devi_bind_driver(cdip, flags);
		} else {
			rval = ndi_devi_online(cdip, flags);
		}
		/*
		 * We log the message into trace buffer if the device
		 * is "ses" and into syslog for any other device
		 * type. This is to prevent the ndi_devi_online failure
		 * message that appears for V880/A5K ses devices.
		 */
		if (rval == NDI_SUCCESS) {
			mutex_enter(&ptgt->tgt_mutex);
			plun->lun_state |= FCP_LUN_INIT;
			mutex_exit(&ptgt->tgt_mutex);
		} else if (strncmp(ddi_node_name(cdip), "ses", 3) != 0) {
			fcp_log(CE_NOTE, pptr->port_dip,
			    "!ndi_devi_online:"
			    " failed for %s: target=%x lun=%x %x",
			    ddi_get_name(cdip), ptgt->tgt_d_id,
			    plun->lun_num, rval);
		} else {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_3, 0,
			    " !ndi_devi_online:"
			    " failed for %s: target=%x lun=%x %x",
			    ddi_get_name(cdip), ptgt->tgt_d_id,
			    plun->lun_num, rval);
		}
	} else {
		cdip = mdi_pi_get_client(PIP(cip));
		mutex_exit(&plun->lun_mutex);
		mutex_exit(&pptr->port_mutex);

		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_3, 0,
		    "!Invoking mdi_pi_online for %s: target=%x lun=%x",
		    ddi_get_name(cdip), ptgt->tgt_d_id, plun->lun_num);

		/*
		 * Hold path and exit phci to avoid deadlock with power
		 * management code during mdi_pi_online.
		 */
		mdi_hold_path(PIP(cip));
		mdi_devi_exit_phci(pptr->port_dip, *circ);

		rval = mdi_pi_online(PIP(cip), flags);

		mdi_devi_enter_phci(pptr->port_dip, circ);
		mdi_rele_path(PIP(cip));

		if (rval == MDI_SUCCESS) {
			mutex_enter(&ptgt->tgt_mutex);
			plun->lun_state |= FCP_LUN_INIT;
			mutex_exit(&ptgt->tgt_mutex);

			/*
			 * Clear MPxIO path permanent disable in case
			 * fcp hotplug dropped the offline event.
			 */
			(void) mdi_pi_enable_path(PIP(cip), DRIVER_DISABLE);

		} else if (rval == MDI_NOT_SUPPORTED) {
			child_info_t	*old_cip = cip;

			/*
			 * MPxIO does not support this device yet.
			 * Enumerate in legacy mode.
			 */
			mutex_enter(&pptr->port_mutex);
			mutex_enter(&plun->lun_mutex);
			plun->lun_mpxio = 0;
			plun->lun_cip = NULL;
			cdip = fcp_create_dip(plun, lcount, tcount);
			plun->lun_cip = cip = CIP(cdip);
			if (cip == NULL) {
				fcp_log(CE_WARN, pptr->port_dip,
				    "!fcp_online_child: "
				    "Create devinfo failed for LU=%p", plun);
				mutex_exit(&plun->lun_mutex);

				mutex_enter(&ptgt->tgt_mutex);
				plun->lun_state |= FCP_LUN_OFFLINE;
				mutex_exit(&ptgt->tgt_mutex);

				mutex_exit(&pptr->port_mutex);

				/*
				 * free the mdi_pathinfo node
				 */
				(void) mdi_pi_free(PIP(old_cip), 0);
			} else {
				FCP_TRACE(fcp_logq, pptr->port_instbuf,
				    fcp_trace, FCP_BUF_LEVEL_3, 0,
				    "fcp_online_child: creating devinfo "
				    "node 0x%p for plun 0x%p",
				    cip, plun);
				mutex_exit(&plun->lun_mutex);
				mutex_exit(&pptr->port_mutex);
				/*
				 * free the mdi_pathinfo node
				 */
				(void) mdi_pi_free(PIP(old_cip), 0);
				mutex_enter(&pptr->port_mutex);
				mutex_enter(&plun->lun_mutex);
				goto again;
			}
		} else {
			if (cdip) {
				fcp_log(CE_NOTE, pptr->port_dip,
				    "!fcp_online_child: mdi_pi_online:"
				    " failed for %s: target=%x lun=%x %x",
				    ddi_get_name(cdip), ptgt->tgt_d_id,
				    plun->lun_num, rval);
			}
		}
		rval = (rval == MDI_SUCCESS) ? NDI_SUCCESS : NDI_FAILURE;
	}

	if (rval == NDI_SUCCESS) {
		if (cdip) {
			(void) ndi_event_retrieve_cookie(
			    pptr->port_ndi_event_hdl, cdip, FCAL_INSERT_EVENT,
			    &fcp_insert_eid, NDI_EVENT_NOPASS);
			(void) ndi_event_run_callbacks(pptr->port_ndi_event_hdl,
			    cdip, fcp_insert_eid, NULL);
		}
	}
	mutex_enter(&pptr->port_mutex);
	mutex_enter(&plun->lun_mutex);
	return (rval);
}

/* ARGSUSED */
static int
fcp_offline_child(struct fcp_lun *plun, child_info_t *cip, int lcount,
    int tcount, int flags, int *circ)
{
	int rval;
	struct fcp_port		*pptr = plun->lun_tgt->tgt_port;
	struct fcp_tgt	*ptgt = plun->lun_tgt;
	dev_info_t		*cdip;

	ASSERT(MUTEX_HELD(&plun->lun_mutex));
	ASSERT(MUTEX_HELD(&pptr->port_mutex));

	if (plun->lun_cip == NULL) {
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_3, 0,
		    "fcp_offline_child: plun->lun_cip is NULL: "
		    "plun: %p lun state: %x num: %d target state: %x",
		    plun, plun->lun_state, plun->lun_num,
		    plun->lun_tgt->tgt_port->port_state);
		return (NDI_FAILURE);
	}

	if (plun->lun_mpxio == 0) {
		cdip = DIP(cip);
		mutex_exit(&plun->lun_mutex);
		mutex_exit(&pptr->port_mutex);
		rval = ndi_devi_offline(DIP(cip), flags);
		if (rval != NDI_SUCCESS) {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_3, 0,
			    "fcp_offline_child: ndi_devi_offline failed "
			    "rval=%x cip=%p", rval, cip);
		}
	} else {
		cdip = mdi_pi_get_client(PIP(cip));
		mutex_exit(&plun->lun_mutex);
		mutex_exit(&pptr->port_mutex);

		/*
		 * Exit phci to avoid deadlock with power management code
		 * during mdi_pi_offline
		 */
		mdi_hold_path(PIP(cip));
		mdi_devi_exit_phci(pptr->port_dip, *circ);

		rval = mdi_pi_offline(PIP(cip), flags);

		mdi_devi_enter_phci(pptr->port_dip, circ);
		mdi_rele_path(PIP(cip));

		if (rval == MDI_SUCCESS) {
			/*
			 * Clear MPxIO path permanent disable as the path is
			 * already offlined.
			 */
			(void) mdi_pi_enable_path(PIP(cip), DRIVER_DISABLE);

			if (flags & NDI_DEVI_REMOVE) {
				(void) mdi_pi_free(PIP(cip), 0);
			}
		} else {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_3, 0,
			    "fcp_offline_child: mdi_pi_offline failed "
			    "rval=%x cip=%p", rval, cip);
		}
		rval = (rval == MDI_SUCCESS) ? NDI_SUCCESS : NDI_FAILURE;
	}

	mutex_enter(&ptgt->tgt_mutex);
	plun->lun_state &= ~FCP_LUN_INIT;
	mutex_exit(&ptgt->tgt_mutex);

	mutex_enter(&pptr->port_mutex);
	mutex_enter(&plun->lun_mutex);

	if (rval == NDI_SUCCESS) {
		cdip = NULL;
		if (flags & NDI_DEVI_REMOVE) {
			/*
			 * If the guid of the LUN changes, lun_cip will not
			 * equal to cip, and after offlining the LUN with the
			 * old guid, we should keep lun_cip since it's the cip
			 * of the LUN with the new guid.
			 * Otherwise remove our reference to child node.
			 */
			if (plun->lun_cip == cip) {
				plun->lun_cip = NULL;
			}
			if (plun->lun_old_guid) {
				kmem_free(plun->lun_old_guid,
				    plun->lun_old_guid_size);
				plun->lun_old_guid = NULL;
				plun->lun_old_guid_size = 0;
			}
		}
	}

	if (cdip) {
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_3, 0, "!%s failed for %s:"
		    " target=%x lun=%x", "ndi_offline",
		    ddi_get_name(cdip), ptgt->tgt_d_id, plun->lun_num);
	}

	return (rval);
}

static void
fcp_remove_child(struct fcp_lun *plun)
{
	ASSERT(MUTEX_HELD(&plun->lun_mutex));

	if (fcp_is_child_present(plun, plun->lun_cip) == FC_SUCCESS) {
		if (plun->lun_mpxio == 0) {
			(void) ndi_prop_remove_all(DIP(plun->lun_cip));
			(void) ndi_devi_free(DIP(plun->lun_cip));
		} else {
			mutex_exit(&plun->lun_mutex);
			mutex_exit(&plun->lun_tgt->tgt_mutex);
			mutex_exit(&plun->lun_tgt->tgt_port->port_mutex);
			FCP_TRACE(fcp_logq,
			    plun->lun_tgt->tgt_port->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_3, 0,
			    "lun=%p pip freed %p", plun, plun->lun_cip);
			(void) mdi_prop_remove(PIP(plun->lun_cip), NULL);
			(void) mdi_pi_free(PIP(plun->lun_cip), 0);
			mutex_enter(&plun->lun_tgt->tgt_port->port_mutex);
			mutex_enter(&plun->lun_tgt->tgt_mutex);
			mutex_enter(&plun->lun_mutex);
		}
	}

	plun->lun_cip = NULL;
}

/*
 * called when a timeout occurs
 *
 * can be scheduled during an attach or resume (if not already running)
 *
 * one timeout is set up for all ports
 *
 * acquires and releases the global mutex
 */
/*ARGSUSED*/
static void
fcp_watch(void *arg)
{
	struct fcp_port	*pptr;
	struct fcp_ipkt	*icmd;
	struct fcp_ipkt	*nicmd;
	struct fcp_pkt	*cmd;
	struct fcp_pkt	*ncmd;
	struct fcp_pkt	*tail;
	struct fcp_pkt	*pcmd;
	struct fcp_pkt	*save_head;
	struct fcp_port	*save_port;

	/* increment global watchdog time */
	fcp_watchdog_time += fcp_watchdog_timeout;

	mutex_enter(&fcp_global_mutex);

	/* scan each port in our list */
	for (pptr = fcp_port_head; pptr != NULL; pptr = pptr->port_next) {
		save_port = fcp_port_head;
		pptr->port_state |= FCP_STATE_IN_WATCHDOG;
		mutex_exit(&fcp_global_mutex);

		mutex_enter(&pptr->port_mutex);
		if (pptr->port_ipkt_list == NULL &&
		    (pptr->port_state & (FCP_STATE_SUSPENDED |
		    FCP_STATE_DETACHING | FCP_STATE_POWER_DOWN))) {
			pptr->port_state &= ~FCP_STATE_IN_WATCHDOG;
			mutex_exit(&pptr->port_mutex);
			mutex_enter(&fcp_global_mutex);
			goto end_of_watchdog;
		}

		/*
		 * We check if a list of targets need to be offlined.
		 */
		if (pptr->port_offline_tgts) {
			fcp_scan_offline_tgts(pptr);
		}

		/*
		 * We check if a list of luns need to be offlined.
		 */
		if (pptr->port_offline_luns) {
			fcp_scan_offline_luns(pptr);
		}

		/*
		 * We check if a list of targets or luns need to be reset.
		 */
		if (pptr->port_reset_list) {
			fcp_check_reset_delay(pptr);
		}

		mutex_exit(&pptr->port_mutex);

		/*
		 * This is where the pending commands (pkt) are checked for
		 * timeout.
		 */
		mutex_enter(&pptr->port_pkt_mutex);
		tail = pptr->port_pkt_tail;

		for (pcmd = NULL, cmd = pptr->port_pkt_head;
		    cmd != NULL; cmd = ncmd) {
			ncmd = cmd->cmd_next;
			/*
			 * If a command is in this queue the bit CFLAG_IN_QUEUE
			 * must be set.
			 */
			ASSERT(cmd->cmd_flags & CFLAG_IN_QUEUE);
			/*
			 * FCP_INVALID_TIMEOUT will be set for those
			 * command that need to be failed. Mostly those
			 * cmds that could not be queued down for the
			 * "timeout" value. cmd->cmd_timeout is used
			 * to try and requeue the command regularly.
			 */
			if (cmd->cmd_timeout >= fcp_watchdog_time) {
				/*
				 * This command hasn't timed out yet.  Let's
				 * go to the next one.
				 */
				pcmd = cmd;
				goto end_of_loop;
			}

			if (cmd == pptr->port_pkt_head) {
				ASSERT(pcmd == NULL);
				pptr->port_pkt_head = cmd->cmd_next;
			} else {
				ASSERT(pcmd != NULL);
				pcmd->cmd_next = cmd->cmd_next;
			}

			if (cmd == pptr->port_pkt_tail) {
				ASSERT(cmd->cmd_next == NULL);
				pptr->port_pkt_tail = pcmd;
				if (pcmd) {
					pcmd->cmd_next = NULL;
				}
			}
			cmd->cmd_next = NULL;

			/*
			 * save the current head before dropping the
			 * mutex - If the head doesn't remain the
			 * same after re acquiring the mutex, just
			 * bail out and revisit on next tick.
			 *
			 * PS: The tail pointer can change as the commands
			 * get requeued after failure to retransport
			 */
			save_head = pptr->port_pkt_head;
			mutex_exit(&pptr->port_pkt_mutex);

			if (cmd->cmd_fp_pkt->pkt_timeout ==
			    FCP_INVALID_TIMEOUT) {
				struct scsi_pkt		*pkt = cmd->cmd_pkt;
				struct fcp_lun	*plun;
				struct fcp_tgt	*ptgt;

				plun = ADDR2LUN(&pkt->pkt_address);
				ptgt = plun->lun_tgt;

				FCP_TRACE(fcp_logq, pptr->port_instbuf,
				    fcp_trace, FCP_BUF_LEVEL_2, 0,
				    "SCSI cmd 0x%x to D_ID=%x timed out",
				    pkt->pkt_cdbp[0], ptgt->tgt_d_id);

				cmd->cmd_state == FCP_PKT_ABORTING ?
				    fcp_fail_cmd(cmd, CMD_RESET,
				    STAT_DEV_RESET) : fcp_fail_cmd(cmd,
				    CMD_TIMEOUT, STAT_ABORTED);
			} else {
				fcp_retransport_cmd(pptr, cmd);
			}
			mutex_enter(&pptr->port_pkt_mutex);
			if (save_head && save_head != pptr->port_pkt_head) {
				/*
				 * Looks like linked list got changed (mostly
				 * happens when an an OFFLINE LUN code starts
				 * returning overflow queue commands in
				 * parallel. So bail out and revisit during
				 * next tick
				 */
				break;
			}
		end_of_loop:
			/*
			 * Scan only upto the previously known tail pointer
			 * to avoid excessive processing - lots of new packets
			 * could have been added to the tail or the old ones
			 * re-queued.
			 */
			if (cmd == tail) {
				break;
			}
		}
		mutex_exit(&pptr->port_pkt_mutex);

		mutex_enter(&pptr->port_mutex);
		for (icmd = pptr->port_ipkt_list; icmd != NULL; icmd = nicmd) {
			struct fcp_tgt *ptgt = icmd->ipkt_tgt;

			nicmd = icmd->ipkt_next;
			if ((icmd->ipkt_restart != 0) &&
			    (icmd->ipkt_restart >= fcp_watchdog_time)) {
				/* packet has not timed out */
				continue;
			}

			/* time for packet re-transport */
			if (icmd == pptr->port_ipkt_list) {
				pptr->port_ipkt_list = icmd->ipkt_next;
				if (pptr->port_ipkt_list) {
					pptr->port_ipkt_list->ipkt_prev =
					    NULL;
				}
			} else {
				icmd->ipkt_prev->ipkt_next = icmd->ipkt_next;
				if (icmd->ipkt_next) {
					icmd->ipkt_next->ipkt_prev =
					    icmd->ipkt_prev;
				}
			}
			icmd->ipkt_next = NULL;
			icmd->ipkt_prev = NULL;
			mutex_exit(&pptr->port_mutex);

			if (fcp_is_retryable(icmd)) {
				fc_ulp_rscn_info_t *rscnp =
				    (fc_ulp_rscn_info_t *)icmd->ipkt_fpkt->
				    pkt_ulp_rscn_infop;

				FCP_TRACE(fcp_logq, pptr->port_instbuf,
				    fcp_trace, FCP_BUF_LEVEL_2, 0,
				    "%x to D_ID=%x Retrying..",
				    icmd->ipkt_opcode,
				    icmd->ipkt_fpkt->pkt_cmd_fhdr.d_id);

				/*
				 * Update the RSCN count in the packet
				 * before resending.
				 */

				if (rscnp != NULL) {
					rscnp->ulp_rscn_count =
					    fc_ulp_get_rscn_count(pptr->
					    port_fp_handle);
				}

				mutex_enter(&pptr->port_mutex);
				mutex_enter(&ptgt->tgt_mutex);
				if (!FCP_STATE_CHANGED(pptr, ptgt, icmd)) {
					mutex_exit(&ptgt->tgt_mutex);
					mutex_exit(&pptr->port_mutex);
					switch (icmd->ipkt_opcode) {
						int rval;
					case LA_ELS_PLOGI:
						if ((rval = fc_ulp_login(
						    pptr->port_fp_handle,
						    &icmd->ipkt_fpkt, 1)) ==
						    FC_SUCCESS) {
							mutex_enter(
							    &pptr->port_mutex);
							continue;
						}
						if (fcp_handle_ipkt_errors(
						    pptr, ptgt, icmd, rval,
						    "PLOGI") == DDI_SUCCESS) {
							mutex_enter(
							    &pptr->port_mutex);
							continue;
						}
						break;

					case LA_ELS_PRLI:
						if ((rval = fc_ulp_issue_els(
						    pptr->port_fp_handle,
						    icmd->ipkt_fpkt)) ==
						    FC_SUCCESS) {
							mutex_enter(
							    &pptr->port_mutex);
							continue;
						}
						if (fcp_handle_ipkt_errors(
						    pptr, ptgt, icmd, rval,
						    "PRLI") == DDI_SUCCESS) {
							mutex_enter(
							    &pptr->port_mutex);
							continue;
						}
						break;

					default:
						if ((rval = fcp_transport(
						    pptr->port_fp_handle,
						    icmd->ipkt_fpkt, 1)) ==
						    FC_SUCCESS) {
							mutex_enter(
							    &pptr->port_mutex);
							continue;
						}
						if (fcp_handle_ipkt_errors(
						    pptr, ptgt, icmd, rval,
						    "PRLI") == DDI_SUCCESS) {
							mutex_enter(
							    &pptr->port_mutex);
							continue;
						}
						break;
					}
				} else {
					mutex_exit(&ptgt->tgt_mutex);
					mutex_exit(&pptr->port_mutex);
				}
			} else {
				fcp_print_error(icmd->ipkt_fpkt);
			}

			(void) fcp_call_finish_init(pptr, ptgt,
			    icmd->ipkt_link_cnt, icmd->ipkt_change_cnt,
			    icmd->ipkt_cause);
			fcp_icmd_free(pptr, icmd);
			mutex_enter(&pptr->port_mutex);
		}

		pptr->port_state &= ~FCP_STATE_IN_WATCHDOG;
		mutex_exit(&pptr->port_mutex);
		mutex_enter(&fcp_global_mutex);

	end_of_watchdog:
		/*
		 * Bail out early before getting into trouble
		 */
		if (save_port != fcp_port_head) {
			break;
		}
	}

	if (fcp_watchdog_init > 0) {
		/* reschedule timeout to go again */
		fcp_watchdog_id =
		    timeout(fcp_watch, NULL, fcp_watchdog_tick);
	}
	mutex_exit(&fcp_global_mutex);
}


static void
fcp_check_reset_delay(struct fcp_port *pptr)
{
	uint32_t		tgt_cnt;
	int			level;
	struct fcp_tgt	*ptgt;
	struct fcp_lun	*plun;
	struct fcp_reset_elem *cur = NULL;
	struct fcp_reset_elem *next = NULL;
	struct fcp_reset_elem *prev = NULL;

	ASSERT(mutex_owned(&pptr->port_mutex));

	next = pptr->port_reset_list;
	while ((cur = next) != NULL) {
		next = cur->next;

		if (cur->timeout < fcp_watchdog_time) {
			prev = cur;
			continue;
		}

		ptgt = cur->tgt;
		plun = cur->lun;
		tgt_cnt = cur->tgt_cnt;

		if (ptgt) {
			level = RESET_TARGET;
		} else {
			ASSERT(plun != NULL);
			level = RESET_LUN;
			ptgt = plun->lun_tgt;
		}
		if (prev) {
			prev->next = next;
		} else {
			/*
			 * Because we drop port mutex while doing aborts for
			 * packets, we can't rely on reset_list pointing to
			 * our head
			 */
			if (cur == pptr->port_reset_list) {
				pptr->port_reset_list = next;
			} else {
				struct fcp_reset_elem *which;

				which = pptr->port_reset_list;
				while (which && which->next != cur) {
					which = which->next;
				}
				ASSERT(which != NULL);

				which->next = next;
				prev = which;
			}
		}

		kmem_free(cur, sizeof (*cur));

		if (tgt_cnt == ptgt->tgt_change_cnt) {
			mutex_enter(&ptgt->tgt_mutex);
			if (level == RESET_TARGET) {
				fcp_update_tgt_state(ptgt,
				    FCP_RESET, FCP_LUN_BUSY);
			} else {
				fcp_update_lun_state(plun,
				    FCP_RESET, FCP_LUN_BUSY);
			}
			mutex_exit(&ptgt->tgt_mutex);

			mutex_exit(&pptr->port_mutex);
			fcp_abort_all(pptr, ptgt, plun, tgt_cnt);
			mutex_enter(&pptr->port_mutex);
		}
	}
}


static void
fcp_abort_all(struct fcp_port *pptr, struct fcp_tgt *ttgt,
    struct fcp_lun *rlun, int tgt_cnt)
{
	int			rval;
	struct fcp_lun	*tlun, *nlun;
	struct fcp_pkt	*pcmd = NULL, *ncmd = NULL,
	    *cmd = NULL, *head = NULL,
	    *tail = NULL;

	mutex_enter(&pptr->port_pkt_mutex);
	for (cmd = pptr->port_pkt_head; cmd != NULL; cmd = ncmd) {
		struct fcp_lun *plun = ADDR2LUN(&cmd->cmd_pkt->pkt_address);
		struct fcp_tgt *ptgt = plun->lun_tgt;

		ncmd = cmd->cmd_next;

		if (ptgt != ttgt && plun != rlun) {
			pcmd = cmd;
			continue;
		}

		if (pcmd != NULL) {
			ASSERT(pptr->port_pkt_head != cmd);
			pcmd->cmd_next = ncmd;
		} else {
			ASSERT(cmd == pptr->port_pkt_head);
			pptr->port_pkt_head = ncmd;
		}
		if (pptr->port_pkt_tail == cmd) {
			ASSERT(cmd->cmd_next == NULL);
			pptr->port_pkt_tail = pcmd;
			if (pcmd != NULL) {
				pcmd->cmd_next = NULL;
			}
		}

		if (head == NULL) {
			head = tail = cmd;
		} else {
			ASSERT(tail != NULL);
			tail->cmd_next = cmd;
			tail = cmd;
		}
		cmd->cmd_next = NULL;
	}
	mutex_exit(&pptr->port_pkt_mutex);

	for (cmd = head; cmd != NULL; cmd = ncmd) {
		struct scsi_pkt *pkt = cmd->cmd_pkt;

		ncmd = cmd->cmd_next;
		ASSERT(pkt != NULL);

		mutex_enter(&pptr->port_mutex);
		if (ttgt->tgt_change_cnt == tgt_cnt) {
			mutex_exit(&pptr->port_mutex);
			cmd->cmd_flags &= ~CFLAG_IN_QUEUE;
			pkt->pkt_reason = CMD_RESET;
			pkt->pkt_statistics |= STAT_DEV_RESET;
			cmd->cmd_state = FCP_PKT_IDLE;
			fcp_post_callback(cmd);
		} else {
			mutex_exit(&pptr->port_mutex);
		}
	}

	/*
	 * If the FCA will return all the commands in its queue then our
	 * work is easy, just return.
	 */

	if (pptr->port_reset_action == FC_RESET_RETURN_ALL) {
		return;
	}

	/*
	 * For RESET_LUN get hold of target pointer
	 */
	if (ttgt == NULL) {
		ASSERT(rlun != NULL);

		ttgt = rlun->lun_tgt;

		ASSERT(ttgt != NULL);
	}

	/*
	 * There are some severe race conditions here.
	 * While we are trying to abort the pkt, it might be completing
	 * so mark it aborted and if the abort does not succeed then
	 * handle it in the watch thread.
	 */
	mutex_enter(&ttgt->tgt_mutex);
	nlun = ttgt->tgt_lun;
	mutex_exit(&ttgt->tgt_mutex);
	while ((tlun = nlun) != NULL) {
		int restart = 0;
		if (rlun && rlun != tlun) {
			mutex_enter(&ttgt->tgt_mutex);
			nlun = tlun->lun_next;
			mutex_exit(&ttgt->tgt_mutex);
			continue;
		}
		mutex_enter(&tlun->lun_mutex);
		cmd = tlun->lun_pkt_head;
		while (cmd != NULL) {
			if (cmd->cmd_state == FCP_PKT_ISSUED) {
				struct scsi_pkt *pkt;

				restart = 1;
				cmd->cmd_state = FCP_PKT_ABORTING;
				mutex_exit(&tlun->lun_mutex);
				rval = fc_ulp_abort(pptr->port_fp_handle,
				    cmd->cmd_fp_pkt, KM_SLEEP);
				if (rval == FC_SUCCESS) {
					pkt = cmd->cmd_pkt;
					pkt->pkt_reason = CMD_RESET;
					pkt->pkt_statistics |= STAT_DEV_RESET;
					cmd->cmd_state = FCP_PKT_IDLE;
					fcp_post_callback(cmd);
				} else {
					caddr_t msg;

					(void) fc_ulp_error(rval, &msg);

					/*
					 * This part is tricky. The abort
					 * failed and now the command could
					 * be completing.  The cmd_state ==
					 * FCP_PKT_ABORTING should save
					 * us in fcp_cmd_callback. If we
					 * are already aborting ignore the
					 * command in fcp_cmd_callback.
					 * Here we leave this packet for 20
					 * sec to be aborted in the
					 * fcp_watch thread.
					 */
					fcp_log(CE_WARN, pptr->port_dip,
					    "!Abort failed after reset %s",
					    msg);

					cmd->cmd_timeout =
					    fcp_watchdog_time +
					    cmd->cmd_pkt->pkt_time +
					    FCP_FAILED_DELAY;

					cmd->cmd_fp_pkt->pkt_timeout =
					    FCP_INVALID_TIMEOUT;
					/*
					 * This is a hack, cmd is put in the
					 * overflow queue so that it can be
					 * timed out finally
					 */
					cmd->cmd_flags |= CFLAG_IN_QUEUE;

					mutex_enter(&pptr->port_pkt_mutex);
					if (pptr->port_pkt_head) {
						ASSERT(pptr->port_pkt_tail
						    != NULL);
						pptr->port_pkt_tail->cmd_next
						    = cmd;
						pptr->port_pkt_tail = cmd;
					} else {
						ASSERT(pptr->port_pkt_tail
						    == NULL);
						pptr->port_pkt_head =
						    pptr->port_pkt_tail
						    = cmd;
					}
					cmd->cmd_next = NULL;
					mutex_exit(&pptr->port_pkt_mutex);
				}
				mutex_enter(&tlun->lun_mutex);
				cmd = tlun->lun_pkt_head;
			} else {
				cmd = cmd->cmd_forw;
			}
		}
		mutex_exit(&tlun->lun_mutex);

		mutex_enter(&ttgt->tgt_mutex);
		restart == 1 ? (nlun = ttgt->tgt_lun) : (nlun = tlun->lun_next);
		mutex_exit(&ttgt->tgt_mutex);

		mutex_enter(&pptr->port_mutex);
		if (tgt_cnt != ttgt->tgt_change_cnt) {
			mutex_exit(&pptr->port_mutex);
			return;
		} else {
			mutex_exit(&pptr->port_mutex);
		}
	}
}


/*
 * unlink the soft state, returning the soft state found (if any)
 *
 * acquires and releases the global mutex
 */
struct fcp_port *
fcp_soft_state_unlink(struct fcp_port *pptr)
{
	struct fcp_port	*hptr;		/* ptr index */
	struct fcp_port	*tptr;		/* prev hptr */

	mutex_enter(&fcp_global_mutex);
	for (hptr = fcp_port_head, tptr = NULL;
	    hptr != NULL;
	    tptr = hptr, hptr = hptr->port_next) {
		if (hptr == pptr) {
			/* we found a match -- remove this item */
			if (tptr == NULL) {
				/* we're at the head of the list */
				fcp_port_head = hptr->port_next;
			} else {
				tptr->port_next = hptr->port_next;
			}
			break;			/* success */
		}
	}
	if (fcp_port_head == NULL) {
		fcp_cleanup_blacklist(&fcp_lun_blacklist);
	}
	mutex_exit(&fcp_global_mutex);
	return (hptr);
}


/*
 * called by fcp_scsi_hba_tgt_init to find a LUN given a
 * WWN and a LUN number
 */
/* ARGSUSED */
static struct fcp_lun *
fcp_lookup_lun(struct fcp_port *pptr, uchar_t *wwn, uint16_t lun)
{
	int hash;
	struct fcp_tgt *ptgt;
	struct fcp_lun *plun;

	ASSERT(mutex_owned(&pptr->port_mutex));

	hash = FCP_HASH(wwn);
	for (ptgt = pptr->port_tgt_hash_table[hash]; ptgt != NULL;
	    ptgt = ptgt->tgt_next) {
		if (bcmp((caddr_t)wwn, (caddr_t)&ptgt->tgt_port_wwn.raw_wwn[0],
		    sizeof (ptgt->tgt_port_wwn)) == 0) {
			mutex_enter(&ptgt->tgt_mutex);
			for (plun = ptgt->tgt_lun;
			    plun != NULL;
			    plun = plun->lun_next) {
				if (plun->lun_num == lun) {
					mutex_exit(&ptgt->tgt_mutex);
					return (plun);
				}
			}
			mutex_exit(&ptgt->tgt_mutex);
			return (NULL);
		}
	}
	return (NULL);
}

/*
 *     Function: fcp_prepare_pkt
 *
 *  Description: This function prepares the SCSI cmd pkt, passed by the caller,
 *		 for fcp_start(). It binds the data or partially maps it.
 *		 Builds the FCP header and starts the initialization of the
 *		 Fibre Channel header.
 *
 *     Argument: *pptr		FCP port.
 *		 *cmd		FCP packet.
 *		 *plun		LUN the command will be sent to.
 *
 *	Context: User, Kernel and Interrupt context.
 */
static void
fcp_prepare_pkt(struct fcp_port *pptr, struct fcp_pkt *cmd,
    struct fcp_lun *plun)
{
	fc_packet_t		*fpkt = cmd->cmd_fp_pkt;
	struct fcp_tgt		*ptgt = plun->lun_tgt;
	struct fcp_cmd		*fcmd = &cmd->cmd_fcp_cmd;

	ASSERT(cmd->cmd_pkt->pkt_comp ||
	    (cmd->cmd_pkt->pkt_flags & FLAG_NOINTR));

	if (cmd->cmd_pkt->pkt_numcookies) {
		if (cmd->cmd_pkt->pkt_dma_flags & DDI_DMA_READ) {
			fcmd->fcp_cntl.cntl_read_data = 1;
			fcmd->fcp_cntl.cntl_write_data = 0;
			fpkt->pkt_tran_type = FC_PKT_FCP_READ;
		} else {
			fcmd->fcp_cntl.cntl_read_data = 0;
			fcmd->fcp_cntl.cntl_write_data = 1;
			fpkt->pkt_tran_type = FC_PKT_FCP_WRITE;
		}

		fpkt->pkt_data_cookie = cmd->cmd_pkt->pkt_cookies;

		fpkt->pkt_data_cookie_cnt = cmd->cmd_pkt->pkt_numcookies;
		ASSERT(fpkt->pkt_data_cookie_cnt <=
		    pptr->port_data_dma_attr.dma_attr_sgllen);

		cmd->cmd_dmacount = cmd->cmd_pkt->pkt_dma_len;

		/* FCA needs pkt_datalen to be set */
		fpkt->pkt_datalen = cmd->cmd_dmacount;
		fcmd->fcp_data_len = cmd->cmd_dmacount;
	} else {
		fcmd->fcp_cntl.cntl_read_data = 0;
		fcmd->fcp_cntl.cntl_write_data = 0;
		fpkt->pkt_tran_type = FC_PKT_EXCHANGE;
		fpkt->pkt_datalen = 0;
		fcmd->fcp_data_len = 0;
	}

	/* set up the Tagged Queuing type */
	if (cmd->cmd_pkt->pkt_flags & FLAG_HTAG) {
		fcmd->fcp_cntl.cntl_qtype = FCP_QTYPE_HEAD_OF_Q;
	} else if (cmd->cmd_pkt->pkt_flags & FLAG_OTAG) {
		fcmd->fcp_cntl.cntl_qtype = FCP_QTYPE_ORDERED;
	} else if (cmd->cmd_pkt->pkt_flags & FLAG_STAG) {
		fcmd->fcp_cntl.cntl_qtype = FCP_QTYPE_SIMPLE;
	} else {
		fcmd->fcp_cntl.cntl_qtype = FCP_QTYPE_UNTAGGED;
	}

	fcmd->fcp_ent_addr = plun->lun_addr;

	if (pptr->port_fcp_dma != FC_NO_DVMA_SPACE) {
		FCP_CP_OUT((uint8_t *)fcmd, fpkt->pkt_cmd,
		    fpkt->pkt_cmd_acc, sizeof (struct fcp_cmd));
	} else {
		ASSERT(fpkt->pkt_cmd_dma == NULL && fpkt->pkt_resp_dma == NULL);
	}

	cmd->cmd_pkt->pkt_reason = CMD_CMPLT;
	cmd->cmd_pkt->pkt_state = 0;
	cmd->cmd_pkt->pkt_statistics = 0;
	cmd->cmd_pkt->pkt_resid = 0;

	cmd->cmd_fp_pkt->pkt_data_dma = cmd->cmd_pkt->pkt_handle;

	if (cmd->cmd_pkt->pkt_flags & FLAG_NOINTR) {
		fpkt->pkt_tran_flags = (FC_TRAN_CLASS3 | FC_TRAN_NO_INTR);
		fpkt->pkt_comp = NULL;
	} else {
		fpkt->pkt_tran_flags = (FC_TRAN_CLASS3 | FC_TRAN_INTR);
		if (cmd->cmd_pkt->pkt_flags & FLAG_IMMEDIATE_CB) {
			fpkt->pkt_tran_flags |= FC_TRAN_IMMEDIATE_CB;
		}
		fpkt->pkt_comp = fcp_cmd_callback;
	}

	mutex_enter(&pptr->port_mutex);
	if (pptr->port_state & FCP_STATE_SUSPENDED) {
		fpkt->pkt_tran_flags |= FC_TRAN_DUMPING;
	}
	mutex_exit(&pptr->port_mutex);

	fpkt->pkt_cmd_fhdr.d_id = ptgt->tgt_d_id;
	fpkt->pkt_cmd_fhdr.s_id = pptr->port_id;

	/*
	 * Save a few kernel cycles here
	 */
#ifndef	__lock_lint
	fpkt->pkt_fca_device = ptgt->tgt_fca_dev;
#endif /* __lock_lint */
}

static void
fcp_post_callback(struct fcp_pkt *cmd)
{
	scsi_hba_pkt_comp(cmd->cmd_pkt);
}


/*
 * called to do polled I/O by fcp_start()
 *
 * return a transport status value, i.e. TRAN_ACCECPT for success
 */
static int
fcp_dopoll(struct fcp_port *pptr, struct fcp_pkt *cmd)
{
	int	rval;

#ifdef	DEBUG
	mutex_enter(&pptr->port_pkt_mutex);
	pptr->port_npkts++;
	mutex_exit(&pptr->port_pkt_mutex);
#endif /* DEBUG */

	if (cmd->cmd_fp_pkt->pkt_timeout) {
		cmd->cmd_fp_pkt->pkt_timeout = cmd->cmd_pkt->pkt_time;
	} else {
		cmd->cmd_fp_pkt->pkt_timeout = FCP_POLL_TIMEOUT;
	}

	ASSERT(cmd->cmd_fp_pkt->pkt_comp == NULL);

	cmd->cmd_state = FCP_PKT_ISSUED;

	rval = fc_ulp_transport(pptr->port_fp_handle, cmd->cmd_fp_pkt);

#ifdef	DEBUG
	mutex_enter(&pptr->port_pkt_mutex);
	pptr->port_npkts--;
	mutex_exit(&pptr->port_pkt_mutex);
#endif /* DEBUG */

	cmd->cmd_state = FCP_PKT_IDLE;

	switch (rval) {
	case FC_SUCCESS:
		if (cmd->cmd_fp_pkt->pkt_state == FC_PKT_SUCCESS) {
			fcp_complete_pkt(cmd->cmd_fp_pkt);
			rval = TRAN_ACCEPT;
		} else {
			rval = TRAN_FATAL_ERROR;
		}
		break;

	case FC_TRAN_BUSY:
		rval = TRAN_BUSY;
		cmd->cmd_pkt->pkt_resid = 0;
		break;

	case FC_BADPACKET:
		rval = TRAN_BADPKT;
		break;

	default:
		rval = TRAN_FATAL_ERROR;
		break;
	}

	return (rval);
}


/*
 * called by some of the following transport-called routines to convert
 * a supplied dip ptr to a port struct ptr (i.e. to the soft state)
 */
static struct fcp_port *
fcp_dip2port(dev_info_t *dip)
{
	int	instance;

	instance = ddi_get_instance(dip);
	return (ddi_get_soft_state(fcp_softstate, instance));
}


/*
 * called internally to return a LUN given a dip
 */
struct fcp_lun *
fcp_get_lun_from_cip(struct fcp_port *pptr, child_info_t *cip)
{
	struct fcp_tgt *ptgt;
	struct fcp_lun *plun;
	int i;


	ASSERT(mutex_owned(&pptr->port_mutex));

	for (i = 0; i < FCP_NUM_HASH; i++) {
		for (ptgt = pptr->port_tgt_hash_table[i];
		    ptgt != NULL;
		    ptgt = ptgt->tgt_next) {
			mutex_enter(&ptgt->tgt_mutex);
			for (plun = ptgt->tgt_lun; plun != NULL;
			    plun = plun->lun_next) {
				mutex_enter(&plun->lun_mutex);
				if (plun->lun_cip == cip) {
					mutex_exit(&plun->lun_mutex);
					mutex_exit(&ptgt->tgt_mutex);
					return (plun); /* match found */
				}
				mutex_exit(&plun->lun_mutex);
			}
			mutex_exit(&ptgt->tgt_mutex);
		}
	}
	return (NULL);				/* no LUN found */
}

/*
 * pass an element to the hotplug list, kick the hotplug thread
 * and wait for the element to get processed by the hotplug thread.
 * on return the element is freed.
 *
 * return zero success and non-zero on failure
 *
 * acquires/releases the target mutex
 *
 */
static int
fcp_pass_to_hp_and_wait(struct fcp_port *pptr, struct fcp_lun *plun,
    child_info_t *cip, int what, int link_cnt, int tgt_cnt, int flags)
{
	struct fcp_hp_elem	*elem;
	int			rval;

	mutex_enter(&plun->lun_tgt->tgt_mutex);
	if ((elem = fcp_pass_to_hp(pptr, plun, cip,
	    what, link_cnt, tgt_cnt, flags, 1)) == NULL) {
		mutex_exit(&plun->lun_tgt->tgt_mutex);
		fcp_log(CE_CONT, pptr->port_dip,
		    "Can not pass_to_hp: what: %d; D_ID=%x, LUN=%x\n",
		    what, plun->lun_tgt->tgt_d_id, plun->lun_num);
		return (NDI_FAILURE);
	}
	mutex_exit(&plun->lun_tgt->tgt_mutex);
	mutex_enter(&elem->mutex);
	if (elem->wait) {
		while (elem->wait) {
			cv_wait(&elem->cv, &elem->mutex);
		}
	}
	rval = (elem->result);
	mutex_exit(&elem->mutex);
	mutex_destroy(&elem->mutex);
	cv_destroy(&elem->cv);
	kmem_free(elem, sizeof (struct fcp_hp_elem));
	return (rval);
}

/*
 * pass an element to the hotplug list, and then
 * kick the hotplug thread
 *
 * return Boolean success, i.e. non-zero if all goes well, else zero on error
 *
 * acquires/releases the hotplug mutex
 *
 * called with the target mutex owned
 *
 * memory acquired in NOSLEEP mode
 * NOTE: if wait is set to 1 then the caller is responsible for waiting on
 *	 for the hp daemon to process the request and is responsible for
 *	 freeing the element
 */
static struct fcp_hp_elem *
fcp_pass_to_hp(struct fcp_port *pptr, struct fcp_lun *plun,
    child_info_t *cip, int what, int link_cnt, int tgt_cnt, int flags, int wait)
{
	struct fcp_hp_elem	*elem;
	dev_info_t *pdip;

	ASSERT(pptr != NULL);
	ASSERT(plun != NULL);
	ASSERT(plun->lun_tgt != NULL);
	ASSERT(mutex_owned(&plun->lun_tgt->tgt_mutex));

	/* create space for a hotplug element */
	if ((elem = kmem_zalloc(sizeof (struct fcp_hp_elem), KM_NOSLEEP))
	    == NULL) {
		fcp_log(CE_WARN, NULL,
		    "!can't allocate memory for hotplug element");
		return (NULL);
	}

	/* fill in hotplug element */
	elem->port = pptr;
	elem->lun = plun;
	elem->cip = cip;
	elem->old_lun_mpxio = plun->lun_mpxio;
	elem->what = what;
	elem->flags = flags;
	elem->link_cnt = link_cnt;
	elem->tgt_cnt = tgt_cnt;
	elem->wait = wait;
	mutex_init(&elem->mutex, NULL, MUTEX_DRIVER, NULL);
	cv_init(&elem->cv, NULL, CV_DRIVER, NULL);

	/* schedule the hotplug task */
	pdip = pptr->port_dip;
	mutex_enter(&plun->lun_mutex);
	if (elem->what == FCP_ONLINE || elem->what == FCP_OFFLINE) {
		plun->lun_event_count++;
		elem->event_cnt = plun->lun_event_count;
	}
	mutex_exit(&plun->lun_mutex);
	if (taskq_dispatch(DEVI(pdip)->devi_taskq, fcp_hp_task,
	    (void *)elem, KM_NOSLEEP) == NULL) {
		mutex_enter(&plun->lun_mutex);
		if (elem->what == FCP_ONLINE || elem->what == FCP_OFFLINE) {
			plun->lun_event_count--;
		}
		mutex_exit(&plun->lun_mutex);
		kmem_free(elem, sizeof (*elem));
		return (0);
	}

	return (elem);
}


static void
fcp_retransport_cmd(struct fcp_port *pptr, struct fcp_pkt *cmd)
{
	int			rval;
	struct scsi_address	*ap;
	struct fcp_lun	*plun;
	struct fcp_tgt	*ptgt;
	fc_packet_t	*fpkt;

	ap = &cmd->cmd_pkt->pkt_address;
	plun = ADDR2LUN(ap);
	ptgt = plun->lun_tgt;

	ASSERT(cmd->cmd_flags & CFLAG_IN_QUEUE);

	cmd->cmd_state = FCP_PKT_IDLE;

	mutex_enter(&pptr->port_mutex);
	mutex_enter(&ptgt->tgt_mutex);
	if (((plun->lun_state & (FCP_LUN_BUSY | FCP_LUN_OFFLINE)) == 0) &&
	    (!(pptr->port_state & FCP_STATE_ONLINING))) {
		fc_ulp_rscn_info_t *rscnp;

		cmd->cmd_state = FCP_PKT_ISSUED;

		/*
		 * It is possible for pkt_pd to be NULL if tgt_pd_handle was
		 * originally NULL, hence we try to set it to the pd pointed
		 * to by the SCSI device we're trying to get to.
		 */

		fpkt = cmd->cmd_fp_pkt;
		if ((fpkt->pkt_pd == NULL) && (ptgt->tgt_pd_handle != NULL)) {
			fpkt->pkt_pd = ptgt->tgt_pd_handle;
			/*
			 * We need to notify the transport that we now have a
			 * reference to the remote port handle.
			 */
			fc_ulp_hold_remote_port(ptgt->tgt_pd_handle);
		}

		mutex_exit(&ptgt->tgt_mutex);
		mutex_exit(&pptr->port_mutex);

		ASSERT((cmd->cmd_pkt->pkt_flags & FLAG_NOINTR) == 0);

		/* prepare the packet */

		fcp_prepare_pkt(pptr, cmd, plun);

		rscnp = (fc_ulp_rscn_info_t *)cmd->cmd_fp_pkt->
		    pkt_ulp_rscn_infop;

		cmd->cmd_timeout = cmd->cmd_pkt->pkt_time ?
		    fcp_watchdog_time + cmd->cmd_pkt->pkt_time : 0;

		if (rscnp != NULL) {
			rscnp->ulp_rscn_count =
			    fc_ulp_get_rscn_count(pptr->
			    port_fp_handle);
		}

		rval = fcp_transport(pptr->port_fp_handle,
		    cmd->cmd_fp_pkt, 0);

		if (rval == FC_SUCCESS) {
			return;
		}
		cmd->cmd_state &= ~FCP_PKT_ISSUED;
	} else {
		mutex_exit(&ptgt->tgt_mutex);
		mutex_exit(&pptr->port_mutex);
	}

	fcp_queue_pkt(pptr, cmd);
}


static void
fcp_fail_cmd(struct fcp_pkt *cmd, uchar_t reason, uint_t statistics)
{
	ASSERT(cmd->cmd_flags & CFLAG_IN_QUEUE);

	cmd->cmd_flags &= ~CFLAG_IN_QUEUE;
	cmd->cmd_state = FCP_PKT_IDLE;

	cmd->cmd_pkt->pkt_reason = reason;
	cmd->cmd_pkt->pkt_state = 0;
	cmd->cmd_pkt->pkt_statistics = statistics;

	fcp_post_callback(cmd);
}

/*
 *     Function: fcp_queue_pkt
 *
 *  Description: This function queues the packet passed by the caller into
 *		 the list of packets of the FCP port.
 *
 *     Argument: *pptr		FCP port.
 *		 *cmd		FCP packet to queue.
 *
 * Return Value: None
 *
 *	Context: User, Kernel and Interrupt context.
 */
static void
fcp_queue_pkt(struct fcp_port *pptr, struct fcp_pkt *cmd)
{
	ASSERT((cmd->cmd_pkt->pkt_flags & FLAG_NOQUEUE) == NULL);

	mutex_enter(&pptr->port_pkt_mutex);
	cmd->cmd_flags |= CFLAG_IN_QUEUE;
	ASSERT(cmd->cmd_state != FCP_PKT_ISSUED);
	cmd->cmd_timeout = fcp_watchdog_time + FCP_QUEUE_DELAY;

	/*
	 * zero pkt_time means hang around for ever
	 */
	if (cmd->cmd_pkt->pkt_time) {
		if (cmd->cmd_fp_pkt->pkt_timeout > FCP_QUEUE_DELAY) {
			cmd->cmd_fp_pkt->pkt_timeout -= FCP_QUEUE_DELAY;
		} else {
			/*
			 * Indicate the watch thread to fail the
			 * command by setting it to highest value
			 */
			cmd->cmd_timeout = fcp_watchdog_time;
			cmd->cmd_fp_pkt->pkt_timeout = FCP_INVALID_TIMEOUT;
		}
	}

	if (pptr->port_pkt_head) {
		ASSERT(pptr->port_pkt_tail != NULL);

		pptr->port_pkt_tail->cmd_next = cmd;
		pptr->port_pkt_tail = cmd;
	} else {
		ASSERT(pptr->port_pkt_tail == NULL);

		pptr->port_pkt_head = pptr->port_pkt_tail = cmd;
	}
	cmd->cmd_next = NULL;
	mutex_exit(&pptr->port_pkt_mutex);
}

/*
 *     Function: fcp_update_targets
 *
 *  Description: This function applies the specified change of state to all
 *		 the targets listed.  The operation applied is 'set'.
 *
 *     Argument: *pptr		FCP port.
 *		 *dev_list	Array of fc_portmap_t structures.
 *		 count		Length of dev_list.
 *		 state		State bits to update.
 *		 cause		Reason for the update.
 *
 * Return Value: None
 *
 *	Context: User, Kernel and Interrupt context.
 *		 The mutex pptr->port_mutex must be held.
 */
static void
fcp_update_targets(struct fcp_port *pptr, fc_portmap_t *dev_list,
    uint32_t count, uint32_t state, int cause)
{
	fc_portmap_t		*map_entry;
	struct fcp_tgt	*ptgt;

	ASSERT(MUTEX_HELD(&pptr->port_mutex));

	while (count--) {
		map_entry = &(dev_list[count]);
		ptgt = fcp_lookup_target(pptr,
		    (uchar_t *)&(map_entry->map_pwwn));
		if (ptgt == NULL) {
			continue;
		}

		mutex_enter(&ptgt->tgt_mutex);
		ptgt->tgt_trace = 0;
		ptgt->tgt_change_cnt++;
		ptgt->tgt_statec_cause = cause;
		ptgt->tgt_tmp_cnt = 1;
		fcp_update_tgt_state(ptgt, FCP_SET, state);
		mutex_exit(&ptgt->tgt_mutex);
	}
}

static int
fcp_call_finish_init(struct fcp_port *pptr, struct fcp_tgt *ptgt,
    int lcount, int tcount, int cause)
{
	int rval;

	mutex_enter(&pptr->port_mutex);
	rval = fcp_call_finish_init_held(pptr, ptgt, lcount, tcount, cause);
	mutex_exit(&pptr->port_mutex);

	return (rval);
}


static int
fcp_call_finish_init_held(struct fcp_port *pptr, struct fcp_tgt *ptgt,
    int lcount, int tcount, int cause)
{
	int	finish_init = 0;
	int	finish_tgt = 0;
	int	do_finish_init = 0;
	int	rval = FCP_NO_CHANGE;

	if (cause == FCP_CAUSE_LINK_CHANGE ||
	    cause == FCP_CAUSE_LINK_DOWN) {
		do_finish_init = 1;
	}

	if (ptgt != NULL) {
		FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace,
		    FCP_BUF_LEVEL_2, 0,
		    "link_cnt: %d,%d; tgt_cnt: %d,%d; tmp_cnt: %d,%d;"
		    " cause = %d, d_id = 0x%x, tgt_done = %d",
		    pptr->port_link_cnt, lcount, ptgt->tgt_change_cnt, tcount,
		    pptr->port_tmp_cnt, ptgt->tgt_tmp_cnt, cause,
		    ptgt->tgt_d_id, ptgt->tgt_done);

		mutex_enter(&ptgt->tgt_mutex);

		if (tcount && (ptgt->tgt_change_cnt != tcount)) {
			rval = FCP_DEV_CHANGE;
			if (do_finish_init && ptgt->tgt_done == 0) {
				ptgt->tgt_done++;
				finish_init = 1;
			}
		} else {
			if (--ptgt->tgt_tmp_cnt <= 0) {
				ptgt->tgt_tmp_cnt = 0;
				finish_tgt = 1;

				if (do_finish_init) {
					finish_init = 1;
				}
			}
		}
		mutex_exit(&ptgt->tgt_mutex);
	} else {
		FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace,
		    FCP_BUF_LEVEL_2, 0,
		    "Call Finish Init for NO target");

		if (do_finish_init) {
			finish_init = 1;
		}
	}

	if (finish_tgt) {
		ASSERT(ptgt != NULL);

		mutex_enter(&ptgt->tgt_mutex);
#ifdef	DEBUG
		bzero(ptgt->tgt_tmp_cnt_stack,
		    sizeof (ptgt->tgt_tmp_cnt_stack));

		ptgt->tgt_tmp_cnt_depth = getpcstack(ptgt->tgt_tmp_cnt_stack,
		    FCP_STACK_DEPTH);
#endif /* DEBUG */
		mutex_exit(&ptgt->tgt_mutex);

		(void) fcp_finish_tgt(pptr, ptgt, lcount, tcount, cause);
	}

	if (finish_init && lcount == pptr->port_link_cnt) {
		ASSERT(pptr->port_tmp_cnt > 0);
		if (--pptr->port_tmp_cnt == 0) {
			fcp_finish_init(pptr);
		}
	} else if (lcount != pptr->port_link_cnt) {
		FCP_TRACE(fcp_logq, pptr->port_instbuf,
		    fcp_trace, FCP_BUF_LEVEL_2, 0,
		    "fcp_call_finish_init_held,1: state change occured"
		    " for D_ID=0x%x", (ptgt) ? ptgt->tgt_d_id : 0);
	}

	return (rval);
}


static void
fcp_reconfigure_luns(void * tgt_handle)
{
	uint32_t		dev_cnt;
	fc_portmap_t		*devlist;
	struct fcp_tgt	*ptgt = (struct fcp_tgt *)tgt_handle;
	struct fcp_port		*pptr = ptgt->tgt_port;

	/*
	 * If the timer that fires this off got canceled too late, the
	 * target could have been destroyed.
	 */

	if (ptgt->tgt_tid == NULL) {
		return;
	}

	devlist = kmem_zalloc(sizeof (*devlist), KM_NOSLEEP);
	if (devlist == NULL) {
		fcp_log(CE_WARN, pptr->port_dip,
		    "!fcp%d: failed to allocate for portmap",
		    pptr->port_instance);
		return;
	}

	dev_cnt = 1;
	devlist->map_pd = ptgt->tgt_pd_handle;
	devlist->map_hard_addr.hard_addr = ptgt->tgt_hard_addr;
	devlist->map_did.port_id = ptgt->tgt_d_id;

	bcopy(&ptgt->tgt_node_wwn.raw_wwn[0], &devlist->map_nwwn, FC_WWN_SIZE);
	bcopy(&ptgt->tgt_port_wwn.raw_wwn[0], &devlist->map_pwwn, FC_WWN_SIZE);

	devlist->map_state = PORT_DEVICE_LOGGED_IN;
	devlist->map_type = PORT_DEVICE_NEW;
	devlist->map_flags = 0;

	fcp_statec_callback(NULL, pptr->port_fp_handle, FC_STATE_DEVICE_CHANGE,
	    pptr->port_topology, devlist, dev_cnt, pptr->port_id);

	/*
	 * Clear the tgt_tid after no more references to
	 * the fcp_tgt
	 */
	mutex_enter(&ptgt->tgt_mutex);
	ptgt->tgt_tid = NULL;
	mutex_exit(&ptgt->tgt_mutex);

	kmem_free(devlist, sizeof (*devlist));
}


static void
fcp_free_targets(struct fcp_port *pptr)
{
	int			i;
	struct fcp_tgt	*ptgt;

	mutex_enter(&pptr->port_mutex);
	for (i = 0; i < FCP_NUM_HASH; i++) {
		ptgt = pptr->port_tgt_hash_table[i];
		while (ptgt != NULL) {
			struct fcp_tgt *next_tgt = ptgt->tgt_next;

			fcp_free_target(ptgt);
			ptgt = next_tgt;
		}
	}
	mutex_exit(&pptr->port_mutex);
}


static void
fcp_free_target(struct fcp_tgt *ptgt)
{
	struct fcp_lun	*plun;
	timeout_id_t		tid;

	mutex_enter(&ptgt->tgt_mutex);
	tid = ptgt->tgt_tid;

	/*
	 * Cancel any pending timeouts for this target.
	 */

	if (tid != NULL) {
		/*
		 * Set tgt_tid to NULL first to avoid a race in the callback.
		 * If tgt_tid is NULL, the callback will simply return.
		 */
		ptgt->tgt_tid = NULL;
		mutex_exit(&ptgt->tgt_mutex);
		(void) untimeout(tid);
		mutex_enter(&ptgt->tgt_mutex);
	}

	plun = ptgt->tgt_lun;
	while (plun != NULL) {
		struct fcp_lun *next_lun = plun->lun_next;

		fcp_dealloc_lun(plun);
		plun = next_lun;
	}

	mutex_exit(&ptgt->tgt_mutex);
	fcp_dealloc_tgt(ptgt);
}

/*
 *     Function: fcp_is_retryable
 *
 *  Description: Indicates if the internal packet is retryable.
 *
 *     Argument: *icmd		FCP internal packet.
 *
 * Return Value: 0	Not retryable
 *		 1	Retryable
 *
 *	Context: User, Kernel and Interrupt context
 */
static int
fcp_is_retryable(struct fcp_ipkt *icmd)
{
	if (icmd->ipkt_port->port_state & (FCP_STATE_SUSPENDED |
	    FCP_STATE_DETACHING | FCP_STATE_POWER_DOWN)) {
		return (0);
	}

	return (((fcp_watchdog_time + icmd->ipkt_fpkt->pkt_timeout) <
	    icmd->ipkt_port->port_deadline) ? 1 : 0);
}

/*
 *     Function: fcp_create_on_demand
 *
 *     Argument: *pptr		FCP port.
 *		 *pwwn		Port WWN.
 *
 * Return Value: 0	Success
 *		 EIO
 *		 ENOMEM
 *		 EBUSY
 *		 EINVAL
 *
 *	Context: User and Kernel context
 */
static int
fcp_create_on_demand(struct fcp_port *pptr, uchar_t *pwwn)
{
	int			wait_ms;
	int			tcount;
	int			lcount;
	int			ret;
	int			error;
	int			rval = EIO;
	int			ntries;
	fc_portmap_t		*devlist;
	opaque_t		pd;
	struct fcp_lun		*plun;
	struct fcp_tgt		*ptgt;
	int			old_manual = 0;

	/* Allocates the fc_portmap_t structure. */
	devlist = kmem_zalloc(sizeof (*devlist), KM_SLEEP);

	/*
	 * If FC_INVALID_RSCN_COUNT is non-zero, we will have to init as shown
	 * in the commented statement below:
	 *
	 * devlist->map_rscn_info.ulp_rscn_count = FC_INVALID_RSCN_COUNT;
	 *
	 * Below, the deadline for the discovery process is set.
	 */
	mutex_enter(&pptr->port_mutex);
	pptr->port_deadline = fcp_watchdog_time + FCP_ICMD_DEADLINE;
	mutex_exit(&pptr->port_mutex);

	/*
	 * We try to find the remote port based on the WWN provided by the
	 * caller.  We actually ask fp/fctl if it has it.
	 */
	pd = fc_ulp_get_remote_port(pptr->port_fp_handle,
	    (la_wwn_t *)pwwn, &error, 1);

	if (pd == NULL) {
		kmem_free(devlist, sizeof (*devlist));
		return (rval);
	}

	/*
	 * The remote port was found.  We ask fp/fctl to update our
	 * fc_portmap_t structure.
	 */
	ret = fc_ulp_pwwn_to_portmap(pptr->port_fp_handle,
	    (la_wwn_t *)pwwn, devlist);
	if (ret != FC_SUCCESS) {
		kmem_free(devlist, sizeof (*devlist));
		return (rval);
	}

	/*
	 * The map flag field is set to indicates that the creation is being
	 * done at the user request (Ioclt probably luxadm or cfgadm).
	 */
	devlist->map_type = PORT_DEVICE_USER_CREATE;

	mutex_enter(&pptr->port_mutex);

	/*
	 * We check to see if fcp already has a target that describes the
	 * device being created.  If not it is created.
	 */
	ptgt = fcp_lookup_target(pptr, pwwn);
	if (ptgt == NULL) {
		lcount = pptr->port_link_cnt;
		mutex_exit(&pptr->port_mutex);

		ptgt = fcp_alloc_tgt(pptr, devlist, lcount);
		if (ptgt == NULL) {
			fcp_log(CE_WARN, pptr->port_dip,
			    "!FC target allocation failed");
			return (ENOMEM);
		}

		mutex_enter(&pptr->port_mutex);
	}

	mutex_enter(&ptgt->tgt_mutex);
	ptgt->tgt_statec_cause = FCP_CAUSE_USER_CREATE;
	ptgt->tgt_tmp_cnt = 1;
	ptgt->tgt_device_created = 0;
	/*
	 * If fabric and auto config is set but the target was
	 * manually unconfigured then reset to the manual_config_only to
	 * 0 so the device will get configured.
	 */
	if (FC_TOP_EXTERNAL(pptr->port_topology) &&
	    fcp_enable_auto_configuration &&
	    ptgt->tgt_manual_config_only == 1) {
		old_manual = 1;
		ptgt->tgt_manual_config_only = 0;
	}
	mutex_exit(&ptgt->tgt_mutex);

	fcp_update_targets(pptr, devlist, 1,
	    FCP_LUN_BUSY | FCP_LUN_MARK, FCP_CAUSE_USER_CREATE);

	lcount = pptr->port_link_cnt;
	tcount = ptgt->tgt_change_cnt;

	if (fcp_handle_mapflags(pptr, ptgt, devlist, lcount,
	    tcount, FCP_CAUSE_USER_CREATE) == TRUE) {
		if (FC_TOP_EXTERNAL(pptr->port_topology) &&
		    fcp_enable_auto_configuration && old_manual) {
			mutex_enter(&ptgt->tgt_mutex);
			ptgt->tgt_manual_config_only = 1;
			mutex_exit(&ptgt->tgt_mutex);
		}

		if (pptr->port_link_cnt != lcount ||
		    ptgt->tgt_change_cnt != tcount) {
			rval = EBUSY;
		}
		mutex_exit(&pptr->port_mutex);

		FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace,
		    FCP_BUF_LEVEL_3, 0,
		    "fcp_create_on_demand: mapflags ptgt=%x, "
		    "lcount=%x::port_link_cnt=%x, "
		    "tcount=%x: tgt_change_cnt=%x, rval=%x",
		    ptgt, lcount, pptr->port_link_cnt,
		    tcount, ptgt->tgt_change_cnt, rval);
		return (rval);
	}

	/*
	 * Due to lack of synchronization mechanisms, we perform
	 * periodic monitoring of our request; Because requests
	 * get dropped when another one supercedes (either because
	 * of a link change or a target change), it is difficult to
	 * provide a clean synchronization mechanism (such as a
	 * semaphore or a conditional variable) without exhaustively
	 * rewriting the mainline discovery code of this driver.
	 */
	wait_ms = 500;

	ntries = fcp_max_target_retries;

	FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace,
	    FCP_BUF_LEVEL_3, 0,
	    "fcp_create_on_demand(1): ntries=%x, ptgt=%x, "
	    "lcount=%x::port_link_cnt=%x, "
	    "tcount=%x::tgt_change_cnt=%x, rval=%x, tgt_device_created=%x "
	    "tgt_tmp_cnt =%x",
	    ntries, ptgt, lcount, pptr->port_link_cnt,
	    tcount, ptgt->tgt_change_cnt, rval, ptgt->tgt_device_created,
	    ptgt->tgt_tmp_cnt);

	mutex_enter(&ptgt->tgt_mutex);
	while (ntries-- != 0 && pptr->port_link_cnt == lcount &&
	    ptgt->tgt_change_cnt == tcount && ptgt->tgt_device_created == 0) {
		mutex_exit(&ptgt->tgt_mutex);
		mutex_exit(&pptr->port_mutex);

		delay(drv_usectohz(wait_ms * 1000));

		mutex_enter(&pptr->port_mutex);
		mutex_enter(&ptgt->tgt_mutex);
	}


	if (pptr->port_link_cnt != lcount || ptgt->tgt_change_cnt != tcount) {
		rval = EBUSY;
	} else {
		if (ptgt->tgt_tmp_cnt == 0 && ptgt->tgt_node_state ==
		    FCP_TGT_NODE_PRESENT) {
			rval = 0;
		}
	}

	FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace,
	    FCP_BUF_LEVEL_3, 0,
	    "fcp_create_on_demand(2): ntries=%x, ptgt=%x, "
	    "lcount=%x::port_link_cnt=%x, "
	    "tcount=%x::tgt_change_cnt=%x, rval=%x, tgt_device_created=%x "
	    "tgt_tmp_cnt =%x",
	    ntries, ptgt, lcount, pptr->port_link_cnt,
	    tcount, ptgt->tgt_change_cnt, rval, ptgt->tgt_device_created,
	    ptgt->tgt_tmp_cnt);

	if (rval) {
		if (FC_TOP_EXTERNAL(pptr->port_topology) &&
		    fcp_enable_auto_configuration && old_manual) {
			ptgt->tgt_manual_config_only = 1;
		}
		mutex_exit(&ptgt->tgt_mutex);
		mutex_exit(&pptr->port_mutex);
		kmem_free(devlist, sizeof (*devlist));

		FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace,
		    FCP_BUF_LEVEL_3, 0,
		    "fcp_create_on_demand(3): ntries=%x, ptgt=%x, "
		    "lcount=%x::port_link_cnt=%x, "
		    "tcount=%x::tgt_change_cnt=%x, rval=%x, "
		    "tgt_device_created=%x, tgt D_ID=%x",
		    ntries, ptgt, lcount, pptr->port_link_cnt,
		    tcount, ptgt->tgt_change_cnt, rval,
		    ptgt->tgt_device_created, ptgt->tgt_d_id);
		return (rval);
	}

	if ((plun = ptgt->tgt_lun) != NULL) {
		tcount = plun->lun_tgt->tgt_change_cnt;
	} else {
		rval = EINVAL;
	}
	lcount = pptr->port_link_cnt;

	/*
	 * Configuring the target with no LUNs will fail. We
	 * should reset the node state so that it is not
	 * automatically configured when the LUNs are added
	 * to this target.
	 */
	if (ptgt->tgt_lun_cnt == 0) {
		ptgt->tgt_node_state = FCP_TGT_NODE_NONE;
	}
	mutex_exit(&ptgt->tgt_mutex);
	mutex_exit(&pptr->port_mutex);

	while (plun) {
		child_info_t	*cip;

		mutex_enter(&plun->lun_mutex);
		cip = plun->lun_cip;
		mutex_exit(&plun->lun_mutex);

		mutex_enter(&ptgt->tgt_mutex);
		if (!(plun->lun_state & FCP_LUN_OFFLINE)) {
			mutex_exit(&ptgt->tgt_mutex);

			rval = fcp_pass_to_hp_and_wait(pptr, plun, cip,
			    FCP_ONLINE, lcount, tcount,
			    NDI_ONLINE_ATTACH);
			if (rval != NDI_SUCCESS) {
				FCP_TRACE(fcp_logq,
				    pptr->port_instbuf, fcp_trace,
				    FCP_BUF_LEVEL_3, 0,
				    "fcp_create_on_demand: "
				    "pass_to_hp_and_wait failed "
				    "rval=%x", rval);
				rval = EIO;
			} else {
				mutex_enter(&LUN_TGT->tgt_mutex);
				plun->lun_state &= ~(FCP_LUN_OFFLINE |
				    FCP_LUN_BUSY);
				mutex_exit(&LUN_TGT->tgt_mutex);
			}
			mutex_enter(&ptgt->tgt_mutex);
		}

		plun = plun->lun_next;
		mutex_exit(&ptgt->tgt_mutex);
	}

	kmem_free(devlist, sizeof (*devlist));

	if (FC_TOP_EXTERNAL(pptr->port_topology) &&
	    fcp_enable_auto_configuration && old_manual) {
		mutex_enter(&ptgt->tgt_mutex);
		/* if successful then set manual to 0 */
		if (rval == 0) {
			ptgt->tgt_manual_config_only = 0;
		} else {
			/* reset to 1 so the user has to do the config */
			ptgt->tgt_manual_config_only = 1;
		}
		mutex_exit(&ptgt->tgt_mutex);
	}

	return (rval);
}


static void
fcp_ascii_to_wwn(caddr_t string, uchar_t bytes[], unsigned int byte_len)
{
	int		count;
	uchar_t		byte;

	count = 0;
	while (*string) {
		byte = FCP_ATOB(*string); string++;
		byte = byte << 4 | FCP_ATOB(*string); string++;
		bytes[count++] = byte;

		if (count >= byte_len) {
			break;
		}
	}
}

static void
fcp_wwn_to_ascii(uchar_t wwn[], char *string)
{
	int		i;

	for (i = 0; i < FC_WWN_SIZE; i++) {
		(void) sprintf(string + (i * 2),
		    "%02x", wwn[i]);
	}

}

static void
fcp_print_error(fc_packet_t *fpkt)
{
	struct fcp_ipkt	*icmd = (struct fcp_ipkt *)
	    fpkt->pkt_ulp_private;
	struct fcp_port	*pptr;
	struct fcp_tgt	*ptgt;
	struct fcp_lun	*plun;
	caddr_t			buf;
	int			scsi_cmd = 0;

	ptgt = icmd->ipkt_tgt;
	plun = icmd->ipkt_lun;
	pptr = ptgt->tgt_port;

	buf = kmem_zalloc(256, KM_NOSLEEP);
	if (buf == NULL) {
		return;
	}

	switch (icmd->ipkt_opcode) {
	case SCMD_REPORT_LUN:
		(void) sprintf(buf, "!REPORT LUN to D_ID=0x%%x"
		    " lun=0x%%x failed");
		scsi_cmd++;
		break;

	case SCMD_INQUIRY_PAGE83:
		(void) sprintf(buf, "!INQUIRY-83 to D_ID=0x%%x"
		    " lun=0x%%x failed");
		scsi_cmd++;
		break;

	case SCMD_INQUIRY:
		(void) sprintf(buf, "!INQUIRY to D_ID=0x%%x"
		    " lun=0x%%x failed");
		scsi_cmd++;
		break;

	case LA_ELS_PLOGI:
		(void) sprintf(buf, "!PLOGI to D_ID=0x%%x failed");
		break;

	case LA_ELS_PRLI:
		(void) sprintf(buf, "!PRLI to D_ID=0x%%x failed");
		break;
	}

	if (scsi_cmd && fpkt->pkt_state == FC_PKT_SUCCESS) {
		struct fcp_rsp		response, *rsp;
		uchar_t			asc, ascq;
		caddr_t			sense_key = NULL;
		struct fcp_rsp_info	fcp_rsp_err, *bep;

		if (icmd->ipkt_nodma) {
			rsp = (struct fcp_rsp *)fpkt->pkt_resp;
			bep = (struct fcp_rsp_info *)((caddr_t)rsp +
			    sizeof (struct fcp_rsp));
		} else {
			rsp = &response;
			bep = &fcp_rsp_err;

			FCP_CP_IN(fpkt->pkt_resp, rsp, fpkt->pkt_resp_acc,
			    sizeof (struct fcp_rsp));

			FCP_CP_IN(fpkt->pkt_resp + sizeof (struct fcp_rsp),
			    bep, fpkt->pkt_resp_acc,
			    sizeof (struct fcp_rsp_info));
		}


		if (fcp_validate_fcp_response(rsp, pptr) != FC_SUCCESS) {
			(void) sprintf(buf + strlen(buf),
			    " : Bad FCP response values rsvd1=%%x, rsvd2=%%x,"
			    " sts-rsvd1=%%x, sts-rsvd2=%%x, rsplen=%%x,"
			    " senselen=%%x. Giving up");

			fcp_log(CE_WARN, pptr->port_dip, buf,
			    ptgt->tgt_d_id, plun->lun_num, rsp->reserved_0,
			    rsp->reserved_1, rsp->fcp_u.fcp_status.reserved_0,
			    rsp->fcp_u.fcp_status.reserved_1,
			    rsp->fcp_response_len, rsp->fcp_sense_len);

			kmem_free(buf, 256);
			return;
		}

		if (rsp->fcp_u.fcp_status.rsp_len_set &&
		    bep->rsp_code != FCP_NO_FAILURE) {
			(void) sprintf(buf + strlen(buf),
			    " FCP Response code = 0x%x", bep->rsp_code);
		}

		if (rsp->fcp_u.fcp_status.scsi_status & STATUS_CHECK) {
			struct scsi_extended_sense sense_info, *sense_ptr;

			if (icmd->ipkt_nodma) {
				sense_ptr = (struct scsi_extended_sense *)
				    ((caddr_t)fpkt->pkt_resp +
				    sizeof (struct fcp_rsp) +
				    rsp->fcp_response_len);
			} else {
				sense_ptr = &sense_info;

				FCP_CP_IN(fpkt->pkt_resp +
				    sizeof (struct fcp_rsp) +
				    rsp->fcp_response_len, &sense_info,
				    fpkt->pkt_resp_acc,
				    sizeof (struct scsi_extended_sense));
			}

			if (sense_ptr->es_key < NUM_SENSE_KEYS +
			    NUM_IMPL_SENSE_KEYS) {
				sense_key = sense_keys[sense_ptr->es_key];
			} else {
				sense_key = "Undefined";
			}

			asc = sense_ptr->es_add_code;
			ascq = sense_ptr->es_qual_code;

			(void) sprintf(buf + strlen(buf),
			    ": sense key=%%s, ASC=%%x," " ASCQ=%%x."
			    " Giving up");

			fcp_log(CE_WARN, pptr->port_dip, buf,
			    ptgt->tgt_d_id, plun->lun_num, sense_key,
			    asc, ascq);
		} else {
			(void) sprintf(buf + strlen(buf),
			    " : SCSI status=%%x. Giving up");

			fcp_log(CE_WARN, pptr->port_dip, buf,
			    ptgt->tgt_d_id, plun->lun_num,
			    rsp->fcp_u.fcp_status.scsi_status);
		}
	} else {
		caddr_t state, reason, action, expln;

		(void) fc_ulp_pkt_error(fpkt, &state, &reason,
		    &action, &expln);

		(void) sprintf(buf + strlen(buf), ": State:%%s,"
		    " Reason:%%s. Giving up");

		if (scsi_cmd) {
			fcp_log(CE_WARN, pptr->port_dip, buf,
			    ptgt->tgt_d_id, plun->lun_num, state, reason);
		} else {
			fcp_log(CE_WARN, pptr->port_dip, buf,
			    ptgt->tgt_d_id, state, reason);
		}
	}

	kmem_free(buf, 256);
}


static int
fcp_handle_ipkt_errors(struct fcp_port *pptr, struct fcp_tgt *ptgt,
    struct fcp_ipkt *icmd, int rval, caddr_t op)
{
	int	ret = DDI_FAILURE;
	char	*error;

	switch (rval) {
	case FC_DEVICE_BUSY_NEW_RSCN:
		/*
		 * This means that there was a new RSCN that the transport
		 * knows about (which the ULP *may* know about too) but the
		 * pkt that was sent down was related to an older RSCN. So, we
		 * are just going to reset the retry count and deadline and
		 * continue to retry. The idea is that transport is currently
		 * working on the new RSCN and will soon let the ULPs know
		 * about it and when it does the existing logic will kick in
		 * where it will change the tcount to indicate that something
		 * changed on the target. So, rediscovery will start and there
		 * will not be an infinite retry.
		 *
		 * For a full flow of how the RSCN info is transferred back and
		 * forth, see fp.c
		 */
		icmd->ipkt_retries = 0;
		icmd->ipkt_port->port_deadline = fcp_watchdog_time +
		    FCP_ICMD_DEADLINE;

		FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace,
		    FCP_BUF_LEVEL_3, 0,
		    "fcp_handle_ipkt_errors: rval=%x  for D_ID=%x",
		    rval, ptgt->tgt_d_id);
		/* FALLTHROUGH */

	case FC_STATEC_BUSY:
	case FC_DEVICE_BUSY:
	case FC_PBUSY:
	case FC_FBUSY:
	case FC_TRAN_BUSY:
	case FC_OFFLINE:
		FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace,
		    FCP_BUF_LEVEL_3, 0,
		    "fcp_handle_ipkt_errors: rval=%x  for D_ID=%x",
		    rval, ptgt->tgt_d_id);
		if (icmd->ipkt_retries < FCP_MAX_RETRIES &&
		    fcp_is_retryable(icmd)) {
			fcp_queue_ipkt(pptr, icmd->ipkt_fpkt);
			ret = DDI_SUCCESS;
		}
		break;

	case FC_LOGINREQ:
		/*
		 * FC_LOGINREQ used to be handled just like all the cases
		 * above. It has been changed to handled a PRLI that fails
		 * with FC_LOGINREQ different than other ipkts that fail
		 * with FC_LOGINREQ. If a PRLI fails with FC_LOGINREQ it is
		 * a simple matter to turn it into a PLOGI instead, so that's
		 * exactly what we do here.
		 */
		if (icmd->ipkt_opcode == LA_ELS_PRLI) {
			ret = fcp_send_els(icmd->ipkt_port, icmd->ipkt_tgt,
			    icmd, LA_ELS_PLOGI, icmd->ipkt_link_cnt,
			    icmd->ipkt_change_cnt, icmd->ipkt_cause);
		} else {
			FCP_TRACE(fcp_logq, pptr->port_instbuf, fcp_trace,
			    FCP_BUF_LEVEL_3, 0,
			    "fcp_handle_ipkt_errors: rval=%x  for D_ID=%x",
			    rval, ptgt->tgt_d_id);
			if (icmd->ipkt_retries < FCP_MAX_RETRIES &&
			    fcp_is_retryable(icmd)) {
				fcp_queue_ipkt(pptr, icmd->ipkt_fpkt);
				ret = DDI_SUCCESS;
			}
		}
		break;

	default:
		mutex_enter(&pptr->port_mutex);
		mutex_enter(&ptgt->tgt_mutex);
		if (!FCP_STATE_CHANGED(pptr, ptgt, icmd)) {
			mutex_exit(&ptgt->tgt_mutex);
			mutex_exit(&pptr->port_mutex);

			(void) fc_ulp_error(rval, &error);
			fcp_log(CE_WARN, pptr->port_dip,
			    "!Failed to send %s to D_ID=%x error=%s",
			    op, ptgt->tgt_d_id, error);
		} else {
			FCP_TRACE(fcp_logq, pptr->port_instbuf,
			    fcp_trace, FCP_BUF_LEVEL_2, 0,
			    "fcp_handle_ipkt_errors,1: state change occured"
			    " for D_ID=0x%x", ptgt->tgt_d_id);
			mutex_exit(&ptgt->tgt_mutex);
			mutex_exit(&pptr->port_mutex);
		}
		break;
	}

	return (ret);
}


/*
 * Check of outstanding commands on any LUN for this target
 */
static int
fcp_outstanding_lun_cmds(struct fcp_tgt *ptgt)
{
	struct	fcp_lun	*plun;
	struct	fcp_pkt	*cmd;

	for (plun = ptgt->tgt_lun; plun != NULL; plun = plun->lun_next) {
		mutex_enter(&plun->lun_mutex);
		for (cmd = plun->lun_pkt_head; cmd != NULL;
		    cmd = cmd->cmd_forw) {
			if (cmd->cmd_state == FCP_PKT_ISSUED) {
				mutex_exit(&plun->lun_mutex);
				return (FC_SUCCESS);
			}
		}
		mutex_exit(&plun->lun_mutex);
	}

	return (FC_FAILURE);
}

static fc_portmap_t *
fcp_construct_map(struct fcp_port *pptr, uint32_t *dev_cnt)
{
	int			i;
	fc_portmap_t		*devlist;
	fc_portmap_t		*devptr = NULL;
	struct fcp_tgt	*ptgt;

	mutex_enter(&pptr->port_mutex);
	for (i = 0, *dev_cnt = 0; i < FCP_NUM_HASH; i++) {
		for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL;
		    ptgt = ptgt->tgt_next) {
			if (!(ptgt->tgt_state & FCP_TGT_ORPHAN)) {
				++*dev_cnt;
			}
		}
	}

	devptr = devlist = kmem_zalloc(sizeof (*devlist) * *dev_cnt,
	    KM_NOSLEEP);
	if (devlist == NULL) {
		mutex_exit(&pptr->port_mutex);
		fcp_log(CE_WARN, pptr->port_dip,
		    "!fcp%d: failed to allocate for portmap for construct map",
		    pptr->port_instance);
		return (devptr);
	}

	for (i = 0; i < FCP_NUM_HASH; i++) {
		for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL;
		    ptgt = ptgt->tgt_next) {
			if (!(ptgt->tgt_state & FCP_TGT_ORPHAN)) {
				int ret;

				ret = fc_ulp_pwwn_to_portmap(
				    pptr->port_fp_handle,
				    (la_wwn_t *)&ptgt->tgt_port_wwn.raw_wwn[0],
				    devlist);

				if (ret == FC_SUCCESS) {
					devlist++;
					continue;
				}

				devlist->map_pd = NULL;
				devlist->map_did.port_id = ptgt->tgt_d_id;
				devlist->map_hard_addr.hard_addr =
				    ptgt->tgt_hard_addr;

				devlist->map_state = PORT_DEVICE_INVALID;
				devlist->map_type = PORT_DEVICE_OLD;

				bcopy(&ptgt->tgt_node_wwn.raw_wwn[0],
				    &devlist->map_nwwn, FC_WWN_SIZE);

				bcopy(&ptgt->tgt_port_wwn.raw_wwn[0],
				    &devlist->map_pwwn, FC_WWN_SIZE);

				devlist++;
			}
		}
	}

	mutex_exit(&pptr->port_mutex);

	return (devptr);
}
/*
 * Inimate MPxIO that the lun is busy and cannot accept regular IO
 */
static void
fcp_update_mpxio_path_verifybusy(struct fcp_port *pptr)
{
	int i;
	struct fcp_tgt	*ptgt;
	struct fcp_lun	*plun;

	for (i = 0; i < FCP_NUM_HASH; i++) {
		for (ptgt = pptr->port_tgt_hash_table[i]; ptgt != NULL;
		    ptgt = ptgt->tgt_next) {
			mutex_enter(&ptgt->tgt_mutex);
			for (plun = ptgt->tgt_lun; plun != NULL;
			    plun = plun->lun_next) {
				if (plun->lun_mpxio &&
				    plun->lun_state & FCP_LUN_BUSY) {
					if (!fcp_pass_to_hp(pptr, plun,
					    plun->lun_cip,
					    FCP_MPXIO_PATH_SET_BUSY,
					    pptr->port_link_cnt,
					    ptgt->tgt_change_cnt, 0, 0)) {
						FCP_TRACE(fcp_logq,
						    pptr->port_instbuf,
						    fcp_trace,
						    FCP_BUF_LEVEL_2, 0,
						    "path_verifybusy: "
						    "disable lun %p failed!",
						    plun);
					}
				}
			}
			mutex_exit(&ptgt->tgt_mutex);
		}
	}
}

static int
fcp_update_mpxio_path(struct fcp_lun *plun, child_info_t *cip, int what)
{
	dev_info_t		*cdip = NULL;
	dev_info_t		*pdip = NULL;

	ASSERT(plun);

	mutex_enter(&plun->lun_mutex);
	if (fcp_is_child_present(plun, cip) == FC_FAILURE) {
		mutex_exit(&plun->lun_mutex);
		return (NDI_FAILURE);
	}
	mutex_exit(&plun->lun_mutex);
	cdip = mdi_pi_get_client(PIP(cip));
	pdip = mdi_pi_get_phci(PIP(cip));

	ASSERT(cdip != NULL);
	ASSERT(pdip != NULL);

	if (what == FCP_MPXIO_PATH_CLEAR_BUSY) {
		/* LUN ready for IO */
		(void) mdi_pi_enable_path(PIP(cip), DRIVER_DISABLE_TRANSIENT);
	} else {
		/* LUN busy to accept IO */
		(void) mdi_pi_disable_path(PIP(cip), DRIVER_DISABLE_TRANSIENT);
	}
	return (NDI_SUCCESS);
}

/*
 * Caller must free the returned string of MAXPATHLEN len
 * If the device is offline (-1 instance number) NULL
 * will be returned.
 */
static char *
fcp_get_lun_path(struct fcp_lun *plun) {
	dev_info_t	*dip = NULL;
	char	*path = NULL;
	if (plun == NULL) {
		return (NULL);
	}
	if (plun->lun_mpxio == 0) {
		dip = DIP(plun->lun_cip);
	} else {
		dip = mdi_pi_get_client(PIP(plun->lun_cip));
	}
	if (dip == NULL) {
		return (NULL);
	}
	if (ddi_get_instance(dip) < 0) {
		return (NULL);
	}
	path = kmem_alloc(MAXPATHLEN, KM_SLEEP);
	if (path == NULL) {
		return (NULL);
	}

	(void) ddi_pathname(dip, path);
	/*
	 * In reality, the user wants a fully valid path (one they can open)
	 * but this string is lacking the mount point, and the minor node.
	 * It would be nice if we could "figure these out" somehow
	 * and fill them in.  Otherwise, the userland code has to understand
	 * driver specific details of which minor node is the "best" or
	 * "right" one to expose.  (Ex: which slice is the whole disk, or
	 * which tape doesn't rewind)
	 */
	return (path);
}

static int
fcp_scsi_bus_config(dev_info_t *parent, uint_t flag,
    ddi_bus_config_op_t op, void *arg, dev_info_t **childp)
{
	int64_t reset_delay;
	int rval, retry = 0;
	struct fcp_port *pptr = fcp_dip2port(parent);

	reset_delay = (int64_t)(USEC_TO_TICK(FCP_INIT_WAIT_TIMEOUT)) -
	    (lbolt64 - pptr->port_attach_time);
	if (reset_delay < 0) {
		reset_delay = 0;
	}

	if (fcp_bus_config_debug) {
		flag |= NDI_DEVI_DEBUG;
	}

	switch (op) {
	case BUS_CONFIG_ONE:
		/*
		 * Retry the command since we need to ensure
		 * the fabric devices are available for root
		 */
		while (retry++ < fcp_max_bus_config_retries) {
			rval =	(ndi_busop_bus_config(parent,
			    flag | NDI_MDI_FALLBACK, op,
			    arg, childp, (clock_t)reset_delay));
			if (rval == 0) {
				return (rval);
			}
		}

		/*
		 * drain taskq to make sure nodes are created and then
		 * try again.
		 */
		taskq_wait(DEVI(parent)->devi_taskq);
		return (ndi_busop_bus_config(parent, flag | NDI_MDI_FALLBACK,
		    op, arg, childp, 0));

	case BUS_CONFIG_DRIVER:
	case BUS_CONFIG_ALL: {
		/*
		 * delay till all devices report in (port_tmp_cnt == 0)
		 * or FCP_INIT_WAIT_TIMEOUT
		 */
		mutex_enter(&pptr->port_mutex);
		while ((reset_delay > 0) && pptr->port_tmp_cnt) {
			(void) cv_timedwait(&pptr->port_config_cv,
			    &pptr->port_mutex,
			    ddi_get_lbolt() + (clock_t)reset_delay);
			reset_delay =
			    (int64_t)(USEC_TO_TICK(FCP_INIT_WAIT_TIMEOUT)) -
			    (lbolt64 - pptr->port_attach_time);
		}
		mutex_exit(&pptr->port_mutex);
		/* drain taskq to make sure nodes are created */
		taskq_wait(DEVI(parent)->devi_taskq);
		return (ndi_busop_bus_config(parent, flag, op,
		    arg, childp, 0));
	}

	default:
		return (NDI_FAILURE);
	}
	/*NOTREACHED*/
}

static int
fcp_scsi_bus_unconfig(dev_info_t *parent, uint_t flag,
    ddi_bus_config_op_t op, void *arg)
{
	if (fcp_bus_config_debug) {
		flag |= NDI_DEVI_DEBUG;
	}

	return (ndi_busop_bus_unconfig(parent, flag, op, arg));
}


/*
 * Routine to copy GUID into the lun structure.
 * returns 0 if copy was successful and 1 if encountered a
 * failure and did not copy the guid.
 */
static int
fcp_copy_guid_2_lun_block(struct fcp_lun *plun, char *guidp)
{

	int retval = 0;

	/* add one for the null terminator */
	const unsigned int len = strlen(guidp) + 1;

	if ((guidp == NULL) || (plun == NULL)) {
		return (1);
	}

	/*
	 * if the plun->lun_guid already has been allocated,
	 * then check the size. if the size is exact, reuse
	 * it....if not free it an allocate the required size.
	 * The reallocation should NOT typically happen
	 * unless the GUIDs reported changes between passes.
	 * We free up and alloc again even if the
	 * size was more than required. This is due to the
	 * fact that the field lun_guid_size - serves
	 * dual role of indicating the size of the wwn
	 * size and ALSO the allocation size.
	 */
	if (plun->lun_guid) {
		if (plun->lun_guid_size != len) {
			/*
			 * free the allocated memory and
			 * initialize the field
			 * lun_guid_size to 0.
			 */
			kmem_free(plun->lun_guid, plun->lun_guid_size);
			plun->lun_guid = NULL;
			plun->lun_guid_size = 0;
		}
	}
	/*
	 * alloc only if not already done.
	 */
	if (plun->lun_guid == NULL) {
		plun->lun_guid = kmem_zalloc(len, KM_NOSLEEP);
		if (plun->lun_guid == NULL) {
			cmn_err(CE_WARN, "fcp_copy_guid_2_lun_block:"
			    "Unable to allocate"
			    "Memory for GUID!!! size %d", len);
			retval = 1;
		} else {
			plun->lun_guid_size = len;
		}
	}
	if (plun->lun_guid) {
		/*
		 * now copy the GUID
		 */
		bcopy(guidp, plun->lun_guid, plun->lun_guid_size);
	}
	return (retval);
}

/*
 * fcp_reconfig_wait
 *
 * Wait for a rediscovery/reconfiguration to complete before continuing.
 */

static void
fcp_reconfig_wait(struct fcp_port *pptr)
{
	clock_t		reconfig_start, wait_timeout;

	/*
	 * Quick check.	 If pptr->port_tmp_cnt is 0, there is no
	 * reconfiguration in progress.
	 */

	mutex_enter(&pptr->port_mutex);
	if (pptr->port_tmp_cnt == 0) {
		mutex_exit(&pptr->port_mutex);
		return;
	}
	mutex_exit(&pptr->port_mutex);

	/*
	 * If we cause a reconfig by raising power, delay until all devices
	 * report in (port_tmp_cnt returns to 0)
	 */

	reconfig_start = ddi_get_lbolt();
	wait_timeout = drv_usectohz(FCP_INIT_WAIT_TIMEOUT);

	mutex_enter(&pptr->port_mutex);

	while (((ddi_get_lbolt() - reconfig_start) < wait_timeout) &&
	    pptr->port_tmp_cnt) {

		(void) cv_timedwait(&pptr->port_config_cv, &pptr->port_mutex,
		    reconfig_start + wait_timeout);
	}

	mutex_exit(&pptr->port_mutex);

	/*
	 * Even if fcp_tmp_count isn't 0, continue without error.  The port
	 * we want may still be ok.  If not, it will error out later
	 */
}

/*
 * Read masking info from fp.conf and construct the global fcp_lun_blacklist.
 * We rely on the fcp_global_mutex to provide protection against changes to
 * the fcp_lun_blacklist.
 *
 * You can describe a list of target port WWNs and LUN numbers which will
 * not be configured. LUN numbers will be interpreted as decimal. White
 * spaces and ',' can be used in the list of LUN numbers.
 *
 * To prevent LUNs 1 and 2 from being configured for target
 * port 510000f010fd92a1 and target port 510000e012079df1, set:
 *
 * pwwn-lun-blacklist=
 * "510000f010fd92a1,1,2",
 * "510000e012079df1,1,2";
 */
static void
fcp_read_blacklist(dev_info_t *dip,
    struct fcp_black_list_entry **pplun_blacklist) {
	char **prop_array	= NULL;
	char *curr_pwwn		= NULL;
	char *curr_lun		= NULL;
	uint32_t prop_item	= 0;
	int idx			= 0;
	int len			= 0;

	ASSERT(mutex_owned(&fcp_global_mutex));
	if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, dip,
	    DDI_PROP_DONTPASS | DDI_PROP_NOTPROM,
	    LUN_BLACKLIST_PROP, &prop_array, &prop_item) != DDI_PROP_SUCCESS) {
		return;
	}

	for (idx = 0; idx < prop_item; idx++) {

		curr_pwwn = prop_array[idx];
		while (*curr_pwwn == ' ') {
			curr_pwwn++;
		}
		if (strlen(curr_pwwn) <= (sizeof (la_wwn_t) * 2 + 1)) {
			fcp_log(CE_WARN, NULL, "Invalid WWN %s in the blacklist"
			    ", please check.", curr_pwwn);
			continue;
		}
		if ((*(curr_pwwn + sizeof (la_wwn_t) * 2) != ' ') &&
		    (*(curr_pwwn + sizeof (la_wwn_t) * 2) != ',')) {
			fcp_log(CE_WARN, NULL, "Invalid WWN %s in the blacklist"
			    ", please check.", curr_pwwn);
			continue;
		}
		for (len = 0; len < sizeof (la_wwn_t) * 2; len++) {
			if (isxdigit(curr_pwwn[len]) != TRUE) {
				fcp_log(CE_WARN, NULL, "Invalid WWN %s in the "
				    "blacklist, please check.", curr_pwwn);
				break;
			}
		}
		if (len != sizeof (la_wwn_t) * 2) {
			continue;
		}

		curr_lun = curr_pwwn + sizeof (la_wwn_t) * 2 + 1;
		*(curr_lun - 1) = '\0';
		fcp_mask_pwwn_lun(curr_pwwn, curr_lun, pplun_blacklist);
	}

	ddi_prop_free(prop_array);
}

/*
 * Get the masking info about one remote target port designated by wwn.
 * Lun ids could be separated by ',' or white spaces.
 */
static void
fcp_mask_pwwn_lun(char *curr_pwwn, char *curr_lun,
    struct fcp_black_list_entry **pplun_blacklist) {
	int		idx			= 0;
	uint32_t	offset			= 0;
	unsigned long	lun_id			= 0;
	char		lunid_buf[16];
	char		*pend			= NULL;
	int		illegal_digit		= 0;

	while (offset < strlen(curr_lun)) {
		while ((curr_lun[offset + idx] != ',') &&
		    (curr_lun[offset + idx] != '\0') &&
		    (curr_lun[offset + idx] != ' ')) {
			if (isdigit(curr_lun[offset + idx]) == 0) {
				illegal_digit++;
			}
			idx++;
		}
		if (illegal_digit > 0) {
			offset += (idx+1);	/* To the start of next lun */
			idx = 0;
			illegal_digit = 0;
			fcp_log(CE_WARN, NULL, "Invalid LUN %s for WWN %s in "
			    "the blacklist, please check digits.",
			    curr_lun, curr_pwwn);
			continue;
		}
		if (idx >= (sizeof (lunid_buf) / sizeof (lunid_buf[0]))) {
			fcp_log(CE_WARN, NULL, "Invalid LUN %s for WWN %s in "
			    "the blacklist, please check the length of LUN#.",
			    curr_lun, curr_pwwn);
			break;
		}
		if (idx == 0) {	/* ignore ' ' or ',' or '\0' */
		    offset++;
		    continue;
		}

		bcopy(curr_lun + offset, lunid_buf, idx);
		lunid_buf[idx] = '\0';
		if (ddi_strtoul(lunid_buf, &pend, 10, &lun_id) == 0) {
			fcp_add_one_mask(curr_pwwn, lun_id, pplun_blacklist);
		} else {
			fcp_log(CE_WARN, NULL, "Invalid LUN %s for WWN %s in "
			    "the blacklist, please check %s.",
			    curr_lun, curr_pwwn, lunid_buf);
		}
		offset += (idx+1);	/* To the start of next lun */
		idx = 0;
	}
}

/*
 * Add one masking record
 */
static void
fcp_add_one_mask(char *curr_pwwn, uint32_t lun_id,
    struct fcp_black_list_entry **pplun_blacklist) {
	struct fcp_black_list_entry	*tmp_entry	= *pplun_blacklist;
	struct fcp_black_list_entry	*new_entry	= NULL;
	la_wwn_t			wwn;

	fcp_ascii_to_wwn(curr_pwwn, wwn.raw_wwn, sizeof (la_wwn_t));
	while (tmp_entry) {
		if ((bcmp(&tmp_entry->wwn, &wwn,
		    sizeof (la_wwn_t)) == 0) && (tmp_entry->lun == lun_id)) {
			return;
		}

		tmp_entry = tmp_entry->next;
	}

	/* add to black list */
	new_entry = (struct fcp_black_list_entry *)kmem_zalloc
	    (sizeof (struct fcp_black_list_entry), KM_SLEEP);
	bcopy(&wwn, &new_entry->wwn, sizeof (la_wwn_t));
	new_entry->lun = lun_id;
	new_entry->masked = 0;
	new_entry->next = *pplun_blacklist;
	*pplun_blacklist = new_entry;
}

/*
 * Check if we should mask the specified lun of this fcp_tgt
 */
static int
fcp_should_mask(la_wwn_t *wwn, uint32_t lun_id) {
	struct fcp_black_list_entry *remote_port;

	remote_port = fcp_lun_blacklist;
	while (remote_port != NULL) {
		if (bcmp(wwn, &remote_port->wwn, sizeof (la_wwn_t)) == 0) {
			if (remote_port->lun == lun_id) {
				remote_port->masked++;
				if (remote_port->masked == 1) {
					fcp_log(CE_NOTE, NULL, "LUN %d of port "
					    "%02x%02x%02x%02x%02x%02x%02x%02x "
					    "is masked due to black listing.\n",
					    lun_id, wwn->raw_wwn[0],
					    wwn->raw_wwn[1], wwn->raw_wwn[2],
					    wwn->raw_wwn[3], wwn->raw_wwn[4],
					    wwn->raw_wwn[5], wwn->raw_wwn[6],
					    wwn->raw_wwn[7]);
				}
				return (TRUE);
			}
		}
		remote_port = remote_port->next;
	}
	return (FALSE);
}

/*
 * Release all allocated resources
 */
static void
fcp_cleanup_blacklist(struct fcp_black_list_entry **pplun_blacklist) {
	struct fcp_black_list_entry	*tmp_entry	= *pplun_blacklist;
	struct fcp_black_list_entry	*current_entry	= NULL;

	ASSERT(mutex_owned(&fcp_global_mutex));
	/*
	 * Traverse all luns
	 */
	while (tmp_entry) {
		current_entry = tmp_entry;
		tmp_entry = tmp_entry->next;
		kmem_free(current_entry, sizeof (struct fcp_black_list_entry));
	}
	*pplun_blacklist = NULL;
}