Mercurial > illumos > illumos-gate
changeset 4851:5e98cf4c2164
PSARC 2006/708 ddi-forceload
PSARC 2006/710 scsi_get_device_type_string
PSARC 2007/379 MPXIO/scsi_vhci failover-ops configuration - revisited
6504975 ddi_modopen should allow open in subdirectory
6542649 step0: opensource mpxio
line wrap: on
line diff
--- a/usr/src/pkgdefs/SUNWckr/prototype_i386 Fri Aug 10 11:19:25 2007 -0700 +++ b/usr/src/pkgdefs/SUNWckr/prototype_i386 Fri Aug 10 17:00:37 2007 -0700 @@ -163,6 +163,13 @@ d none boot/acpi/tables 755 root sys f none kernel/mac/mac_ether 755 root sys f none kernel/mac/mac_wifi 755 root sys +d none kernel/misc/scsi_vhci 755 root sys +f none kernel/misc/scsi_vhci/scsi_vhci_f_asym_emc 755 root sys +f none kernel/misc/scsi_vhci/scsi_vhci_f_asym_lsi 755 root sys +f none kernel/misc/scsi_vhci/scsi_vhci_f_asym_sun 755 root sys +f none kernel/misc/scsi_vhci/scsi_vhci_f_sym 755 root sys +f none kernel/misc/scsi_vhci/scsi_vhci_f_sym_emc 755 root sys +f none kernel/misc/scsi_vhci/scsi_vhci_f_tpgs 755 root sys f none kernel/misc/acpica 755 root sys f none kernel/misc/agpmaster 755 root sys f none kernel/misc/bootdev 755 root sys @@ -352,6 +359,14 @@ d none kernel/mac/amd64 755 root sys f none kernel/mac/amd64/mac_ether 755 root sys f none kernel/mac/amd64/mac_wifi 755 root sys +d none kernel/misc/scsi_vhci/amd64 755 root sys +f none kernel/misc/scsi_vhci/amd64/scsi_vhci_f_asym_emc 755 root sys +f none kernel/misc/scsi_vhci/amd64/scsi_vhci_f_asym_lsi 755 root sys +f none kernel/misc/scsi_vhci/amd64/scsi_vhci_f_asym_sun 755 root sys +f none kernel/misc/scsi_vhci/amd64/scsi_vhci_f_sym 755 root sys +f none kernel/misc/scsi_vhci/amd64/scsi_vhci_f_sym_emc 755 root sys +f none kernel/misc/scsi_vhci/amd64/scsi_vhci_f_tpgs 755 root sys + d none kernel/misc/amd64 755 root sys f none kernel/misc/amd64/acpica 755 root sys f none kernel/misc/amd64/agpmaster 755 root sys
--- a/usr/src/pkgdefs/SUNWckr/prototype_sparc Fri Aug 10 11:19:25 2007 -0700 +++ b/usr/src/pkgdefs/SUNWckr/prototype_sparc Fri Aug 10 17:00:37 2007 -0700 @@ -161,6 +161,14 @@ d none kernel/mac/sparcv9 755 root sys f none kernel/mac/sparcv9/mac_ether 755 root sys f none kernel/mac/sparcv9/mac_wifi 755 root sys +d none kernel/misc/scsi_vhci 755 root sys +d none kernel/misc/scsi_vhci/sparcv9 755 root sys +f none kernel/misc/scsi_vhci/sparcv9/scsi_vhci_f_asym_emc 755 root sys +f none kernel/misc/scsi_vhci/sparcv9/scsi_vhci_f_asym_lsi 755 root sys +f none kernel/misc/scsi_vhci/sparcv9/scsi_vhci_f_asym_sun 755 root sys +f none kernel/misc/scsi_vhci/sparcv9/scsi_vhci_f_sym 755 root sys +f none kernel/misc/scsi_vhci/sparcv9/scsi_vhci_f_sym_emc 755 root sys +f none kernel/misc/scsi_vhci/sparcv9/scsi_vhci_f_tpgs 755 root sys d none kernel/misc/sparcv9 755 root sys f none kernel/misc/sparcv9/busra 755 root sys f none kernel/misc/sparcv9/cardbus 755 root sys
--- a/usr/src/pkgdefs/common_files/i.scsivhciconf Fri Aug 10 11:19:25 2007 -0700 +++ b/usr/src/pkgdefs/common_files/i.scsivhciconf Fri Aug 10 17:00:37 2007 -0700 @@ -3,9 +3,8 @@ # CDDL HEADER START # # The contents of this file are subject to the terms of the -# Common Development and Distribution License, Version 1.0 only -# (the "License"). You may not use this file except in compliance -# with the License. +# 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. @@ -20,10 +19,9 @@ # # CDDL HEADER END # -# # ident "%Z%%M% %I% %E% SMI" # -# Copyright 2004 Sun Microsystems, Inc. All rights reserved. +# Copyright 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # @@ -78,6 +76,100 @@ /^# Force load driver to support hotplug activity (do not remove this property).$/d /^ddi-forceattach=1;$/d EOF + # + # convert old 'scsi-options' oriented device-type-scsi-options-list to + # new 'name' oriented scsi-vhci-failover-override + # + cat > $SEDSCRIPT << EOF +/^# For enabling MPxIO support for 3rd party symmetric device need an$/d +/^# entry similar to following in this file. Just replace the "SUN SENA"$/d +/^# part with the Vendor ID\/Product ID for the device, exactly as reported by$/d +/^# Inquiry cmd.$/d +/^# device-type-scsi-options-list =$/d +/^# "SUN SENA", "symmetric-option";$/d +/^# symmetric-option = 0x1000000;$/d +s/device-type-scsi-options-list/scsi-vhci-failover-override/ +EOF + # old 'symetric' scsi-options property of 1000000 -> f_sym name + SYMMETRIC_OPTION=`grep -v '^#' $2 | grep 1000000 | \ + awk -F= '{print $1}'|awk '{print $1}'` + if [ "$SYMMETRIC_OPTION"x != "x" ]; then + echo "/^[ ]*$SYMMETRIC_OPTION/d" >> $SEDSCRIPT + echo "s/$SYMMETRIC_OPTION/f_sym/" >> $SEDSCRIPT + fi + + # old 'disable' scsi-options property of 7000000 -> NONE name + DISABLE_OPTION=`grep -v '^#' $2 | grep 7000000 | \ + awk -F= '{print $1}'|awk '{print $1}'` + if [ "$DISABLE_OPTION"x != "x" ]; then + echo "/^[ ]*$DISABLE_OPTION/d" >> $SEDSCRIPT + echo "s/$DISABLE_OPTION/NONE/" >> $SEDSCRIPT + fi +} + +# +# Make FAILOVER_MODULE_BLOCK file +# +make_fmbfile() +{ + # + # Form file with new scsi_vhci FAILOVER_MODULE_BLOCK information + # + cat > $FMBFILE << EOF +#BEGIN: FAILOVER_MODULE_BLOCK (DO NOT MOVE OR DELETE) +# +# Declare scsi_vhci failover module paths with 'ddi-forceload' so that +# they get loaded early enough to be available for scsi_vhci root use. +# +# NOTE: Correct operation depends on the value of 'ddi-forceload', this +# value should not be changed. The ordering of entries is from +# most-specific failover modules (with a "probe" implementation that is +# completely VID/PID table based), to most generic (failover modules that +# are based on T10 standards like TPGS). By convention the last part of a +# failover module path, after "/scsi_vhci_", is called the +# "failover-module-name", which begins with "f_" (like "f_asym_sun"). The +# "failover-module-name" is also used in the override mechanism below. +ddi-forceload = + "misc/scsi_vhci/scsi_vhci_f_asym_sun", + "misc/scsi_vhci/scsi_vhci_f_asym_lsi", + "misc/scsi_vhci/scsi_vhci_f_asym_emc", + "misc/scsi_vhci/scsi_vhci_f_sym_emc", + "misc/scsi_vhci/scsi_vhci_f_sym", + "misc/scsi_vhci/scsi_vhci_f_tpgs"; + +# +# For a device that has a GUID, discovered on a pHCI with mpxio enabled, vHCI +# access also depends on one of the scsi_vhci failover modules accepting the +# device. The default way this occurs is by a failover module's "probe" +# implementation (sfo_device_probe) indicating the device is supported under +# scsi_vhci. To override this default probe-oriented configuration in +# order to +# +# 1) establish support for a device not currently accepted under scsi_vhci +# +# or 2) override the module selected by "probe" +# +# or 3) disable scsi_vhci support for a device +# +# you can add a 'scsi-vhci-failover-override' tuple, as documented in +# scsi_get_device_type_string(9F). For each tuple, the first part provides +# basic device identity information (vid/pid) and the second part selects +# the failover module by "failover-module-name". If you want to disable +# scsi_vhci support for a device, use the special failover-module-name "NONE". +# Currently, for each failover-module-name in 'scsi-vhci-failover-override' +# (except "NONE") there needs to be a +# "misc/scsi_vhci/scsi_vhci_<failover-module-name>" in 'ddi-forceload' above. +# +# " 111111" +# "012345670123456789012345", "failover-module-name" or "NONE" +# "|-VID--||-----PID------|", +# scsi-vhci-failover-override = +# "STK FLEXLINE 400", "f_asym_lsi", +# "SUN T4", "f_tpgs", +# "CME XIRTEMMYS", "NONE"; +# +#END: FAILOVER_MODULE_BLOCK (DO NOT MOVE OR DELETE) +EOF } # @@ -93,9 +185,10 @@ SEDSCRIPT=$PREFIX.sed NEWIDENT1=$PREFIX.ident1 NEWIDENT2=$PREFIX.ident2 + FMBFILE=$PREFIX.fb # make sed script to do most of the work of upgrading - make_sed_script + make_sed_script $1 $2 # make sed script to replace old ident with new ident IDENT="^#.*ident.*SMI\"$" @@ -109,8 +202,14 @@ retval=$? if [ $retval -eq 0 ]; then cp $TMPSCSIVHCI $2 + grep "FAILOVER_MODULE_BLOCK" $2 >/dev/null 2>&1 + if [ $? -ne 0 ]; then + make_fmbfile + cat $FMBFILE >> $2 + fi fi - rm -f $NEWIDENT1 $NEWIDENT2 $SEDSCRIPT $TMPSCSIVHCI + + rm -f $NEWIDENT1 $NEWIDENT2 $SEDSCRIPT $TMPSCSIVHCI $FMBFILE return $retval }
--- a/usr/src/uts/Makefile.targ Fri Aug 10 11:19:25 2007 -0700 +++ b/usr/src/uts/Makefile.targ Fri Aug 10 17:00:37 2007 -0700 @@ -188,6 +188,9 @@ $(ROOT_KGSS_DIR)/%: $(OBJS_DIR)/% $(ROOT_KGSS_DIR) FRC $(INS.file) +$(ROOT_SCSI_VHCI_DIR)/%: $(OBJS_DIR)/% $(ROOT_SCSI_VHCI_DIR) FRC + $(INS.file) + $(ROOT_MACH_DIR)/%: $(OBJS_DIR)/% $(ROOT_MACH_DIR) FRC $(INS.file)
--- a/usr/src/uts/Makefile.uts Fri Aug 10 11:19:25 2007 -0700 +++ b/usr/src/uts/Makefile.uts Fri Aug 10 17:00:37 2007 -0700 @@ -425,6 +425,7 @@ ROOT_SYS_DIR_32 = $(ROOT_MOD_DIR)/sys ROOT_MISC_DIR_32 = $(ROOT_MOD_DIR)/misc ROOT_KGSS_DIR_32 = $(ROOT_MOD_DIR)/misc/kgss +ROOT_SCSI_VHCI_DIR_32 = $(ROOT_MOD_DIR)/misc/scsi_vhci ROOT_NLMISC_DIR_32 = $(ROOT_MOD_DIR)/misc ROOT_MACH_DIR_32 = $(ROOT_MOD_DIR)/mach ROOT_CPU_DIR_32 = $(ROOT_MOD_DIR)/cpu @@ -447,6 +448,7 @@ ROOT_SYS_DIR_64 = $(ROOT_MOD_DIR)/sys/$(SUBDIR64) ROOT_MISC_DIR_64 = $(ROOT_MOD_DIR)/misc/$(SUBDIR64) ROOT_KGSS_DIR_64 = $(ROOT_MOD_DIR)/misc/kgss/$(SUBDIR64) +ROOT_SCSI_VHCI_DIR_64 = $(ROOT_MOD_DIR)/misc/scsi_vhci/$(SUBDIR64) ROOT_NLMISC_DIR_64 = $(ROOT_MOD_DIR)/misc/$(SUBDIR64) ROOT_MACH_DIR_64 = $(ROOT_MOD_DIR)/mach/$(SUBDIR64) ROOT_CPU_DIR_64 = $(ROOT_MOD_DIR)/cpu/$(SUBDIR64) @@ -469,6 +471,7 @@ ROOT_SYS_DIR = $(ROOT_SYS_DIR_$(CLASS)) ROOT_MISC_DIR = $(ROOT_MISC_DIR_$(CLASS)) ROOT_KGSS_DIR = $(ROOT_KGSS_DIR_$(CLASS)) +ROOT_SCSI_VHCI_DIR = $(ROOT_SCSI_VHCI_DIR_$(CLASS)) ROOT_NLMISC_DIR = $(ROOT_NLMISC_DIR_$(CLASS)) ROOT_MACH_DIR = $(ROOT_MACH_DIR_$(CLASS)) ROOT_CPU_DIR = $(ROOT_CPU_DIR_$(CLASS)) @@ -487,6 +490,7 @@ ROOT_MOD_DIRS_32 += $(ROOT_IPP_DIR_32) ROOT_MOD_DIRS_32 += $(ROOT_MISC_DIR_32) $(ROOT_MACH_DIR_32) ROOT_MOD_DIRS_32 += $(ROOT_KGSS_DIR_32) +ROOT_MOD_DIRS_32 += $(ROOT_SCSI_VHCI_DIR_32) ROOT_MOD_DIRS_32 += $(ROOT_CPU_DIR_32) $(ROOT_FONT_DIR_32) ROOT_MOD_DIRS_32 += $(ROOT_TOD_DIR_32) $(ROOT_DACF_DIR_32) ROOT_MOD_DIRS_32 += $(ROOT_CRYPTO_DIR_32) $(ROOT_MAC_DIR_32)
--- a/usr/src/uts/common/Makefile.files Fri Aug 10 11:19:25 2007 -0700 +++ b/usr/src/uts/common/Makefile.files Fri Aug 10 17:00:37 2007 -0700 @@ -659,6 +659,14 @@ scsi_hba.o scsi_transport.o scsi_confsubr.o \ scsi_reset_notify.o +SCSI_VHCI_OBJS += scsi_vhci.o mpapi_impl.o + +SCSI_VHCI_F_SYM_OBJS += sym.o + +SCSI_VHCI_F_TPGS_OBJS += tpgs.o + +SCSI_VHCI_F_ASYM_SUN_OBJS += asym_sun.o + SGEN_OBJS += sgen.o SATA_OBJS += sata.o
--- a/usr/src/uts/common/Makefile.rules Fri Aug 10 11:19:25 2007 -0700 +++ b/usr/src/uts/common/Makefile.rules Fri Aug 10 17:00:37 2007 -0700 @@ -62,7 +62,7 @@ $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) -$(OBJS_DIR)/%.o: $(COMMONBASE)/acl/%.c +$(OBJS_DIR)/%.o: $(COMMONBASE)/acl/%.c $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) @@ -287,7 +287,7 @@ $(COMPILE.c) $(KGSSDFLAGS) -o $@ $< $(CTFCONVERT_O) -$(OBJS_DIR)/%.o: $(UTSBASE)/common/gssapi/mechs/dummy/%.c +$(OBJS_DIR)/%.o: $(UTSBASE)/common/gssapi/mechs/dummy/%.c $(COMPILE.c) $(KGSSDFLAGS) -o $@ $< $(CTFCONVERT_O) @@ -463,11 +463,11 @@ $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) -$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/audio/sada/mixer/%.c +$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/audio/sada/mixer/%.c $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) -$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/bge/%.c +$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/bge/%.c $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) @@ -650,7 +650,7 @@ $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) -$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/rge/%.c +$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/rge/%.c $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) @@ -691,6 +691,14 @@ $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) +$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/scsi/adapters/scsi_vhci/%.c + $(COMPILE.c) -o $@ $< + $(CTFCONVERT_O) + +$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/scsi/adapters/scsi_vhci/fops/%.c + $(COMPILE.c) -o $@ $< + $(CTFCONVERT_O) + $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/usb/clients/audio/usb_ac/%.c $(COMPILE.c) -o $@ $< $(CTFCONVERT_O) @@ -1151,7 +1159,7 @@ $(LINTS_DIR)/%.ln: $(COMMONBASE)/zfs/%.c @($(LHEAD) $(LINT.c) $< $(LTAIL)) -$(LINTS_DIR)/%.ln: $(UTSBASE)/common/gssapi/%.c +$(LINTS_DIR)/%.ln: $(UTSBASE)/common/gssapi/%.c @($(LHEAD) $(LINT.c) $(KGSSDFLAGS) $< $(LTAIL)) $(LINTS_DIR)/%.ln: $(UTSBASE)/common/gssapi/mechs/dummy/%.c @@ -1443,6 +1451,12 @@ $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/scsi/adapters/%.c @($(LHEAD) $(LINT.c) $< $(LTAIL)) +$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/scsi/adapters/scsi_vhci/%.c + @($(LHEAD) $(LINT.c) $< $(LTAIL)) + +$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/scsi/adapters/scsi_vhci/fops/%.c + @($(LHEAD) $(LINT.c) $< $(LTAIL)) + $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/scsi/conf/%.c @($(LHEAD) $(LINT.c) $< $(LTAIL)) @@ -1575,7 +1589,7 @@ $(LINTS_DIR)/%.ln: $(UTSBASE)/common/ktli/%.c @($(LHEAD) $(LINT.c) $< $(LTAIL)) -$(LINTS_DIR)/%.ln: $(COMMONBASE)/lvm/%.c +$(LINTS_DIR)/%.ln: $(COMMONBASE)/lvm/%.c @($(LHEAD) $(LINT.c) $< $(LTAIL)) $(LINTS_DIR)/%.ln: $(COMMONBASE)/crypto/md4/%.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/common/io/scsi/adapters/scsi_vhci/fops/asym_sun.c Fri Aug 10 17:00:37 2007 -0700 @@ -0,0 +1,552 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Implementation of "scsi_vhci_f_asym_sun" asymmetric failover_ops. + * + * Note : f_asym_sun method is the same as the one originally used by SUN's + * T3 (Purple) device. + */ + +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/scsi/scsi.h> +#include <sys/scsi/adapters/scsi_vhci.h> + +/* Supported device table entries. */ +char *purple_dev_table[] = { +/* " 111111" */ +/* "012345670123456789012345" */ +/* "|-VID--||-----PID------|" */ + + "SUN T300 ", + "SUN T4 ", + NULL, +}; + +/* Failover module plumbing. */ +SCSI_FAILOVER_OP("f_asym_sun", purple, "%I%"); + +#define PURPLE_FO_CMD_RETRY_DELAY 1000000 /* 1 seconds */ +#define PURPLE_FO_RETRY_DELAY 2000000 /* 2 seconds */ +/* + * max time for failover to complete is 3 minutes. Compute + * number of retries accordingly, to ensure we wait for at least + * 3 minutes + */ +#define PURPLE_FO_MAX_RETRIES (3*60*1000000)/PURPLE_FO_RETRY_DELAY + +/* + * max number of retries for purple failover to complete where the ping + * command is failing due to transport errors or commands being rejected by + * purple. + * PURPLE_FO_MAX_RETRIES takes into account the case where CMD_CMPLTs but + * purple takes time to complete the failover. + */ +#define PURPLE_FO_MAX_CMD_RETRIES 3 + +#define T3_SCSI_ASC_FO_IN_PROGRESS 0x90 +#define T3_SCSI_ASCQ_PATH_ACT2INACT 0x00 +#define T3_SCSI_ASCQ_PATH_INACT2ACT 0x01 +#define T3_SCSI_ASC_PATH_INACTIVE 0x04 +#define T3_SCSI_ASCQ_PATH_INACTIVE 0x88 + +static void purple_get_fo_mode(struct scsi_device *sd, + int *mode, int *ownership, int *xlf_capable); + +/* ARGSUSED */ +static int +purple_device_probe(struct scsi_device *sd, struct scsi_inquiry *stdinq, +void **ctpriv) +{ + char **dt; + int xlf = 0, mode = 0, ownership = 0; + + VHCI_DEBUG(6, (CE_NOTE, NULL, "purple_device_probe: vidpid %s\n", + stdinq->inq_vid)); + + for (dt = purple_dev_table; *dt; dt++) { + if (strncmp(stdinq->inq_vid, *dt, strlen(*dt))) + continue; + + /* match */ + purple_get_fo_mode(sd, &mode, &ownership, &xlf); + if (mode == SCSI_EXPLICIT_FAILOVER) + return (SFO_DEVICE_PROBE_VHCI); + else + return (SFO_DEVICE_PROBE_PHCI); + } + return (SFO_DEVICE_PROBE_PHCI); +} + +/* ARGSUSED */ +static void +purple_device_unprobe(struct scsi_device *sd, void *ctpriv) +{ + /* + * For future use + */ +} + +/* ARGSUSED */ +static void +purple_get_fo_mode(struct scsi_device *sd, int *mode, +int *ownership, int *xlf_capable) +{ + char inqbuf[0xff], *ptr, *end; + int retval = 0; + struct buf *bp; + struct scsi_pkt *pkt; + struct scsi_address *ap; + + *mode = *ownership = *xlf_capable = 0; + bp = getrbuf(KM_NOSLEEP); + if (bp == NULL) + return; + bp->b_un.b_addr = inqbuf; + bp->b_flags = B_READ; + bp->b_bcount = 0xff; + bp->b_resid = 0; + + ap = &sd->sd_address; + pkt = scsi_init_pkt(ap, NULL, bp, CDB_GROUP0, + sizeof (struct scsi_arq_status), 0, 0, NULL, NULL); + if (pkt == NULL) { + freerbuf(bp); + return; + } + + pkt->pkt_cdbp[0] = SCMD_INQUIRY; + pkt->pkt_cdbp[1] = 0x1; + pkt->pkt_cdbp[2] = 0x83; + pkt->pkt_cdbp[4] = 0xff; + pkt->pkt_time = 90; + + retval = vhci_do_scsi_cmd(pkt); + scsi_destroy_pkt(pkt); + freerbuf(bp); + if (retval == 0) { + VHCI_DEBUG(4, (CE_NOTE, NULL, "!(sd:%p)failed to get mode" + " and ownership info\n", (void *)sd)); + return; + } + + ptr = inqbuf; + ptr += 4; /* identification descriptor 0 */ + end = inqbuf + 4 + inqbuf[3]; + while (((ptr[1] & 0x0f) != 0xf) && (ptr < end)) + ptr += ptr[3] + 4; /* next identification descriptor */ + if (ptr >= end) { + VHCI_DEBUG(4, (CE_NOTE, NULL, "!(sd:%p)p_g_m_a_o:assuming" + " implicit mode\n", (void *)sd)); + *mode = SCSI_IMPLICIT_FAILOVER; + *ownership = 0; + return; + } + ptr += 4; /* Port Failover Identifier */ + *mode = ptr[0]; + if ((ptr[1] & 0x3) == 0x01) + *ownership = 0; + else if ((ptr[1] & 0x3) == 0x00) + *ownership = 1; + if (ptr[1] & 0x4) { + *xlf_capable = 1; + } else { + *xlf_capable = 0; + } +} + +static int +purple_activate_explicit(struct scsi_device *sd, int xlf_capable) +{ + char cdb[CDB_GROUP1]; + struct scsi_address *ap; + struct scsi_pkt *pkt; + int retval; + + bzero(cdb, CDB_GROUP1); + + ap = &sd->sd_address; + pkt = scsi_init_pkt(ap, NULL, NULL, CDB_GROUP1, + sizeof (struct scsi_arq_status), 0, 0, NULL, NULL); + if (pkt == NULL) + return (0); + + pkt->pkt_cdbp[0] = 0xD0; + if (xlf_capable) { + /* + * Bit 2/1: 1/0: implicitly drop any reservation + * Bit 0: Grab bit - 1 means an explicit failover will be + * triggered + */ + pkt->pkt_cdbp[1] = 0x05; + } else { + pkt->pkt_cdbp[1] = 0x01; /* no reservation check, "grab" lun */ + } + + retval = vhci_do_scsi_cmd(pkt); + scsi_destroy_pkt(pkt); + + return (retval); +} + +/* ARGSUSED */ +static int +purple_path_activate(struct scsi_device *sd, char *pathclass, +void *ctpriv) +{ + struct buf *bp; + struct scsi_pkt *pkt; + struct scsi_address *ap; + int err, retry_cnt, retry_cmd_cnt; + int mode, ownership, retval, xlf; + struct scsi_extended_sense *sns; + + ap = &sd->sd_address; + + mode = ownership = 0; + + purple_get_fo_mode(sd, &mode, &ownership, &xlf); + if (ownership == 1) { + VHCI_DEBUG(4, (CE_NOTE, NULL, "!path already active for 0x%p\n", + (void *)sd)); + return (0); + } + + if (mode != SCSI_IMPLICIT_FAILOVER) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "!mode is EXPLICIT for 0x%p xlf %x\n", + (void *)sd, xlf)); + retval = purple_activate_explicit(sd, xlf); + if (retval == 0) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "!(sd:%p)purple_path_activate failed(1)\n", + (void *)sd)); + return (1); + } + } else { + VHCI_DEBUG(4, (CE_NOTE, NULL, "!mode is IMPLICIT for 0x%p\n", + (void *)sd)); + } + + bp = scsi_alloc_consistent_buf(ap, (struct buf *)NULL, DEV_BSIZE, + B_READ, NULL, NULL); + if (!bp) { + cmn_err(CE_WARN, "!No resources (buf) to initiate T3 path " + "activation"); + return (1); + } + + pkt = scsi_init_pkt(ap, NULL, bp, CDB_GROUP1, + sizeof (struct scsi_arq_status), 0, PKT_CONSISTENT, NULL, NULL); + if (!pkt) { + cmn_err(CE_WARN, "!Packet alloc failure during T3 " + "path activation"); + scsi_free_consistent_buf(bp); + return (1); + } + + (void) scsi_setup_cdb((union scsi_cdb *)(uintptr_t)pkt->pkt_cdbp, + SCMD_READ, 1, 1, 0); + pkt->pkt_time = 3*30; + pkt->pkt_flags |= FLAG_NOINTR; + + retry_cnt = 0; + retry_cmd_cnt = 0; +retry: + err = scsi_transport(pkt); + if (err != TRAN_ACCEPT) { + /* + * Retry TRAN_BUSY till PURPLE_FO_MAX_RETRIES is exhausted. + * All other errors are fatal and should not be retried. + */ + if ((err == TRAN_BUSY) && + (retry_cnt++ < PURPLE_FO_MAX_RETRIES)) { + drv_usecwait(PURPLE_FO_RETRY_DELAY); + goto retry; + } + cmn_err(CE_WARN, "T3 failover failed, " + "couldn't transport packet"); + scsi_destroy_pkt(pkt); + scsi_free_consistent_buf(bp); + return (1); + } + + switch (pkt->pkt_reason) { + case CMD_TIMEOUT: + cmn_err(CE_WARN, "!T3 failover failed: timed out "); + scsi_destroy_pkt(pkt); + scsi_free_consistent_buf(bp); + return (1); + case CMD_CMPLT: + /* + * Re-initialize retry_cmd_cnt. Allow transport and + * cmd errors to go through a full retry count when + * these are encountered. This way TRAN/CMD errors + * retry count is not exhausted due to CMD_CMPLTs + * delay for a T3 fo to finish. This allows the system + * to brave a hick-up on the link at any given time, + * while waiting for the fo to complete. + */ + retry_cmd_cnt = 0; + if (pkt->pkt_state & STATE_ARQ_DONE) { + sns = &(((struct scsi_arq_status *)(uintptr_t) + (pkt->pkt_scbp))->sts_sensedata); + if (sns->es_key == KEY_UNIT_ATTENTION) { + /* + * swallow unit attention + */ + goto retry; + } else if ((sns->es_key == KEY_NOT_READY) && + (sns->es_add_code == + T3_SCSI_ASC_FO_IN_PROGRESS) && + (sns->es_qual_code == + T3_SCSI_ASCQ_PATH_INACT2ACT)) { + if (retry_cnt++ >= + PURPLE_FO_MAX_RETRIES) { + cmn_err(CE_WARN, "!T3 failover" + " failed: timed out " + "waiting for path to " + "become active"); + scsi_destroy_pkt(pkt); + scsi_free_consistent_buf(bp); + return (1); + } + VHCI_DEBUG(6, (CE_NOTE, NULL, + "!(sd:%p)lun becoming active...\n", + (void *)sd)); + drv_usecwait(PURPLE_FO_RETRY_DELAY); + goto retry; + } + cmn_err(CE_NOTE, "!T3 failover failed;" + " sense key:%x, ASC: %x, " + "ASCQ:%x", sns->es_key, + sns->es_add_code, sns->es_qual_code); + scsi_destroy_pkt(pkt); + scsi_free_consistent_buf(bp); + return (1); + } + switch (SCBP_C(pkt)) { + case STATUS_GOOD: + break; + case STATUS_CHECK: + VHCI_DEBUG(4, (CE_WARN, NULL, + "!(sd:%p)T3:" + " cont allegiance during purple " + "activation", (void *)sd)); + scsi_destroy_pkt(pkt); + scsi_free_consistent_buf(bp); + return (1); + case STATUS_QFULL: + VHCI_DEBUG(6, (CE_NOTE, NULL, "QFULL " + "status returned during purple " + "path activation for 0x%p\n", + (void *)sd)); + drv_usecwait(5000); + goto retry; + case STATUS_BUSY: + VHCI_DEBUG(6, (CE_NOTE, NULL, "BUSY " + "status returned during purple " + "path activation for 0x%p\n", + (void *)sd)); + drv_usecwait(5000); + goto retry; + default: + VHCI_DEBUG(4, (CE_WARN, NULL, + "!(sd:%p) Bad status " + "returned during purple " + "activation (pkt 0x%p, " + "status %x)", + (void *)sd, (void *)pkt, + SCBP_C(pkt))); + scsi_destroy_pkt(pkt); + scsi_free_consistent_buf(bp); + return (1); + } + break; + case CMD_INCOMPLETE: + case CMD_RESET: + case CMD_ABORTED: + case CMD_TRAN_ERR: + /* + * Increased the number of retries when these error + * cases are encountered. Also added a 1 sec wait + * before retrying. + */ + if (retry_cmd_cnt++ < PURPLE_FO_MAX_CMD_RETRIES) { + drv_usecwait(PURPLE_FO_CMD_RETRY_DELAY); + VHCI_DEBUG(4, (CE_WARN, NULL, + "!Retrying T3 path activation due to " + "pkt reason:%x, retry cnt:%d", + pkt->pkt_reason, retry_cmd_cnt)); + goto retry; + } + /* FALLTHROUGH */ + default: + cmn_err(CE_WARN, "!T3 path activation did not " + "complete successfully," + "(pkt reason %x)", pkt->pkt_reason); + scsi_destroy_pkt(pkt); + scsi_free_consistent_buf(bp); + return (1); + } + + VHCI_DEBUG(4, (CE_NOTE, NULL, "!T3 path activation success\n")); + scsi_destroy_pkt(pkt); + scsi_free_consistent_buf(bp); + return (0); +} + +/* ARGSUSED */ +static int purple_path_deactivate(struct scsi_device *sd, char *pathclass, +void *ctpriv) +{ + return (0); +} + +/* ARGSUSED */ +static int +purple_path_get_opinfo(struct scsi_device *sd, struct scsi_path_opinfo +*opinfo, void *ctpriv) +{ + struct scsi_inquiry *inq; + struct buf *bp; + struct scsi_pkt *pkt; + struct scsi_address *ap; + int retval, mode, ownership, xlf; + + ap = &sd->sd_address; + + bp = scsi_alloc_consistent_buf(ap, (struct buf *)NULL, SUN_INQSIZE, + B_READ, NULL, NULL); + if (!bp) + return (1); + pkt = scsi_init_pkt(ap, NULL, bp, CDB_GROUP0, + sizeof (struct scsi_arq_status), 0, PKT_CONSISTENT, NULL, NULL); + if (!pkt) { + scsi_free_consistent_buf(bp); + return (1); + } + (void) scsi_setup_cdb((union scsi_cdb *)(uintptr_t)pkt->pkt_cdbp, + SCMD_INQUIRY, 0, SUN_INQSIZE, 0); + pkt->pkt_time = 60; + + retval = vhci_do_scsi_cmd(pkt); + if (retval == 0) { + scsi_destroy_pkt(pkt); + scsi_free_consistent_buf(bp); + return (1); + } + + inq = (struct scsi_inquiry *)bp->b_un.b_addr; + + opinfo->opinfo_rev = OPINFO_REV; + + /* + * Ignore to check inquiry dual port bit. + * T3 can return this bit as 0 when one of its controller goes down. + * Instead relying on inquiry port bit only. + */ + if (inq->inq_port == 0) { + (void) strcpy(opinfo->opinfo_path_attr, "primary"); + } else { + (void) strcpy(opinfo->opinfo_path_attr, "secondary"); + } + + scsi_destroy_pkt(pkt); + scsi_free_consistent_buf(bp); + + purple_get_fo_mode(sd, &mode, &ownership, &xlf); + + if (ownership == 1) + opinfo->opinfo_path_state = SCSI_PATH_ACTIVE; + else + opinfo->opinfo_path_state = SCSI_PATH_INACTIVE; + opinfo->opinfo_xlf_capable = xlf; + opinfo->opinfo_pswtch_best = 30; + opinfo->opinfo_pswtch_worst = 3*30; + opinfo->opinfo_mode = (uint16_t)mode; + opinfo->opinfo_preferred = 1; + + return (0); +} + +/* ARGSUSED */ +static int purple_path_ping(struct scsi_device *sd, void *ctpriv) +{ + /* + * For future use + */ + return (1); +} + +/* ARGSUSED */ +static int +purple_analyze_sense(struct scsi_device *sd, struct scsi_extended_sense +*sense, void *ctpriv) +{ + if (sense->es_key == KEY_NOT_READY) { + if (sense->es_add_code == T3_SCSI_ASC_FO_IN_PROGRESS) { + if (sense->es_qual_code == T3_SCSI_ASCQ_PATH_INACT2ACT) + return (SCSI_SENSE_INACT2ACT); + else if (sense->es_qual_code == + T3_SCSI_ASCQ_PATH_ACT2INACT) + return (SCSI_SENSE_ACT2INACT); + } else if ((sense->es_add_code == T3_SCSI_ASC_PATH_INACTIVE) && + (sense->es_qual_code == T3_SCSI_ASCQ_PATH_INACTIVE)) { + return (SCSI_SENSE_INACTIVE); + } + } + + /* + * At this point sense data may be for power-on-reset UNIT ATTN or + * hardware errors, vendor unique sense data etc. For all these cases + * return SCSI_SENSE_UNKNOWN. + */ + VHCI_DEBUG(6, (CE_NOTE, NULL, "!T3 analyze sense UNKNOWN:" + " sense key:%x, ASC: %x, ASCQ:%x\n", sense->es_key, + sense->es_add_code, sense->es_qual_code)); + return (SCSI_SENSE_UNKNOWN); +} + +/* ARGSUSED */ +static int +purple_pathclass_next(char *cur, char **nxt, void *ctpriv) +{ + if (cur == NULL) { + *nxt = PCLASS_PRIMARY; + return (0); + } else if (strcmp(cur, PCLASS_PRIMARY) == 0) { + *nxt = PCLASS_SECONDARY; + return (0); + } else if (strcmp(cur, PCLASS_SECONDARY) == 0) { + return (ENOENT); + } else { + return (EINVAL); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/common/io/scsi/adapters/scsi_vhci/fops/sym.c Fri Aug 10 17:00:37 2007 -0700 @@ -0,0 +1,174 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Implementation of "scsi_vhci_f_sym" symmetric failover_ops. + * + * This file was historically meant for only symmetric implementation. It has + * been extended to manage SUN "supported" symmetric controllers. The supported + * VID/PID shall be listed in the symmetric_dev_table. + */ + +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/scsi/scsi.h> +#include <sys/scsi/adapters/scsi_vhci.h> + +/* Supported device table entries. */ +char *symmetric_dev_table[] = { +/* " 111111" */ +/* "012345670123456789012345" */ +/* "|-VID--||-----PID------|" */ + /* disks */ + "IBM DDYFT", + "IBM IC", + "SEAGATE ST", + /* enclosures */ + "SUN SENA", /* SES device */ + "SUN SESS01", /* VICOM SVE box */ + "SUNW SUNWGS", /* Daktari enclosure */ + /* arrays */ + "HITACHI OPEN", /* Hitachi storage */ + "SUN PSX1000", /* Pirus Matterhorn */ + "SUN SE6920", /* Pirus */ + "SUN SE6940", /* DSP - Nauset */ + "SUN StorEdge 3510", /* Minnow FC */ + "SUN StorEdge 3511", /* Minnow SATA RAID */ + "SUN StorageTek 6920", /* DSP */ + "SUN StorageTek 6940", /* DSP - Nauset */ + "SUN StorageTek NAS", /* StorageTek NAS */ + "SUN MRA300_R", /* Shamrock - Controller */ + "SUN MRA300_E", /* Shamrock - Expansion */ + + NULL +}; + +/* Failover module plumbing. */ +SCSI_FAILOVER_OP(SFO_NAME_SYM, symmetric, "%I%"); + +/* ARGSUSED */ +static int +symmetric_device_probe(struct scsi_device *sd, struct scsi_inquiry *stdinq, +void **ctpriv) +{ + char **dt; + + VHCI_DEBUG(6, (CE_NOTE, NULL, "!inq str: %s\n", stdinq->inq_vid)); + for (dt = symmetric_dev_table; *dt; dt++) + if (strncmp(stdinq->inq_vid, *dt, strlen(*dt)) == 0) + return (SFO_DEVICE_PROBE_VHCI); + + /* + * No match, check for generic Sun supported disks: + * + * "|-VID--||-----PID------|" + * "012345670123456789012345" + * ".................SUN..G." + * ".................SUN..T." + * ".................SUN...G" + * ".................SUN...T" + */ + if (bcmp(&stdinq->inq_pid[9], "SUN", 3) == 0) { + if ((stdinq->inq_pid[14] == 'G' || stdinq->inq_pid[15] == 'G' || + stdinq->inq_pid[14] == 'T' || stdinq->inq_pid[15] == 'T') && + (stdinq->inq_dtype == DTYPE_DIRECT)) { + return (SFO_DEVICE_PROBE_VHCI); + } + } + return (SFO_DEVICE_PROBE_PHCI); +} + +/* ARGSUSED */ +static void +symmetric_device_unprobe(struct scsi_device *sd, void *ctpriv) +{ + /* + * NOP for symmetric + */ +} + +/* ARGSUSED */ +static int +symmetric_path_activate(struct scsi_device *sd, char *pathclass, void *ctpriv) +{ + return (0); +} + +/* ARGSUSED */ +static int +symmetric_path_deactivate(struct scsi_device *sd, char *pathclass, +void *ctpriv) +{ + return (0); +} + +/* ARGSUSED */ +static int +symmetric_path_get_opinfo(struct scsi_device *sd, +struct scsi_path_opinfo *opinfo, void *ctpriv) +{ + opinfo->opinfo_rev = OPINFO_REV; + (void) strcpy(opinfo->opinfo_path_attr, "primary"); + opinfo->opinfo_path_state = SCSI_PATH_ACTIVE; + opinfo->opinfo_pswtch_best = 0; /* N/A */ + opinfo->opinfo_pswtch_worst = 0; /* N/A */ + opinfo->opinfo_xlf_capable = 0; + opinfo->opinfo_mode = SCSI_NO_FAILOVER; + opinfo->opinfo_preferred = 1; + + return (0); +} + +/* ARGSUSED */ +static int +symmetric_path_ping(struct scsi_device *sd, void *ctpriv) +{ + return (1); +} + +/* ARGSUSED */ +static int +symmetric_analyze_sense(struct scsi_device *sd, +struct scsi_extended_sense *sense, void *ctpriv) +{ + return (SCSI_SENSE_NOFAILOVER); +} + +/* ARGSUSED */ +static int +symmetric_pathclass_next(char *cur, char **nxt, void *ctpriv) +{ + if (cur == NULL) { + *nxt = PCLASS_PRIMARY; + return (0); + } else if (strcmp(cur, PCLASS_PRIMARY) == 0) { + return (ENOENT); + } else { + return (EINVAL); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/common/io/scsi/adapters/scsi_vhci/fops/tpgs.c Fri Aug 10 17:00:37 2007 -0700 @@ -0,0 +1,1063 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Implementation of "scsi_vhci_f_tpgs" T10 standard based failover_ops. + * + * NOTE: for non-sequential devices only. + */ + +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/scsi/scsi.h> +#include <sys/scsi/adapters/scsi_vhci.h> + +/* Supported device table entries. */ +char *std_dev_table[] = { NULL }; + +/* Failover module plumbing. */ +SCSI_FAILOVER_OP(SFO_NAME_TPGS, std, "%I%"); + +#define STD_SCSI_CMD_LEN 0xff + +#define STD_FO_CMD_RETRY_DELAY 1000000 /* 1 seconds */ +#define STD_FO_RETRY_DELAY 2000000 /* 2 seconds */ +/* + * max time for failover to complete is 3 minutes. Compute + * number of retries accordingly, to ensure we wait for at least + * 3 minutes + */ +#define STD_FO_MAX_RETRIES (3*60*1000000)/STD_FO_RETRY_DELAY + +/* + * max number of retries for std failover to complete where the ping + * command is failing due to transport errors or commands being rejected by + * std. + * STD_FO_MAX_RETRIES takes into account the case where CMD_CMPLTs but + * std takes time to complete the failover. + */ +#define STD_FO_MAX_CMD_RETRIES 3 + +#define STD_ACTIVE_OPTIMIZED 0x0 +#define STD_ACTIVE_NONOPTIMIZED 0x1 +#define STD_STANDBY 0x2 +#define STD_UNAVAILABLE 0x3 +#define STD_TRANSITIONING 0xf + +#define STD_SCSI_TPG_SERVICE_ACTION 0x0A +#define STD_SCSI_ASC_STATE_TRANS 0x04 +#define STD_SCSI_ASCQ_STATE_TRANS_FAIL 0x0A +#define STD_SCSI_ASC_STATE_CHG 0x2A +#define STD_SCSI_ASCQ_STATE_CHG_SUCC 0x06 +#define STD_SCSI_ASCQ_STATE_CHG_FAILED 0x07 +#define STD_SCSI_ASC_INVAL_PARAM_LIST 0x26 +#define STD_SCSI_ASC_INVAL_CMD_OPCODE 0x20 +#define STD_LOGICAL_UNIT_NOT_ACCESSIBLE 0x04 +#define STD_TGT_PORT_UNAVAILABLE 0x0C + +#define SCMD_REPORT_TARGET_PORT_GROUP 0xA3; +#define SCMD_SET_TARGET_PORT_GROUP 0xA4; + +/* Special exported for direct use by MP-API */ +int std_set_target_groups(struct scsi_address *, int, int); + +/* + * External function definitions + */ +extern void vhci_mpapi_update_tpg_data(struct scsi_address *, char *); + +static int std_get_fo_mode(struct scsi_device *, + int *, int *, int *, int *); +static int std_report_target_groups(struct scsi_address *, struct buf *, + int, int, int *, int *); + +/* ARGSUSED */ +static int +std_device_probe(struct scsi_device *sd, struct scsi_inquiry *inq, +void **ctpriv) +{ + unsigned int tpgs_bits; + unsigned char *inqbuf = (unsigned char *)inq; + unsigned char dtype = (inq->inq_dtype & DTYPE_MASK); + + int mode, state, xlf, preferred = 0; + + VHCI_DEBUG(6, (CE_NOTE, NULL, "std_device_probe: vidpid %s\n", + inq->inq_vid)); + + tpgs_bits = ((inqbuf[5] & 0x30) >> 4); + + if (tpgs_bits == 0) { + VHCI_DEBUG(4, (CE_WARN, NULL, + "!std_device_probe: not a standard tpgs device")); + return (SFO_DEVICE_PROBE_PHCI); + } + + if (dtype == DTYPE_SEQUENTIAL) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "!std_device_probe: Detected a " + "Standard Asymmetric device " + "not yet supported\n")); + return (SFO_DEVICE_PROBE_PHCI); + } + + if (std_get_fo_mode(sd, &mode, &state, &xlf, &preferred)) { + VHCI_DEBUG(4, (CE_WARN, NULL, "!unable to fetch fo " + "mode: sd(%p)", (void *) sd)); + return (SFO_DEVICE_PROBE_PHCI); + } + + if (tpgs_bits == SCSI_IMPLICIT_FAILOVER) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!std_device_probe: Detected a " + "Standard Asymmetric device " + "with implicit failover\n")); + return (SFO_DEVICE_PROBE_VHCI); + } + if (tpgs_bits == SCSI_EXPLICIT_FAILOVER) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!std_device_probe: Detected a " + "Standard Asymmetric device " + "with explicit failover\n")); + return (SFO_DEVICE_PROBE_VHCI); + } + if (tpgs_bits == SCSI_BOTH_FAILOVER) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!std_device_probe: Detected a " + "Standard Asymmetric device " + "which supports both implicit and explicit failover\n")); + return (SFO_DEVICE_PROBE_VHCI); + } + VHCI_DEBUG(1, (CE_WARN, NULL, + "!std_device_probe: " + "Unknown tpgs_bits: %x", tpgs_bits)); + return (SFO_DEVICE_PROBE_PHCI); +} + +/* ARGSUSED */ +static void +std_device_unprobe(struct scsi_device *sd, void *ctpriv) +{ + /* + * For future use + */ +} + +static int +std_inquiry(struct scsi_address *ap, struct buf *bp, int *mode) +{ + struct scsi_pkt *pkt; + char buf[STD_SCSI_CMD_LEN]; + int buf_size = sizeof (buf); + unsigned int tpgs_bits; + int retval; + + *mode = 0; + bp->b_un.b_addr = (caddr_t)&buf; + bp->b_flags = B_READ; + bp->b_bcount = buf_size; + bp->b_resid = 0; + + pkt = scsi_init_pkt(ap, NULL, bp, CDB_GROUP0, + sizeof (struct scsi_arq_status), 0, 0, SLEEP_FUNC, NULL); + pkt->pkt_cdbp[0] = SCMD_INQUIRY; + pkt->pkt_cdbp[4] = (unsigned char)buf_size; + pkt->pkt_time = 60; + + retval = vhci_do_scsi_cmd(pkt); + scsi_destroy_pkt(pkt); + if (retval == 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, + "!std_inquiry: Failure returned from vhci_do_scsi_cmd")); + return (1); + } + + tpgs_bits = ((buf[5] & 0x30) >> 4); + if (tpgs_bits == 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, + "!std_inquiry: zero tpgs_bits")); + return (1); + } + retval = 0; + if (tpgs_bits == SCSI_IMPLICIT_FAILOVER) { + *mode = SCSI_IMPLICIT_FAILOVER; + } else if (tpgs_bits == SCSI_EXPLICIT_FAILOVER) { + *mode = SCSI_EXPLICIT_FAILOVER; + } else if (tpgs_bits == SCSI_BOTH_FAILOVER) { + *mode = SCSI_BOTH_FAILOVER; + } else { + VHCI_DEBUG(1, (CE_WARN, NULL, + "!std_inquiry: Illegal mode returned: %x mode: %x", + tpgs_bits, *mode)); + retval = 1; + } + + return (retval); +} + +static int +std_page83(struct scsi_address *ap, struct buf *bp, + int *rel_tgt_port, int *tgt_port, int *lu) +{ + char *ptr, *end; + struct scsi_pkt *pkt; + char *bufp; + unsigned int buf_len, rx_bsize; + + /* + * lets start the buf size with 512 bytes. If this + * if found to be insufficient, we can allocate + * appropriate size in the next iteration. + */ + buf_len = 512; + +once_again: + bufp = kmem_zalloc(buf_len, KM_NOSLEEP); + if (bufp == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "!std_page83: " + "request packet allocation for %d failed....", + buf_len)); + return (1); + } + + + bp->b_un.b_addr = bufp; + bp->b_flags = B_READ; + bp->b_bcount = buf_len; + bp->b_resid = 0; + + pkt = scsi_init_pkt(ap, NULL, bp, CDB_GROUP0, + sizeof (struct scsi_arq_status), 0, 0, NULL, NULL); + if (pkt == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, + "!std_page83: Failure returned from scsi_init_pkt")); + kmem_free((void *)bufp, buf_len); + return (1); + } + + pkt->pkt_cdbp[0] = SCMD_INQUIRY; + pkt->pkt_cdbp[1] = 0x1; + pkt->pkt_cdbp[2] = 0x83; + pkt->pkt_cdbp[3] = (unsigned char)((buf_len >> 8) & 0xff); + pkt->pkt_cdbp[4] = (unsigned char)(buf_len & 0xff); + pkt->pkt_time = 90; + + if (vhci_do_scsi_cmd(pkt) == 0) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!std_page83: vhci_do_scsi_cmd failed\n")); + kmem_free((void *)bufp, buf_len); + scsi_destroy_pkt(pkt); + return (1); + } + + /* + * Now lets check if the size that was provided was + * sufficient. If not, allocate the appropriate size + * and retry the command again. + */ + rx_bsize = (((bufp[2] & 0xff) << 8) | (bufp[3] & 0xff)); + rx_bsize += 4; + if (rx_bsize > buf_len) { + /* + * Need to allocate more buf and retry again + */ + VHCI_DEBUG(1, (CE_NOTE, NULL, "!std_page83: " + "bufsize: %d greater than allocated buf: %d\n", + rx_bsize, buf_len)); + VHCI_DEBUG(1, (CE_NOTE, NULL, "Retrying for size %d\n", + rx_bsize)); + kmem_free((void *)bufp, buf_len); + buf_len = (unsigned int)(rx_bsize); + goto once_again; + } + + ptr = bufp; + ptr += 4; /* identification descriptor 0 */ + end = bufp + rx_bsize; + while (ptr < end) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "std_page83: desc[1/4/5/6/7]:" + "%x %x %x %x %x\n", + ptr[1], ptr[4], ptr[5], ptr[6], ptr[7])); + if ((ptr[1] & 0x0f) == 0x04) { + *rel_tgt_port = 0; + *rel_tgt_port |= ((ptr[6] & 0xff) << 8); + *rel_tgt_port |= (ptr[7] & 0xff); + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!std_page83: relative target port: %x\n", + *rel_tgt_port)); + } else if ((ptr[1] & 0x0f) == 0x05) { + *tgt_port = 0; + *tgt_port = ((ptr[6] & 0xff) << 8); + *tgt_port |= (ptr[7] & 0xff); + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!std_page83: target port: %x\n", *tgt_port)); + } else if ((ptr[1] & 0x0f) == 0x06) { + *lu = 0; + *lu |= ((ptr[6] & 0xff)<< 8); + *lu |= (ptr[7] & 0xff); + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!std_page83: logical unit: %x\n", *lu)); + } + ptr += ptr[3] + 4; /* next identification descriptor */ + } + kmem_free((void *)bufp, buf_len); + scsi_destroy_pkt(pkt); + return (0); +} + +#ifdef DEBUG +static void +print_buf(char *buf, int buf_size) +{ + int i = 0, j; + int loop, left; + + loop = buf_size / 8; + left = buf_size % 8; + + VHCI_DEBUG(4, (CE_NOTE, NULL, "!buf_size: %x loop: %x left: %x", + buf_size, loop, left)); + + for (j = 0; j < loop; j++) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "!buf[%d-%d]: %x %x %x %x %x %x %x %x", + i, i + 7, buf[i], buf[i+1], buf[i+2], buf[i+3], + buf[i+4], buf[i+5], buf[i+6], buf[i+7])); + i += 8; + } + + if (left) { + VHCI_DEBUG(4, (CE_CONT, NULL, + "NOTICE: buf[%d-%d]:", i, i + left)); + for (j = 0; j < left; j++) { + VHCI_DEBUG(4, (CE_CONT, NULL, " %x", buf[i + j])); + } + VHCI_DEBUG(4, (CE_CONT, NULL, "\n")); + } +} +#endif + +static int +std_report_target_groups(struct scsi_address *ap, struct buf *bp, + int rel_tgt_port, int tgt_port, int *pstate, int *preferred) +{ + struct scsi_pkt *pkt; + char *ptr, *end, *bufp, *mpapi_ptr; + unsigned int rtpg_len = 0; + unsigned int l_tgt_port = 0, tpgs_state = 0; + unsigned int tgt_port_cnt = 0, lr_tgt_port = 0; + int i, len; + + /* + * Start with buffer size of 512. + * If this is found to be insufficient, required size + * will be allocated and the command will be retried. + */ + len = 512; + +try_again: + bufp = kmem_zalloc(len, KM_NOSLEEP); + if (bufp == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "!std_report_target_groups: " + "request packet allocation for %d failed....", + len)); + return (1); + } + + bp->b_un.b_addr = bufp; + bp->b_flags = B_READ; + bp->b_bcount = len; + bp->b_resid = 0; + + pkt = scsi_init_pkt(ap, NULL, bp, CDB_GROUP5, + sizeof (struct scsi_arq_status), 0, 0, NULL, NULL); + + if (pkt == NULL) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!std_report_target_groups: scsi_init_pkt error\n")); + kmem_free((void *)bufp, len); + return (1); + } + + pkt->pkt_cdbp[0] = SCMD_REPORT_TARGET_PORT_GROUP; + pkt->pkt_cdbp[1] = STD_SCSI_TPG_SERVICE_ACTION; + pkt->pkt_cdbp[6] = ((len >> 24) & 0xff); + pkt->pkt_cdbp[7] = ((len >> 16) & 0xff); + pkt->pkt_cdbp[8] = ((len >> 8) & 0xff); + pkt->pkt_cdbp[9] = len & 0xff; + pkt->pkt_time = 90; + + VHCI_DEBUG(6, (CE_NOTE, NULL, + "!std_report_target_groups: sending target port group:" + " cdb[6/7/8/9]: %x/%x/%x/%x\n", pkt->pkt_cdbp[6], + pkt->pkt_cdbp[7], pkt->pkt_cdbp[8], pkt->pkt_cdbp[9])); + if (vhci_do_scsi_cmd(pkt) == 0) { + VHCI_DEBUG(4, (CE_NOTE, NULL, "!std_report_target_groups:" + " vhci_do_scsi_cmd failed\n")); + kmem_free((void *)bufp, len); + scsi_destroy_pkt(pkt); + return (1); + } + ptr = bufp; + VHCI_DEBUG(6, (CE_NOTE, NULL, "!std_report_target_groups:" + " returned from target" + " port group: buf[0/1/2/3]: %x/%x/%x/%x\n", + ptr[0], ptr[1], ptr[2], ptr[3])); + rtpg_len = (unsigned int)((0xff & ptr[0]) << 24); + rtpg_len |= (unsigned int)((0xff & ptr[1]) << 16); + rtpg_len |= (unsigned int)((0xff & ptr[2]) << 8); + rtpg_len |= (unsigned int)(0xff & ptr[3]); + rtpg_len += 4; + if (rtpg_len > len) { + VHCI_DEBUG(4, (CE_NOTE, NULL, "!std_report_target_groups: " + "bufsize: %d greater than allocated buf: %d\n", + rtpg_len, len)); + VHCI_DEBUG(4, (CE_NOTE, NULL, "Retrying for size %d\n", + rtpg_len)); + kmem_free((void *)bufp, len); + len = (unsigned int)(rtpg_len + 1); + goto try_again; + } +#ifdef DEBUG + print_buf(bufp, rtpg_len); +#endif + end = ptr + rtpg_len; + ptr += 4; + while (ptr < end) { + mpapi_ptr = ptr; + l_tgt_port = ((ptr[2] & 0xff) << 8) + (ptr[3] & 0xff); + tpgs_state = ptr[0] & 0x0f; + tgt_port_cnt = (ptr[7] & 0xff); + VHCI_DEBUG(4, (CE_NOTE, NULL, "!std_report_tgt_groups:" + " tpgs state: %x" + " tgt_group: %x count: %x\n", tpgs_state, + l_tgt_port, tgt_port_cnt)); + ptr += 8; + for (i = 0; i < tgt_port_cnt; i++) { + lr_tgt_port = 0; + lr_tgt_port |= ((ptr[2] & 0Xff) << 8); + lr_tgt_port |= (ptr[3] & 0xff); + + if ((lr_tgt_port == rel_tgt_port) && + (l_tgt_port == tgt_port)) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "!std_report_tgt_groups:" + " found tgt_port: %x rel_tgt_port:%x" + " tpgs_state: %x\n", tgt_port, rel_tgt_port, + tpgs_state)); + /* + * once we have the preferred flag + * and a non-optimized state flag + * we will get preferred flag from the + * report target groups + */ + if (tpgs_state == STD_ACTIVE_OPTIMIZED) { + *pstate = STD_ACTIVE_OPTIMIZED; + *preferred = PCLASS_PREFERRED; + } else if (tpgs_state == + STD_ACTIVE_NONOPTIMIZED) { + *pstate = STD_ACTIVE_NONOPTIMIZED; + *preferred = PCLASS_NONPREFERRED; + } else if (tpgs_state == STD_STANDBY) { + *pstate = STD_STANDBY; + *preferred = PCLASS_NONPREFERRED; + } else { + *pstate = STD_UNAVAILABLE; + *preferred = PCLASS_NONPREFERRED; + } + vhci_mpapi_update_tpg_data(ap, mpapi_ptr); + kmem_free((void *)bufp, len); + scsi_destroy_pkt(pkt); + return (0); + } + VHCI_DEBUG(4, (CE_NOTE, NULL, "!std_report_tgt_groups:" + " tgt_port: %x rel_tgt_port:%x\n", tgt_port, + rel_tgt_port)); + ptr += 4; + } + } + *pstate = SCSI_PATH_INACTIVE; + *preferred = PCLASS_NONPREFERRED; + VHCI_DEBUG(1, (CE_NOTE, NULL, "!std_report_tgt_groups: " + "NO rel_TGTPRT MATCH!!! Assigning Default: state: %x " + "preferred: %d\n", *pstate, *preferred)); + kmem_free((void *)bufp, len); + scsi_destroy_pkt(pkt); + return (1); +} + +/* + * get the failover mode, ownership and if it has extended failover + * capability. The mode(bits5-4/byte5) is defined as implicit, explicit, or + * both. The state is defined as online-optimized(0h), + * online-nonoptimized(1h), standby(2h), offline(3h), + * and transitioning(fh). Currently, there is online, + * standby, and offline(defined in sunmdi.h). + * Online-nonoptimized will be a mode of secondary + * and an ownership of online. Thought about using a different mode but + * it appears the states are really for the states for secondary mode. + * We currently have IS_ONLINING, IS_OFFLINING - should we have TRANSITIONING + * to mean from online-optimized to online-nonoptimized or does onlining + * cover this? + */ +/* ARGSUSED */ +static int +std_get_fo_mode(struct scsi_device *sd, int *mode, + int *state, int *xlf_capable, int *preferred) +{ + int retval = 0; + struct buf *bp; + struct scsi_address *ap; + int lu = 0, rel_tgt_port = 0, tgt_port = 0x0; + + VHCI_DEBUG(6, (CE_NOTE, NULL, "!std_get_fo_mode: enter\n")); + *mode = *state = *xlf_capable = 0; + bp = getrbuf(KM_NOSLEEP); + if (bp == NULL) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!std_get_fo_mode: " + " failed getrbuf\n")); + return (1); + } + + ap = &sd->sd_address; + if (std_inquiry(ap, bp, mode)) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!std_get_fo_mode: " + " failed std_inquiry\n")); + retval = 1; + } else if (std_page83(ap, bp, &rel_tgt_port, &tgt_port, &lu)) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!std_get_fo_mode: " + " failed std_page83\n")); + retval = 1; + } else if (std_report_target_groups(ap, bp, rel_tgt_port, tgt_port, + state, preferred)) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!std_get_fo_mode: " + " failed std_report_target_groups\n")); + retval = 1; + } + + freerbuf(bp); + if (retval == 0) { + VHCI_DEBUG(6, (CE_NOTE, NULL, "!std_get_fo_mode: " + "SUCCESS\n")); + } + return (retval); +} + +/* ARGSUSED */ +static int +std_activate_explicit(struct scsi_device *sd, int xlf_capable) +{ + cmn_err(CE_NOTE, "Explicit Activation is done by " + "std_set_target_groups() call from MPAPI"); + return (1); +} + +/* + * Process the packet reason of CMD_PKT_CMPLT - return 0 if no + * retry and 1 if a retry should be done + */ +static int +std_process_cmplt_pkt(struct scsi_device *sd, struct scsi_pkt *pkt, + int *retry_cnt) +{ + struct scsi_extended_sense *sns; + + /* + * Re-initialize retry_cmd_cnt. Allow transport and + * cmd errors to go through a full retry count when + * these are encountered. This way TRAN/CMD errors + * retry count is not exhausted due to CMD_CMPLTs + * delay. This allows the system + * to brave a hick-up on the link at any given time, + * while waiting for the fo to complete. + */ + if (pkt->pkt_state & STATE_ARQ_DONE) { + sns = &(((struct scsi_arq_status *)(uintptr_t) + (pkt->pkt_scbp))->sts_sensedata); + if (sns->es_key == KEY_UNIT_ATTENTION) { + /* + * tpgs access state changed + */ + if (sns->es_add_code == STD_SCSI_ASC_STATE_CHG && + sns->es_qual_code == STD_SCSI_ASCQ_STATE_CHG_SUCC) { + /* XXX: update path info? */ + cmn_err(CE_WARN, "!Device failover" + " state change"); + } + return (1); + } else if (sns->es_key == KEY_NOT_READY) { + if ((*retry_cnt)++ >= + STD_FO_MAX_RETRIES) { + cmn_err(CE_WARN, "!Device failover" + " failed: timed out waiting " + "for path to become active"); + return (0); + } + VHCI_DEBUG(6, (CE_NOTE, NULL, + "!(sd:%p)lun " + "becoming active...\n", (void *)sd)); + drv_usecwait(STD_FO_RETRY_DELAY); + return (1); + } + cmn_err(CE_NOTE, "!Failover failed;" + " sense key:%x, ASC: %x, " + "ASCQ:%x", sns->es_key, + sns->es_add_code, sns->es_qual_code); + return (0); + } + switch (SCBP_C(pkt)) { + case STATUS_GOOD: + break; + case STATUS_CHECK: + VHCI_DEBUG(4, (CE_WARN, NULL, + "!(sd:%p):" + " status returned CHECK during std" + " path activation", (void *)sd)); + return (0); + case STATUS_QFULL: + VHCI_DEBUG(6, (CE_NOTE, NULL, "QFULL " + "status returned QFULL during std " + "path activation for %p\n", (void *)sd)); + drv_usecwait(5000); + return (1); + case STATUS_BUSY: + VHCI_DEBUG(6, (CE_NOTE, NULL, "BUSY " + "status returned BUSY during std " + "path activation for %p\n", (void *)sd)); + drv_usecwait(5000); + return (1); + default: + VHCI_DEBUG(4, (CE_WARN, NULL, + "!(sd:%p) Bad status returned during std " + "activation (pkt %p, status %x)", + (void *)sd, (void *)pkt, SCBP_C(pkt))); + return (0); + } + return (0); +} + +/* + * For now we are going to use primary/online and secondary/online. + * There is no standby path returned by the dsp and we may have + * to do something different for other devices that use standby + */ +/* ARGSUSED */ +static int +std_path_activate(struct scsi_device *sd, char *pathclass, +void *ctpriv) +{ + struct buf *bp; + struct scsi_pkt *pkt; + struct scsi_address *ap; + int err, retry_cnt, retry_cmd_cnt; + int mode, state, retval, xlf, preferred; + + ap = &sd->sd_address; + + mode = state = 0; + + if (std_get_fo_mode(sd, &mode, &state, &xlf, &preferred)) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!std_path_activate:" + " failed std_get_fo_mode\n")); + return (1); + } + if ((state == STD_ACTIVE_OPTIMIZED) || + (state == STD_ACTIVE_NONOPTIMIZED)) { + VHCI_DEBUG(4, (CE_NOTE, NULL, "!path already active for %p\n", + (void *)sd)); + return (0); + } + + if (mode != SCSI_IMPLICIT_FAILOVER) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "!mode is EXPLICIT for %p xlf %x\n", + (void *)sd, xlf)); + retval = std_activate_explicit(sd, xlf); + if (retval != 0) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "!(sd:%p)std_path_activate failed(1)\n", + (void *)sd)); + return (1); + } + } else { + VHCI_DEBUG(4, (CE_NOTE, NULL, "STD mode is IMPLICIT for %p\n", + (void *)sd)); + } + + bp = scsi_alloc_consistent_buf(ap, (struct buf *)NULL, DEV_BSIZE, + B_READ, NULL, NULL); + if (!bp) { + VHCI_DEBUG(4, (CE_WARN, NULL, + "!(sd:%p)std_path_activate failed to alloc buffer", + (void *)sd)); + return (1); + } + + pkt = scsi_init_pkt(ap, NULL, bp, CDB_GROUP1, + sizeof (struct scsi_arq_status), 0, PKT_CONSISTENT, NULL, NULL); + if (!pkt) { + VHCI_DEBUG(4, (CE_WARN, NULL, + "!(sd:%p)std_path_activate failed to initialize packet", + (void *)sd)); + scsi_free_consistent_buf(bp); + return (1); + } + + (void) scsi_setup_cdb((union scsi_cdb *)(uintptr_t)pkt->pkt_cdbp, + SCMD_READ, 1, 1, 0); + pkt->pkt_time = 3*30; + pkt->pkt_flags |= FLAG_NOINTR; + + retry_cnt = 0; + retry_cmd_cnt = 0; +retry: + err = scsi_transport(pkt); + if (err != TRAN_ACCEPT) { + /* + * Retry TRAN_BUSY till STD_FO_MAX_RETRIES is exhausted. + * All other errors are fatal and should not be retried. + */ + if ((err == TRAN_BUSY) && + (retry_cnt++ < STD_FO_MAX_RETRIES)) { + drv_usecwait(STD_FO_RETRY_DELAY); + goto retry; + } + cmn_err(CE_WARN, "Failover failed, " + "couldn't transport packet"); + scsi_destroy_pkt(pkt); + scsi_free_consistent_buf(bp); + return (1); + } + switch (pkt->pkt_reason) { + case CMD_CMPLT: + retry_cmd_cnt = 0; + retval = std_process_cmplt_pkt(sd, pkt, &retry_cnt); + if (retval != 0) { + goto retry; + } + break; + case CMD_TIMEOUT: + cmn_err(CE_WARN, "!Failover failed: timed out "); + retval = 1; + break; + case CMD_INCOMPLETE: + case CMD_RESET: + case CMD_ABORTED: + case CMD_TRAN_ERR: + /* + * Increased the number of retries when these error + * cases are encountered. Also added a 1 sec wait + * before retrying. + */ + if (retry_cmd_cnt++ < STD_FO_MAX_CMD_RETRIES) { + drv_usecwait(STD_FO_CMD_RETRY_DELAY); + VHCI_DEBUG(4, (CE_WARN, NULL, + "!Retrying path activation due to " + "pkt reason:%x, retry cnt:%d", + pkt->pkt_reason, retry_cmd_cnt)); + goto retry; + } + /* FALLTHROUGH */ + default: + cmn_err(CE_WARN, "!Path activation did not " + "complete successfully," + "(pkt reason %x)", pkt->pkt_reason); + retval = 1; + break; + } + + + VHCI_DEBUG(4, (CE_NOTE, NULL, "!Path activation success\n")); + scsi_destroy_pkt(pkt); + scsi_free_consistent_buf(bp); + return (retval); +} + +/* ARGSUSED */ +static int std_path_deactivate(struct scsi_device *sd, char *pathclass, +void *ctpriv) +{ + return (0); +} + +/* ARGSUSED */ +static int +std_path_get_opinfo(struct scsi_device *sd, struct scsi_path_opinfo +*opinfo, void *ctpriv) +{ + int mode, preferred, state, xlf; + + opinfo->opinfo_rev = OPINFO_REV; + + if (std_get_fo_mode(sd, &mode, &state, &xlf, &preferred)) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!std_path_getopinfo:" + " failed std_get_fo_mode\n")); + return (1); + } + + if (state == STD_ACTIVE_OPTIMIZED) { + opinfo->opinfo_path_state = SCSI_PATH_ACTIVE; + } else if (state == STD_ACTIVE_NONOPTIMIZED) { + opinfo->opinfo_path_state = SCSI_PATH_ACTIVE_NONOPT; + } else if (state == STD_STANDBY) { + opinfo->opinfo_path_state = SCSI_PATH_INACTIVE; + } else if (state == STD_UNAVAILABLE) { + opinfo->opinfo_path_state = SCSI_PATH_INACTIVE; + } + if (preferred) { + (void) strcpy(opinfo->opinfo_path_attr, PCLASS_PRIMARY); + } else { + (void) strcpy(opinfo->opinfo_path_attr, PCLASS_SECONDARY); + } + VHCI_DEBUG(4, (CE_NOTE, NULL, "std_path_get_opinfo: " + "class: %s state: %s\n", opinfo->opinfo_path_attr, + opinfo->opinfo_path_state == SCSI_PATH_ACTIVE ? + "ACTIVE" : "INACTIVE")); + opinfo->opinfo_xlf_capable = 0; + opinfo->opinfo_pswtch_best = 30; + opinfo->opinfo_pswtch_worst = 3*30; + opinfo->opinfo_preferred = (uint16_t)preferred; + opinfo->opinfo_mode = (uint16_t)mode; + + return (0); +} + +/* ARGSUSED */ +static int std_path_ping(struct scsi_device *sd, void *ctpriv) +{ + /* + * For future use + */ + return (1); +} + +/* + * Analyze the sense code to determine whether failover process + */ +/* ARGSUSED */ +static int +std_analyze_sense(struct scsi_device *sd, struct scsi_extended_sense +*sense, void *ctpriv) +{ + int rval = SCSI_SENSE_UNKNOWN; + + if ((sense->es_key == KEY_UNIT_ATTENTION) && + (sense->es_add_code == STD_SCSI_ASC_STATE_CHG) && + (sense->es_qual_code == STD_SCSI_ASCQ_STATE_CHG_SUCC)) { + rval = SCSI_SENSE_STATE_CHANGED; + VHCI_DEBUG(4, (CE_NOTE, NULL, "!std_analyze_sense:" + " sense_key:%x, add_code: %x, qual_code:%x" + " sense:%x\n", sense->es_key, sense->es_add_code, + sense->es_qual_code, rval)); + } else if ((sense->es_key == KEY_NOT_READY) && + (sense->es_add_code == STD_LOGICAL_UNIT_NOT_ACCESSIBLE) && + (sense->es_qual_code == STD_TGT_PORT_UNAVAILABLE)) { + rval = SCSI_SENSE_INACTIVE; + VHCI_DEBUG(4, (CE_NOTE, NULL, "!std_analyze_sense:" + " sense_key:%x, add_code: %x, qual_code:%x" + " sense:%x\n", sense->es_key, sense->es_add_code, + sense->es_qual_code, rval)); + } else if ((sense->es_key == KEY_ILLEGAL_REQUEST) && + (sense->es_add_code == STD_SCSI_ASC_INVAL_PARAM_LIST)) { + rval = SCSI_SENSE_NOFAILOVER; + VHCI_DEBUG(1, (CE_NOTE, NULL, "!std_analyze_sense:" + " sense_key:%x, add_code: %x, qual_code:%x" + " sense:%x\n", sense->es_key, sense->es_add_code, + sense->es_qual_code, rval)); + } else if ((sense->es_key == KEY_ILLEGAL_REQUEST) && + (sense->es_add_code == STD_SCSI_ASC_INVAL_CMD_OPCODE)) { + rval = SCSI_SENSE_NOFAILOVER; + VHCI_DEBUG(1, (CE_NOTE, NULL, "!std_analyze_sense:" + " sense_key:%x, add_code: %x, qual_code:%x" + " sense:%x\n", sense->es_key, sense->es_add_code, + sense->es_qual_code, rval)); + } else { + /* + * At this point sense data may be for power-on-reset + * UNIT ATTN hardware errors, vendor unqiue sense data etc. + * For all these cases, return SCSI_SENSE_UNKNOWN. + */ + VHCI_DEBUG(1, (CE_NOTE, NULL, "!Analyze sense UNKNOWN:" + " sense key:%x, ASC:%x, ASCQ:%x\n", sense->es_key, + sense->es_add_code, sense->es_qual_code)); + } + + return (rval); +} + +/* ARGSUSED */ +static int +std_pathclass_next(char *cur, char **nxt, void *ctpriv) +{ + /* + * The first phase does not have a standby path so + * there will be no explicit failover - when standard tpgs. + * standard defines preferred flag then we should start + * using this as the selection mechanism - there can be + * preferred primary standby that we should fail to first and then + * nonpreferred secondary standby. + */ + if (cur == NULL) { + *nxt = PCLASS_PRIMARY; + return (0); + } else if (strcmp(cur, PCLASS_PRIMARY) == 0) { + *nxt = PCLASS_SECONDARY; + return (0); + } else if (strcmp(cur, PCLASS_SECONDARY) == 0) { + return (ENOENT); + } else { + return (EINVAL); + } +} + +int +std_set_target_groups(struct scsi_address *ap, int set_state, int tpg_id) +{ + struct scsi_pkt *pkt; + struct buf *bp; + int len, rval, ss = SCSI_SENSE_UNKNOWN; + char *bufp; + struct scsi_extended_sense *sns; + + len = 8; + + bp = getrbuf(KM_NOSLEEP); + if (bp == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "!std_set_target_groups: " + " failed getrbuf")); + return (1); + } + + bufp = kmem_zalloc(len, KM_NOSLEEP); + if (bufp == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "!std_set_target_groups: " + "request packet allocation for %d failed....", len)); + freerbuf(bp); + return (1); + } + + bp->b_un.b_addr = bufp; + bp->b_flags = B_READ; + bp->b_bcount = len; + bp->b_resid = 0; + + bufp[4] = (0x0f & set_state); + bufp[6] = (0xff00 & tpg_id) >> 8; + bufp[7] = (0x00ff & tpg_id); + + pkt = scsi_init_pkt(ap, NULL, bp, CDB_GROUP5, + sizeof (struct scsi_arq_status), 0, 0, NULL, NULL); + + if (pkt == NULL) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!std_set_target_groups: scsi_init_pkt error\n")); + freerbuf(bp); + kmem_free((void *)bufp, len); + return (1); + } + + /* + * Sends 1 TPG descriptor only. Hence Parameter list length pkt_cdbp[9] + * is set to 8 bytes - Refer SPC3 for details. + */ + pkt->pkt_cdbp[0] = SCMD_SET_TARGET_PORT_GROUP; + pkt->pkt_cdbp[1] = STD_SCSI_TPG_SERVICE_ACTION; + pkt->pkt_cdbp[9] = 8; + pkt->pkt_time = 90; + + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!std_set_target_groups: sending set target port group:" + " cdb[0/1/6/7/8/9]: %x/%x/%x/%x/%x/%x\n", pkt->pkt_cdbp[0], + pkt->pkt_cdbp[1], pkt->pkt_cdbp[6], pkt->pkt_cdbp[7], + pkt->pkt_cdbp[8], pkt->pkt_cdbp[9])); + +#ifdef DEBUG + print_buf(bufp, len); +#endif + rval = vhci_do_scsi_cmd(pkt); + + if (rval == 0) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!std_set_target_groups:" + " vhci_do_scsi_cmd failed\n")); + freerbuf(bp); + kmem_free((void *)bufp, len); + scsi_destroy_pkt(pkt); + return (-1); + } else if ((pkt->pkt_reason == CMD_CMPLT) && + (SCBP_C(pkt) == STATUS_CHECK) && + (pkt->pkt_state & STATE_ARQ_DONE)) { + sns = &(((struct scsi_arq_status *)(uintptr_t) + (pkt->pkt_scbp))->sts_sensedata); + + if ((sns->es_key == KEY_UNIT_ATTENTION) && + (sns->es_add_code == STD_SCSI_ASC_STATE_CHG) && + (sns->es_qual_code == STD_SCSI_ASCQ_STATE_CHG_SUCC)) { + ss = SCSI_SENSE_STATE_CHANGED; + VHCI_DEBUG(4, (CE_NOTE, NULL, "!std_set_target_groups:" + " sense:%x, add_code: %x, qual_code:%x" + " sense:%x\n", sns->es_key, sns->es_add_code, + sns->es_qual_code, ss)); + } else if ((sns->es_key == KEY_ILLEGAL_REQUEST) && + (sns->es_add_code == STD_SCSI_ASC_INVAL_PARAM_LIST)) { + ss = SCSI_SENSE_NOFAILOVER; + VHCI_DEBUG(1, (CE_NOTE, NULL, "!std_set_target_groups:" + " sense:%x, add_code: %x, qual_code:%x" + " sense:%x\n", sns->es_key, sns->es_add_code, + sns->es_qual_code, ss)); + } else if ((sns->es_key == KEY_ILLEGAL_REQUEST) && + (sns->es_add_code == STD_SCSI_ASC_INVAL_CMD_OPCODE)) { + ss = SCSI_SENSE_NOFAILOVER; + VHCI_DEBUG(1, (CE_NOTE, NULL, "!std_set_target_groups:" + " sense_key:%x, add_code: %x, qual_code:%x" + " sense:%x\n", sns->es_key, sns->es_add_code, + sns->es_qual_code, rval)); + } else { + /* + * At this point sns data may be for power-on-reset + * UNIT ATTN hardware errors, vendor unqiue sense etc. + * For all these cases, sense is unknown. + */ + ss = SCSI_SENSE_NOFAILOVER; + VHCI_DEBUG(1, (CE_NOTE, NULL, "!std_set_target_groups: " + " sense UNKNOWN: sense key:%x, ASC:%x, ASCQ:%x\n", + sns->es_key, sns->es_add_code, sns->es_qual_code)); + } + + if (ss == SCSI_SENSE_STATE_CHANGED) { + freerbuf(bp); + kmem_free((void *)bufp, len); + scsi_destroy_pkt(pkt); + return (0); + } + } + + freerbuf(bp); + kmem_free((void *)bufp, len); + scsi_destroy_pkt(pkt); + return (1); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/common/io/scsi/adapters/scsi_vhci/mpapi_impl.c Fri Aug 10 17:00:37 2007 -0700 @@ -0,0 +1,4188 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * SNIA Multipath Management API implementation + */ + +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/disp.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/sunmdi.h> +#include <sys/mdi_impldefs.h> +#include <sys/scsi/scsi.h> +#include <sys/scsi/impl/services.h> +#include <sys/scsi/impl/scsi_reset_notify.h> +#include <sys/scsi/adapters/scsi_vhci.h> + +/* used to manually force a request sense */ +int vhci_force_manual_sense = 0; + +#define STD_ACTIVE_OPTIMIZED 0x0 +#define STD_ACTIVE_NONOPTIMIZED 0x1 +#define STD_STANDBY 0x2 +#define STD_UNAVAILABLE 0x3 +#define STD_TRANSITIONING 0xf + +/* + * MP-API Prototypes + */ +int vhci_mpapi_init(struct scsi_vhci *); +void vhci_mpapi_add_dev_prod(struct scsi_vhci *, char *); +int vhci_mpapi_ctl(dev_t, int, intptr_t, int, cred_t *, int *); +void vhci_update_mpapi_data(struct scsi_vhci *, + scsi_vhci_lun_t *, mdi_pathinfo_t *); +void* vhci_get_mpapi_item(struct scsi_vhci *, mpapi_list_header_t *, + uint8_t, void*); +int vhci_mpapi_sync_init_port_list(dev_info_t *, void *); +int vhci_mpapi_get_vhci(dev_info_t *, void *); +void vhci_mpapi_set_path_state(dev_info_t *, mdi_pathinfo_t *, int); +void vhci_mpapi_synthesize_tpg_data(struct scsi_vhci *, scsi_vhci_lun_t *, + mdi_pathinfo_t *); +void vhci_mpapi_update_tpg_data(struct scsi_address *, char *); +int vhci_mpapi_update_tpg_acc_state_for_lu(struct scsi_vhci *, + scsi_vhci_lun_t *); + +/* Static Functions */ +static int vhci_get_driver_prop(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_dev_prod_list(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_dev_prod_prop(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_lu_list(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_lu_list_from_tpg(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_tpg_list_for_lu(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_lu_prop(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_path_list_for_mp_lu(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_path_list_for_init_port(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_path_list_for_target_port(struct scsi_vhci *, + mp_iocdata_t *, void *, void *, int); +static int vhci_get_path_prop(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_init_port_list(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_init_port_prop(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_target_port_prop(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_tpg_prop(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_target_port_list_for_tpg(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_set_tpg_access_state(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_prop_lb_list(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_get_prop_lb_prop(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_assign_lu_to_tpg(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_enable_auto_failback(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_disable_auto_failback(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_enable_path(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_disable_path(struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_send_uscsi_cmd(dev_t dev, struct scsi_vhci *, mp_iocdata_t *, + void *, void *, int); +static int vhci_mpapi_validate(void *, mp_iocdata_t *, int, cred_t *); +static uint64_t vhci_mpapi_create_oid(mpapi_priv_t *, uint8_t); +static int vhci_mpapi_ioctl(dev_t dev, struct scsi_vhci *, void *, + mp_iocdata_t *, int, cred_t *); +static int vhci_mpapi_add_to_list(mpapi_list_header_t *, mpapi_item_list_t *); +static mpapi_item_list_t *vhci_mpapi_create_item(struct scsi_vhci *, + uint8_t, void *); +static mpapi_item_list_t *vhci_mpapi_get_tpg_item(struct scsi_vhci *, + uint32_t, void *, char *, void *); +static mpapi_list_header_t *vhci_mpapi_create_list_head(); +static int vhci_get_mpiocdata(const void *, mp_iocdata_t *, int); +static int vhci_is_model_type32(int); +static int vhci_mpapi_copyout_iocdata(void *, void *, int); +static int vhci_mpapi_chk_last_path(mdi_pathinfo_t *); +static int vhci_mpapi_sync_lu_oid_list(struct scsi_vhci *); +static mpapi_item_list_t *vhci_mpapi_get_tpg_for_lun(struct scsi_vhci *, + char *, void *, void *); +static int vhci_mpapi_check_tp_in_tpg(mpapi_tpg_data_t *tpgdata, void *tp); +static void vhci_mpapi_log_sysevent(dev_info_t *, uint64_t *, char *); +static mpapi_item_list_t *vhci_mpapi_match_pip(struct scsi_vhci *, + mpapi_item_list_t *, void *); +static mpapi_item_list_t *vhci_mpapi_match_lu(struct scsi_vhci *, + mpapi_item_list_t *, void *); +static void *vhci_mpapi_get_rel_tport_pair(struct scsi_vhci *vhci, + mpapi_list_header_t *list, void *tgt_port, uint32_t rel_tid); + +/* + * Extern variables, structures and functions + */ +extern void *vhci_softstate; +extern char vhci_version_name[]; +extern int (*tpgs_set_target_groups)(struct scsi_address *, int, int); + +extern void mdi_vhci_walk_phcis(dev_info_t *, + int (*)(dev_info_t *, void *), void *); +extern void vhci_update_pathstates(void *); +extern int vhci_uscsi_iostart(struct buf *bp); + +/* + * Routine for SCSI VHCI MPAPI IOCTL implementation. + */ +/* ARGSUSED */ +int +vhci_mpapi_ctl(dev_t dev, int cm, intptr_t data, int mode, + cred_t *credp, int *rval) +{ + struct scsi_vhci *vhci; + dev_info_t *vdip; + int retval = 0; + mp_iocdata_t mpio_blk; + mp_iocdata_t *mpioc = &mpio_blk; + + /* Check for validity of vhci structure */ + vhci = ddi_get_soft_state(vhci_softstate, MINOR2INST(getminor(dev))); + if (vhci == NULL) { + return (ENXIO); + } + + mutex_enter(&vhci->vhci_mutex); + if ((vhci->vhci_state & VHCI_STATE_OPEN) == 0) { + mutex_exit(&vhci->vhci_mutex); + return (ENXIO); + } + mutex_exit(&vhci->vhci_mutex); + + /* Get the vhci dip */ + vdip = vhci->vhci_dip; + ASSERT(vdip != NULL); + + /* + * Get IOCTL parameters from userland + */ + if (vhci_get_mpiocdata((const void *)data, mpioc, mode) != 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "!vhci_mpapi_ctl: " + "vhci_get_mpiocdata() failed")); + } + if (mpioc->mp_cmd < MP_API_SUBCMD_MIN || + mpioc->mp_cmd > MP_API_SUBCMD_MAX) { + return (ENXIO); + } + + retval = vhci_mpapi_ioctl(dev, vhci, (void *)data, mpioc, mode, credp); + + return (retval); +} + +/* ARGSUSED */ +static int +vhci_mpapi_validate(void *udata, mp_iocdata_t *mpioc, int mode, cred_t *credp) +{ + int rval = 0, olen = 0; + int mode32 = 0; + + if (vhci_is_model_type32(mode) == 1) { + mode32 = 1; + } + + switch (mpioc->mp_cmd) { + + case MP_GET_DEV_PROD_LIST: + case MP_GET_LU_LIST: /* XXX: This wont come; Plugin already has it */ + case MP_GET_INIT_PORT_LIST: /* XXX: This call wont come either */ + case MP_GET_TPG_LIST: + case MP_GET_PROPRIETARY_LOADBALANCE_LIST: + { + if ((mpioc->mp_olen == 0) || + (mpioc->mp_obuf == NULL) || + (mpioc->mp_xfer != MP_XFER_READ)) { + rval = EINVAL; + } + if (mpioc->mp_olen == 0) { + /* We don't know alen yet, No point trying to set it */ + mpioc->mp_errno = MP_MORE_DATA; + rval = MP_MORE_DATA; + } + } + break; + + case MP_GET_DRIVER_PROP: + { + olen = sizeof (mp_driver_prop_t); + /* Adjust olen to account for the caddr_t in 32-bit mode */ + if (mode32 == 1) { + olen -= 4; + } + + if ((mpioc->mp_obuf == NULL) || + (mpioc->mp_olen < olen) || + (mpioc->mp_xfer != MP_XFER_READ)) { + rval = EINVAL; + } + if (mpioc->mp_olen < olen) { + mpioc->mp_alen = olen; + mpioc->mp_errno = MP_MORE_DATA; + } + } + break; + + case MP_GET_DEV_PROD_PROP: + { + olen = sizeof (mp_dev_prod_prop_t); + + if ((mpioc->mp_olen < olen) || + (mpioc->mp_ilen < sizeof (uint64_t)) || + (mpioc->mp_obuf == NULL) || + (mpioc->mp_ibuf == NULL) || + (mpioc->mp_xfer != MP_XFER_READ)) { + rval = EINVAL; + } + if (mpioc->mp_olen < olen) { + mpioc->mp_alen = olen; + mpioc->mp_errno = MP_MORE_DATA; + } + } + break; + + case MP_GET_LU_PROP: + { + olen = sizeof (mp_logical_unit_prop_t); + /* Adjust olen to account for the caddr_t in 32-bit mode */ + if (mode32 == 1) { + olen -= 4; + } + + if ((mpioc->mp_ilen != sizeof (uint64_t)) || + (mpioc->mp_ibuf == NULL) || + (mpioc->mp_olen < olen) || + (mpioc->mp_obuf == NULL) || + (mpioc->mp_xfer != MP_XFER_READ)) { + rval = EINVAL; + } + if (mpioc->mp_olen < olen) { + mpioc->mp_alen = olen; + mpioc->mp_errno = MP_MORE_DATA; + } + } + break; + + case MP_GET_PATH_PROP: + { + olen = sizeof (mp_path_prop_t); + /* Adjust olen to account for the caddr_t in 32-bit mode */ + if (mode32 == 1) { + olen -= 4; + } + + if ((mpioc->mp_ilen != sizeof (uint64_t)) || + (mpioc->mp_ibuf == NULL) || + (mpioc->mp_olen < olen) || + (mpioc->mp_obuf == NULL) || + (mpioc->mp_xfer != MP_XFER_READ)) { + rval = EINVAL; + } + if (mpioc->mp_olen < olen) { + mpioc->mp_alen = olen; + mpioc->mp_errno = MP_MORE_DATA; + } + } + break; + + case MP_GET_INIT_PORT_PROP: + { + olen = sizeof (mp_init_port_prop_t); + + if ((mpioc->mp_ilen != sizeof (uint64_t)) || + (mpioc->mp_ibuf == NULL) || + (mpioc->mp_olen < olen) || + (mpioc->mp_obuf == NULL) || + (mpioc->mp_xfer != MP_XFER_READ)) { + rval = EINVAL; + } + if (mpioc->mp_olen < olen) { + mpioc->mp_alen = olen; + mpioc->mp_errno = MP_MORE_DATA; + } + } + break; + + case MP_GET_TARGET_PORT_PROP: + { + olen = sizeof (mp_target_port_prop_t); + + if ((mpioc->mp_ilen != sizeof (uint64_t)) || + (mpioc->mp_ibuf == NULL) || + (mpioc->mp_olen < olen) || + (mpioc->mp_obuf == NULL) || + (mpioc->mp_xfer != MP_XFER_READ)) { + rval = EINVAL; + } + if (mpioc->mp_olen < olen) { + mpioc->mp_alen = olen; + mpioc->mp_errno = MP_MORE_DATA; + } + } + break; + + case MP_GET_TPG_PROP: + { + olen = sizeof (mp_tpg_prop_t); + + if ((mpioc->mp_ilen != sizeof (uint64_t)) || + (mpioc->mp_ibuf == NULL) || + (mpioc->mp_olen < olen) || + (mpioc->mp_obuf == NULL) || + (mpioc->mp_xfer != MP_XFER_READ)) { + rval = EINVAL; + } + if (mpioc->mp_olen < olen) { + mpioc->mp_alen = olen; + mpioc->mp_errno = MP_MORE_DATA; + } + } + break; + + case MP_GET_PROPRIETARY_LOADBALANCE_PROP: + { + olen = sizeof (mp_proprietary_loadbalance_prop_t); + /* Adjust olen to account for the caddr_t in 32-bit mode */ + if (mode32 == 1) { + olen -= 4; + } + + if ((mpioc->mp_ilen != sizeof (uint64_t)) || + (mpioc->mp_ibuf == NULL) || + (mpioc->mp_olen < olen) || + (mpioc->mp_obuf == NULL) || + (mpioc->mp_xfer != MP_XFER_READ)) { + rval = EINVAL; + } + if (mpioc->mp_olen < olen) { + mpioc->mp_alen = olen; + mpioc->mp_errno = MP_MORE_DATA; + } + } + break; + + case MP_GET_PATH_LIST_FOR_MP_LU: + case MP_GET_PATH_LIST_FOR_INIT_PORT: + case MP_GET_PATH_LIST_FOR_TARGET_PORT: + case MP_GET_LU_LIST_FROM_TPG: + case MP_GET_TPG_LIST_FOR_LU: + case MP_GET_TARGET_PORT_LIST_FOR_TPG: + { + if ((mpioc->mp_ilen != sizeof (uint64_t)) || + (mpioc->mp_ibuf == NULL) || + (mpioc->mp_olen == 0) || + (mpioc->mp_obuf == NULL) || + (mpioc->mp_xfer != MP_XFER_READ)) { + rval = EINVAL; + } + if (mpioc->mp_olen == 0) { + /* We don't know alen yet, No point trying to set it */ + mpioc->mp_errno = MP_MORE_DATA; + rval = MP_MORE_DATA; + } + } + break; + + case MP_SET_TPG_ACCESS_STATE: + { + if (drv_priv(credp) != 0) { + rval = EPERM; + break; + } + if ((mpioc->mp_ilen != sizeof (mp_set_tpg_state_req_t)) || + (mpioc->mp_ibuf == NULL) || + (mpioc->mp_xfer != MP_XFER_WRITE)) { + rval = EINVAL; + } + } + break; + + case MP_ENABLE_AUTO_FAILBACK: + case MP_DISABLE_AUTO_FAILBACK: + { + if (drv_priv(credp) != 0) { + rval = EPERM; + break; + } + if ((mpioc->mp_ibuf == NULL) || + (mpioc->mp_xfer != MP_XFER_WRITE)) { + rval = EINVAL; + } + } + break; + + case MP_ENABLE_PATH: + case MP_DISABLE_PATH: + { + if (drv_priv(credp) != 0) { + rval = EPERM; + break; + } + if ((mpioc->mp_ilen != sizeof (uint64_t)) || + (mpioc->mp_ibuf == NULL) || + (mpioc->mp_xfer != MP_XFER_WRITE)) { + rval = EINVAL; + } + } + break; + + case MP_SEND_SCSI_CMD: + { + cred_t *cr; + int olen = 0; + + cr = ddi_get_cred(); + if (drv_priv(credp) != 0 && drv_priv(cr) != 0) { + rval = EPERM; + break; + } + if (mode32 == 1) { + olen = sizeof (struct uscsi_cmd32); + } else { + olen = sizeof (struct uscsi_cmd); + } + /* oid is in the ibuf and the uscsi cmd is in the obuf */ + if ((mpioc->mp_ilen != sizeof (uint64_t)) || + (mpioc->mp_ibuf == NULL) || + (mpioc->mp_olen != olen) || + (mpioc->mp_obuf == NULL)) { + rval = EINVAL; + } + } + break; + + case MP_ASSIGN_LU_TO_TPG: + { + if (drv_priv(credp) != 0) { + rval = EPERM; + break; + } + if ((mpioc->mp_ilen != sizeof (mp_lu_tpg_pair_t)) || + (mpioc->mp_ibuf == NULL) || + (mpioc->mp_xfer != MP_XFER_WRITE)) { + rval = EINVAL; + } + } + break; + + default: + { + rval = EINVAL; + } + + } /* Closing the main switch */ + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_driver_prop(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = 0; + mp_driver_prop_t *mpdp = (mp_driver_prop_t *)output_data; + + if (output_data == NULL) { + return (EINVAL); + } + + (void) strlcpy(mpdp->driverVersion, vhci_version_name, + sizeof (mpdp->driverVersion)); + mpdp->supportedLoadBalanceTypes = + MP_DRVR_LOAD_BALANCE_TYPE_NONE | + MP_DRVR_LOAD_BALANCE_TYPE_ROUNDROBIN | + MP_DRVR_LOAD_BALANCE_TYPE_LBA_REGION; + mpdp->canSetTPGAccess = B_TRUE; + mpdp->canOverridePaths = B_FALSE; + mpdp->exposesPathDeviceFiles = B_FALSE; + (void) strlcpy(mpdp->deviceFileNamespace, "/devices/scsi_vhci", + sizeof (mpdp->deviceFileNamespace)); + mpdp->onlySupportsSpecifiedProducts = 1; + mpdp->maximumWeight = 1; + mpdp->failbackPollingRateMax = 0; + mpdp->currentFailbackPollingRate = 0; + mpdp->autoFailbackSupport = MP_DRVR_AUTO_FAILBACK_SUPPORT; + mutex_enter(&vhci->vhci_mutex); + mpdp->autoFailbackEnabled = + ((vhci->vhci_conf_flags & VHCI_CONF_FLAGS_AUTO_FAILBACK) ? + 1 : 0); + mutex_exit(&vhci->vhci_mutex); + mpdp->defaultLoadBalanceType = + MP_DRVR_LOAD_BALANCE_TYPE_ROUNDROBIN; + mpdp->probingPollingRateMax = 0; + mpdp->currentProbingPollingRate = 0; + mpdp->autoProbingSupport = 0; + mpdp->autoProbingEnabled = 0; + + if (ddi_copyout(output_data, (void *)mpioc->mp_obuf, + mpioc->mp_olen, mode) != 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_driver_prop: " + "ddi_copyout() for 64-bit failed")); + mpioc->mp_errno = EFAULT; + } else { + mpioc->mp_errno = 0; + mpioc->mp_alen = sizeof (mp_iocdata_t); + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_dev_prod_list(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int count = 0, rval = 0; + int list_len = mpioc->mp_olen/sizeof (uint64_t); + uint64_t *oid_list = (uint64_t *)(output_data); + mpapi_item_list_t *ilist; + + if (output_data == NULL) { + return (EINVAL); + } + + /* + * XXX: Get the Plugin OID from the input_data and apply below + * Currently, we know we have only 1 plugin, so it ok to directly + * return this only plugin's device product list. + */ + + ilist = vhci->mp_priv-> + obj_hdr_list[MP_OBJECT_TYPE_DEVICE_PRODUCT]->head; + + while (ilist != NULL) { + if (count < list_len) { + oid_list[count] = (uint64_t)ilist->item->oid.raw_oid; + } else { + rval = MP_MORE_DATA; + } + ilist = ilist->next; + count++; + } + + mpioc->mp_alen = (uint32_t)(count * sizeof (uint64_t)); + if ((rval == MP_MORE_DATA) || (mpioc->mp_alen > mpioc->mp_olen)) { + mpioc->mp_errno = MP_MORE_DATA; + return (EINVAL); + } + + if (ddi_copyout(output_data, (void *)mpioc->mp_obuf, + (count * sizeof (uint64_t)), mode) != 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_dev_prod_list: " + "ddi_copyout() failed")); + mpioc->mp_errno = EFAULT; + rval = EINVAL; + } else { + mpioc->mp_errno = 0; + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_dev_prod_prop(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = 0; + uint64_t *oid = (uint64_t *)(input_data); + mp_dev_prod_prop_t *dev_prop = NULL; + mpapi_item_list_t *ilist; + + if ((output_data == NULL) || (input_data == NULL)) { + return (EINVAL); + } + ilist = vhci->mp_priv-> + obj_hdr_list[MP_OBJECT_TYPE_DEVICE_PRODUCT]->head; + while ((ilist != NULL) && (*oid != ilist->item->oid.raw_oid)) { + ilist = ilist->next; + } + if (ilist != NULL) { + dev_prop = (mp_dev_prod_prop_t *)(ilist->item->idata); + if (dev_prop == NULL) { + return (EINVAL); + } + } else { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_dev_prod_prop: " + "OID NOT FOUND")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + /* + * Here were are not using the 'output_data' that is + * passed as the required information is already + * in the required format! + */ + if (ddi_copyout((void *)dev_prop, mpioc->mp_obuf, + sizeof (mp_dev_prod_prop_t), mode) != 0) { + return (EFAULT); + } + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_lu_list(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int count = 0, rval = 0; + int list_len = mpioc->mp_olen/sizeof (uint64_t); + uint64_t *oid_list = (uint64_t *)(output_data); + mpapi_item_list_t *ilist; + mpapi_lu_data_t *ld; + + if (output_data == NULL) { + return (EINVAL); + } + + ilist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_MULTIPATH_LU]->head; + + while (ilist != NULL) { + if (count < list_len) { + oid_list[count] = (uint64_t)(ilist->item->oid.raw_oid); + } else { + rval = MP_MORE_DATA; + } + ld = ilist->item->idata; + if (ld->valid == 0) { + count--; + } + ilist = ilist->next; + count++; + } + + mpioc->mp_alen = (uint32_t)(count * sizeof (uint64_t)); + if ((rval == MP_MORE_DATA) || (mpioc->mp_alen > mpioc->mp_olen)) { + mpioc->mp_errno = MP_MORE_DATA; + return (EINVAL); + } + + if (ddi_copyout(output_data, (void *)mpioc->mp_obuf, + (count * sizeof (uint64_t)), mode) != 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_lu_list: " + "ddi_copyout() FAILED")); + mpioc->mp_errno = EFAULT; + rval = EINVAL; + } else { + mpioc->mp_errno = 0; + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_lu_list_from_tpg(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int count = 0, rval = 0; + int list_len = mpioc->mp_olen/sizeof (uint64_t); + uint64_t *oid_list = (uint64_t *)(output_data); + uint64_t *oid = (uint64_t *)(input_data); + mpapi_item_list_t *ilist, *tpg_lu_list = NULL; + mpapi_tpg_data_t *mptpglu; + mpapi_lu_data_t *ld; + + ilist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_TARGET_PORT_GROUP] + ->head; + + while ((ilist != NULL) && (*oid != ilist->item->oid.raw_oid)) + ilist = ilist->next; + + if (ilist == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_lu_list_from_tpg: " + "OID NOT FOUND")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + rval = EINVAL; + } else if (*oid == ilist->item->oid.raw_oid) { + mptpglu = (mpapi_tpg_data_t *)(ilist->item->idata); + if (mptpglu->valid == 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_lu_list_from_" + "tpg: OID NOT FOUND - TPG IS INVALID")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + tpg_lu_list = mptpglu->lu_list->head; + } else { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_lu_list_from_tpg: " + "Unknown Error")); + } + + while (tpg_lu_list != NULL) { + if (count < list_len) { + oid_list[count] = (uint64_t)tpg_lu_list-> + item->oid.raw_oid; + } else { + rval = MP_MORE_DATA; + } + /* + * Get rid of the latest entry if item is invalid + */ + ld = tpg_lu_list->item->idata; + if (ld->valid == 0) { + count--; + } + tpg_lu_list = tpg_lu_list->next; + count++; + } + + mpioc->mp_alen = (uint32_t)(count * sizeof (uint64_t)); + if ((rval == MP_MORE_DATA) || (mpioc->mp_alen > mpioc->mp_olen)) { + mpioc->mp_errno = MP_MORE_DATA; + return (EINVAL); + } + + if ((count > 0) && (ddi_copyout(output_data, (void *)mpioc->mp_obuf, + (count * sizeof (uint64_t)), mode) != 0)) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_lu_list_from_tpg: " + "ddi_copyout() FAILED")); + mpioc->mp_errno = EFAULT; + rval = EINVAL; + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_tpg_list_for_lu(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int count = 0, rval = 0; + int list_len = mpioc->mp_olen/sizeof (uint64_t); + uint64_t *oid_list = (uint64_t *)(output_data); + uint64_t *oid = (uint64_t *)(input_data); + mpapi_item_list_t *ilist, *mplu_tpg_list = NULL; + mpapi_lu_data_t *mplutpg; + mpapi_tpg_data_t *tpgd; + + ilist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_MULTIPATH_LU]->head; + + while ((ilist != NULL) && (*oid != ilist->item->oid.raw_oid)) + ilist = ilist->next; + + if (ilist == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_tpg_list_for_lu: " + "OID NOT FOUND")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + rval = EINVAL; + } else if (*oid == ilist->item->oid.raw_oid) { + mplutpg = (mpapi_lu_data_t *)(ilist->item->idata); + if (mplutpg->valid == 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_tpg_list_for_" + "lu: OID NOT FOUND - LU IS OFFLINE")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + mplu_tpg_list = mplutpg->tpg_list->head; + } else { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_tpg_list_for_lu: " + "Unknown Error")); + } + + while (mplu_tpg_list != NULL) { + if (count < list_len) { + oid_list[count] = + (uint64_t)mplu_tpg_list->item->oid.raw_oid; + } else { + rval = MP_MORE_DATA; + } + tpgd = mplu_tpg_list->item->idata; + if (tpgd->valid == 0) { + count--; + } + mplu_tpg_list = mplu_tpg_list->next; + count++; + } + + mpioc->mp_alen = (uint32_t)(count * sizeof (uint64_t)); + if ((rval == MP_MORE_DATA) || (mpioc->mp_alen > mpioc->mp_olen)) { + mpioc->mp_errno = MP_MORE_DATA; + return (EINVAL); + } + + if ((count > 0) && (ddi_copyout(output_data, (void *)mpioc->mp_obuf, + (count * sizeof (uint64_t)), mode) != 0)) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_tpg_list_for_lu: " + "ddi_copyout() FAILED")); + mpioc->mp_errno = EFAULT; + rval = EINVAL; + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_lu_prop(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = 0; + uint64_t *oid = (uint64_t *)(input_data); + mp_logical_unit_prop_t *mplup_prop; + mpapi_item_list_t *ilist; + mpapi_lu_data_t *mplup; + + mplup_prop = (mp_logical_unit_prop_t *)output_data; + ilist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_MULTIPATH_LU]->head; + + while ((ilist != NULL) && (*oid != ilist->item->oid.raw_oid)) { + ilist = ilist->next; + } + + if (ilist != NULL) { + mplup = (mpapi_lu_data_t *)(ilist->item->idata); + if (mplup == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_lu_prop: " + "idata in ilist is NULL")); + return (EINVAL); + } else if (mplup->valid == 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_lu_prop: " + "OID NOT FOUND - LU GONE OFFLINE")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + mplup_prop = (mp_logical_unit_prop_t *)(&mplup->prop); + } else { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_lu_prop: " + "OID NOT FOUND")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + + /* + * Here were are not using the 'output_data' that is + * passed as the required information is already + * in the required format! + */ + if (ddi_copyout((void *)mplup_prop, mpioc->mp_obuf, + sizeof (mp_logical_unit_prop_t), mode) != 0) { + return (EFAULT); + } + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_path_list_for_mp_lu(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int count = 0, rval = 0; + int list_len = mpioc->mp_olen/sizeof (uint64_t); + uint64_t *oid_list = (uint64_t *)(output_data); + uint64_t *oid = (uint64_t *)(input_data); + mpapi_item_list_t *ilist, *mplu_path_list = NULL; + mpapi_lu_data_t *mplup; + + ilist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_MULTIPATH_LU]->head; + + while ((ilist != NULL) && (*oid != ilist->item->oid.raw_oid)) + ilist = ilist->next; + + if (ilist == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_path_list_for_mp_lu: " + "OID NOT FOUND")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + rval = EINVAL; + } else if (*oid == ilist->item->oid.raw_oid) { + mplup = (mpapi_lu_data_t *)(ilist->item->idata); + if (mplup->valid == 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_path_list_for_" + "mp_lu: MP_DRVR_PATH_STATE_LU_ERR - LU OFFLINE")); + mpioc->mp_errno = MP_DRVR_PATH_STATE_LU_ERR; + return (EINVAL); + } + mplu_path_list = mplup->path_list->head; + } else { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_path_list_for_mp_lu: " + "Unknown Error")); + } + + while (mplu_path_list != NULL) { + if (count < list_len) { + oid_list[count] = (uint64_t)mplu_path_list-> + item->oid.raw_oid; + } else { + rval = MP_MORE_DATA; + } + mplu_path_list = mplu_path_list->next; + count++; + } + + mpioc->mp_alen = (uint32_t)(count * sizeof (uint64_t)); + if ((rval == MP_MORE_DATA) || (mpioc->mp_alen > mpioc->mp_olen)) { + mpioc->mp_errno = MP_MORE_DATA; + return (EINVAL); + } + + if ((count > 0) && (ddi_copyout(output_data, (void *)mpioc->mp_obuf, + (count * sizeof (uint64_t)), mode) != 0)) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_path_list_for_mp_lu: " + "ddi_copyout() FAILED")); + mpioc->mp_errno = EFAULT; + rval = EINVAL; + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_path_list_for_init_port(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int count = 0, rval = 0; + int list_len = mpioc->mp_olen/sizeof (uint64_t); + uint64_t *oid_list = (uint64_t *)(output_data); + uint64_t *oid = (uint64_t *)(input_data); + mpapi_item_list_t *ilist, *mpinit_path_list = NULL; + mpapi_initiator_data_t *mpinitp; + + ilist = vhci->mp_priv-> + obj_hdr_list[MP_OBJECT_TYPE_INITIATOR_PORT]->head; + + /* + * While walking the mpapi database for initiator ports invalidate all + * initiator ports. The succeeding call to walk the phci list through + * MDI walker will validate the currently existing pHCIS. + */ + while (ilist != NULL) { + mpinitp = ilist->item->idata; + mpinitp->valid = 0; + ilist = ilist->next; + } + + mdi_vhci_walk_phcis(vhci->vhci_dip, vhci_mpapi_sync_init_port_list, + vhci); + + ilist = vhci->mp_priv-> + obj_hdr_list[MP_OBJECT_TYPE_INITIATOR_PORT]->head; + + while ((ilist != NULL) && (*oid != ilist->item->oid.raw_oid)) + ilist = ilist->next; + + if (ilist == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_path_list_for_init_" + "port: OID NOT FOUND")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + rval = EINVAL; + } else if (*oid == ilist->item->oid.raw_oid) { + mpinitp = (mpapi_initiator_data_t *)(ilist->item->idata); + if (mpinitp->valid == 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_path_list_for_" + "init_port: OID NOT FOUND - INIT PORT INVALID")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + mpinit_path_list = mpinitp->path_list->head; + } else { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_path_list_for_init_" + "port: Unknown Error")); + } + + while (mpinit_path_list != NULL) { + if (count < list_len) { + oid_list[count] = (uint64_t)mpinit_path_list-> + item->oid.raw_oid; + } else { + rval = MP_MORE_DATA; + } + mpinit_path_list = mpinit_path_list->next; + count++; + } + + mpioc->mp_alen = (uint32_t)(count * sizeof (uint64_t)); + if ((rval == MP_MORE_DATA) || (mpioc->mp_alen > mpioc->mp_olen)) { + mpioc->mp_errno = MP_MORE_DATA; + return (EINVAL); + } + + if ((count > 0) && (ddi_copyout(output_data, (void *)mpioc->mp_obuf, + (count * sizeof (uint64_t)), mode) != 0)) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_path_list_for_init_" + "port: ddi_copyout() FAILED")); + mpioc->mp_errno = EFAULT; + rval = EINVAL; + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_path_list_for_target_port(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int count = 0, rval = 0; + int list_len = mpioc->mp_olen/sizeof (uint64_t); + uint64_t *oid_list = (uint64_t *)(output_data); + uint64_t *oid = (uint64_t *)(input_data); + mpapi_item_list_t *ilist, *mptp_path_list = NULL; + mpapi_tport_data_t *mptpp; + + ilist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_TARGET_PORT]->head; + + while ((ilist != NULL) && (*oid != ilist->item->oid.raw_oid)) + ilist = ilist->next; + + if (ilist == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_path_list_for_target_" + "port: OID NOT FOUND")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + rval = EINVAL; + } else if (*oid == ilist->item->oid.raw_oid) { + mptpp = (mpapi_tport_data_t *)(ilist->item->idata); + if (mptpp->valid == 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_path_list_for_" + "target_port: OID NOT FOUND - TGT PORT INVALID")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + mptp_path_list = mptpp->path_list->head; + } else { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_path_list_for_target_" + "port: Unknown Error")); + } + + while (mptp_path_list != NULL) { + if (count < list_len) { + oid_list[count] = + (uint64_t)mptp_path_list->item->oid.raw_oid; + } else { + rval = MP_MORE_DATA; + } + mptp_path_list = mptp_path_list->next; + count++; + } + + mpioc->mp_alen = (uint32_t)(count * sizeof (uint64_t)); + if ((rval == MP_MORE_DATA) || (mpioc->mp_alen > mpioc->mp_olen)) { + mpioc->mp_errno = MP_MORE_DATA; + return (EINVAL); + } + + if ((count > 0) && (ddi_copyout(output_data, (void *)mpioc->mp_obuf, + (count * sizeof (uint64_t)), mode) != 0)) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_path_list_for_target_" + "port: ddi_copyout() FAILED")); + mpioc->mp_errno = EFAULT; + rval = EINVAL; + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_path_prop(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = 0; + uint64_t oid; + mp_path_prop_t *mpp_prop = (mp_path_prop_t *)output_data; + mpapi_item_list_t *ilist; + mpapi_path_data_t *mpp; + + ilist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_PATH_LU]->head; + + rval = ddi_copyin(mpioc->mp_ibuf, &oid, mpioc->mp_ilen, mode); + + while ((ilist != NULL) && (oid != ilist->item->oid.raw_oid)) + ilist = ilist->next; + + if (ilist != NULL) { + mpp = (mpapi_path_data_t *)(ilist->item->idata); + if (mpp == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_path_prop: " + "idata in ilist is NULL")); + return (EINVAL); + } + mpp_prop = (mp_path_prop_t *)(&mpp->prop); + } else { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_path_prop: " + "OID NOT FOUND")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + + /* + * Here were are not using the 'output_data' that is + * passed as the required information is already + * in the required format! + */ + if (ddi_copyout((void *)mpp_prop, mpioc->mp_obuf, + sizeof (mp_path_prop_t), mode) != 0) { + return (EFAULT); + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_init_port_list(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int count = 0, rval = 0; + int list_len = mpioc->mp_olen/sizeof (uint64_t); + uint64_t *oid_list = (uint64_t *)(output_data); + mpapi_item_list_t *ilist; + mpapi_initiator_data_t *initd; + + ilist = vhci->mp_priv-> + obj_hdr_list[MP_OBJECT_TYPE_INITIATOR_PORT]->head; + + /* + * While walking the mpapi database for initiator ports invalidate all + * initiator ports. The succeeding call to walk the phci list through + * MDI walker will validate the currently existing pHCIS. + */ + while (ilist != NULL) { + initd = ilist->item->idata; + initd->valid = 0; + ilist = ilist->next; + } + + mdi_vhci_walk_phcis(vhci->vhci_dip, vhci_mpapi_sync_init_port_list, + vhci); + + ilist = vhci->mp_priv-> + obj_hdr_list[MP_OBJECT_TYPE_INITIATOR_PORT]->head; + + while (ilist != NULL) { + if (count < list_len) { + oid_list[count] = (uint64_t)ilist->item->oid.raw_oid; + } else { + rval = MP_MORE_DATA; + } + /* + * Get rid of the latest entry if item is invalid + */ + initd = ilist->item->idata; + if (initd->valid == 0) { + count--; + } + ilist = ilist->next; + count++; + } + + mpioc->mp_alen = (uint32_t)(count * sizeof (uint64_t)); + if ((rval == MP_MORE_DATA) || (mpioc->mp_alen > mpioc->mp_olen)) { + mpioc->mp_errno = MP_MORE_DATA; + return (EINVAL); + } + + if (ddi_copyout(output_data, (void *)mpioc->mp_obuf, + (count * sizeof (uint64_t)), mode) != 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_init_port_list: " + "ddi_copyout() FAILED")); + mpioc->mp_errno = EFAULT; + rval = EINVAL; + } else { + mpioc->mp_errno = 0; + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_init_port_prop(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = 0; + uint64_t *oid = (uint64_t *)(input_data); + mp_init_port_prop_t *mpip_prop = (mp_init_port_prop_t *)output_data; + mpapi_item_list_t *ilist; + mpapi_initiator_data_t *mpip; + + ilist = vhci->mp_priv-> + obj_hdr_list[MP_OBJECT_TYPE_INITIATOR_PORT]->head; + + /* + * While walking the mpapi database for initiator ports invalidate all + * initiator ports. The succeeding call to walk the phci list through + * MDI walker will validate the currently existing pHCIS. + */ + while (ilist != NULL) { + mpip = ilist->item->idata; + mpip->valid = 0; + ilist = ilist->next; + } + + mdi_vhci_walk_phcis(vhci->vhci_dip, vhci_mpapi_sync_init_port_list, + vhci); + + ilist = vhci->mp_priv-> + obj_hdr_list[MP_OBJECT_TYPE_INITIATOR_PORT]->head; + + while ((ilist != NULL) && (*oid != ilist->item->oid.raw_oid)) { + ilist = ilist->next; + } + + if (ilist != NULL) { + mpip = (mpapi_initiator_data_t *)(ilist->item->idata); + if (mpip == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_init_port_prop:" + " idata in ilist is NULL")); + return (EINVAL); + } else if (mpip->valid == 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_init_port_prop" + ": OID NOT FOUND - INIT PORT IS INVALID")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + mpip_prop = (mp_init_port_prop_t *)(&mpip->prop); + } else { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_init_port_prop: " + "OID NOT FOUND")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + + /* + * Here were are not using the 'output_data' that is + * passed as the required information is already + * in the required format! + */ + if (ddi_copyout((void *)mpip_prop, mpioc->mp_obuf, + sizeof (mp_init_port_prop_t), mode) != 0) { + return (EFAULT); + } + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_target_port_prop(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = 0; + uint64_t *oid = (uint64_t *)(input_data); + mp_target_port_prop_t *mptp_prop; + mpapi_item_list_t *ilist; + mpapi_tport_data_t *mptp; + + mptp_prop = (mp_target_port_prop_t *)output_data; + ilist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_TARGET_PORT]->head; + + while ((ilist != NULL) && (*oid != ilist->item->oid.raw_oid)) { + ilist = ilist->next; + } + + if (ilist != NULL) { + mptp = (mpapi_tport_data_t *)(ilist->item->idata); + if (mptp == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_target_port_" + "prop: idata in ilist is NULL")); + return (EINVAL); + } else if (mptp->valid == 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_target_port_" + "prop: OID NOT FOUND - TARGET PORT INVALID")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + mptp_prop = (mp_target_port_prop_t *)(&mptp->prop); + } else { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_target_port_prop: " + "OID NOT FOUND")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + /* + * Here were are not using the 'output_data' that is + * passed as the required information is already + * in the required format! + */ + if (ddi_copyout((void *)mptp_prop, mpioc->mp_obuf, + sizeof (mp_target_port_prop_t), mode) != 0) { + return (EFAULT); + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_tpg_prop(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = 0; + uint64_t *oid = (uint64_t *)(input_data); + mp_tpg_prop_t *mptpg_prop; + mpapi_item_list_t *ilist; + mpapi_tpg_data_t *mptpg; + + mptpg_prop = (mp_tpg_prop_t *)output_data; + ilist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_TARGET_PORT_GROUP]-> + head; + + while ((ilist != NULL) && (*oid != ilist->item->oid.raw_oid)) { + ilist = ilist->next; + } + + if (ilist != NULL) { + mptpg = (mpapi_tpg_data_t *)(ilist->item->idata); + if (mptpg == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_tpg_prop: " + "idata in ilist is NULL")); + return (EINVAL); + } else if (mptpg->valid == 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_tpg_prop: " + "OID NOT FOUND - TPG INVALID")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + mptpg_prop = (mp_tpg_prop_t *)(&mptpg->prop); + } else { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_tpg_prop: " + "OID NOT FOUND")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + /* + * Here were are not using the 'output_data' that is + * passed as the required information is already + * in the required format! + */ + if (ddi_copyout((void *)mptpg_prop, mpioc->mp_obuf, + sizeof (mp_tpg_prop_t), mode) != 0) { + return (EFAULT); + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_target_port_list_for_tpg(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int count = 0, rval = 0; + int list_len = mpioc->mp_olen/sizeof (uint64_t); + uint64_t *oid_list = (uint64_t *)(output_data); + uint64_t *oid = (uint64_t *)(input_data); + mpapi_item_list_t *ilist, *tpg_tp_list = NULL; + mpapi_tpg_data_t *mptpgtp; + mpapi_tport_data_t *mptpp; + + ilist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_TARGET_PORT_GROUP] + ->head; + + while ((ilist != NULL) && (*oid != ilist->item->oid.raw_oid)) + ilist = ilist->next; + + if (ilist == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_target_port_list_for_" + "tpg: OID NOT FOUND")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + rval = EINVAL; + } else if (*oid == ilist->item->oid.raw_oid) { + mptpgtp = (mpapi_tpg_data_t *)(ilist->item->idata); + if (mptpgtp->valid == 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_target_port_" + "list_for_tpg: OID NOT FOUND - TPG INVALID")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + tpg_tp_list = mptpgtp->tport_list->head; + } else { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_target_port_list_for_" + "tpg: Unknown Error")); + } + + while (tpg_tp_list != NULL) { + if (count < list_len) { + oid_list[count] = (uint64_t)tpg_tp_list-> + item->oid.raw_oid; + } else { + rval = MP_MORE_DATA; + } + mptpp = tpg_tp_list->item->idata; + if (mptpp->valid == 0) { + count--; + } + tpg_tp_list = tpg_tp_list->next; + count++; + } + + mpioc->mp_alen = (uint32_t)(count * sizeof (uint64_t)); + if ((rval == MP_MORE_DATA) || (mpioc->mp_alen > mpioc->mp_olen)) { + mpioc->mp_errno = MP_MORE_DATA; + return (EINVAL); + } + + if ((count > 0) && (ddi_copyout(output_data, (void *)mpioc->mp_obuf, + (count * sizeof (uint64_t)), mode) != 0)) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_target_port_list_for_" + "tpg: ddi_copyout() FAILED")); + mpioc->mp_errno = EFAULT; + rval = EINVAL; + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_set_tpg_access_state(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = 0, retval = 0, held = 0; + uint32_t desired_state, t10_tpgid; + uint64_t lu_oid, tpg_oid; + mp_set_tpg_state_req_t mp_set_tpg; + mpapi_item_list_t *lu_list, *tpg_list; + mpapi_tpg_data_t *mptpgd; + scsi_vhci_lun_t *svl; + scsi_vhci_priv_t *svp; + mdi_pathinfo_t *pip; + struct scsi_address *ap = NULL; + + lu_list = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_MULTIPATH_LU] + ->head; + tpg_list = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_TARGET_PORT_GROUP] + ->head; + + rval = ddi_copyin(mpioc->mp_ibuf, &mp_set_tpg, mpioc->mp_ilen, mode); + lu_oid = mp_set_tpg.luTpgPair.luId; + tpg_oid = mp_set_tpg.luTpgPair.tpgId; + desired_state = mp_set_tpg.desiredState; + + VHCI_DEBUG(1, (CE_NOTE, NULL, "vhci_set_tpg_access_state: lu_oid: %lx," + "tpg_oid: %lx, des_as: %x\n", (long)lu_oid, (long)tpg_oid, + desired_state)); + + while ((lu_list != NULL) && (lu_oid != lu_list->item->oid.raw_oid)) + lu_list = lu_list->next; + while ((tpg_list != NULL) && (tpg_oid != tpg_list->item->oid.raw_oid)) + tpg_list = tpg_list->next; + + if ((lu_list == NULL) || (tpg_list == NULL)) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_set_tpg_access_state: " + "OID NOT FOUND")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + if ((desired_state != MP_DRVR_ACCESS_STATE_ACTIVE) && + (desired_state != MP_DRVR_ACCESS_STATE_ACTIVE_OPTIMIZED) && + (desired_state != MP_DRVR_ACCESS_STATE_ACTIVE_NONOPTIMIZED) && + (desired_state != MP_DRVR_ACCESS_STATE_STANDBY)) { + mpioc->mp_errno = MP_DRVR_ILLEGAL_ACCESS_STATE_REQUEST; + return (EINVAL); + } + mptpgd = (mpapi_tpg_data_t *)(tpg_list->item->idata); + if (desired_state == mptpgd->prop.accessState) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_set_tpg_access_" + "state: TPG already in desired State")); + return (EINVAL); + } + t10_tpgid = mptpgd->prop.tpgId; + + /* + * All input seems to be ok, Go ahead & change state. + */ + svl = ((mpapi_lu_data_t *)(lu_list->item->idata))->resp; + if (!SCSI_FAILOVER_IS_TPGS(svl->svl_fops)) { + + VHCI_HOLD_LUN(svl, VH_SLEEP, held); + /* + * retval specifically cares about failover + * status and not about this routine's success. + */ + retval = mdi_failover(vhci->vhci_dip, svl->svl_dip, + MDI_FAILOVER_SYNC); + if (retval != 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_set_tpg_access_" + "state: FAILOVER FAILED: %x", retval)); + VHCI_RELEASE_LUN(svl); + return (EIO); + } else { + /* + * Don't set TPG's accessState here. Let mdi_failover's + * call-back routine "vhci_failover()" call + * vhci_mpapi_update_tpg_acc_state_for_lu(). + */ + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_set_tpg_access_" + "state: FAILOVER SUCCESS: %x", retval)); + } + VHCI_RELEASE_LUN(svl); + } else { + /* + * Send SET_TARGET_PORT_GROUP SCSI Command. This is supported + * ONLY by devices which have TPGS EXPLICIT Failover support. + */ + retval = mdi_select_path(svl->svl_dip, NULL, + MDI_SELECT_ONLINE_PATH, NULL, &pip); + if ((rval != MDI_SUCCESS) || (pip == NULL)) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_set_tpg_access_" + "state: Unable to find path: %x", retval)); + return (EINVAL); + } + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(pip); + if (svp == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_set_tpg_access_" + "state: Unable to find vhci private data")); + mdi_rele_path(pip); + return (EINVAL); + } + if (svp->svp_psd == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_set_tpg_access_" + "state: Unable to find scsi device")); + mdi_rele_path(pip); + return (EINVAL); + } + mdi_rele_path(pip); + ap = &svp->svp_psd->sd_address; + ASSERT(ap != NULL); + + retval = (*tpgs_set_target_groups) + (ap, desired_state, t10_tpgid); + if (retval != 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_set_tpg_access_" + "state:(ALUA) FAILOVER FAILED: %x", retval)); + return (EIO); + } else { + /* + * Don't set accessState here. + * std_report_target_groups() call needs to sync up + * properly. + */ + VHCI_DEBUG(4, (CE_WARN, NULL, "vhci_set_tpg_access_" + "state:(ALUA) FAILOVER SUCCESS: %x", retval)); + + VHCI_HOLD_LUN(svl, VH_NOSLEEP, held); + if (!held) { + return (TRAN_BUSY); + } else { + vhci_update_pathstates((void *)svl); + } + if (desired_state != mptpgd->prop.accessState) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_set_tpg_" + "access_state: TPGAccessState NOT Set: " + "des_state=%x, cur_state=%x", desired_state, + mptpgd->prop.accessState)); + return (EIO); + } + + } + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_prop_lb_list(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = 0; + uint64_t *oid_list = (uint64_t *)(output_data); + + oid_list[0] = NULL; + + if (ddi_copyout(output_data, (void *)mpioc->mp_obuf, + (sizeof (uint64_t)), mode) != 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_prop_lb_list: " + "ddi_copyout() FAILED")); + mpioc->mp_errno = EFAULT; + rval = EINVAL; + } else { + mpioc->mp_errno = 0; + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_get_prop_lb_prop(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = EINVAL; + + return (rval); +} + +/* + * Operation not supported currently as we do not know + * support any devices that allow this in the first place. + */ +/* ARGSUSED */ +static int +vhci_assign_lu_to_tpg(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = ENOTSUP; + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_enable_auto_failback(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = 0; + mpapi_item_list_t *ilist; + mpapi_lu_data_t *lud; + + mutex_enter(&vhci->vhci_mutex); + vhci->vhci_conf_flags |= VHCI_CONF_FLAGS_AUTO_FAILBACK; + mutex_exit(&vhci->vhci_mutex); + + /* Enable auto-failback for each lun in MPAPI database */ + ilist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_MULTIPATH_LU]->head; + while (ilist != NULL) { + lud = ilist->item->idata; + lud->prop.autoFailbackEnabled = 1; + ilist = ilist->next; + } + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_disable_auto_failback(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = 0; + mpapi_item_list_t *ilist; + mpapi_lu_data_t *lud; + + mutex_enter(&vhci->vhci_mutex); + vhci->vhci_conf_flags &= ~VHCI_CONF_FLAGS_AUTO_FAILBACK; + mutex_exit(&vhci->vhci_mutex); + + /* Disable auto-failback for each lun in MPAPI database */ + ilist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_MULTIPATH_LU]->head; + while (ilist != NULL) { + lud = ilist->item->idata; + lud->prop.autoFailbackEnabled = 0; + ilist = ilist->next; + } + + return (rval); +} + +/* + * Find the oid in the object type list. If found lock and return + * the item. If not found return NULL. The caller must unlock the item. + */ +void * +vhci_mpapi_hold_item(struct scsi_vhci *vhci, uint64_t *oid, uint8_t obj_type) +{ + mpapi_item_list_t *ilist; + + ilist = vhci->mp_priv->obj_hdr_list[obj_type]->head; + while ((ilist != NULL) && (*oid != ilist->item->oid.raw_oid)) + ilist = ilist->next; + + if (ilist == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_hold_item: " + "OID NOT FOUND. oid: %p", (void *)oid)); + return (NULL); + } + if (*oid == ilist->item->oid.raw_oid) { + mutex_enter(&ilist->item->item_mutex); + return (ilist); + } + VHCI_DEBUG(4, (CE_WARN, NULL, "vhci_mpapi_hold_item: " + "Unknown Error. oid: %p", (void *)oid)); + return (NULL); +} + +/* + * Check that the pip sent in by the user is still associated with + * the same oid. This is done through checking the path name. + */ +mdi_pathinfo_t * +vhci_mpapi_chk_path(struct scsi_vhci *vhci, mpapi_item_list_t *ilist) +{ + mdi_pathinfo_t *pip; + mpapi_path_data_t *mpp; + + mpp = (mpapi_path_data_t *)(ilist->item->idata); + if (mpp == NULL || mpp->valid == 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_chk_path: " + "pathinfo is not valid: %p", (void *)mpp)); + return (NULL); + } + pip = mpp->resp; + /* make sure it is the same pip by checking path */ + if (vhci_mpapi_match_pip(vhci, ilist, pip) == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_chk_path: " + "Can not match pip: %p", (void *)pip)); + return (NULL); + } + return (pip); +} + +/* + * Get the pip from the oid passed in. the vhci_mpapi_chk_path + * will check the name with the passed in pip name. the mdi_select_path() + * path will lock the pip and this should get released by the caller + */ +mdi_pathinfo_t * +vhci_mpapi_hold_pip(struct scsi_vhci *vhci, mpapi_item_list_t *ilist, int flags) +{ + mdi_pathinfo_t *pip, *opip, *npip; + scsi_vhci_lun_t *svl; + int rval; + mpapi_path_data_t *mpp; + + mpp = (mpapi_path_data_t *)(ilist->item->idata); + pip = mpp->resp; + /* make sure it is the same pip by checking path */ + if (vhci_mpapi_chk_path(vhci, ilist) == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_hold_pip: " + "Can not match pip: %p", (void *)pip)); + return (NULL); + } + + svl = mdi_client_get_vhci_private(mdi_pi_get_client(pip)); + opip = npip = NULL; + + /* + * use the select path to find the right pip since + * it does all the state checking and locks the pip + */ + rval = mdi_select_path(svl->svl_dip, NULL, + flags, NULL, &npip); + do { + if ((rval != MDI_SUCCESS) || (npip == NULL)) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_hold_pip:" + " Unable to find path: %x.", rval)); + return (NULL); + } + if (npip == pip) { + break; + } + opip = npip; + rval = mdi_select_path(svl->svl_dip, NULL, + flags, opip, &npip); + mdi_rele_path(opip); + } while ((npip != NULL) && (rval == MDI_SUCCESS)); + return (npip); +} + +/* + * Initialize the uscsi command. Lock the pip and the item in + * the item list. + */ +static mp_uscsi_cmd_t * +vhci_init_uscsi_cmd(struct scsi_vhci *vhci, + mp_iocdata_t *mpioc, uint64_t *oid, mpapi_item_list_t **list) +{ + int arq_enabled; + mp_uscsi_cmd_t *mp_uscmdp; + scsi_vhci_priv_t *svp; + struct scsi_address *ap; + mdi_pathinfo_t *pip; + mpapi_item_list_t *ilist; + struct buf *bp; + + VHCI_DEBUG(4, (CE_WARN, NULL, + "vhci_init_uscsi_cmd: enter")); + + *list = NULL; + /* lock the item */ + if ((ilist = (mpapi_item_list_t *)vhci_mpapi_hold_item( + vhci, oid, MP_OBJECT_TYPE_PATH_LU)) == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, + "vhci_init_uscsi_cmd: exit EINVAL")); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (NULL); + } + + /* lock the pip */ + if ((pip = vhci_mpapi_hold_pip(vhci, ilist, + (MDI_SELECT_STANDBY_PATH|MDI_SELECT_ONLINE_PATH))) == 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, + "vhci_init_uscsi_cmd: exit PATH_UNAVAIL")); + mpioc->mp_errno = MP_DRVR_PATH_UNAVAILABLE; + mutex_exit(&ilist->item->item_mutex); + return (NULL); + }; + + /* get the address of the pip */ + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(pip); + if (svp == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_init_uscsi_cmd:" + " Unable to find vhci private data")); + mpioc->mp_errno = MP_DRVR_PATH_UNAVAILABLE; + mdi_rele_path(pip); + mutex_exit(&ilist->item->item_mutex); + return (NULL); + } + if (svp->svp_psd == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_init_uscsi_cmd:" + " Unable to find scsi device")); + mpioc->mp_errno = MP_DRVR_PATH_UNAVAILABLE; + mdi_rele_path(pip); + mutex_exit(&ilist->item->item_mutex); + return (NULL); + } + ap = &svp->svp_psd->sd_address; + ASSERT(ap != NULL); + + /* initialize the buffer */ + bp = getrbuf(KM_SLEEP); + ASSERT(bp != NULL); + + /* initialize the mp_uscsi_cmd */ + mp_uscmdp = kmem_zalloc((size_t)sizeof (mp_uscsi_cmd_t), KM_SLEEP); + ASSERT(mp_uscmdp != NULL); + mp_uscmdp->ap = ap; + mp_uscmdp->pip = pip; + mp_uscmdp->cmdbp = bp; + mp_uscmdp->rqbp = NULL; + + bp->b_private = mp_uscmdp; + + /* used to debug a manual sense */ + if (vhci_force_manual_sense) { + (void) scsi_ifsetcap(ap, "auto-rqsense", 0, 0); + } else { + if (scsi_ifgetcap(ap, "auto-rqsense", 1) != 1) { + (void) scsi_ifsetcap(ap, "auto-rqsense", 1, 1); + } + } + arq_enabled = scsi_ifgetcap(ap, "auto-rqsense", 1); + if (arq_enabled == 1) { + mp_uscmdp->arq_enabled = 1; + } else { + mp_uscmdp->arq_enabled = 0; + } + /* set the list pointer for the caller */ + *list = ilist; + VHCI_DEBUG(4, (CE_WARN, NULL, + "vhci_init_uscsi_cmd: mp_uscmdp: %p ilist: %p mp_errno: %d " + "bp: %p arq: %d", + (void *)mp_uscmdp, (void *)*list, mpioc->mp_errno, + (void *)bp, arq_enabled)); + + return (mp_uscmdp); +} + + +/* + * Initialize the uscsi information and then issue the command. + */ +/* ARGSUSED */ +static int +vhci_send_uscsi_cmd(dev_t dev, struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = 0, uioseg = 0; + struct uscsi_cmd *uscmdp; + uint64_t *oid = (uint64_t *)(input_data); + mp_uscsi_cmd_t *mp_uscmdp; + mpapi_item_list_t *ilist; + + VHCI_DEBUG(4, (CE_WARN, NULL, + "vhci_send_uscsi_cmd: enter: mode: %x", mode)); + mpioc->mp_errno = 0; + mp_uscmdp = vhci_init_uscsi_cmd(vhci, mpioc, oid, &ilist); + if (mp_uscmdp == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, + "vhci_send_uscsi_cmd: exit INVALID_ID. rval: %d", rval)); + return (EINVAL); + } + rval = scsi_uscsi_alloc_and_copyin((intptr_t)mpioc->mp_obuf, + mode, mp_uscmdp->ap, &uscmdp); + if (rval != 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_send_uscsi_cmd: " + "scsi_uscsi_alloc_and_copyin failed. rval: %d", rval)); + mpioc->mp_errno = EINVAL; + mdi_rele_path(mp_uscmdp->pip); + mutex_exit(&ilist->item->item_mutex); + if (mp_uscmdp->cmdbp) + freerbuf(mp_uscmdp->cmdbp); + kmem_free(mp_uscmdp, sizeof (mp_uscsi_cmd_t)); + return (EINVAL); + } + /* initialize the mp_uscsi_cmd with the uscsi_cmd from uscsi_alloc */ + mp_uscmdp->uscmdp = uscmdp; + + uioseg = (mode & FKIOCTL) ? UIO_SYSSPACE : UIO_USERSPACE; + + /* start the command sending the buffer as an argument */ + rval = scsi_uscsi_handle_cmd(dev, uioseg, + uscmdp, vhci_uscsi_iostart, mp_uscmdp->cmdbp, mp_uscmdp); + if (rval != 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_send_uscsi_cmd: " + "scsi_uscsi_handle_cmd failed. rval: %d", rval)); + mpioc->mp_errno = EIO; + } + + if (scsi_uscsi_copyout_and_free((intptr_t)mpioc->mp_obuf, + uscmdp) != 0 && rval == 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_send_uscsi_cmd: " + "scsi_uscsi_copyout_and_free failed. rval: %d", rval)); + mpioc->mp_errno = EFAULT; + rval = EFAULT; + } + /* cleanup */ + mdi_rele_path(mp_uscmdp->pip); + mutex_exit(&ilist->item->item_mutex); + if (mp_uscmdp->cmdbp) + freerbuf(mp_uscmdp->cmdbp); + kmem_free(mp_uscmdp, sizeof (mp_uscsi_cmd_t)); + VHCI_DEBUG(4, (CE_WARN, NULL, + "vhci_send_uscsi_cmd: rval: %d mp_errno: %d", + rval, mpioc->mp_errno)); + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_enable_path(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = 0; + uint64_t *oid = (uint64_t *)(input_data); + mdi_pathinfo_t *pip; + mpapi_item_list_t *ilist; + mpapi_path_data_t *mpp; + + if ((ilist = (mpapi_item_list_t *)vhci_mpapi_hold_item(vhci, oid, + MP_OBJECT_TYPE_PATH_LU)) == NULL) { + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + + mpp = (mpapi_path_data_t *)(ilist->item->idata); + pip = (mdi_pathinfo_t *)mpp->resp; + + if (vhci_mpapi_chk_path(vhci, ilist) == NULL) { + mutex_exit(&ilist->item->item_mutex); + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + + if (mdi_pi_enable_path(pip, USER_DISABLE) != 0) { + rval = EFAULT; + } else { + mpp->prop.disabled = 0; + vhci_mpapi_log_sysevent(vhci->vhci_dip, + &(((mpoid_t *)oid)->raw_oid), ESC_SUN_MP_PATH_CHANGE); + } + mutex_exit(&ilist->item->item_mutex); + return (rval); +} + +/* ARGSUSED */ +static int +vhci_disable_path(struct scsi_vhci *vhci, mp_iocdata_t *mpioc, + void *input_data, void *output_data, int mode) +{ + int rval = 0; + uint64_t *oid = (uint64_t *)(input_data); + mdi_pathinfo_t *pip = NULL; + mpapi_item_list_t *ilist; + mpapi_path_data_t *mpp; + + if ((ilist = (mpapi_item_list_t *)vhci_mpapi_hold_item(vhci, oid, + MP_OBJECT_TYPE_PATH_LU)) == NULL) { + mpioc->mp_errno = MP_DRVR_INVALID_ID; + return (EINVAL); + } + + mpp = (mpapi_path_data_t *)(ilist->item->idata); + pip = (mdi_pathinfo_t *)mpp->resp; + + if (vhci_mpapi_chk_path(vhci, ilist) == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_disable_path: Request " + "received to disable last path. Cant disable, Sorry!")); + mutex_exit(&ilist->item->item_mutex); + return (EINVAL); + } + if (vhci_mpapi_chk_last_path(pip) != 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_disable_path(1): Request " + "received to disable last path. Cant disable, Sorry!")); + mutex_exit(&ilist->item->item_mutex); + return (EINVAL); + } + + if (mdi_pi_disable_path(pip, USER_DISABLE) != 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_disable_path(2): Request " + "received to disable last path. Cant disable, Sorry!")); + rval = EFAULT; + } else { + mpp->prop.disabled = 1; + vhci_mpapi_log_sysevent(vhci->vhci_dip, + &(((mpoid_t *)oid)->raw_oid), ESC_SUN_MP_PATH_CHANGE); + } + mutex_exit(&ilist->item->item_mutex); + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_mpapi_ioctl(dev_t dev, struct scsi_vhci *vhci, void *udata, + mp_iocdata_t *mpioc, int mode, cred_t *credp) +{ + int rval = 0; + uint64_t oid; + void *input_data = NULL, *output_data = NULL; + + /* validate mpioc */ + rval = vhci_mpapi_validate(udata, mpioc, mode, credp); + + if (rval == EINVAL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_ioctl: " + " vhci_mpapi_validate() Returned %x: INVALID DATA", rval)); + if (vhci_mpapi_copyout_iocdata(mpioc, udata, mode)) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_ioctl: " + "vhci_mpapi_copyout_iocdata FAILED in EINVAL")); + } + return (rval); + } else if (rval == EPERM) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_ioctl: " + " vhci_mpapi_validate() Returned %x: NO CREDS", rval)); + if (vhci_mpapi_copyout_iocdata(mpioc, udata, mode)) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_ioctl: " + "vhci_mpapi_copyout_iocdata FAILED in EPERM")); + } + return (rval); + /* Process good cases & also cases where we need to get correct alen */ + } else if ((rval == 0) || (rval == MP_MORE_DATA)) { + /* allocate an input buffer */ + if ((mpioc->mp_ibuf) && (mpioc->mp_ilen != 0)) { + input_data = kmem_zalloc(mpioc->mp_ilen, + KM_SLEEP); + ASSERT(input_data != NULL); + rval = ddi_copyin(mpioc->mp_ibuf, + input_data, mpioc->mp_ilen, mode); + oid = (uint64_t)(*((uint64_t *)input_data)); + + VHCI_DEBUG(7, (CE_NOTE, NULL, "Requesting op for " + "OID = %lx w/ mpioc = %p mp_cmd = %x\n", + (long)oid, (void *)mpioc, mpioc->mp_cmd)); + + } + if ((mpioc->mp_xfer == MP_XFER_READ) && (mpioc->mp_olen != 0)) { + output_data = kmem_zalloc(mpioc->mp_olen, KM_SLEEP); + ASSERT(output_data != NULL); + } + } + + if (vhci_mpapi_sync_lu_oid_list(vhci) != 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "!vhci_mpapi_ioctl: " + "vhci_mpapi_sync_lu_oid_list() failed")); + } + mdi_vhci_walk_phcis(vhci->vhci_dip, + vhci_mpapi_sync_init_port_list, vhci); + + /* process ioctls */ + switch (mpioc->mp_cmd) { + case MP_GET_DRIVER_PROP: + rval = vhci_get_driver_prop(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_DEV_PROD_LIST: + rval = vhci_get_dev_prod_list(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_DEV_PROD_PROP: + rval = vhci_get_dev_prod_prop(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_LU_LIST: + rval = vhci_get_lu_list(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_LU_LIST_FROM_TPG: + rval = vhci_get_lu_list_from_tpg(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_TPG_LIST_FOR_LU: + rval = vhci_get_tpg_list_for_lu(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_LU_PROP: + rval = vhci_get_lu_prop(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_PATH_LIST_FOR_MP_LU: + rval = vhci_get_path_list_for_mp_lu(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_PATH_LIST_FOR_INIT_PORT: + rval = vhci_get_path_list_for_init_port(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_PATH_LIST_FOR_TARGET_PORT: + rval = vhci_get_path_list_for_target_port(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_PATH_PROP: + rval = vhci_get_path_prop(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_INIT_PORT_LIST: /* Not Required */ + rval = vhci_get_init_port_list(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_INIT_PORT_PROP: + rval = vhci_get_init_port_prop(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_TARGET_PORT_PROP: + rval = vhci_get_target_port_prop(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_TPG_LIST: /* Not Required */ + rval = vhci_get_tpg_list_for_lu(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_TPG_PROP: + rval = vhci_get_tpg_prop(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_TARGET_PORT_LIST_FOR_TPG: + rval = vhci_get_target_port_list_for_tpg(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_SET_TPG_ACCESS_STATE: + rval = vhci_set_tpg_access_state(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_ASSIGN_LU_TO_TPG: + rval = vhci_assign_lu_to_tpg(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_PROPRIETARY_LOADBALANCE_LIST: + rval = vhci_get_prop_lb_list(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_GET_PROPRIETARY_LOADBALANCE_PROP: + rval = vhci_get_prop_lb_prop(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_ENABLE_AUTO_FAILBACK: + rval = vhci_enable_auto_failback(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_DISABLE_AUTO_FAILBACK: + rval = vhci_disable_auto_failback(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_ENABLE_PATH: + rval = vhci_enable_path(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_DISABLE_PATH: + rval = vhci_disable_path(vhci, mpioc, + input_data, output_data, mode); + break; + case MP_SEND_SCSI_CMD: + rval = vhci_send_uscsi_cmd(dev, vhci, mpioc, + input_data, output_data, mode); + break; + default: + rval = EINVAL; + break; + } + + VHCI_DEBUG(6, (CE_NOTE, NULL, "vhci_mpapi_ioctl: output_data = %p, " + "mp_obuf = %p, mp_olen = %lx, mp_alen = %lx, mp_errno = %x, " + "mode = %x, rval=%x\n", (void *)output_data, (void *)mpioc->mp_obuf, + mpioc->mp_olen, mpioc->mp_alen, mpioc->mp_errno, mode, rval)); + + if (vhci_mpapi_copyout_iocdata(mpioc, udata, mode)) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_ioctl: " + "vhci_mpapi_copyout_iocdata FAILED")); + rval = EFAULT; + } + + if (input_data) { + kmem_free(input_data, mpioc->mp_ilen); + } + + if (output_data) { + kmem_free(output_data, mpioc->mp_olen); + } + + return (rval); +} + +/* ARGSUSED */ +int +vhci_mpapi_init(struct scsi_vhci *vhci) +{ + mpapi_item_list_t *ilist; + mpapi_item_t *item; + mp_driver_prop_t *drv; + uint8_t i; + + /* + * This tstamp value is present in the upper 32-bits of all OIDs + * that are issued in this boot session. Use it to identify + * stale OIDs that an application/ioctl may pass to you and + * reject it - Done in vhci_mpapi_validate() routine. + */ + mutex_enter(&tod_lock); + vhci->mp_priv->tstamp = (time32_t)(tod_get().tv_sec); + mutex_exit(&tod_lock); + + for (i = 0; i < MP_MAX_OBJECT_TYPE; i++) { + vhci->mp_priv->obj_hdr_list[i] = vhci_mpapi_create_list_head(); + } + + /* + * Let us now allocate and initialize the drv block. + */ + ilist = kmem_zalloc(sizeof (mpapi_item_list_t), KM_SLEEP); + item = kmem_zalloc(sizeof (mpapi_item_t), KM_SLEEP); + ilist->item = item; + item->oid.raw_oid = vhci_mpapi_create_oid(vhci->mp_priv, + MP_OBJECT_TYPE_PLUGIN); + drv = kmem_zalloc(sizeof (mp_driver_prop_t), KM_SLEEP); + drv->driverVersion[0] = '\0'; + drv->supportedLoadBalanceTypes = + (MP_DRVR_LOAD_BALANCE_TYPE_ROUNDROBIN | + MP_DRVR_LOAD_BALANCE_TYPE_LBA_REGION); + drv->canSetTPGAccess = TRUE; + drv->canOverridePaths = FALSE; + drv->exposesPathDeviceFiles = FALSE; + drv->deviceFileNamespace[0] = '\0'; + drv->onlySupportsSpecifiedProducts = 1; + drv->maximumWeight = 1; + drv->failbackPollingRateMax = 0; + drv->currentFailbackPollingRate = 0; + drv->autoFailbackSupport = 1; + drv->autoFailbackEnabled = 1; + drv->defaultLoadBalanceType = MP_DRVR_LOAD_BALANCE_TYPE_ROUNDROBIN; + drv->probingPollingRateMax = 0; + drv->currentProbingPollingRate = 0; + drv->autoProbingSupport = 0; + drv->autoProbingEnabled = 0; + item->idata = drv; + mutex_init(&item->item_mutex, NULL, MUTEX_DRIVER, NULL); + if (vhci_mpapi_add_to_list(vhci->mp_priv->obj_hdr_list + [MP_OBJECT_TYPE_PLUGIN], ilist) != 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_init: " + "vhci_mpapi_create_add_to_list() of PLUGIN failed")); + return (EFAULT); + + } + return (0); +} + +void +vhci_mpapi_add_dev_prod(struct scsi_vhci *vhci, char *vidpid) +{ + mpapi_item_list_t *dev_prod_list; + mpapi_item_t *dev_prod_item; + mp_dev_prod_prop_t *dev_prod; + + /* add to list */ + dev_prod_list = kmem_zalloc(sizeof (mpapi_item_list_t), KM_SLEEP); + dev_prod_item = kmem_zalloc(sizeof (mpapi_item_t), KM_SLEEP); + dev_prod_list->item = dev_prod_item; + dev_prod_list->item->oid.raw_oid = vhci_mpapi_create_oid + (vhci->mp_priv, MP_OBJECT_TYPE_DEVICE_PRODUCT); + dev_prod = kmem_zalloc(sizeof (mp_dev_prod_prop_t), KM_SLEEP); + + (void) strncpy(dev_prod->prodInfo.vendor, vidpid, strlen(vidpid)); + dev_prod->supportedLoadBalanceTypes = + MP_DRVR_LOAD_BALANCE_TYPE_ROUNDROBIN; + dev_prod->id = dev_prod_list->item->oid.raw_oid; + + dev_prod_list->item->idata = dev_prod; + (void) vhci_mpapi_add_to_list(vhci->mp_priv->obj_hdr_list + [MP_OBJECT_TYPE_DEVICE_PRODUCT], (void *)dev_prod_list); + vhci_mpapi_log_sysevent(vhci->vhci_dip, + &(dev_prod_list->item->oid.raw_oid), + ESC_SUN_MP_DEV_PROD_ADD); +} + +/* ARGSUSED */ +static uint64_t +vhci_mpapi_create_oid(mpapi_priv_t *mp_priv, uint8_t obj_type) +{ + mpoid_t oid; + + oid.disc_oid.tstamp = mp_priv->tstamp; + oid.disc_oid.type = obj_type; + oid.disc_oid.seq_id = ++(mp_priv->oid_seq[obj_type]); + return (oid.raw_oid); +} + +/* ARGSUSED */ +static int +vhci_mpapi_add_to_list(mpapi_list_header_t *hdr, mpapi_item_list_t *item) +{ + + mpapi_list_header_t *tmp_hdr = hdr; + mpapi_item_list_t *tmp_item = item; + + if (item == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_add_to_list: " + "NULL item passed")); + return (EFAULT); + } + if (hdr == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_add_to_list: " + "NULL hdr passed")); + return (EFAULT); + } + /* + * Check if the item is already there in the list. + * Catches duplicates while assigning TPGs. + */ + tmp_item = tmp_hdr->head; + while (tmp_item != NULL) { + if (item == tmp_item) { + VHCI_DEBUG(4, (CE_WARN, NULL, "vhci_mpapi_add_to_list: " + "Item already in list")); + return (1); + } else { + tmp_item = tmp_item->next; + } + } + + item->next = NULL; + if (hdr->head == NULL) { + hdr->head = item; + hdr->tail = item; + } else { + hdr->tail->next = item; + hdr->tail = item; + } + + return (0); +} + +/* + * Local convenience routine to fetch reference to a mpapi item entry if it + * exits based on the pointer to the vhci resource that is passed. + * Returns NULL if no entry is found. + */ +/* ARGSUSED */ +void* +vhci_get_mpapi_item(struct scsi_vhci *vhci, mpapi_list_header_t *list, + uint8_t obj_type, void* res) +{ + mpapi_item_list_t *ilist; + + if (list == NULL) { + /* + * Since the listhead is null, the search is being + * performed in implicit mode - that is to use the + * level one list. + */ + ilist = vhci->mp_priv->obj_hdr_list[obj_type]->head; + } else { + /* + * The search is being performed on a sublist within + * one of the toplevel list items. Use the listhead + * that is passed in. + */ + ilist = list->head; + } + + if (res == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_mpapi_item: " + " Got Item w/ NULL resource ptr")); + return (NULL); + } + + /* + * Since the resource field within the item data is specific + * to a particular object type, we need to use the object type + * to enable us to perform the search and compare appropriately. + */ + switch (obj_type) { + case MP_OBJECT_TYPE_INITIATOR_PORT: + while (ilist) { + void *wwn = ((mpapi_initiator_data_t *) + ilist->item->idata)->resp; + if (strncmp(wwn, res, strlen(res)) == 0) { + /* Found a match */ + return ((void*)ilist); + } + ilist = ilist->next; + } + break; + + case MP_OBJECT_TYPE_TARGET_PORT: + while (ilist) { + void *wwn = ((mpapi_tport_data_t *)ilist-> + item->idata)->resp; + if (strncmp(wwn, res, strlen(res)) == 0) { + /* Found a match */ + return ((void*)ilist); + } + ilist = ilist->next; + } + break; + + case MP_OBJECT_TYPE_TARGET_PORT_GROUP: + /* + * For TPG Synthesis, Use TPG specific routines + * Use this case only for ALUA devices which give TPG ID + */ + while (ilist) { + void *tpg_id = ((mpapi_tpg_data_t *)ilist-> + item->idata)->resp; + if (strncmp(tpg_id, res, strlen(res)) == 0) { + /* Found a match */ + return ((void*)ilist); + } + ilist = ilist->next; + } + break; + + case MP_OBJECT_TYPE_MULTIPATH_LU: + return ((void *)(vhci_mpapi_match_lu + (vhci, ilist, res))); + + case MP_OBJECT_TYPE_PATH_LU: + return ((void *)(vhci_mpapi_match_pip + (vhci, ilist, res))); + + default: + /* + * This should not happen + */ + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_mpapi_item:" + "Got Unsupported OBJECT TYPE")); + return (NULL); + } + return (NULL); +} + +/* + * Local convenience routine to create and initialize mpapi item + * based on the object type passed. + */ +/* ARGSUSED */ +static mpapi_item_list_t * +vhci_mpapi_create_item(struct scsi_vhci *vhci, uint8_t obj_type, void* res) +{ + int major; + int instance; + mpapi_item_list_t *ilist; + mpapi_item_t *item; + char *pname = NULL; + + ilist = kmem_zalloc(sizeof (mpapi_item_list_t), KM_SLEEP); + item = kmem_zalloc(sizeof (mpapi_item_t), KM_SLEEP); + mutex_init(&item->item_mutex, NULL, MUTEX_DRIVER, NULL); + ilist->item = item; + item->oid.raw_oid = 0; + + switch (obj_type) { + case MP_OBJECT_TYPE_INITIATOR_PORT: + { + mpapi_initiator_data_t *init; + dev_info_t *pdip = res; + char *init_port_res; + char *interconnect; + int mp_interconnect_type, len; + int prop_not_ddi_alloced = 0; + + pname = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + major = (int)ddi_driver_major(pdip); + instance = ddi_get_instance(pdip); + (void) ddi_pathname(pdip, pname); + item->oid.raw_oid = + MP_STORE_INST_TO_ID(instance, item->oid.raw_oid); + item->oid.raw_oid = + MP_STORE_MAJOR_TO_ID(major, item->oid.raw_oid); + /* + * Just make a call to keep correct Sequence count. + * Don't use the OID returned though. + */ + (void) vhci_mpapi_create_oid(vhci->mp_priv, obj_type); + init_port_res = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + (void) strlcpy(init_port_res, pname, MAXPATHLEN); + + if ((ddi_prop_lookup_string(DDI_DEV_T_ANY, pdip, 0, + "initiator-interconnect-type", + &interconnect) != DDI_PROP_SUCCESS)) { + /* XXX: initiator-interconnect-type not set */ + VHCI_DEBUG(1, (CE_WARN, NULL, + "vhci_mpapi_create_item: initiator-" + "-interconnect-type prop not found")); + len = strlen("UNKNOWN")+1; + interconnect = kmem_zalloc(len, KM_SLEEP); + (void) strlcpy(interconnect, "UNKNOWN", len); + prop_not_ddi_alloced = 1; + } + /* + * Map the initiator-interconnect-type values between + * SCSA(as defined in services.h) and MPAPI + * (as defined in mpapi_impl.h) + */ + if (strncmp(interconnect, + INTERCONNECT_FABRIC_STR, + strlen(interconnect)) == 0) { + mp_interconnect_type = 2; + } else if (strncmp(interconnect, + INTERCONNECT_PARALLEL_STR, + strlen(interconnect)) == 0) { + mp_interconnect_type = 3; + } else if (strncmp(interconnect, + INTERCONNECT_ISCSI_STR, + strlen(interconnect)) == 0) { + mp_interconnect_type = 4; + } else if (strncmp(interconnect, + INTERCONNECT_IBSRP_STR, + strlen(interconnect)) == 0) { + mp_interconnect_type = 5; + } else { + mp_interconnect_type = 0; + } + + init = kmem_zalloc( + sizeof (mpapi_initiator_data_t), KM_SLEEP); + init->resp = init_port_res; + init->valid = 1; + init->prop.id = item->oid.raw_oid; + init->prop.portType = mp_interconnect_type; + (void) strlcpy(init->prop.portID, pname, + sizeof (init->prop.portID)); + (void) strlcpy(init->prop.osDeviceFile, "/devices", + sizeof (init->prop.osDeviceFile)); + (void) strlcat(init->prop.osDeviceFile, pname, + sizeof (init->prop.osDeviceFile)); + init->path_list = vhci_mpapi_create_list_head(); + item->idata = (void *)init; + vhci_mpapi_log_sysevent(vhci->vhci_dip, + &(item->oid.raw_oid), ESC_SUN_MP_INIT_PORT_CHANGE); + + if (prop_not_ddi_alloced != 1) { + ddi_prop_free(interconnect); + } else { + kmem_free(interconnect, len); + } + if (pname) { + kmem_free(pname, MAXPATHLEN); + } + } + break; + + case MP_OBJECT_TYPE_TARGET_PORT: + { + mpapi_tport_data_t *tport; + char *tgt_port_res; + + item->oid.raw_oid = + vhci_mpapi_create_oid(vhci->mp_priv, obj_type); + tport = kmem_zalloc(sizeof (mpapi_tport_data_t), + KM_SLEEP); + tgt_port_res = kmem_zalloc(strlen(res)+1, KM_SLEEP); + (void) strlcpy(tgt_port_res, res, strlen(res)+1); + tport->resp = tgt_port_res; + tport->valid = 1; + tport->prop.id = item->oid.raw_oid; + tport->prop.relativePortID = 0; + (void) strlcpy(tport->prop.portName, res, + sizeof (tport->prop.portName)); + tport->path_list = vhci_mpapi_create_list_head(); + item->idata = (void *)tport; + vhci_mpapi_log_sysevent(vhci->vhci_dip, + &(item->oid.raw_oid), ESC_SUN_MP_TARGET_PORT_ADD); + } + break; + + case MP_OBJECT_TYPE_TARGET_PORT_GROUP: + { + mpapi_tpg_data_t *tpg; + char *tpg_res; + + item->oid.raw_oid = + vhci_mpapi_create_oid(vhci->mp_priv, obj_type); + tpg = kmem_zalloc( + sizeof (mpapi_tpg_data_t), KM_SLEEP); + tpg_res = kmem_zalloc(strlen(res)+1, KM_SLEEP); + (void) strlcpy(tpg_res, res, strlen(res)+1); + tpg->resp = tpg_res; + tpg->valid = 1; + tpg->prop.id = item->oid.raw_oid; + /* + * T10 TPG ID is a 2 byte value. Keep up with it. + */ + tpg->prop.tpgId = + ((item->oid.raw_oid) & 0x000000000000ffff); + tpg->tport_list = vhci_mpapi_create_list_head(); + tpg->lu_list = vhci_mpapi_create_list_head(); + item->idata = (void *)tpg; + vhci_mpapi_log_sysevent(vhci->vhci_dip, + &(item->oid.raw_oid), ESC_SUN_MP_TPG_ADD); + } + break; + + case MP_OBJECT_TYPE_MULTIPATH_LU: + { + mpapi_lu_data_t *lu; + scsi_vhci_lun_t *svl = res; + /* + * We cant use ddi_get_instance(svl->svl_dip) at this + * point because the dip is not yet in DS_READY state. + */ + item->oid.raw_oid = + vhci_mpapi_create_oid(vhci->mp_priv, obj_type); + + lu = kmem_zalloc(sizeof (mpapi_lu_data_t), KM_SLEEP); + lu->resp = res; + lu->valid = 1; + lu->prop.id = (uint64_t)item->oid.raw_oid; + /* + * XXX: luGroupID is currently unsupported + */ + lu->prop.luGroupID = 0xFFFFFFFF; + + (void) strlcpy(lu->prop.name, svl->svl_lun_wwn, + sizeof (lu->prop.name)); + + (void) strlcpy(lu->prop.deviceFileName, + "/devices/scsi_vhci/ssd@g", + sizeof (lu->prop.deviceFileName)); + (void) strlcat(lu->prop.deviceFileName, lu->prop.name, + sizeof (lu->prop.deviceFileName)); + + if ((svl->svl_fops != NULL) && + !SCSI_FAILOVER_IS_SYM(svl->svl_fops)) { + lu->prop.asymmetric = 1; + } + + lu->prop.autoFailbackEnabled = + ((VHCI_CONF_FLAGS_AUTO_FAILBACK & vhci-> + vhci_conf_flags) ? 1 : 0); + + if (svl->svl_lb_policy_save == LOAD_BALANCE_NONE) { + lu->prop.currentLoadBalanceType = + MP_DRVR_LOAD_BALANCE_TYPE_NONE; + } else if (svl->svl_lb_policy_save == LOAD_BALANCE_RR) { + lu->prop.currentLoadBalanceType = + MP_DRVR_LOAD_BALANCE_TYPE_ROUNDROBIN; + } else if (svl->svl_lb_policy_save == + LOAD_BALANCE_LBA) { + lu->prop.currentLoadBalanceType = + MP_DRVR_LOAD_BALANCE_TYPE_LBA_REGION; + } else { + /* + * We still map Load Balance Type to UNKNOWN + * although "none" also maps to the same case. + * MPAPI spec does not have a "NONE" LB type. + */ + lu->prop.currentLoadBalanceType = + MP_DRVR_LOAD_BALANCE_TYPE_UNKNOWN; + } + /* + * Allocate header lists for cross reference + */ + lu->path_list = vhci_mpapi_create_list_head(); + lu->tpg_list = vhci_mpapi_create_list_head(); + item->idata = (void *)lu; + vhci_mpapi_log_sysevent(vhci->vhci_dip, + &(item->oid.raw_oid), ESC_SUN_MP_LU_CHANGE); + + } + break; + + case MP_OBJECT_TYPE_PATH_LU: + { + mpapi_path_data_t *path; + mdi_pathinfo_t *pip = res; + scsi_vhci_lun_t *svl; + char *iport, *tport; + + item->oid.raw_oid = + vhci_mpapi_create_oid(vhci->mp_priv, obj_type); + path = kmem_zalloc( + sizeof (mpapi_path_data_t), KM_SLEEP); + pname = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + + iport = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + (void) ddi_pathname(mdi_pi_get_phci(pip), iport); + + if (mdi_prop_lookup_string(pip, "target-port", + &tport) != DDI_PROP_SUCCESS) { + /* XXX: target-port prop not found */ + tport = (char *)mdi_pi_get_addr(pip); + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_" + "create_item: mdi_prop_lookup_string() " + "returned failure; ")); + } + + svl = mdi_client_get_vhci_private + (mdi_pi_get_client(pip)); + + (void) strlcat(pname, iport, MAXPATHLEN); + (void) strlcat(pname, tport, MAXPATHLEN); + (void) strlcat(pname, svl->svl_lun_wwn, MAXPATHLEN); + kmem_free(iport, MAXPATHLEN); + + path->resp = res; + path->path_name = pname; + path->valid = 1; + path->prop.id = item->oid.raw_oid; + item->idata = (void *)path; + vhci_mpapi_log_sysevent(vhci->vhci_dip, + &(item->oid.raw_oid), ESC_SUN_MP_PATH_ADD); + } + break; + + case MP_OBJECT_TYPE_DEVICE_PRODUCT: + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_create_item:" + " DEVICE PRODUCT not handled here.")); + break; + + default: + /* + * This should not happen + */ + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_create_item:" + "Got Unsupported OBJECT TYPE")); + return (NULL); + } + + (void) vhci_mpapi_add_to_list(vhci->mp_priv->obj_hdr_list[obj_type], + ilist); + return (ilist); +} + +/* + * Local routine to allocate mpapi list header block + */ +/* ARGSUSED */ +static mpapi_list_header_t * +vhci_mpapi_create_list_head() +{ + mpapi_list_header_t *lh; + + lh = kmem_zalloc(sizeof (mpapi_list_header_t), KM_SLEEP); + lh->head = lh->tail = NULL; + return (lh); +} + +/* + * Routine to create Level 1 mpapi_private data structure and also + * establish cross references between the resources being managed + */ +/* ARGSUSED */ +void +vhci_update_mpapi_data(struct scsi_vhci *vhci, scsi_vhci_lun_t *vlun, + mdi_pathinfo_t *pip) +{ + char *tmp_wwn = NULL, *init = NULL, *path_class; + dev_info_t *pdip; + mpapi_item_list_t *lu_list, *path_list, *init_list, *tgt_list; + mpapi_item_list_t *tp_path_list, *init_path_list, *lu_path_list; + mpapi_lu_data_t *ld; + mpapi_path_data_t *pd; + mpapi_tport_data_t *tpd; + mpapi_initiator_data_t *initd; + int path_class_not_mdi_alloced = 0; + + VHCI_DEBUG(6, (CE_NOTE, NULL, "vhci_update_mpapi_data: vhci: %p, " + "vlun: %p, pip: %p\n", (void *)vhci, (void *)vlun, (void *)pip)); + + /* + * Check that the lun is not a TPGS device + * TPGS devices create the same information in another routine. + */ + if (SCSI_FAILOVER_IS_TPGS(vlun->svl_fops)) { + return; + } + /* + * LEVEL 1 - Actions: + * Check if the appropriate resource pointers already + * exist in the Level 1 list and add them if they are new. + */ + + /* + * Build MP LU list + */ + lu_list = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_MULTIPATH_LU, (void*)vlun); + if (lu_list == NULL) { + /* Need to create lu_list entry */ + lu_list = vhci_mpapi_create_item(vhci, + MP_OBJECT_TYPE_MULTIPATH_LU, (void*)vlun); + } else { + /* + * Matched this lu w/ an existing one in current lu list. + * SAME LUN came online!! So, update the resp in main list. + */ + ld = lu_list->item->idata; + ld->valid = 1; + ld->resp = vlun; + } + + /* + * Find out the "path-class" property on the pip + */ + if (mdi_prop_lookup_string(pip, "path-class", &path_class) + != DDI_PROP_SUCCESS) { + /* XXX: path-class prop not found */ + path_class = kmem_zalloc(MPAPI_SCSI_MAXPCLASSLEN, KM_SLEEP); + (void) strlcpy(path_class, "NONE", MPAPI_SCSI_MAXPCLASSLEN); + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_update_mpapi_data: " + "mdi_prop_lookup_string() returned failure; " + "Hence path_class = NONE")); + path_class_not_mdi_alloced = 1; + } + + /* + * Build Path LU list + */ + path_list = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_PATH_LU, (void*)pip); + if (path_list == NULL) { + /* Need to create path_list entry */ + path_list = vhci_mpapi_create_item(vhci, + MP_OBJECT_TYPE_PATH_LU, (void*)pip); + } else { + /* + * Matched this pip w/ an existing one in current pip list. + * SAME PATH came online!! So, update the resp in main list. + */ + pd = path_list->item->idata; + pd->valid = 1; + pd->resp = pip; + } + + if (MDI_PI_IS_ONLINE(pip)) { + vhci_mpapi_set_path_state(vhci->vhci_dip, pip, + MP_DRVR_PATH_STATE_ACTIVE); + } else if (MDI_PI_IS_STANDBY(pip)) { + vhci_mpapi_set_path_state(vhci->vhci_dip, pip, + MP_DRVR_PATH_STATE_PASSIVE); + } else { + vhci_mpapi_set_path_state(vhci->vhci_dip, pip, + MP_DRVR_PATH_STATE_UNKNOWN); + } + + /* + * Build Initiator Port list + */ + pdip = mdi_pi_get_phci(pip); + init = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + (void) ddi_pathname(pdip, init); + + init_list = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_INITIATOR_PORT, (void*)init); + if (init_list == NULL) { + /* + * Need to create init_list entry + * The resource ptr is no really pdip. It will be changed + * in vhci_mpapi_create_item(). The real resource ptr + * is the Port ID. But we pass the pdip, to create OID. + */ + init_list = vhci_mpapi_create_item(vhci, + MP_OBJECT_TYPE_INITIATOR_PORT, (void*)pdip); + } else { + initd = init_list->item->idata; + initd->valid = 1; + } + kmem_free(init, MAXPATHLEN); + + /* + * Build Target Port list + * Can get the tdip: tdip = mdi_pi_get_client(pip); + * But what's the use? We want TARGET_PORT. + * So try getting Target Port's WWN which is unique per port. + */ + tmp_wwn = NULL; + if (mdi_prop_lookup_string(pip, "target-port", &tmp_wwn) + != DDI_PROP_SUCCESS) { + /* XXX: target-port prop not found */ + tmp_wwn = (char *)mdi_pi_get_addr(pip); + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_update_mpapi_data: " + "mdi_prop_lookup_string() returned failure; " + "Hence tmp_wwn = %p", (void *)tmp_wwn)); + } + + tgt_list = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_TARGET_PORT, (void*)tmp_wwn); + if (tgt_list == NULL) { + /* Need to create tgt_list entry */ + tgt_list = vhci_mpapi_create_item(vhci, + MP_OBJECT_TYPE_TARGET_PORT, (void*)tmp_wwn); + } else { + tpd = tgt_list->item->idata; + tpd->valid = 1; + } + + /* + * LEVEL 2 - Actions: + * Since all the Object type item lists are updated to account + * for the new resources, now lets cross-reference these + * resources (mainly through paths) to maintain the + * relationship between them. + */ + + ld = (mpapi_lu_data_t *)lu_list->item->idata; + if (vhci_get_mpapi_item(vhci, ld->path_list, + MP_OBJECT_TYPE_PATH_LU, (void*)pip) == NULL) { + lu_path_list = kmem_zalloc(sizeof (mpapi_item_list_t), + KM_SLEEP); + lu_path_list->item = path_list->item; + (void) vhci_mpapi_add_to_list(ld->path_list, lu_path_list); + } + + initd = (mpapi_initiator_data_t *)init_list->item->idata; + if (vhci_get_mpapi_item(vhci, initd->path_list, + MP_OBJECT_TYPE_PATH_LU, (void*)pip) == NULL) { + init_path_list = kmem_zalloc(sizeof (mpapi_item_list_t), + KM_SLEEP); + init_path_list->item = path_list->item; + (void) vhci_mpapi_add_to_list(initd->path_list, init_path_list); + } + + tpd = (mpapi_tport_data_t *)tgt_list->item->idata; + if (vhci_get_mpapi_item(vhci, tpd->path_list, + MP_OBJECT_TYPE_PATH_LU, (void*)pip) == NULL) { + tp_path_list = kmem_zalloc( + sizeof (mpapi_item_list_t), KM_SLEEP); + tp_path_list->item = path_list->item; + (void) vhci_mpapi_add_to_list(tpd->path_list, tp_path_list); + } + + /* + * Level-1: Fill-out Path Properties now, since we got all details. + * Actually, It is a structure copy, rather than just filling details. + */ + pd = path_list->item->idata; + (void) strlcpy(pd->pclass, path_class, sizeof (pd->pclass)); + bcopy(&(ld->prop), &(pd->prop.logicalUnit), + sizeof (struct mp_logical_unit_prop)); + bcopy(&(initd->prop), &(pd->prop.initPort), + sizeof (struct mp_init_port_prop)); + bcopy(&(tpd->prop), &(pd->prop.targetPort), + sizeof (struct mp_target_port_prop)); + + vhci_mpapi_synthesize_tpg_data(vhci, vlun, pip); + + if (path_class_not_mdi_alloced == 1) { + kmem_free(path_class, MPAPI_SCSI_MAXPCLASSLEN); + } + +} + +/* + * Routine to search (& return if found) a TPG object with a specified + * accessState for a specified vlun structure. Returns NULL if either + * TPG object or the lu item is not found. + * This routine is used for NON-TPGS devices. + */ +/* ARGSUSED */ +static mpapi_item_list_t * +vhci_mpapi_get_tpg_item(struct scsi_vhci *vhci, uint32_t acc_state, void *vlun, + char *pclass, void *tp) +{ + mpapi_list_header_t *tpghdr, *this_tpghdr; + mpapi_item_list_t *lulist, *tpglist, *this_lulist, *this_tpglist; + mpapi_tpg_data_t *tpgdata, *this_tpgdata; + + VHCI_DEBUG(6, (CE_NOTE, NULL, "vhci_mpapi_get_tpg_item: ENTER: vlun=" + "%p, acc_state=%x, pclass=%s, tp=%s\n", + (void *)vlun, acc_state, pclass, (char *)tp)); + + lulist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_MULTIPATH_LU]->head; + + while (lulist != NULL) { + tpghdr = ((mpapi_lu_data_t *)(lulist->item->idata))->tpg_list; + tpglist = tpghdr->head; + while (tpglist != NULL) { + tpgdata = tpglist->item->idata; + + if ((tpgdata) && + (vhci_mpapi_check_tp_in_tpg(tpgdata, tp) == 1) && + (strncmp(tpgdata->pclass, pclass, + strlen(pclass)) == 0)) { + return (tpglist); + } else { + tpglist = tpglist->next; + } + } + lulist = lulist->next; + } + + this_lulist = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_MULTIPATH_LU, vlun); + if (this_lulist != NULL) { + this_tpghdr = ((mpapi_lu_data_t *)(this_lulist->item->idata)) + ->tpg_list; + this_tpglist = this_tpghdr->head; + while (this_tpglist != NULL) { + this_tpgdata = this_tpglist->item->idata; + + if ((this_tpgdata) && + (strncmp(this_tpgdata->pclass, pclass, + strlen(pclass)) == 0)) { + return (this_tpglist); + } else { + this_tpglist = this_tpglist->next; + } + } + } + + VHCI_DEBUG(4, (CE_WARN, NULL, "vhci_mpapi_get_tpg_item: Returns NULL")); + + return (NULL); +} + +/* + * Routine to search (& return if found) a TPG object with a specified + * accessState for a specified vlun structure. Returns NULL if either + * TPG object or the lu item is not found. + * This routine is used for NON-TPGS devices. + */ +/* ARGSUSED */ +mpapi_item_list_t * +vhci_mpapi_get_tpg_for_lun(struct scsi_vhci *vhci, char *pclass, + void *vlun, void *tp) +{ + mpapi_list_header_t *this_tpghdr; + mpapi_item_list_t *this_lulist, *this_tpglist; + mpapi_tpg_data_t *this_tpgdata; + + VHCI_DEBUG(4, (CE_NOTE, NULL, "vhci_mpapi_get_tpg_for_lun: ENTER: vlun=" + "%p, pclass=%s, tp=%s\n", (void *)vlun, pclass, (char *)tp)); + + this_lulist = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_MULTIPATH_LU, vlun); + if (this_lulist != NULL) { + this_tpghdr = ((mpapi_lu_data_t *)(this_lulist->item->idata)) + ->tpg_list; + this_tpglist = this_tpghdr->head; + while (this_tpglist != NULL) { + this_tpgdata = this_tpglist->item->idata; + + if ((this_tpgdata) && + (vhci_mpapi_check_tp_in_tpg(this_tpgdata, + tp) == 1) && (strncmp(this_tpgdata->pclass, pclass, + strlen(pclass)) == 0)) { + return (this_tpglist); + } + this_tpglist = this_tpglist->next; + } + } + + VHCI_DEBUG(4, (CE_WARN, NULL, "vhci_mpapi_get_tpg_for_lun: Returns " + "NULL")); + + return (NULL); +} + +/* + * Routine to search a Target Port in a TPG + */ +/* ARGSUSED */ +static int +vhci_mpapi_check_tp_in_tpg(mpapi_tpg_data_t *tpgdata, void *tp) +{ + mpapi_item_list_t *tplist; + + if (tpgdata) { + tplist = tpgdata->tport_list->head; + } else { + return (0); + } + + while (tplist != NULL) { + void *resp = ((mpapi_tport_data_t *)tplist-> + item->idata)->resp; + if (strncmp(resp, tp, strlen(resp)) == 0) { + /* Found a match */ + return (1); + } + tplist = tplist->next; + } + + return (0); +} + +/* + * Routine to create Level 1 mpapi_private data structure for TPG object & + * establish cross references between the TPG resources being managed. + * TPG SYNTHESIS MODE: Process for NON-SCSI_FAILOVER_IS_TPGS devices ONLY. + * SCSI_FAILOVER_IS_TPGS devices have TPGS(ALUA support) and provide + * REPORT_TARGET_PORT_GROUP data which we can parse directly in the next + * routine(vhci_mpapi_update_tpg_data) to create TPG list in mpapi_priv block. + */ +/* ARGSUSED */ +void +vhci_mpapi_synthesize_tpg_data(struct scsi_vhci *vhci, scsi_vhci_lun_t *vlun, + mdi_pathinfo_t *pip) +{ + uint32_t as; + char *tmp_wwn = NULL, *path_class = NULL; + mpapi_item_list_t *tpg_tport_list, *tpg_lu_list, *lu_list; + mpapi_item_list_t *lu_tpg_list, *item_list, *tpg_list; + mpapi_tpg_data_t *tpg_data; + int path_class_not_mdi_alloced = 0; + + /* + * Build Target Port Group list + * Start by finding out the affected Target Port. + */ + if (mdi_prop_lookup_string(pip, "target-port", &tmp_wwn) + != DDI_PROP_SUCCESS) { + /* XXX: target-port prop not found */ + tmp_wwn = (char *)mdi_pi_get_addr(pip); + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_synthesize_tpg_data: " + "mdi_prop_lookup_string() returned failure; " + "Hence tmp_wwn = %p", (void *)tmp_wwn)); + } + + /* + * Finding out the "path-class" property + */ + if (mdi_prop_lookup_string(pip, "path-class", &path_class) + != DDI_PROP_SUCCESS) { + /* XXX: path-class prop not found */ + path_class = kmem_zalloc(MPAPI_SCSI_MAXPCLASSLEN, KM_SLEEP); + (void) strlcpy(path_class, "NONE", MPAPI_SCSI_MAXPCLASSLEN); + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_synthesize_tpg_data: " + "mdi_prop_lookup_string() returned failure; " + "Hence path_class = NONE")); + path_class_not_mdi_alloced = 1; + } + + /* + * Check the vlun's accessState through pip; we'll use it later. + */ + if (MDI_PI_IS_ONLINE(pip)) { + as = MP_DRVR_ACCESS_STATE_ACTIVE; + } else if (MDI_PI_IS_STANDBY(pip)) { + as = MP_DRVR_ACCESS_STATE_STANDBY; + } else { + as = MP_DRVR_ACCESS_STATE_UNAVAILABLE; + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_synthesize_tpg_data: " + "Unknown pip state seen in TPG synthesis")); + } + + VHCI_DEBUG(4, (CE_NOTE, NULL, "vhci_mpapi_synthesize_tpg_data: ENTER: " + "vlun=%s, acc_state=%x, path_class=%s, tp=%s\n", + vlun->svl_lun_wwn, as, path_class, tmp_wwn)); + + /* + * Create Level 1 and Level 2 data structures for type + */ + if (!SCSI_FAILOVER_IS_TPGS(vlun->svl_fops)) { + /* + * First check if the lun has a TPG list in its level 2 + * structure then, check if this lun is already + * accounted for through a different Target Port. + * If yes, get the ptr to the TPG & skip new TPG creation. + */ + lu_list = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_MULTIPATH_LU, vlun); + tpg_list = vhci_mpapi_get_tpg_item(vhci, as, vlun, path_class, + (void *)tmp_wwn); + if (tpg_list == NULL) { + tpg_list = vhci_mpapi_create_item(vhci, + MP_OBJECT_TYPE_TARGET_PORT_GROUP, (void *)tmp_wwn); + tpg_data = tpg_list->item->idata; + (void) strlcpy(tpg_data->pclass, path_class, + sizeof (tpg_data->pclass)); + tpg_data->prop.accessState = as; + } else { + tpg_data = tpg_list->item->idata; + } + + if (!SCSI_FAILOVER_IS_SYM(vlun->svl_fops)) { + tpg_data->prop.explicitFailover = 1; + } + + /* + * Level 2, Lun Cross referencing to TPG. + */ + if (vhci_get_mpapi_item(vhci, tpg_data->lu_list, + MP_OBJECT_TYPE_MULTIPATH_LU, (void *)vlun) == NULL) { + tpg_lu_list = kmem_zalloc(sizeof (mpapi_item_list_t), + KM_SLEEP); + item_list = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_MULTIPATH_LU, (void *)vlun); + tpg_lu_list->item = item_list->item; + (void) vhci_mpapi_add_to_list(tpg_data->lu_list, + tpg_lu_list); + } + + /* + * Level 2, Target Port Cross referencing to TPG. + */ + if (vhci_get_mpapi_item(vhci, tpg_data->tport_list, + MP_OBJECT_TYPE_TARGET_PORT, (void *)tmp_wwn) == NULL) { + tpg_tport_list = kmem_zalloc(sizeof (mpapi_item_list_t), + KM_SLEEP); + item_list = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_TARGET_PORT, (void *)tmp_wwn); + tpg_tport_list->item = item_list->item; + (void) vhci_mpapi_add_to_list(tpg_data->tport_list, + tpg_tport_list); + } + + /* + * Level 2, TPG Cross referencing to Lun. + */ + lu_tpg_list = vhci_mpapi_get_tpg_for_lun + (vhci, path_class, vlun, tmp_wwn); + if (lu_tpg_list == NULL) { + lu_tpg_list = kmem_zalloc(sizeof (mpapi_item_list_t), + KM_SLEEP); + lu_tpg_list->item = tpg_list->item; + (void) vhci_mpapi_add_to_list(((mpapi_lu_data_t *) + (lu_list->item->idata))->tpg_list, lu_tpg_list); + } + + /* + * Update the AccessState of related MPAPI TPGs + * This takes care of a special case where a failover doesn't + * happen but a TPG accessState needs to be updated from + * Unavailable to Standby + */ + (void) vhci_mpapi_update_tpg_acc_state_for_lu(vhci, vlun); + } + + if (path_class_not_mdi_alloced == 1) { + kmem_free(path_class, MPAPI_SCSI_MAXPCLASSLEN); + } + +} + +/* + * Routine to create Level 1 mpapi_private data structure for TPG object, + * for devices which support TPG and establish cross references between + * the TPG resources being managed. The RTPG response sent by std_asymmetric + * module is parsed in this routine and mpapi_priv data structure is updated. + */ +/* ARGSUSED */ +void +vhci_mpapi_update_tpg_data(struct scsi_address *ap, char *ptr) +{ + struct scsi_vhci_lun *vlun = ADDR2VLUN(ap); + struct scsi_vhci *vhci = ADDR2VHCI(ap); + struct scsi_device *psd; + scsi_vhci_priv_t *svp; + mdi_pathinfo_t *pip; + dev_info_t *pdip; + char tpg_id[16], *tgt_port, *init = NULL; + unsigned char *inqbuf; + uint32_t int_tpg_id, rel_tid, as; + int i, rel_tport_cnt; + mpapi_item_list_t *path_list, *init_list; + mpapi_item_list_t *tp_path_list, *init_path_list, *lu_path_list; + mpapi_item_list_t *tpg_tport_list, *tpg_lu_list, *lu_list; + mpapi_item_list_t *lu_tpg_list, *item_list, *tpg_list, *tgt_list; + mpapi_lu_data_t *ld; + mpapi_tpg_data_t *tpg_data; + mpapi_path_data_t *pd; + mpapi_tport_data_t *tpd; + mpapi_initiator_data_t *initd; + + /* + * Find out the TPG ID (resource ptr for TPG is T10 TPG ID) + */ + int_tpg_id = ((ptr[2] & 0xff) << 8) | (ptr[3] & 0xff); + (void) sprintf(tpg_id, "%04x", int_tpg_id); + + /* + * Check the TPG's accessState; we'll use it later. + */ + as = (ptr[0] & 0x0f); + if (as == STD_ACTIVE_OPTIMIZED) { + as = MP_DRVR_ACCESS_STATE_ACTIVE_OPTIMIZED; + } else if (as == STD_ACTIVE_NONOPTIMIZED) { + as = MP_DRVR_ACCESS_STATE_ACTIVE_NONOPTIMIZED; + } else if (as == STD_STANDBY) { + as = MP_DRVR_ACCESS_STATE_STANDBY; + } else { + as = MP_DRVR_ACCESS_STATE_UNAVAILABLE; + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_update_tpg_data: " + "UNAVAILABLE accessState seen in ALUA TPG setup")); + } + + /* + * Get the vlun through the following process; + * ADDR2VLUN(ap) doesn't give the scsi_vhci lun + */ + psd = ap->a_hba_tran->tran_sd; + inqbuf = (unsigned char *)psd->sd_inq; + pip = (mdi_pathinfo_t *)(uintptr_t)(psd->sd_private); + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(pip); + vlun = svp->svp_svl; + + /* + * Now get the vhci ptr using the walker + */ + mdi_walk_vhcis(vhci_mpapi_get_vhci, &vhci); + + VHCI_DEBUG(4, (CE_NOTE, NULL, "vhci_mpapi_update_tpg_data: vhci=%p, " + "(vlun)wwn=(%p)%s, pip=%p, ap=%p, ptr=%p, as=%x, tpg_id=%s, fops=" + "%p\n", (void *)vhci, (void *)vlun, vlun->svl_lun_wwn, (void *)pip, + (void *)ap, (void *)ptr, as, tpg_id, (void *)vlun->svl_fops)); + + if ((vhci == NULL) || (vlun == NULL) || (pip == NULL) || + !SCSI_FAILOVER_IS_TPGS(vlun->svl_fops)) { + /* Cant help, unfortunate situation */ + return; + } + + /* + * LEVEL 1 - Actions: + * Check if the appropriate resource pointers already + * exist in the Level 1 list and add them if they are new. + */ + + /* + * Build MP LU list + */ + lu_list = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_MULTIPATH_LU, (void*)vlun); + if (lu_list == NULL) { + /* Need to create lu_list entry */ + lu_list = vhci_mpapi_create_item(vhci, + MP_OBJECT_TYPE_MULTIPATH_LU, (void*)vlun); + } else { + /* + * Matched this lu w/ an existing one in current lu list. + * SAME LUN came online!! So, update the resp in main list. + */ + ld = lu_list->item->idata; + ld->valid = 1; + ld->resp = vlun; + } + + /* + * Build Path LU list + */ + path_list = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_PATH_LU, (void*)pip); + if (path_list == NULL) { + /* Need to create path_list entry */ + path_list = vhci_mpapi_create_item(vhci, + MP_OBJECT_TYPE_PATH_LU, (void*)pip); + } else { + /* + * Matched this pip w/ an existing one in current pip list. + * SAME PATH came online!! So, update the resp in main list. + */ + pd = path_list->item->idata; + pd->valid = 1; + pd->resp = pip; + } + + if (MDI_PI_IS_ONLINE(pip)) { + vhci_mpapi_set_path_state(vhci->vhci_dip, pip, + MP_DRVR_PATH_STATE_ACTIVE); + } else if (MDI_PI_IS_STANDBY(pip)) { + vhci_mpapi_set_path_state(vhci->vhci_dip, pip, + MP_DRVR_PATH_STATE_PASSIVE); + } else { + vhci_mpapi_set_path_state(vhci->vhci_dip, pip, + MP_DRVR_PATH_STATE_UNKNOWN); + } + + /* + * Build Initiator Port list + */ + pdip = mdi_pi_get_phci(pip); + init = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + (void) ddi_pathname(pdip, init); + + init_list = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_INITIATOR_PORT, (void*)init); + if (init_list == NULL) { + /* + * Need to create init_list entry + * The resource ptr is no really pdip. It will be changed + * in vhci_mpapi_create_item(). The real resource ptr + * is the Port ID. But we pass the pdip, to create OID. + */ + init_list = vhci_mpapi_create_item(vhci, + MP_OBJECT_TYPE_INITIATOR_PORT, (void*)pdip); + } else { + initd = init_list->item->idata; + initd->valid = 1; + } + kmem_free(init, MAXPATHLEN); + + /* + * LEVEL 2 - Actions: + * Since all the Object type item lists are updated to account + * for the new resources, now lets cross-reference these + * resources (mainly through paths) to maintain the + * relationship between them. + */ + + ld = (mpapi_lu_data_t *)lu_list->item->idata; + if (vhci_get_mpapi_item(vhci, ld->path_list, + MP_OBJECT_TYPE_PATH_LU, (void*)pip) == NULL) { + lu_path_list = kmem_zalloc(sizeof (mpapi_item_list_t), + KM_SLEEP); + lu_path_list->item = path_list->item; + (void) vhci_mpapi_add_to_list(ld->path_list, lu_path_list); + } + + initd = (mpapi_initiator_data_t *)init_list->item->idata; + if (vhci_get_mpapi_item(vhci, initd->path_list, + MP_OBJECT_TYPE_PATH_LU, (void*)pip) == NULL) { + init_path_list = kmem_zalloc(sizeof (mpapi_item_list_t), + KM_SLEEP); + init_path_list->item = path_list->item; + (void) vhci_mpapi_add_to_list(initd->path_list, init_path_list); + } + + /* + * Create Level 1 & Level 2 data structures + * Parse REPORT_TARGET_PORT_GROUP data & update mpapi database. + */ + + tpg_list = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_TARGET_PORT_GROUP, &tpg_id); + if (tpg_list == NULL) { + tpg_list = vhci_mpapi_create_item(vhci, + MP_OBJECT_TYPE_TARGET_PORT_GROUP, &tpg_id); + } + tpg_data = tpg_list->item->idata; + tpg_data->prop.accessState = as; + tpg_data->prop.tpgId = int_tpg_id; + + /* + * Set explicitFailover for TPG - + * based on tpgs_bits setting in Std Inquiry response. + */ + if ((((inqbuf[5] & 0x30) >> 4) == 2) || + (((inqbuf[5] & 0x30) >> 4) == 3)) { + tpg_data->prop.explicitFailover = 1; + } else if (((inqbuf[5] & 0x30) >> 4) == 1) { + tpg_data->prop.explicitFailover = 0; + } else if (((inqbuf[5] & 0x30) >> 4) == 0) { + tpg_data->prop.explicitFailover = 0; + } + + /* + * Level 2, Lun Cross referencing to TPG. + */ + if (vhci_get_mpapi_item(vhci, tpg_data->lu_list, + MP_OBJECT_TYPE_MULTIPATH_LU, (void *)vlun) == NULL) { + tpg_lu_list = kmem_zalloc(sizeof (mpapi_item_list_t), + KM_SLEEP); + item_list = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_MULTIPATH_LU, (void *)vlun); + tpg_lu_list->item = item_list->item; + (void) vhci_mpapi_add_to_list(tpg_data->lu_list, + tpg_lu_list); + } + + /* + * Level 2, TPG Cross referencing to Lun. + */ + if (vhci_get_mpapi_item(vhci, ld->tpg_list, + MP_OBJECT_TYPE_TARGET_PORT_GROUP, &tpg_id) == 0) { + lu_tpg_list = kmem_zalloc(sizeof (mpapi_item_list_t), + KM_SLEEP); + lu_tpg_list->item = tpg_list->item; + (void) vhci_mpapi_add_to_list(((mpapi_lu_data_t *) + (lu_list->item->idata))->tpg_list, lu_tpg_list); + } + + /* + * Building Target Port list is different here. + * For each different Relative Target Port. we have a new MPAPI + * Target Port OID generated. + * Just find out the main Target Port property here. + */ + tgt_port = NULL; + if (mdi_prop_lookup_string(pip, "target-port", &tgt_port) + != DDI_PROP_SUCCESS) { + /* XXX: target-port prop not found */ + tgt_port = (char *)mdi_pi_get_addr(pip); + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_update_tpg_data: " + "mdi_prop_lookup_string() returned failure; " + "Hence tgt_port = %p", (void *)tgt_port)); + } + + /* + * Level 1, Relative Target Port + Target Port Creation + */ + rel_tport_cnt = (ptr[7] & 0xff); + ptr += 8; + for (i = 0; i < rel_tport_cnt; i++) { + rel_tid = 0; + rel_tid |= ((ptr[2] & 0Xff) << 8); + rel_tid |= (ptr[3] & 0xff); + + VHCI_DEBUG(4, (CE_NOTE, NULL, "vhci_mpapi_update_tpg_data: " + "TgtPort=%s, RelTgtPort=%x\n", tgt_port, rel_tid)); + + tgt_list = vhci_mpapi_get_rel_tport_pair(vhci, NULL, + (void *)tgt_port, rel_tid); + if (tgt_list == NULL) { + /* Need to create tgt_list entry */ + tgt_list = vhci_mpapi_create_item(vhci, + MP_OBJECT_TYPE_TARGET_PORT, + (void *)tgt_port); + tpd = tgt_list->item->idata; + tpd->valid = 1; + tpd->prop.relativePortID = rel_tid; + } else { + tpd = tgt_list->item->idata; + tpd->valid = 1; + } + + tpd = (mpapi_tport_data_t *)tgt_list->item->idata; + if (vhci_get_mpapi_item(vhci, tpd->path_list, + MP_OBJECT_TYPE_PATH_LU, (void*)pip) == NULL) { + tp_path_list = kmem_zalloc(sizeof (mpapi_item_list_t), + KM_SLEEP); + tp_path_list->item = path_list->item; + (void) vhci_mpapi_add_to_list(tpd->path_list, + tp_path_list); + } + + if (vhci_mpapi_get_rel_tport_pair(vhci, + tpg_data->tport_list, tgt_port, rel_tid) == NULL) { + tpg_tport_list = kmem_zalloc + (sizeof (mpapi_item_list_t), KM_SLEEP); + tpg_tport_list->item = tgt_list->item; + (void) vhci_mpapi_add_to_list(tpg_data-> + tport_list, tpg_tport_list); + } + ptr += 4; + } + + /* + * Level-1: Fill-out Path Properties now, since we got all details. + * Actually, It is a structure copy, rather than just filling details. + */ + pd = path_list->item->idata; + bcopy(&(ld->prop), &(pd->prop.logicalUnit), + sizeof (struct mp_logical_unit_prop)); + bcopy(&(initd->prop), &(pd->prop.initPort), + sizeof (struct mp_init_port_prop)); + bcopy(&(tpd->prop), &(pd->prop.targetPort), + sizeof (struct mp_target_port_prop)); +} + +/* + * Routine to get mpapi ioctl argument structure from userland. + */ +/* ARGSUSED */ +static int +vhci_get_mpiocdata(const void *data, mp_iocdata_t *mpioc, int mode) +{ + int retval = 0; + +#ifdef _MULTI_DATAMODEL + switch (ddi_model_convert_from(mode & FMODELS)) { + case DDI_MODEL_ILP32: + { + mp_iocdata32_t ioc32; + + VHCI_DEBUG(6, (CE_WARN, NULL, "vhci_get_mpiocdata: " + "Case DDI_MODEL_ILP32")); + if (ddi_copyin((void *)data, (void *)&ioc32, + sizeof (mp_iocdata32_t), mode)) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_mpiocdata: " + "ddi_copyin() FAILED")); + retval = EFAULT; + break; + } + mpioc->mp_xfer = (uint16_t)(uintptr_t)ioc32.mp_xfer; + mpioc->mp_cmd = (uint16_t)(uintptr_t)ioc32.mp_cmd; + mpioc->mp_flags = (uint16_t)(uintptr_t)ioc32.mp_flags; + mpioc->mp_cmd_flags = (uint16_t)ioc32.mp_cmd_flags; + mpioc->mp_ilen = (size_t)(uintptr_t)ioc32.mp_ilen; + mpioc->mp_ibuf = (caddr_t)(uintptr_t)ioc32.mp_ibuf; + mpioc->mp_olen = (size_t)(uintptr_t)ioc32.mp_olen; + mpioc->mp_obuf = (caddr_t)(uintptr_t)ioc32.mp_obuf; + mpioc->mp_alen = (size_t)(uintptr_t)ioc32.mp_alen; + mpioc->mp_abuf = (caddr_t)(uintptr_t)ioc32.mp_abuf; + mpioc->mp_errno = (int)(uintptr_t)ioc32.mp_errno; + break; + } + + case DDI_MODEL_NONE: + if (ddi_copyin(data, (void*)mpioc, sizeof (*mpioc), mode)) { + retval = EFAULT; + break; + } + break; + + default: + if (ddi_copyin(data, (void*)mpioc, sizeof (*mpioc), mode)) { + retval = EFAULT; + break; + } + break; + } +#else /* _MULTI_DATAMODEL */ + if (ddi_copyin(data, (void *)mpioc, sizeof (*mpioc), mode)) { + retval = EFAULT; + } +#endif /* _MULTI_DATAMODEL */ + + if (retval) { + VHCI_DEBUG(2, (CE_WARN, NULL, "vhci_get_mpiocdata: cmd <%x> " + "iocdata copyin failed", mpioc->mp_cmd)); + } + + return (retval); +} + +/* ARGSUSED */ +static int +vhci_is_model_type32(int mode) +{ +#ifdef _MULTI_DATAMODEL + switch (ddi_model_convert_from(mode & FMODELS)) { + case DDI_MODEL_ILP32: + return (1); + default: + return (0); + } +#else /* _MULTI_DATAMODEL */ + return (0); +#endif /* _MULTI_DATAMODEL */ +} + +/* + * Convenience routine to copy mp_iocdata(32) to user land + */ +/* ARGSUSED */ +static int +vhci_mpapi_copyout_iocdata(void *mpioc, void *udata, int mode) +{ + int rval = 0; + + if (vhci_is_model_type32(mode)) { + mp_iocdata32_t *mpioc32; + + mpioc32 = (mp_iocdata32_t *)kmem_zalloc + (sizeof (mp_iocdata32_t), KM_SLEEP); + mpioc32->mp_xfer = (uint16_t)((mp_iocdata_t *)mpioc)->mp_xfer; + mpioc32->mp_cmd = (uint16_t)((mp_iocdata_t *)mpioc)->mp_cmd; + mpioc32->mp_flags = (uint16_t)((mp_iocdata_t *)mpioc)->mp_flags; + mpioc32->mp_cmd_flags = (uint16_t)((mp_iocdata_t *) + mpioc)->mp_cmd_flags; + mpioc32->mp_ilen = (uint32_t)((mp_iocdata_t *)mpioc)->mp_ilen; + mpioc32->mp_ibuf = (caddr32_t)((mp_iocdata32_t *) + mpioc)->mp_ibuf; + mpioc32->mp_olen = (uint32_t)((mp_iocdata_t *)mpioc)->mp_olen; + mpioc32->mp_obuf = (caddr32_t)((mp_iocdata32_t *) + mpioc)->mp_obuf; + mpioc32->mp_alen = (uint32_t)((mp_iocdata_t *)mpioc)->mp_alen; + mpioc32->mp_abuf = (caddr32_t)((mp_iocdata32_t *) + mpioc)->mp_abuf; + mpioc32->mp_errno = (int32_t)((mp_iocdata_t *)mpioc)->mp_errno; + + if (ddi_copyout(mpioc32, udata, sizeof (mp_iocdata32_t), mode) + != 0) { + rval = EFAULT; + } + kmem_free(mpioc32, sizeof (mp_iocdata32_t)); + } else { + /* 64-bit ddicopyout */ + if (ddi_copyout(mpioc, udata, sizeof (mp_iocdata_t), mode) + != 0) { + rval = EFAULT; + } + } + + return (rval); + +} + +/* + * Routine to sync OIDs of MPLU to match with the ssd instance# of the + * scsi_vhci lun, to accommodate the DINFOCACHE implementation of the plugin. + * ssd instance# = devi_instance from the dev_info structure. + * dev_info structure of the scsi_vhci lun is pointed by svl_dip field of + * scsi_vhci_lun structure. + */ +/* ARGSUSED */ +static int +vhci_mpapi_sync_lu_oid_list(struct scsi_vhci *vhci) +{ + int rval = 0; + mpapi_item_list_t *ilist; + mpapi_lu_data_t *lud; + mpapi_path_data_t *pd; + scsi_vhci_lun_t *svl; + dev_info_t *lun_dip; + + ilist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_MULTIPATH_LU]->head; + + while (ilist != NULL) { + if (MP_GET_MAJOR_FROM_ID((uint64_t) + (ilist->item->oid.raw_oid)) != 0) { + lud = ilist->item->idata; + if (lud->valid == 1) { + svl = lud->resp; + ilist->item->oid.raw_oid = + (uint64_t)ddi_get_instance(svl->svl_dip); + lud->prop.id = + (uint64_t)ddi_get_instance(svl->svl_dip); + } + } + ilist = ilist->next; + } + + ilist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_PATH_LU]->head; + while (ilist != NULL) { + pd = ilist->item->idata; + if ((pd->valid == 1) && (MP_GET_MAJOR_FROM_ID((uint64_t) + (pd->prop.logicalUnit.id)) != 0)) { + lun_dip = mdi_pi_get_client + ((mdi_pathinfo_t *)(pd->resp)); + pd->prop.logicalUnit.id = + (uint64_t)ddi_get_instance(lun_dip); + } + ilist = ilist->next; + } + + return (rval); +} + +/* + * Routine to sync Initiator Port List with what MDI maintains. This means + * MP API knows about Initiator Ports which don't have a pip. + */ +/* ARGSUSED */ +int +vhci_mpapi_sync_init_port_list(dev_info_t *pdip, void *arg) +{ + int init_not_ddi_alloced = 0; + struct scsi_vhci *vhci = arg; + char *init, *init_port_res; + mpapi_item_list_t *init_list; + mpapi_initiator_data_t *initd; + + if ((ddi_prop_lookup_string(DDI_DEV_T_ANY, pdip, + DDI_PROP_DONTPASS, "initiator-port", &init) != DDI_PROP_SUCCESS)) { + /* XXX: initiator-port prop not found */ + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_sync_init_port_list: " + "initiator-port prop not found")); + init = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + init_not_ddi_alloced = 1; + (void) ddi_pathname(pdip, init); + } + + init_port_res = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + (void) ddi_pathname(pdip, init_port_res); + + init_list = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_INITIATOR_PORT, (void*)init_port_res); + if (init_list == NULL) { + /* + * Need to create init_list entry + * The resource ptr is not really pdip. It will be changed + * in vhci_mpapi_create_item(). The real resource ptr + * is the Port ID. But we pass the pdip, to create OID. + */ + init_list = vhci_mpapi_create_item(vhci, + MP_OBJECT_TYPE_INITIATOR_PORT, (void*)pdip); + } + + initd = init_list->item->idata; + initd->valid = 1; + (void) strlcpy(initd->prop.portID, init, sizeof (initd->prop.portID)); + + if (init_not_ddi_alloced == 1) { + kmem_free(init, MAXPATHLEN); + } else if (init) { + ddi_prop_free(init); + } + kmem_free(init_port_res, MAXPATHLEN); + + return (DDI_WALK_CONTINUE); +} + +/* ARGSUSED */ +static void +vhci_mpapi_log_sysevent(dev_info_t *dip, uint64_t *oid, char *subclass) +{ + nvlist_t *attr_list; + + if (nvlist_alloc(&attr_list, NV_UNIQUE_NAME_TYPE, + KM_SLEEP) != DDI_SUCCESS) { + goto alloc_failed; + } + + if (nvlist_add_uint64_array(attr_list, "oid", oid, 1) != DDI_SUCCESS) { + goto error; + } + + (void) ddi_log_sysevent(dip, DDI_VENDOR_SUNW, EC_SUN_MP, subclass, + attr_list, NULL, DDI_SLEEP); + +error: + nvlist_free(attr_list); + return; + +alloc_failed: + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_log_sysevent: " + "Unable to send sysevent")); + +} + +/* ARGSUSED */ +void +vhci_mpapi_set_path_state(dev_info_t *vdip, mdi_pathinfo_t *pip, int state) +{ + struct scsi_vhci *vhci; + struct scsi_vhci_lun *svl; + scsi_vhci_priv_t *svp; + mpapi_item_list_t *ilist, *lu_list; + mpapi_path_data_t *pp; + mpapi_lu_data_t *ld; + + vhci = ddi_get_soft_state(vhci_softstate, ddi_get_instance(vdip)); + + ilist = vhci_get_mpapi_item(vhci, NULL, MP_OBJECT_TYPE_PATH_LU, pip); + + if (ilist != NULL) { + mutex_enter(&ilist->item->item_mutex); + pp = ilist->item->idata; + pp->prop.pathState = state; + pp->valid = 1; + } else { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_set_path_state: " + "pip(%p) not found", (void *)pip)); + return; + } + + /* + * Find if there are any paths at all to the lun + */ + if ((state == MP_DRVR_PATH_STATE_REMOVED) || (state == + MP_DRVR_PATH_STATE_PATH_ERR) || (state == + MP_DRVR_PATH_STATE_LU_ERR) || (state == + MP_DRVR_PATH_STATE_UNKNOWN)) { + pp->valid = 0; + + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(pip); + svl = svp->svp_svl; + /* + * Update the AccessState of related MPAPI TPGs + * This takes care of a special case where a path goes offline + * & the TPG accessState may need an update from + * Active/Standby to Unavailable. + */ + if (!SCSI_FAILOVER_IS_TPGS(svl->svl_fops)) { + (void) vhci_mpapi_update_tpg_acc_state_for_lu(vhci, + svl); + } + + /* + * Following means the lun is offline + */ + if (vhci_mpapi_chk_last_path(pip) == -1) { + lu_list = vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_MULTIPATH_LU, (void *)svl); + if (lu_list != NULL) { + ld = lu_list->item->idata; + ld->valid = 0; + } + } + } + mutex_exit(&ilist->item->item_mutex); + +} + +/* ARGSUSED */ +static mpapi_item_list_t * +vhci_mpapi_match_pip(struct scsi_vhci *vhci, mpapi_item_list_t *ilist, + void *res) +{ + mpapi_path_data_t *pd; + scsi_vhci_lun_t *this_svl; + mdi_pathinfo_t *this_pip; + char *this_iport; + char *this_tport; + char *pname; + + this_pip = (mdi_pathinfo_t *)res; + if ((this_pip == NULL) || (ilist == NULL)) { + return (NULL); + } + + this_iport = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + (void) ddi_pathname(mdi_pi_get_phci(this_pip), this_iport); + + if (mdi_prop_lookup_string(this_pip, "target-port", &this_tport) + != DDI_PROP_SUCCESS) { + /* XXX: target-port prop not found */ + this_tport = (char *)mdi_pi_get_addr(this_pip); + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_mpapi_match_pip: " + "mdi_prop_lookup_string() returned failure; " + "Hence this_tport = %p", (void *)this_tport)); + } + + this_svl = mdi_client_get_vhci_private(mdi_pi_get_client(this_pip)); + + pname = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + (void) strlcat(pname, this_iport, MAXPATHLEN); + (void) strlcat(pname, this_tport, MAXPATHLEN); + (void) strlcat(pname, this_svl->svl_lun_wwn, MAXPATHLEN); + kmem_free(this_iport, MAXPATHLEN); + + while (ilist != NULL) { + pd = (mpapi_path_data_t *)(ilist->item->idata); + if ((pd != NULL) && (strncmp + (pd->path_name, pname, strlen(pname)) == 0)) { + VHCI_DEBUG(6, (CE_WARN, NULL, "vhci_mpapi_match_pip: " + "path_name = %s", pd->path_name)); + kmem_free(pname, MAXPATHLEN); + return (ilist); + } + ilist = ilist->next; + } + + kmem_free(pname, MAXPATHLEN); + return (NULL); +} + +/* ARGSUSED */ +static +mpapi_item_list_t *vhci_mpapi_match_lu(struct scsi_vhci *vhci, + mpapi_item_list_t *ilist, void *res) +{ + mpapi_lu_data_t *ld; + scsi_vhci_lun_t *this_svl; + + this_svl = (scsi_vhci_lun_t *)res; + if ((this_svl == NULL) || (ilist == NULL)) { + return (NULL); + } + + while (ilist != NULL) { + ld = (mpapi_lu_data_t *)(ilist->item->idata); + if ((ld != NULL) && (strncmp + (ld->prop.name, this_svl->svl_lun_wwn, + strlen(this_svl->svl_lun_wwn)) == 0)) { + VHCI_DEBUG(6, (CE_WARN, NULL, "vhci_mpapi_match_lu: " + "this_wwn = %s", this_svl->svl_lun_wwn)); + return (ilist); + } + ilist = ilist->next; + } + + return (NULL); +} + +/* + * Routine to handle TPG AccessState Change - Called after each LU failover + */ +int +vhci_mpapi_update_tpg_acc_state_for_lu(struct scsi_vhci *vhci, + scsi_vhci_lun_t *vlun) +{ + int rval = 0; + mpapi_item_list_t *lu_list, *path_list, *tpg_list; + mpapi_lu_data_t *lu_data; + mpapi_path_data_t *path_data; + mpapi_tpg_data_t *tpg_data; + + lu_list = vhci_get_mpapi_item(vhci, NULL, MP_OBJECT_TYPE_MULTIPATH_LU, + (void *)vlun); + if (lu_list == NULL) { + return (-1); + } + lu_data = lu_list->item->idata; + if (lu_data == NULL) { + return (-1); + } + lu_data->resp = vlun; + lu_data->valid = 1; + + /* + * For each "pclass of PATH" and "pclass of TPG" match of this LU, + * Update the TPG AccessState to reflect the state of the path. + * Exit the inner loop after the 1st successful ACTIVE/STANDBY update + * is made, because subsequent matches also lead to the same TPG. + */ + tpg_list = lu_data->tpg_list->head; + while (tpg_list != NULL) { + tpg_data = tpg_list->item->idata; + path_list = lu_data->path_list->head; + while (path_list != NULL) { + path_data = path_list->item->idata; + if (strncmp(path_data->pclass, tpg_data->pclass, + strlen(tpg_data->pclass)) == 0) { + VHCI_DEBUG(4, (CE_NOTE, NULL, "vhci_mpapi_" + "update_tpg_acc_state_for_lu: Operating on " + "LUN(%s), PATH(%p), TPG(%x: %s)\n", + lu_data->prop.name, path_data->resp, + tpg_data->prop.tpgId, tpg_data->pclass)); + if (MDI_PI_IS_ONLINE(path_data->resp)) { + tpg_data->prop.accessState = + MP_DRVR_ACCESS_STATE_ACTIVE; + break; + } else if (MDI_PI_IS_STANDBY(path_data->resp)) { + tpg_data->prop.accessState = + MP_DRVR_ACCESS_STATE_STANDBY; + break; + } else { + tpg_data->prop.accessState = + MP_DRVR_ACCESS_STATE_UNAVAILABLE; + } + } + path_list = path_list->next; + } + tpg_list = tpg_list->next; + } + + return (rval); +} + +int +vhci_mpapi_get_vhci(dev_info_t *vdip, void *ptr2vhci) +{ + struct scsi_vhci *local_vhci; + + if (strncmp("scsi_vhci", ddi_get_name(vdip), + strlen("scsi_vhci")) == 0) { + local_vhci = ddi_get_soft_state(vhci_softstate, + ddi_get_instance(vdip)); + bcopy(&local_vhci, ptr2vhci, sizeof (local_vhci)); + return (DDI_WALK_TERMINATE); + } + + return (DDI_WALK_CONTINUE); + +} + +/* ARGSUSED */ +void * +vhci_mpapi_get_rel_tport_pair(struct scsi_vhci *vhci, mpapi_list_header_t *list, + void *tgt_port, uint32_t rel_tid) +{ + mpapi_item_list_t *ilist; + mpapi_tport_data_t *tpd; + + if (list == NULL) { + /* + * Since the listhead is null, the search is being + * performed in implicit mode - that is to use the + * level one list. + */ + ilist = vhci->mp_priv->obj_hdr_list[MP_OBJECT_TYPE_TARGET_PORT] + ->head; + } else { + /* + * The search is being performed on a sublist within + * one of the toplevel list items. Use the listhead + * that is passed in. + */ + ilist = list->head; + } + + if (tgt_port == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_get_mpapi_item: " + " Got Target Port w/ NULL resource")); + return (NULL); + } + + while (ilist) { + tpd = (mpapi_tport_data_t *)ilist->item->idata; + if ((strncmp(tpd->resp, tgt_port, strlen(tgt_port)) == 0) && + (tpd->prop.relativePortID == rel_tid)) { + /* Match */ + return ((void*)ilist); + } else { + ilist = ilist->next; + } + } + + return (NULL); +} + +/* + * Returns 0, if 2 more paths are available to the lun; + * Returns 1, if ONLY 1 path is available to the lun; + * Return -1 for all other cases. + */ +static int +vhci_mpapi_chk_last_path(mdi_pathinfo_t *pip) +{ + dev_info_t *pdip = NULL, *cdip = NULL; + int count = 0, circular; + mdi_pathinfo_t *ret_pip; + + if (pip == NULL) { + return (-1); + } else { + pdip = mdi_pi_get_phci(pip); + cdip = mdi_pi_get_client(pip); + } + + if ((pdip == NULL) || (cdip == NULL)) { + return (-1); + } + + ndi_devi_enter(cdip, &circular); + ret_pip = mdi_get_next_phci_path(cdip, NULL); + + while ((ret_pip != NULL) && (count < 2)) { + mdi_pi_lock(ret_pip); + if ((MDI_PI_IS_ONLINE(ret_pip) || + MDI_PI_IS_STANDBY(ret_pip) || + MDI_PI_IS_INIT(ret_pip)) && + !(MDI_PI_IS_DISABLE(ret_pip) || + MDI_PI_IS_TRANSIENT(ret_pip))) { + count++; + } + mdi_pi_unlock(ret_pip); + ret_pip = mdi_get_next_phci_path(cdip, ret_pip); + } + ndi_devi_exit(cdip, circular); + + if (count > 1) { + return (0); + } else if (count == 1) { + return (1); + } + + return (-1); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/common/io/scsi/adapters/scsi_vhci/scsi_vhci.c Fri Aug 10 17:00:37 2007 -0700 @@ -0,0 +1,8346 @@ +/* + * 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 2007 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#pragma ident "%Z%%M% %I% %E% SMI" + +/* + * Multiplexed I/O SCSI vHCI implementation + */ + +#include <sys/conf.h> +#include <sys/file.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/scsi/scsi.h> +#include <sys/scsi/impl/scsi_reset_notify.h> +#include <sys/sunmdi.h> +#include <sys/mdi_impldefs.h> +#include <sys/scsi/adapters/scsi_vhci.h> +#include <sys/disp.h> +#include <sys/byteorder.h> + +extern uintptr_t scsi_callback_id; +extern ddi_dma_attr_t scsi_alloc_attr; + +#ifdef DEBUG +int vhci_debug = VHCI_DEBUG_DEFAULT_VAL; +#endif + +/* retry for the vhci_do_prout command when a not ready is returned */ +int vhci_prout_not_ready_retry = 180; + +/* + * These values are defined to support the internal retry of + * SCSI packets for better sense code handling. + */ +#define VHCI_CMD_CMPLT 0 +#define VHCI_CMD_RETRY 1 +#define VHCI_CMD_ERROR -1 + +#define PROPFLAGS (DDI_PROP_DONTPASS | DDI_PROP_NOTPROM) +#define VHCI_SCSI_PERR 0x47 +#define VHCI_PGR_ILLEGALOP -2 +#define VHCI_NUM_UPDATE_TASKQ 8 +#define VHCI_STD_INQ_SIZE 128 + +/* + * Version Macros + */ +#define VHCI_NAME_VERSION "SCSI VHCI Driver %I%" +char vhci_version_name[] = VHCI_NAME_VERSION; + +int vhci_first_time = 0; +clock_t vhci_to_ticks = 0; +int vhci_init_wait_timeout = VHCI_INIT_WAIT_TIMEOUT; +kcondvar_t vhci_cv; +kmutex_t vhci_global_mutex; +void *vhci_softstate = NULL; /* for soft state */ + +/* + * Flag to delay the retry of the reserve command + */ +int vhci_reserve_delay = 100000; +static int vhci_path_quiesce_timeout = 60; +static uchar_t zero_key[MHIOC_RESV_KEY_SIZE]; + +/* uscsi delay for a TRAN_BUSY */ +static int vhci_uscsi_delay = 100000; +static int vhci_uscsi_retry_count = 180; +/* uscsi_restart_sense timeout id in case it needs to get canceled */ +static timeout_id_t vhci_restart_timeid = 0; + +/* + * Bidirectional map of 'target-port' to port id <pid> for support of + * iostat(1M) '-Xx' and '-Yx' output. + */ +static kmutex_t vhci_targetmap_mutex; +static uint_t vhci_targetmap_pid = 1; +static mod_hash_t *vhci_targetmap_bypid; /* <pid> -> 'target-port' */ +static mod_hash_t *vhci_targetmap_byport; /* 'target-port' -> <pid> */ + +/* + * functions exported by scsi_vhci struct cb_ops + */ +static int vhci_open(dev_t *, int, int, cred_t *); +static int vhci_close(dev_t, int, int, cred_t *); +static int vhci_ioctl(dev_t, int, intptr_t, int, cred_t *, int *); + +/* + * functions exported by scsi_vhci struct dev_ops + */ +static int vhci_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **); +static int vhci_attach(dev_info_t *, ddi_attach_cmd_t); +static int vhci_detach(dev_info_t *, ddi_detach_cmd_t); + +/* + * functions exported by scsi_vhci scsi_hba_tran_t transport table + */ +static int vhci_scsi_tgt_init(dev_info_t *, dev_info_t *, + scsi_hba_tran_t *, struct scsi_device *); +static void vhci_scsi_tgt_free(dev_info_t *, dev_info_t *, scsi_hba_tran_t *, + struct scsi_device *); +static int vhci_pgr_register_start(scsi_vhci_lun_t *, struct scsi_pkt *); +static int vhci_scsi_start(struct scsi_address *, struct scsi_pkt *); +static int vhci_scsi_abort(struct scsi_address *, struct scsi_pkt *); +static int vhci_scsi_reset(struct scsi_address *, int); +static int vhci_scsi_reset_target(struct scsi_address *, int level, + uint8_t select_path); +static int vhci_scsi_reset_bus(struct scsi_address *); +static int vhci_scsi_getcap(struct scsi_address *, char *, int); +static int vhci_scsi_setcap(struct scsi_address *, char *, int, int); +static int vhci_commoncap(struct scsi_address *, char *, int, int, int); +static int vhci_pHCI_cap(struct scsi_address *ap, char *cap, int val, int whom, + mdi_pathinfo_t *pip); +static struct scsi_pkt *vhci_scsi_init_pkt(struct scsi_address *, + struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(), caddr_t); +static void vhci_scsi_destroy_pkt(struct scsi_address *, struct scsi_pkt *); +static void vhci_scsi_dmafree(struct scsi_address *, struct scsi_pkt *); +static void vhci_scsi_sync_pkt(struct scsi_address *, struct scsi_pkt *); +static int vhci_scsi_reset_notify(struct scsi_address *, int, void (*)(caddr_t), + caddr_t); +static int vhci_scsi_get_bus_addr(struct scsi_device *, char *, int); +static int vhci_scsi_get_name(struct scsi_device *, char *, int); +static int vhci_scsi_bus_power(dev_info_t *, void *, pm_bus_power_op_t, + void *, void *); +static int vhci_scsi_bus_config(dev_info_t *, uint_t, ddi_bus_config_op_t, + void *, dev_info_t **); + +/* + * functions registered with the mpxio framework via mdi_vhci_ops_t + */ +static int vhci_pathinfo_init(dev_info_t *, mdi_pathinfo_t *, int); +static int vhci_pathinfo_uninit(dev_info_t *, mdi_pathinfo_t *, int); +static int vhci_pathinfo_state_change(dev_info_t *, mdi_pathinfo_t *, + mdi_pathinfo_state_t, uint32_t, int); +static int vhci_pathinfo_online(dev_info_t *, mdi_pathinfo_t *, int); +static int vhci_pathinfo_offline(dev_info_t *, mdi_pathinfo_t *, int); +static int vhci_failover(dev_info_t *, dev_info_t *, int); +static void vhci_client_attached(dev_info_t *); + +static int vhci_ctl(dev_t, int, intptr_t, int, cred_t *, int *); +static int vhci_devctl(dev_t, int, intptr_t, int, cred_t *, int *); +static int vhci_ioc_get_phci_path(sv_iocdata_t *, caddr_t, int, caddr_t); +static int vhci_ioc_get_client_path(sv_iocdata_t *, caddr_t, int, caddr_t); +static int vhci_ioc_get_paddr(sv_iocdata_t *, caddr_t, int, caddr_t); +static int vhci_ioc_send_client_path(caddr_t, sv_iocdata_t *, int, caddr_t); +static void vhci_ioc_devi_to_path(dev_info_t *, caddr_t); +static int vhci_get_phci_path_list(dev_info_t *, sv_path_info_t *, uint_t); +static int vhci_get_client_path_list(dev_info_t *, sv_path_info_t *, uint_t); +static int vhci_get_iocdata(const void *, sv_iocdata_t *, int, caddr_t); +static int vhci_get_iocswitchdata(const void *, sv_switch_to_cntlr_iocdata_t *, + int, caddr_t); +static int vhci_ioc_alloc_pathinfo(sv_path_info_t **, sv_path_info_t **, + uint_t, sv_iocdata_t *, int, caddr_t); +static void vhci_ioc_free_pathinfo(sv_path_info_t *, sv_path_info_t *, uint_t); +static int vhci_ioc_send_pathinfo(sv_path_info_t *, sv_path_info_t *, uint_t, + sv_iocdata_t *, int, caddr_t); +static int vhci_handle_ext_fo(struct scsi_pkt *, int); +static int vhci_efo_watch_cb(caddr_t, struct scsi_watch_result *); +static int vhci_quiesce_lun(struct scsi_vhci_lun *); +static int vhci_pgr_validate_and_register(scsi_vhci_priv_t *); +static void vhci_dispatch_scsi_start(void *); +static void vhci_efo_done(void *); +static void vhci_initiate_auto_failback(void *); +static void vhci_update_pHCI_pkt(struct vhci_pkt *, struct scsi_pkt *); +static int vhci_update_pathinfo(struct scsi_device *, mdi_pathinfo_t *, + struct scsi_failover_ops *, scsi_vhci_lun_t *, struct scsi_vhci *); +static void vhci_kstat_create_pathinfo(mdi_pathinfo_t *); +static int vhci_quiesce_paths(dev_info_t *, dev_info_t *, + scsi_vhci_lun_t *, char *, char *); + +static char *vhci_devnm_to_guid(char *); +static int vhci_bind_transport(struct scsi_address *, struct vhci_pkt *, + int, int (*func)(caddr_t)); +static void vhci_intr(struct scsi_pkt *); +static int vhci_do_prout(scsi_vhci_priv_t *); +static void vhci_run_cmd(void *); +static int vhci_do_prin(struct vhci_pkt **); +static struct scsi_pkt *vhci_create_retry_pkt(struct vhci_pkt *); +static struct vhci_pkt *vhci_sync_retry_pkt(struct vhci_pkt *); +static struct scsi_vhci_lun *vhci_lun_lookup(dev_info_t *); +static struct scsi_vhci_lun *vhci_lun_lookup_alloc(dev_info_t *, char *, int *); +static void vhci_lun_free(dev_info_t *); +static int vhci_recovery_reset(scsi_vhci_lun_t *, struct scsi_address *, + uint8_t, uint8_t); +void vhci_update_pathstates(void *); + +#ifdef DEBUG +static void vhci_print_prin_keys(vhci_prin_readkeys_t *, int); +#endif +static void vhci_print_prout_keys(scsi_vhci_lun_t *, char *); +static void vhci_uscsi_iodone(struct scsi_pkt *pkt); + +/* + * MP-API related functions + */ +extern int vhci_mpapi_init(struct scsi_vhci *); +extern void vhci_mpapi_add_dev_prod(struct scsi_vhci *, char *); +extern int vhci_mpapi_ctl(dev_t, int, intptr_t, int, cred_t *, int *); +extern void vhci_update_mpapi_data(struct scsi_vhci *, + scsi_vhci_lun_t *, mdi_pathinfo_t *); +extern void* vhci_get_mpapi_item(struct scsi_vhci *, mpapi_list_header_t *, + uint8_t, void*); +extern void vhci_mpapi_set_path_state(dev_info_t *, mdi_pathinfo_t *, int); +extern int vhci_mpapi_update_tpg_acc_state_for_lu(struct scsi_vhci *, + scsi_vhci_lun_t *); + +/* Special export to MP-API of tpgs non-'fops' entry point */ +int (*tpgs_set_target_groups)(struct scsi_address *, int, int); + +#define VHCI_DMA_MAX_XFER_CAP 0xffffffffULL + +#define VHCI_MAX_PGR_RETRIES 3 + +/* + * Macros for the device-type mpxio options + */ +#define LOAD_BALANCE_OPTIONS "load-balance-options" +#define LOGICAL_BLOCK_REGION_SIZE "region-size" +#define MPXIO_OPTIONS_LIST "device-type-mpxio-options-list" +#define DEVICE_TYPE_STR "device-type" +#define isdigit(ch) ((ch) >= '0' && (ch) <= '9') + +static struct cb_ops vhci_cb_ops = { + vhci_open, /* open */ + vhci_close, /* close */ + nodev, /* strategy */ + nodev, /* print */ + nodev, /* dump */ + nodev, /* read */ + nodev, /* write */ + vhci_ioctl, /* ioctl */ + nodev, /* devmap */ + nodev, /* mmap */ + nodev, /* segmap */ + nochpoll, /* chpoll */ + ddi_prop_op, /* cb_prop_op */ + 0, /* streamtab */ + D_NEW | D_MP, /* cb_flag */ + CB_REV, /* rev */ + nodev, /* aread */ + nodev /* awrite */ +}; + +static struct dev_ops vhci_ops = { + DEVO_REV, + 0, + vhci_getinfo, + nulldev, /* identify */ + nulldev, /* probe */ + vhci_attach, /* attach and detach are mandatory */ + vhci_detach, + nodev, /* reset */ + &vhci_cb_ops, /* cb_ops */ + NULL, /* bus_ops */ + NULL, /* power */ +}; + +extern struct mod_ops mod_driverops; + +static struct modldrv modldrv = { + &mod_driverops, + vhci_version_name, /* module name */ + &vhci_ops +}; + +static struct modlinkage modlinkage = { + MODREV_1, + &modldrv, + NULL +}; + +static mdi_vhci_ops_t vhci_opinfo = { + MDI_VHCI_OPS_REV, + vhci_pathinfo_init, /* Pathinfo node init callback */ + vhci_pathinfo_uninit, /* Pathinfo uninit callback */ + vhci_pathinfo_state_change, /* Pathinfo node state change */ + vhci_failover, /* failover callback */ + vhci_client_attached /* client attached callback */ +}; + +/* + * The scsi_failover table defines an ordered set of 'fops' modules supported + * by scsi_vhci. Currently, initialize this table from the 'ddi-forceload' + * property specified in scsi_vhci.conf. + */ +struct scsi_failover { + ddi_modhandle_t sf_mod; + struct scsi_failover_ops *sf_sfo; +} *scsi_failover_table; +uint_t scsi_nfailover; + +int +_init(void) +{ + int rval; + + /* + * Allocate soft state and prepare to do ddi_soft_state_zalloc() + * before registering with the transport first. + */ + if ((rval = ddi_soft_state_init(&vhci_softstate, + sizeof (struct scsi_vhci), 1)) != 0) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!_init:soft state init failed\n")); + return (rval); + } + + if ((rval = scsi_hba_init(&modlinkage)) != 0) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!_init: scsi hba init failed\n")); + ddi_soft_state_fini(&vhci_softstate); + return (rval); + } + + mutex_init(&vhci_global_mutex, NULL, MUTEX_DRIVER, NULL); + cv_init(&vhci_cv, NULL, CV_DRIVER, NULL); + + mutex_init(&vhci_targetmap_mutex, NULL, MUTEX_DRIVER, NULL); + vhci_targetmap_byport = mod_hash_create_strhash( + "vhci_targetmap_byport", 256, mod_hash_null_valdtor); + vhci_targetmap_bypid = mod_hash_create_idhash( + "vhci_targetmap_bypid", 256, mod_hash_null_valdtor); + + if ((rval = mod_install(&modlinkage)) != 0) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!_init: mod_install failed\n")); + if (vhci_targetmap_bypid) + mod_hash_destroy_idhash(vhci_targetmap_bypid); + if (vhci_targetmap_byport) + mod_hash_destroy_strhash(vhci_targetmap_byport); + mutex_destroy(&vhci_targetmap_mutex); + cv_destroy(&vhci_cv); + mutex_destroy(&vhci_global_mutex); + scsi_hba_fini(&modlinkage); + ddi_soft_state_fini(&vhci_softstate); + } + 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) { + VHCI_DEBUG(4, (CE_NOTE, NULL, "!_fini: mod_remove failed\n")); + return (rval); + } + + if (vhci_targetmap_bypid) + mod_hash_destroy_idhash(vhci_targetmap_bypid); + if (vhci_targetmap_byport) + mod_hash_destroy_strhash(vhci_targetmap_byport); + mutex_destroy(&vhci_targetmap_mutex); + cv_destroy(&vhci_cv); + mutex_destroy(&vhci_global_mutex); + scsi_hba_fini(&modlinkage); + ddi_soft_state_fini(&vhci_softstate); + + return (rval); +} + +int +_info(struct modinfo *modinfop) +{ + return (mod_info(&modlinkage, modinfop)); +} + +/* + * Lookup scsi_failover by "short name" of failover module. + */ +struct scsi_failover_ops * +vhci_failover_ops_by_name(char *name) +{ + struct scsi_failover *sf; + + for (sf = scsi_failover_table; sf->sf_mod; sf++) { + if (sf->sf_sfo == NULL) + continue; + if (strcmp(sf->sf_sfo->sfo_name, name) == 0) + return (sf->sf_sfo); + } + return (NULL); +} + +/* + * Load all scsi_failover_ops 'fops' modules. + */ +static void +vhci_failover_modopen(struct scsi_vhci *vhci) +{ + char **module; + int i; + struct scsi_failover *sf; + char **dt; + int e; + + if (scsi_failover_table) + return; + + /* Get the list of modules from scsi_vhci.conf */ + if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, + vhci->vhci_dip, DDI_PROP_DONTPASS, "ddi-forceload", + &module, &scsi_nfailover) != DDI_PROP_SUCCESS) { + cmn_err(CE_WARN, "scsi_vhci: " + "scsi_vhci.conf is missing 'ddi-forceload'"); + return; + } + if (scsi_nfailover == 0) { + cmn_err(CE_WARN, "scsi_vhci: " + "scsi_vhci.conf has empty 'ddi-forceload'"); + return; + } + + /* allocate failover table based on number of modules */ + scsi_failover_table = (struct scsi_failover *) + kmem_zalloc(sizeof (struct scsi_failover) * (scsi_nfailover + 1), + KM_SLEEP); + + /* loop over modules specified in scsi_vhci.conf and open each module */ + for (i = 0, sf = scsi_failover_table; i < scsi_nfailover; i++) { + if (module[i] == NULL) + continue; + + sf->sf_mod = ddi_modopen(module[i], KRTLD_MODE_FIRST, &e); + if (sf->sf_mod == NULL) { + /* + * A module returns EEXIST if other software is + * supporting the intended function: for example + * the scsi_vhci_f_sum_emc module returns EEXIST + * from _init if EMC powerpath software is installed. + */ + if (e != EEXIST) + cmn_err(CE_WARN, "scsi_vhci: unable to open " + "module '%s', error %d", module[i], e); + continue; + } + sf->sf_sfo = ddi_modsym(sf->sf_mod, + "scsi_vhci_failover_ops", &e); + if (sf->sf_sfo == NULL) { + cmn_err(CE_WARN, "scsi_vhci: " + "unable to import 'scsi_failover_ops' from '%s', " + "error %d", module[i], e); + (void) ddi_modclose(sf->sf_mod); + sf->sf_mod = NULL; + continue; + } + + /* register vid/pid of devices supported with mpapi */ + for (dt = sf->sf_sfo->sfo_devices; *dt; dt++) + vhci_mpapi_add_dev_prod(vhci, *dt); + + /* + * Special processing for SFO_NAME_TPGS module, which contains + * the `tpgs_set_target_groups` implementation needed by the + * MP-API code. + */ + if (strcmp(sf->sf_sfo->sfo_name, SFO_NAME_TPGS) == 0) { + tpgs_set_target_groups = + (int (*)(struct scsi_address *, int, int)) + ddi_modsym(sf->sf_mod, "std_set_target_groups", &e); + if (tpgs_set_target_groups == NULL) { + cmn_err(CE_WARN, "scsi_vhci: " + "unable to import 'std_set_target_groups' " + "from '%s', error %d", module[i], e); + } + } + + sf++; + } + + /* verify that at least the "well-known" modules were there */ + if (vhci_failover_ops_by_name(SFO_NAME_SYM) == NULL) + cmn_err(CE_WARN, "scsi_vhci: well-known module \"" + SFO_NAME_SYM "\" not defined in scsi_vhci.conf's " + "'ddi-forceload'"); + if (vhci_failover_ops_by_name(SFO_NAME_TPGS) == NULL) + cmn_err(CE_WARN, "scsi_vhci: well-known module \"" + SFO_NAME_TPGS "\" not defined in scsi_vhci.conf's " + "'ddi-forceload'"); + + /* call sfo_init for modules that need it */ + for (sf = scsi_failover_table; sf->sf_mod; sf++) { + if (sf->sf_sfo && sf->sf_sfo->sfo_init) + (*sf->sf_sfo->sfo_init)(); + } +} + +/* + * unload all loaded scsi_failover_ops modules + */ +static void +vhci_failover_modclose() +{ + struct scsi_failover *sf; + + for (sf = scsi_failover_table; sf->sf_mod; sf++) { + if ((sf->sf_mod == NULL) || (sf->sf_sfo == NULL)) + continue; + (void) ddi_modclose(sf->sf_mod); + sf->sf_mod = NULL; + sf->sf_sfo = NULL; + } + + if (scsi_failover_table && scsi_nfailover) + kmem_free(scsi_failover_table, + sizeof (struct scsi_failover) * (scsi_nfailover + 1)); + scsi_failover_table = NULL; + scsi_nfailover = 0; +} + +/* ARGSUSED */ +static int +vhci_open(dev_t *devp, int flag, int otype, cred_t *credp) +{ + struct scsi_vhci *vhci; + + if (otype != OTYP_CHR) { + return (EINVAL); + } + + vhci = ddi_get_soft_state(vhci_softstate, MINOR2INST(getminor(*devp))); + if (vhci == NULL) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "vhci_open: failed ENXIO\n")); + return (ENXIO); + } + + mutex_enter(&vhci->vhci_mutex); + if ((flag & FEXCL) && (vhci->vhci_state & VHCI_STATE_OPEN)) { + mutex_exit(&vhci->vhci_mutex); + vhci_log(CE_NOTE, vhci->vhci_dip, + "!vhci%d: Already open\n", getminor(*devp)); + return (EBUSY); + } + + vhci->vhci_state |= VHCI_STATE_OPEN; + mutex_exit(&vhci->vhci_mutex); + return (0); +} + + +/* ARGSUSED */ +static int +vhci_close(dev_t dev, int flag, int otype, cred_t *credp) +{ + struct scsi_vhci *vhci; + + if (otype != OTYP_CHR) { + return (EINVAL); + } + + vhci = ddi_get_soft_state(vhci_softstate, MINOR2INST(getminor(dev))); + if (vhci == NULL) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "vhci_close: failed ENXIO\n")); + return (ENXIO); + } + + mutex_enter(&vhci->vhci_mutex); + vhci->vhci_state &= ~VHCI_STATE_OPEN; + mutex_exit(&vhci->vhci_mutex); + + return (0); +} + +/* ARGSUSED */ +static int +vhci_ioctl(dev_t dev, int cmd, intptr_t data, int mode, + cred_t *credp, int *rval) +{ + if (IS_DEVCTL(cmd)) { + return (vhci_devctl(dev, cmd, data, mode, credp, rval)); + } else if (cmd == MP_CMD) { + return (vhci_mpapi_ctl(dev, cmd, data, mode, credp, rval)); + } else { + return (vhci_ctl(dev, cmd, data, mode, credp, rval)); + } +} + +/* + * attach the module + */ +static int +vhci_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) +{ + int rval = DDI_FAILURE; + int scsi_hba_attached = 0; + int vhci_attached = 0; + int mutex_initted = 0; + int instance; + struct scsi_vhci *vhci; + scsi_hba_tran_t *tran; + char cache_name_buf[64]; + char *data; + + VHCI_DEBUG(4, (CE_NOTE, NULL, "vhci_attach: cmd=0x%x\n", cmd)); + + instance = ddi_get_instance(dip); + + switch (cmd) { + case DDI_ATTACH: + break; + + case DDI_RESUME: + case DDI_PM_RESUME: + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_attach: resume not yet" + "implemented\n")); + return (rval); + + default: + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_attach: unknown ddi command\n")); + return (rval); + } + + /* + * Allocate vhci data structure. + */ + if (ddi_soft_state_zalloc(vhci_softstate, instance) != DDI_SUCCESS) { + VHCI_DEBUG(1, (CE_NOTE, dip, "!vhci_attach:" + "soft state alloc failed\n")); + return (DDI_FAILURE); + } + + if ((vhci = ddi_get_soft_state(vhci_softstate, instance)) == NULL) { + VHCI_DEBUG(1, (CE_NOTE, dip, "!vhci_attach:" + "bad soft state\n")); + ddi_soft_state_free(vhci_softstate, instance); + return (DDI_FAILURE); + } + + /* Allocate packet cache */ + (void) snprintf(cache_name_buf, sizeof (cache_name_buf), + "vhci%d_cache", instance); + + mutex_init(&vhci->vhci_mutex, NULL, MUTEX_DRIVER, NULL); + mutex_initted++; + + /* + * Allocate a transport structure + */ + tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP); + ASSERT(tran != NULL); + + vhci->vhci_tran = tran; + vhci->vhci_dip = dip; + vhci->vhci_instance = instance; + + tran->tran_hba_private = vhci; + tran->tran_tgt_private = NULL; + tran->tran_tgt_init = vhci_scsi_tgt_init; + tran->tran_tgt_probe = NULL; + tran->tran_tgt_free = vhci_scsi_tgt_free; + + tran->tran_start = vhci_scsi_start; + tran->tran_abort = vhci_scsi_abort; + tran->tran_reset = vhci_scsi_reset; + tran->tran_getcap = vhci_scsi_getcap; + tran->tran_setcap = vhci_scsi_setcap; + tran->tran_init_pkt = vhci_scsi_init_pkt; + tran->tran_destroy_pkt = vhci_scsi_destroy_pkt; + tran->tran_dmafree = vhci_scsi_dmafree; + tran->tran_sync_pkt = vhci_scsi_sync_pkt; + tran->tran_reset_notify = vhci_scsi_reset_notify; + + tran->tran_get_bus_addr = vhci_scsi_get_bus_addr; + tran->tran_get_name = vhci_scsi_get_name; + tran->tran_bus_reset = NULL; + tran->tran_quiesce = NULL; + tran->tran_unquiesce = NULL; + + /* + * register event notification routines with scsa + */ + tran->tran_get_eventcookie = NULL; + tran->tran_add_eventcall = NULL; + tran->tran_remove_eventcall = NULL; + tran->tran_post_event = NULL; + + tran->tran_bus_power = vhci_scsi_bus_power; + + tran->tran_bus_config = vhci_scsi_bus_config; + + /* + * Attach this instance with the mpxio framework + */ + if (mdi_vhci_register(MDI_HCI_CLASS_SCSI, dip, &vhci_opinfo, 0) + != MDI_SUCCESS) { + VHCI_DEBUG(1, (CE_NOTE, dip, "!vhci_attach:" + "mdi_vhci_register failed\n")); + goto attach_fail; + } + vhci_attached++; + + /* + * Attach this instance of the hba. + * + * Regarding dma attributes: Since scsi_vhci is a virtual scsi HBA + * driver, it has nothing to do with DMA. However, when calling + * scsi_hba_attach_setup() we need to pass something valid in the + * dma attributes parameter. So we just use scsi_alloc_attr. + * SCSA itself seems to care only for dma_attr_minxfer and + * dma_attr_burstsizes fields of dma attributes structure. + * It expects those fileds to be non-zero. + */ + if (scsi_hba_attach_setup(dip, &scsi_alloc_attr, tran, + SCSI_HBA_TRAN_CLONE) != DDI_SUCCESS) { + VHCI_DEBUG(1, (CE_NOTE, dip, "!vhci_attach:" + "hba attach failed\n")); + goto attach_fail; + } + scsi_hba_attached++; + + if (ddi_create_minor_node(dip, "devctl", S_IFCHR, + INST2DEVCTL(instance), DDI_NT_SCSI_NEXUS, 0) != DDI_SUCCESS) { + VHCI_DEBUG(1, (CE_NOTE, dip, "!vhci_attach:" + " ddi_create_minor_node failed\n")); + goto attach_fail; + } + + /* + * Set pm-want-child-notification property for + * power management of the phci and client + */ + if (ddi_prop_create(DDI_DEV_T_NONE, dip, DDI_PROP_CANSLEEP, + "pm-want-child-notification?", NULL, NULL) != DDI_PROP_SUCCESS) { + cmn_err(CE_WARN, + "%s%d fail to create pm-want-child-notification? prop", + ddi_driver_name(dip), ddi_get_instance(dip)); + goto attach_fail; + } + + vhci->vhci_taskq = taskq_create("vhci_taskq", 1, MINCLSYSPRI, 1, 4, 0); + vhci->vhci_update_pathstates_taskq = + taskq_create("vhci_update_pathstates", VHCI_NUM_UPDATE_TASKQ, + MINCLSYSPRI, 1, 4, 0); + ASSERT(vhci->vhci_taskq); + ASSERT(vhci->vhci_update_pathstates_taskq); + + /* + * Set appropriate configuration flags based on options set in + * conf file. + */ + vhci->vhci_conf_flags = 0; + if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, PROPFLAGS, + "auto-failback", &data) == DDI_SUCCESS) { + if (strcmp(data, "enable") == 0) + vhci->vhci_conf_flags |= VHCI_CONF_FLAGS_AUTO_FAILBACK; + ddi_prop_free(data); + } + + if (!(vhci->vhci_conf_flags & VHCI_CONF_FLAGS_AUTO_FAILBACK)) + vhci_log(CE_NOTE, dip, "!Auto-failback capability " + "disabled through scsi_vhci.conf file."); + + /* + * Allocate an mpapi private structure + */ + vhci->mp_priv = kmem_zalloc(sizeof (mpapi_priv_t), KM_SLEEP); + if (vhci_mpapi_init(vhci) != 0) { + VHCI_DEBUG(1, (CE_WARN, NULL, "!vhci_attach: " + "vhci_mpapi_init() failed")); + } + + vhci_failover_modopen(vhci); /* load failover modules */ + + ddi_report_dev(dip); + return (DDI_SUCCESS); + +attach_fail: + if (vhci_attached) + (void) mdi_vhci_unregister(dip, 0); + + if (scsi_hba_attached) + (void) scsi_hba_detach(dip); + + if (vhci->vhci_tran) + scsi_hba_tran_free(vhci->vhci_tran); + + if (mutex_initted) { + mutex_destroy(&vhci->vhci_mutex); + } + + ddi_soft_state_free(vhci_softstate, instance); + return (DDI_FAILURE); +} + + +/*ARGSUSED*/ +static int +vhci_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) +{ + int instance = ddi_get_instance(dip); + scsi_hba_tran_t *tran; + struct scsi_vhci *vhci; + + VHCI_DEBUG(4, (CE_NOTE, NULL, "vhci_detach: cmd=0x%x\n", cmd)); + + if ((tran = ddi_get_driver_private(dip)) == NULL) + return (DDI_FAILURE); + + vhci = TRAN2HBAPRIVATE(tran); + if (!vhci) { + return (DDI_FAILURE); + } + + switch (cmd) { + case DDI_DETACH: + break; + + case DDI_SUSPEND: + case DDI_PM_SUSPEND: + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_detach: suspend/pm not yet" + "implemented\n")); + return (DDI_FAILURE); + + default: + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_detach: unknown ddi command\n")); + return (DDI_FAILURE); + } + + (void) mdi_vhci_unregister(dip, 0); + (void) scsi_hba_detach(dip); + scsi_hba_tran_free(tran); + + if (ddi_prop_remove(DDI_DEV_T_NONE, dip, + "pm-want-child-notification?") != DDI_PROP_SUCCESS) { + cmn_err(CE_WARN, + "%s%d unable to remove prop pm-want_child_notification?", + ddi_driver_name(dip), ddi_get_instance(dip)); + } + if (vhci_restart_timeid != 0) { + (void) untimeout(vhci_restart_timeid); + } + vhci_restart_timeid = 0; + + mutex_destroy(&vhci->vhci_mutex); + vhci->vhci_dip = NULL; + vhci->vhci_tran = NULL; + taskq_destroy(vhci->vhci_taskq); + taskq_destroy(vhci->vhci_update_pathstates_taskq); + ddi_remove_minor_node(dip, NULL); + ddi_soft_state_free(vhci_softstate, instance); + + vhci_failover_modclose(); /* unload failover modules */ + return (DDI_SUCCESS); +} + +/* + * vhci_getinfo() + * Given the device number, return the devinfo pointer or the + * instance number. + * Note: always succeed DDI_INFO_DEVT2INSTANCE, even before attach. + */ + +/*ARGSUSED*/ +static int +vhci_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result) +{ + struct scsi_vhci *vhcip; + int instance = MINOR2INST(getminor((dev_t)arg)); + + switch (cmd) { + case DDI_INFO_DEVT2DEVINFO: + vhcip = ddi_get_soft_state(vhci_softstate, instance); + if (vhcip != NULL) + *result = vhcip->vhci_dip; + else { + *result = NULL; + return (DDI_FAILURE); + } + break; + + case DDI_INFO_DEVT2INSTANCE: + *result = (void *)(uintptr_t)instance; + break; + + default: + return (DDI_FAILURE); + } + + return (DDI_SUCCESS); +} + + +/*ARGSUSED*/ +static int +vhci_scsi_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip, + scsi_hba_tran_t *hba_tran, struct scsi_device *sd) +{ + char *guid; + scsi_vhci_lun_t *vlun; + struct scsi_vhci *vhci; + clock_t from_ticks; + mdi_pathinfo_t *pip; + int rval; + + ASSERT(hba_dip != NULL); + ASSERT(tgt_dip != NULL); + + vhci = ddi_get_soft_state(vhci_softstate, ddi_get_instance(hba_dip)); + ASSERT(vhci != NULL); + + VHCI_DEBUG(4, (CE_NOTE, hba_dip, + "!tgt_init: called for %s (instance %d)\n", + ddi_driver_name(tgt_dip), ddi_get_instance(tgt_dip))); + + if (ddi_prop_lookup_string(DDI_DEV_T_ANY, tgt_dip, PROPFLAGS, + MDI_CLIENT_GUID_PROP, &guid) != DDI_SUCCESS) { + /* + * This must be the .conf node. The ssd node under + * fp already inserts a delay, so we just return from here. + * We rely on this delay to have all dips be posted to + * the ndi hotplug thread's newdev list. This is + * necessary for the deferred attach mechanism to work + * and opens() done soon after boot to succeed. + */ + VHCI_DEBUG(4, (CE_WARN, hba_dip, "tgt_init: lun guid " + "property failed")); + return (DDI_NOT_WELL_FORMED); + } + + vlun = vhci_lun_lookup(tgt_dip); + + mutex_enter(&vhci_global_mutex); + + from_ticks = ddi_get_lbolt(); + if (vhci_to_ticks == 0) { + vhci_to_ticks = from_ticks + + drv_usectohz(vhci_init_wait_timeout); + } + +#if DEBUG + if (vlun) { + VHCI_DEBUG(1, (CE_WARN, hba_dip, "tgt_init: " + "vhci_scsi_tgt_init: guid %s : found vlun 0x%p " + "from_ticks %lx to_ticks %lx", + guid, (void *)vlun, from_ticks, vhci_to_ticks)); + } else { + VHCI_DEBUG(1, (CE_WARN, hba_dip, "tgt_init: " + "vhci_scsi_tgt_init: guid %s : vlun not found " + "from_ticks %lx to_ticks %lx", guid, from_ticks, + vhci_to_ticks)); + } +#endif + + rval = mdi_select_path(tgt_dip, NULL, + (MDI_SELECT_ONLINE_PATH | MDI_SELECT_STANDBY_PATH), NULL, &pip); + if (rval == MDI_SUCCESS) { + mdi_rele_path(pip); + } + + /* + * Wait for the following conditions : + * 1. no vlun available yet + * 2. no path established + * 3. timer did not expire + */ + while ((vlun == NULL) || (mdi_client_get_path_count(tgt_dip) == 0) || + (rval != MDI_SUCCESS)) { + if (vlun && vlun->svl_not_supported) { + VHCI_DEBUG(1, (CE_WARN, hba_dip, "tgt_init: " + "vlun 0x%p lun guid %s not supported!", + (void *)vlun, guid)); + mutex_exit(&vhci_global_mutex); + ddi_prop_free(guid); + return (DDI_NOT_WELL_FORMED); + } + if ((vhci_first_time == 0) && (from_ticks >= vhci_to_ticks)) { + vhci_first_time = 1; + } + if (vhci_first_time == 1) { + VHCI_DEBUG(1, (CE_WARN, hba_dip, "vhci_scsi_tgt_init: " + "no wait for %s. from_tick %lx, to_tick %lx", + guid, from_ticks, vhci_to_ticks)); + mutex_exit(&vhci_global_mutex); + ddi_prop_free(guid); + return (DDI_NOT_WELL_FORMED); + } + + if (cv_timedwait(&vhci_cv, + &vhci_global_mutex, vhci_to_ticks) == -1) { + /* Timed out */ +#ifdef DEBUG + if (vlun == NULL) { + VHCI_DEBUG(1, (CE_WARN, hba_dip, + "tgt_init: no vlun for %s!", guid)); + } else if (mdi_client_get_path_count(tgt_dip) == 0) { + VHCI_DEBUG(1, (CE_WARN, hba_dip, + "tgt_init: client path count is " + "zero for %s!", guid)); + } else { + VHCI_DEBUG(1, (CE_WARN, hba_dip, + "tgt_init: client path not " + "available yet for %s!", guid)); + } +#endif /* DEBUG */ + mutex_exit(&vhci_global_mutex); + ddi_prop_free(guid); + return (DDI_NOT_WELL_FORMED); + } + vlun = vhci_lun_lookup(tgt_dip); + rval = mdi_select_path(tgt_dip, NULL, + (MDI_SELECT_ONLINE_PATH | MDI_SELECT_STANDBY_PATH), + NULL, &pip); + if (rval == MDI_SUCCESS) { + mdi_rele_path(pip); + } + from_ticks = ddi_get_lbolt(); + } + mutex_exit(&vhci_global_mutex); + + ASSERT(vlun != NULL); + ddi_prop_free(guid); + hba_tran->tran_tgt_private = vlun; + + return (DDI_SUCCESS); +} + +/*ARGSUSED*/ +static void +vhci_scsi_tgt_free(dev_info_t *hba_dip, dev_info_t *tgt_dip, + scsi_hba_tran_t *hba_tran, struct scsi_device *sd) +{ +} + +/* + * a PGR register command has started; copy the info we need + */ +int +vhci_pgr_register_start(scsi_vhci_lun_t *vlun, struct scsi_pkt *pkt) +{ + struct vhci_pkt *vpkt = TGTPKT2VHCIPKT(pkt); + void *addr; + + if (!vpkt->vpkt_tgt_init_bp) + return (TRAN_BADPKT); + + addr = bp_mapin_common(vpkt->vpkt_tgt_init_bp, + (vpkt->vpkt_flags & CFLAG_NOWAIT) ? VM_NOSLEEP : VM_SLEEP); + if (addr == NULL) + return (TRAN_BUSY); + + mutex_enter(&vlun->svl_mutex); + + vhci_print_prout_keys(vlun, "v_pgr_reg_start: before bcopy:"); + + bcopy(addr, &vlun->svl_prout, sizeof (vhci_prout_t) - + (2 * MHIOC_RESV_KEY_SIZE*sizeof (char))); + bcopy(pkt->pkt_cdbp, vlun->svl_cdb, sizeof (vlun->svl_cdb)); + + vhci_print_prout_keys(vlun, "v_pgr_reg_start: after bcopy:"); + + vlun->svl_time = pkt->pkt_time; + vlun->svl_bcount = vpkt->vpkt_tgt_init_bp->b_bcount; + vlun->svl_first_path = vpkt->vpkt_path; + mutex_exit(&vlun->svl_mutex); + return (0); +} + +/* + * Function name : vhci_scsi_start() + * + * Return Values : TRAN_FATAL_ERROR - vhci has been shutdown + * or other fatal failure + * preventing packet transportation + * TRAN_BUSY - request queue is full + * TRAN_ACCEPT - pkt has been submitted to phci + * (or is held in the waitQ) + * Description : Implements SCSA's tran_start() entry point for + * packet transport + * + */ +static int +vhci_scsi_start(struct scsi_address *ap, struct scsi_pkt *pkt) +{ + int rval = TRAN_ACCEPT; + int instance, held; + struct scsi_vhci *vhci = ADDR2VHCI(ap); + struct scsi_vhci_lun *vlun = ADDR2VLUN(ap); + struct vhci_pkt *vpkt = TGTPKT2VHCIPKT(pkt); + int flags = 0; + scsi_vhci_priv_t *svp; + dev_info_t *cdip; + client_lb_t lbp; + int restore_lbp = 0; + /* set if pkt is SCSI-II RESERVE cmd */ + int pkt_reserve_cmd = 0; + int reserve_failed = 0; + + ASSERT(vhci != NULL); + ASSERT(vpkt != NULL); + ASSERT(vpkt->vpkt_state != VHCI_PKT_ISSUED); + cdip = ADDR2DIP(ap); + + /* + * Block IOs if LUN is held or QUIESCED for IOs. + */ + if ((VHCI_LUN_IS_HELD(vlun)) || + ((vlun->svl_flags & VLUN_QUIESCED_FLG) == VLUN_QUIESCED_FLG)) { + return (TRAN_BUSY); + } + + /* + * vhci_lun needs to be quiesced before SCSI-II RESERVE command + * can be issued. This may require a cv_timedwait, which is + * dangerous to perform in an interrupt context. So if this + * is a RESERVE command a taskq is dispatched to service it. + * This taskq shall again call vhci_scsi_start, but we shall be + * sure its not in an interrupt context. + */ + if ((pkt->pkt_cdbp[0] == SCMD_RESERVE) || + (pkt->pkt_cdbp[0] == SCMD_RESERVE_G1)) { + if (!(vpkt->vpkt_state & VHCI_PKT_THRU_TASKQ)) { + if (taskq_dispatch(vhci->vhci_taskq, + vhci_dispatch_scsi_start, (void *) vpkt, + KM_NOSLEEP)) { + return (TRAN_ACCEPT); + } else { + return (TRAN_BUSY); + } + } + + /* + * Here we ensure that simultaneous SCSI-II RESERVE cmds don't + * get serviced for a lun. + */ + VHCI_HOLD_LUN(vlun, VH_NOSLEEP, held); + if (!held) { + return (TRAN_BUSY); + } else if ((vlun->svl_flags & VLUN_QUIESCED_FLG) == + VLUN_QUIESCED_FLG) { + VHCI_RELEASE_LUN(vlun); + return (TRAN_BUSY); + } + + /* + * To ensure that no IOs occur for this LUN for the duration + * of this pkt set the VLUN_QUIESCED_FLG. + * In case this routine needs to exit on error make sure that + * this flag is cleared. + */ + vlun->svl_flags |= VLUN_QUIESCED_FLG; + pkt_reserve_cmd = 1; + + /* + * if this is a SCSI-II RESERVE command, set load balancing + * policy to be ALTERNATE PATH to ensure that all subsequent + * IOs are routed on the same path. This is because if commands + * are routed across multiple paths then IOs on paths other than + * the one on which the RESERVE was executed will get a + * RESERVATION CONFLICT + */ + lbp = mdi_get_lb_policy(cdip); + if (lbp != LOAD_BALANCE_NONE) { + if (vhci_quiesce_lun(vlun) != 1) { + vlun->svl_flags &= ~VLUN_QUIESCED_FLG; + VHCI_RELEASE_LUN(vlun); + return (TRAN_FATAL_ERROR); + } + vlun->svl_lb_policy_save = lbp; + if (mdi_set_lb_policy(cdip, LOAD_BALANCE_NONE) != + MDI_SUCCESS) { + vlun->svl_flags &= ~VLUN_QUIESCED_FLG; + VHCI_RELEASE_LUN(vlun); + return (TRAN_FATAL_ERROR); + } + restore_lbp = 1; + } + /* + * See comments for VLUN_RESERVE_ACTIVE_FLG in scsi_vhci.h + * To narrow this window where a reserve command may be sent + * down an inactive path the path states first need to be + * updated. Before calling vhci_update_pathstates reset + * VLUN_RESERVE_ACTIVE_FLG, just in case it was already set + * for this lun. This shall prevent an unnecessary reset + * from being sent out. + */ + vlun->svl_flags &= ~VLUN_RESERVE_ACTIVE_FLG; + vhci_update_pathstates((void *)vlun); + } + + instance = ddi_get_instance(vhci->vhci_dip); + + /* + * If the command is PRIN with action of zero, then the cmd + * is reading PR keys which requires filtering on completion. + * Data cache sync must be guaranteed. + */ + if ((pkt->pkt_cdbp[0] == SCMD_PRIN) && + (pkt->pkt_cdbp[1] == 0) && + (vpkt->vpkt_org_vpkt == NULL)) { + vpkt->vpkt_tgt_init_pkt_flags |= PKT_CONSISTENT; + } + + /* + * Do not defer bind for PKT_DMA_PARTIAL + */ + if ((vpkt->vpkt_flags & CFLAG_DMA_PARTIAL) == 0) { + + /* This is a non pkt_dma_partial case */ + if ((rval = vhci_bind_transport( + ap, vpkt, vpkt->vpkt_tgt_init_pkt_flags, NULL_FUNC)) + != TRAN_ACCEPT) { + VHCI_DEBUG(6, (CE_WARN, vhci->vhci_dip, + "!vhci%d %x: failed to bind transport: " + "vlun 0x%p pkt_reserved %x restore_lbp %x," + "lbp %x", instance, rval, (void *)vlun, + pkt_reserve_cmd, restore_lbp, lbp)); + if (restore_lbp) + (void) mdi_set_lb_policy(cdip, lbp); + if (pkt_reserve_cmd) + vlun->svl_flags &= ~VLUN_QUIESCED_FLG; + return (rval); + } + VHCI_DEBUG(8, (CE_NOTE, NULL, + "vhci_scsi_start: v_b_t called 0x%p\n", (void *)vpkt)); + } + ASSERT(vpkt->vpkt_hba_pkt != NULL); + ASSERT(vpkt->vpkt_path != NULL); + + /* + * This is the chance to adjust the pHCI's pkt and other information + * from target driver's pkt. + */ + VHCI_DEBUG(8, (CE_NOTE, vhci->vhci_dip, "vhci_scsi_start vpkt %p\n", + (void *)vpkt)); + vhci_update_pHCI_pkt(vpkt, pkt); + + if (vlun->svl_flags & VLUN_RESERVE_ACTIVE_FLG) { + if (vpkt->vpkt_path != vlun->svl_resrv_pip) { + VHCI_DEBUG(1, (CE_WARN, vhci->vhci_dip, + "!vhci_bind: reserve flag set for vlun 0x%p, but, " + "pktpath 0x%p resrv path 0x%p differ. lb_policy %x", + (void *)vlun, (void *)vpkt->vpkt_path, + (void *)vlun->svl_resrv_pip, + mdi_get_lb_policy(cdip))); + reserve_failed = 1; + } + } + + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private( + vpkt->vpkt_path); + if (svp == NULL || reserve_failed) { + if (pkt_reserve_cmd) { + VHCI_DEBUG(6, (CE_WARN, vhci->vhci_dip, + "!vhci_bind returned null svp vlun 0x%p", + (void *)vlun)); + vlun->svl_flags &= ~VLUN_QUIESCED_FLG; + if (restore_lbp) + (void) mdi_set_lb_policy(cdip, lbp); + } +pkt_cleanup: + if ((vpkt->vpkt_flags & CFLAG_DMA_PARTIAL) == 0) { + scsi_destroy_pkt(vpkt->vpkt_hba_pkt); + vpkt->vpkt_hba_pkt = NULL; + if (vpkt->vpkt_path) { + mdi_rele_path(vpkt->vpkt_path); + vpkt->vpkt_path = NULL; + } + } + if ((pkt->pkt_cdbp[0] == SCMD_PROUT) && + (((pkt->pkt_cdbp[1] & 0x1f) == VHCI_PROUT_REGISTER) || + ((pkt->pkt_cdbp[1] & 0x1f) == + VHCI_PROUT_R_AND_IGNORE))) { + sema_v(&vlun->svl_pgr_sema); + } + return (TRAN_BUSY); + } + + VHCI_INCR_PATH_CMDCOUNT(svp); + + /* + * Ensure that no other IOs raced ahead, while a RESERVE cmd was + * QUIESCING the same lun. + */ + if ((!pkt_reserve_cmd) && + ((vlun->svl_flags & VLUN_QUIESCED_FLG) == VLUN_QUIESCED_FLG)) { + VHCI_DECR_PATH_CMDCOUNT(svp); + goto pkt_cleanup; + } + + if ((pkt->pkt_cdbp[0] == SCMD_PRIN) || + (pkt->pkt_cdbp[0] == SCMD_PROUT)) { + /* + * currently this thread only handles running PGR + * commands, so don't bother creating it unless + * something interesting is going to happen (like + * either a PGR out, or a PGR in with enough space + * to hold the keys that are getting returned) + */ + mutex_enter(&vlun->svl_mutex); + if (((vlun->svl_flags & VLUN_TASK_D_ALIVE_FLG) == 0) && + (pkt->pkt_cdbp[0] == SCMD_PROUT)) { + vlun->svl_taskq = taskq_create("vlun_pgr_task_daemon", + 1, MINCLSYSPRI, 1, 4, 0); + vlun->svl_flags |= VLUN_TASK_D_ALIVE_FLG; + } + mutex_exit(&vlun->svl_mutex); + if ((pkt->pkt_cdbp[0] == SCMD_PROUT) && + (((pkt->pkt_cdbp[1] & 0x1f) == VHCI_PROUT_REGISTER) || + ((pkt->pkt_cdbp[1] & 0x1f) == + VHCI_PROUT_R_AND_IGNORE))) { + if (rval = vhci_pgr_register_start(vlun, pkt)) { + /* an error */ + sema_v(&vlun->svl_pgr_sema); + return (rval); + } + } + } + + /* + * SCSI-II RESERVE cmd is not expected in polled mode. + * If this changes it needs to be handled for the polled scenario. + */ + flags = vpkt->vpkt_hba_pkt->pkt_flags; + rval = scsi_transport(vpkt->vpkt_hba_pkt); + if (rval == TRAN_ACCEPT) { + if (flags & FLAG_NOINTR) { + struct scsi_pkt *tpkt = vpkt->vpkt_tgt_pkt; + struct scsi_pkt *pkt = vpkt->vpkt_hba_pkt; + + ASSERT(tpkt != NULL); + *(tpkt->pkt_scbp) = *(pkt->pkt_scbp); + tpkt->pkt_resid = pkt->pkt_resid; + tpkt->pkt_state = pkt->pkt_state; + tpkt->pkt_statistics = pkt->pkt_statistics; + tpkt->pkt_reason = pkt->pkt_reason; + + if ((*(pkt->pkt_scbp) == STATUS_CHECK) && + (pkt->pkt_state & STATE_ARQ_DONE)) { + bcopy(pkt->pkt_scbp, tpkt->pkt_scbp, + vpkt->vpkt_tgt_init_scblen); + } + + VHCI_DECR_PATH_CMDCOUNT(svp); + if ((vpkt->vpkt_flags & CFLAG_DMA_PARTIAL) == 0) { + scsi_destroy_pkt(vpkt->vpkt_hba_pkt); + vpkt->vpkt_hba_pkt = NULL; + if (vpkt->vpkt_path) { + mdi_rele_path(vpkt->vpkt_path); + vpkt->vpkt_path = NULL; + } + } + /* + * This path will not automatically retry pkts + * internally, therefore, vpkt_org_vpkt should + * never be set. + */ + ASSERT(vpkt->vpkt_org_vpkt == NULL); + if (tpkt->pkt_comp) { + (*tpkt->pkt_comp)(tpkt); + } + } + return (rval); + } else if ((pkt->pkt_cdbp[0] == SCMD_PROUT) && + (((pkt->pkt_cdbp[1] & 0x1f) == VHCI_PROUT_REGISTER) || + ((pkt->pkt_cdbp[1] & 0x1f) == VHCI_PROUT_R_AND_IGNORE))) { + /* the command exited with bad status */ + sema_v(&vlun->svl_pgr_sema); + } else if (vpkt->vpkt_tgt_pkt->pkt_cdbp[0] == SCMD_PRIN) { + /* the command exited with bad status */ + sema_v(&vlun->svl_pgr_sema); + } else if (pkt_reserve_cmd) { + VHCI_DEBUG(6, (CE_WARN, vhci->vhci_dip, + "!vhci_scsi_start: reserve failed vlun 0x%p", + (void *)vlun)); + vlun->svl_flags &= ~VLUN_QUIESCED_FLG; + if (restore_lbp) + (void) mdi_set_lb_policy(cdip, lbp); + } + + ASSERT(vpkt->vpkt_hba_pkt != NULL); + VHCI_DECR_PATH_CMDCOUNT(svp); + + /* Do not destroy phci packet information for PKT_DMA_PARTIAL */ + if ((vpkt->vpkt_flags & CFLAG_DMA_PARTIAL) == 0) { + scsi_destroy_pkt(vpkt->vpkt_hba_pkt); + vpkt->vpkt_hba_pkt = NULL; + if (vpkt->vpkt_path) { + MDI_PI_ERRSTAT(vpkt->vpkt_path, MDI_PI_TRANSERR); + mdi_rele_path(vpkt->vpkt_path); + vpkt->vpkt_path = NULL; + } + } + return (TRAN_BUSY); +} + +/* + * Function name : vhci_scsi_reset() + * + * Return Values : 0 - reset failed + * 1 - reset succeeded + */ + +/* ARGSUSED */ +static int +vhci_scsi_reset(struct scsi_address *ap, int level) +{ + int rval = 0; + + cmn_err(CE_WARN, "!vhci_scsi_reset 0x%x", level); + if ((level == RESET_TARGET) || (level == RESET_LUN)) { + return (vhci_scsi_reset_target(ap, level, TRUE)); + } else if (level == RESET_ALL) { + return (vhci_scsi_reset_bus(ap)); + } + + return (rval); +} + +/* + * vhci_recovery_reset: + * Issues reset to the device + * Input: + * vlun - vhci lun pointer of the device + * ap - address of the device + * select_path: + * If select_path is FALSE, then the address specified in ap is + * the path on which reset will be issued. + * If select_path is TRUE, then path is obtained by calling + * mdi_select_path. + * + * recovery_depth: + * Caller can specify the level of reset. + * VHCI_DEPTH_LUN - + * Issues LUN RESET if device supports lun reset. + * VHCI_DEPTH_TARGET - + * If Lun Reset fails or the device does not support + * Lun Reset, issues TARGET RESET + * VHCI_DEPTH_ALL - + * If Lun Reset fails or the device does not support + * Lun Reset, issues TARGET RESET. + * If TARGET RESET does not succeed, issues Bus Reset. + */ + +static int +vhci_recovery_reset(scsi_vhci_lun_t *vlun, struct scsi_address *ap, + uint8_t select_path, uint8_t recovery_depth) +{ + int ret = 0; + + ASSERT(ap != NULL); + + if (vlun && vlun->svl_support_lun_reset == 1) { + ret = vhci_scsi_reset_target(ap, RESET_LUN, + select_path); + } + + recovery_depth--; + + if ((ret == 0) && recovery_depth) { + ret = vhci_scsi_reset_target(ap, RESET_TARGET, + select_path); + recovery_depth--; + } + + if ((ret == 0) && recovery_depth) { + (void) scsi_reset(ap, RESET_ALL); + } + + return (ret); +} + +/* + * Note: The scsi_address passed to this routine could be the scsi_address + * for the virtual device or the physical device. No assumptions should be + * made in this routine about the ap structure and a_hba_tran->tran_tgt_private + * field of ap can not be assumed to be the vhci structure. + * Further note that the child dip would be the dip of the ssd node irrespective + * of the scsi_address passed. + */ + +static int +vhci_scsi_reset_target(struct scsi_address *ap, int level, uint8_t select_path) +{ + dev_info_t *vdip, *pdip, *cdip = ADDR2DIP(ap); + mdi_pathinfo_t *pip = NULL; + mdi_pathinfo_t *npip = NULL; + int rval = -1; + scsi_vhci_priv_t *svp = NULL; + struct scsi_address *pap = NULL; + scsi_hba_tran_t *hba = NULL; + int sps; + struct scsi_vhci *vhci = NULL; + + if (select_path != TRUE) { + ASSERT(ap != NULL); + if (level == RESET_LUN) { + hba = ap->a_hba_tran; + ASSERT(hba != NULL); + return ((*hba->tran_reset)(ap, RESET_LUN)); + } + return (scsi_reset(ap, level)); + } + + ASSERT(cdip != NULL); + vdip = ddi_get_parent(cdip); + ASSERT(vdip != NULL); + vhci = ddi_get_soft_state(vhci_softstate, ddi_get_instance(vdip)); + ASSERT(vhci != NULL); + + rval = mdi_select_path(cdip, NULL, MDI_SELECT_ONLINE_PATH, NULL, &pip); + if ((rval != MDI_SUCCESS) || (pip == NULL)) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_scsi_reset_target: " + "Unable to get a path, dip 0x%p", (void *)cdip)); + return (0); + } +again: + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(pip); + if (svp == NULL) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_scsi_reset_target: " + "priv is NULL, pip 0x%p", (void *)pip)); + mdi_rele_path(pip); + return (0); + } + + if (svp->svp_psd == NULL) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_scsi_reset_target: " + "psd is NULL, pip 0x%p, svp 0x%p", + (void *)pip, (void *)svp)); + mdi_rele_path(pip); + return (0); + } + + pap = &svp->svp_psd->sd_address; + hba = pap->a_hba_tran; + + ASSERT(pap != NULL); + ASSERT(hba != NULL); + + if (hba->tran_reset != NULL) { + if ((*hba->tran_reset)(pap, level) == 0) { + pdip = mdi_pi_get_phci(pip); + vhci_log(CE_WARN, vdip, "!(%s%d):" + " path (%s%d), reset %d failed", + ddi_driver_name(cdip), ddi_get_instance(cdip), + ddi_driver_name(pdip), ddi_get_instance(pdip), + level); + + /* + * Select next path and issue the reset, repeat + * until all paths are exhausted + */ + sps = mdi_select_path(cdip, NULL, + MDI_SELECT_ONLINE_PATH, pip, &npip); + if ((sps != MDI_SUCCESS) || (npip == NULL)) { + mdi_rele_path(pip); + return (0); + } + mdi_rele_path(pip); + pip = npip; + goto again; + } + mdi_rele_path(pip); + mutex_enter(&vhci->vhci_mutex); + scsi_hba_reset_notify_callback(&vhci->vhci_mutex, + &vhci->vhci_reset_notify_listf); + mutex_exit(&vhci->vhci_mutex); + VHCI_DEBUG(6, (CE_NOTE, NULL, "!vhci_scsi_reset_target: " + "reset %d sent down pip:%p for cdip:%p\n", level, + (void *)pip, (void *)cdip)); + return (1); + } + mdi_rele_path(pip); + return (0); +} + + +/* ARGSUSED */ +static int +vhci_scsi_reset_bus(struct scsi_address *ap) +{ + return (1); +} + + +/* + * called by vhci_getcap and vhci_setcap to get and set (respectively) + * SCSI capabilities + */ +/* ARGSUSED */ +static int +vhci_commoncap(struct scsi_address *ap, char *cap, + int val, int tgtonly, int doset) +{ + struct scsi_vhci *vhci = ADDR2VHCI(ap); + struct scsi_vhci_lun *vlun = ADDR2VLUN(ap); + int cidx; + int rval = 0; + + if (cap == (char *)0) { + VHCI_DEBUG(3, (CE_WARN, vhci->vhci_dip, + "!vhci_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 = 0; + } else { + rval = 1; + } + break; + + case SCSI_CAP_LUN_RESET: + if (tgtonly == 0) { + VHCI_DEBUG(1, (CE_WARN, vhci->vhci_dip, + "scsi_vhci_setcap: " + "Returning error since whom = 0")); + rval = -1; + break; + } + /* + * Set the capability accordingly. + */ + mutex_enter(&vlun->svl_mutex); + vlun->svl_support_lun_reset = val; + rval = val; + mutex_exit(&vlun->svl_mutex); + break; + + case SCSI_CAP_SECTOR_SIZE: + mutex_enter(&vlun->svl_mutex); + vlun->svl_sector_size = val; + vlun->svl_setcap_done = 1; + mutex_exit(&vlun->svl_mutex); + (void) vhci_pHCI_cap(ap, cap, val, tgtonly, NULL); + + /* Always return success */ + rval = 1; + break; + + default: + VHCI_DEBUG(6, (CE_WARN, vhci->vhci_dip, + "!vhci_setcap: unsupported %d", cidx)); + rval = UNDEFINED; + break; + } + + VHCI_DEBUG(6, (CE_NOTE, vhci->vhci_dip, + "!set cap: cap=%s, val/tgtonly/doset/rval = " + "0x%x/0x%x/0x%x/%d\n", + cap, val, tgtonly, doset, rval)); + + } else { + /* + * Process getcap request. + */ + switch (cidx) { + case SCSI_CAP_DMA_MAX: + rval = (int)VHCI_DMA_MAX_XFER_CAP; + break; + + case SCSI_CAP_INITIATOR_ID: + rval = 0x00; + break; + + case SCSI_CAP_ARQ: + case SCSI_CAP_RESET_NOTIFICATION: + case SCSI_CAP_TAGGED_QING: + rval = 1; + break; + + case SCSI_CAP_SCSI_VERSION: + rval = 3; + break; + + case SCSI_CAP_INTERCONNECT_TYPE: + rval = INTERCONNECT_FABRIC; + break; + + case SCSI_CAP_LUN_RESET: + /* + * scsi_vhci will always return success for LUN reset. + * When request for doing LUN reset comes + * through scsi_reset entry point, at that time attempt + * will be made to do reset through all the possible + * paths. + */ + mutex_enter(&vlun->svl_mutex); + rval = vlun->svl_support_lun_reset; + mutex_exit(&vlun->svl_mutex); + VHCI_DEBUG(4, (CE_WARN, vhci->vhci_dip, + "scsi_vhci_getcap:" + "Getting the Lun reset capability %d", rval)); + break; + + case SCSI_CAP_SECTOR_SIZE: + mutex_enter(&vlun->svl_mutex); + rval = vlun->svl_sector_size; + mutex_exit(&vlun->svl_mutex); + break; + + default: + VHCI_DEBUG(6, (CE_WARN, vhci->vhci_dip, + "!vhci_getcap: unsupported %d", cidx)); + rval = UNDEFINED; + break; + } + + VHCI_DEBUG(6, (CE_NOTE, vhci->vhci_dip, + "!get cap: cap=%s, val/tgtonly/doset/rval = " + "0x%x/0x%x/0x%x/%d\n", + cap, val, tgtonly, doset, rval)); + } + return (rval); +} + + +/* + * Function name : vhci_scsi_getcap() + * + */ +static int +vhci_scsi_getcap(struct scsi_address *ap, char *cap, int whom) +{ + return (vhci_commoncap(ap, cap, 0, whom, 0)); +} + +static int +vhci_scsi_setcap(struct scsi_address *ap, char *cap, int value, int whom) +{ + return (vhci_commoncap(ap, cap, value, whom, 1)); +} + +/* + * Function name : vhci_scsi_abort() + */ +/* ARGSUSED */ +static int +vhci_scsi_abort(struct scsi_address *ap, struct scsi_pkt *pkt) +{ + return (0); +} + +/* + * Function name : vhci_scsi_init_pkt + * + * Return Values : pointer to scsi_pkt, or NULL + */ +/* ARGSUSED */ +static struct scsi_pkt * +vhci_scsi_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt, + struct buf *bp, int cmdlen, int statuslen, int tgtlen, + int flags, int (*callback)(caddr_t), caddr_t arg) +{ + struct scsi_vhci *vhci = ADDR2VHCI(ap); + struct vhci_pkt *vpkt; + int rval; + int newpkt = 0; + struct scsi_pkt *pktp; + + + if (pkt == NULL) { + if (cmdlen > VHCI_SCSI_CDB_SIZE) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!init pkt: cdb size not supported\n")); + return (NULL); + } + + pktp = scsi_hba_pkt_alloc(vhci->vhci_dip, + ap, cmdlen, statuslen, tgtlen, sizeof (*vpkt), callback, + arg); + + if (pktp == NULL) { + return (NULL); + } + + /* Get the vhci's private structure */ + vpkt = (struct vhci_pkt *)(pktp->pkt_ha_private); + ASSERT(vpkt); + + /* Save the target driver's packet */ + vpkt->vpkt_tgt_pkt = pktp; + + /* + * Save pkt_tgt_init_pkt fields if deferred binding + * is needed or for other purposes. + */ + vpkt->vpkt_tgt_init_pkt_flags = flags; + vpkt->vpkt_flags = (callback == NULL_FUNC) ? CFLAG_NOWAIT : 0; + vpkt->vpkt_state = VHCI_PKT_IDLE; + vpkt->vpkt_tgt_init_cdblen = cmdlen; + vpkt->vpkt_tgt_init_scblen = statuslen; + vpkt->vpkt_tgt_init_privlen = tgtlen; + newpkt = 1; + } else { /* pkt not NULL */ + vpkt = pkt->pkt_ha_private; + } + + VHCI_DEBUG(8, (CE_NOTE, NULL, "vhci_scsi_init_pkt " + "vpkt %p flags %x\n", (void *)vpkt, flags)); + + /* Clear any stale error flags */ + if (bp) { + bioerror(bp, 0); + } + + vpkt->vpkt_tgt_init_bp = bp; + + if (flags & PKT_DMA_PARTIAL) { + + /* + * Immediate binding is needed. + * Target driver may not set this flag in next invocation. + * vhci has to remember this flag was set during first + * invocation of vhci_scsi_init_pkt. + */ + vpkt->vpkt_flags |= CFLAG_DMA_PARTIAL; + } + + if (vpkt->vpkt_flags & CFLAG_DMA_PARTIAL) { + + /* + * Re-initialize some of the target driver packet state + * information. + */ + vpkt->vpkt_tgt_pkt->pkt_state = 0; + vpkt->vpkt_tgt_pkt->pkt_statistics = 0; + vpkt->vpkt_tgt_pkt->pkt_reason = 0; + + /* + * Binding a vpkt->vpkt_path for this IO at init_time. + * If an IO error happens later, target driver will clear + * this vpkt->vpkt_path binding before re-init IO again. + */ + VHCI_DEBUG(8, (CE_NOTE, NULL, + "vhci_scsi_init_pkt: calling v_b_t %p, newpkt %d\n", + (void *)vpkt, newpkt)); + if (pkt && vpkt->vpkt_hba_pkt) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "v_s_i_p calling update_pHCI_pkt resid %ld\n", + pkt->pkt_resid)); + vhci_update_pHCI_pkt(vpkt, pkt); + } + if (callback == SLEEP_FUNC) { + rval = vhci_bind_transport( + ap, vpkt, flags, callback); + } else { + rval = vhci_bind_transport( + ap, vpkt, flags, NULL_FUNC); + } + VHCI_DEBUG(8, (CE_NOTE, NULL, + "vhci_scsi_init_pkt: v_b_t called 0x%p rval 0x%x\n", + (void *)vpkt, rval)); + if (bp) { + if (rval == TRAN_FATAL_ERROR) { + /* + * No paths available. Could not bind + * any pHCI. Setting EFAULT as a way + * to indicate no DMA is mapped. + */ + bioerror(bp, EFAULT); + } else { + /* + * Do not indicate any pHCI errors to + * target driver otherwise. + */ + bioerror(bp, 0); + } + } + if (rval != TRAN_ACCEPT) { + VHCI_DEBUG(8, (CE_NOTE, NULL, + "vhci_scsi_init_pkt: " + "v_b_t failed 0x%p newpkt %x\n", + (void *)vpkt, newpkt)); + if (newpkt) { + scsi_hba_pkt_free(ap, + vpkt->vpkt_tgt_pkt); + } + return (NULL); + } + ASSERT(vpkt->vpkt_hba_pkt != NULL); + ASSERT(vpkt->vpkt_path != NULL); + + /* Update the resid for the target driver */ + vpkt->vpkt_tgt_pkt->pkt_resid = + vpkt->vpkt_hba_pkt->pkt_resid; + } + + return (vpkt->vpkt_tgt_pkt); +} + +/* + * Function name : vhci_scsi_destroy_pkt + * + * Return Values : none + */ +static void +vhci_scsi_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) +{ + struct vhci_pkt *vpkt = (struct vhci_pkt *)pkt->pkt_ha_private; + + VHCI_DEBUG(8, (CE_NOTE, NULL, + "vhci_scsi_destroy_pkt: vpkt 0x%p\n", (void *)vpkt)); + + vpkt->vpkt_tgt_init_pkt_flags = 0; + if (vpkt->vpkt_hba_pkt) { + scsi_destroy_pkt(vpkt->vpkt_hba_pkt); + vpkt->vpkt_hba_pkt = NULL; + } + if (vpkt->vpkt_path) { + mdi_rele_path(vpkt->vpkt_path); + vpkt->vpkt_path = NULL; + } + + ASSERT(vpkt->vpkt_state != VHCI_PKT_ISSUED); + scsi_hba_pkt_free(ap, vpkt->vpkt_tgt_pkt); +} + +/* + * Function name : vhci_scsi_dmafree() + * + * Return Values : none + */ +/*ARGSUSED*/ +static void +vhci_scsi_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt) +{ + struct vhci_pkt *vpkt = (struct vhci_pkt *)pkt->pkt_ha_private; + + VHCI_DEBUG(6, (CE_NOTE, NULL, + "vhci_scsi_dmafree: vpkt 0x%p\n", (void *)vpkt)); + + ASSERT(vpkt != NULL); + if (vpkt->vpkt_hba_pkt) { + scsi_destroy_pkt(vpkt->vpkt_hba_pkt); + vpkt->vpkt_hba_pkt = NULL; + } + if (vpkt->vpkt_path) { + mdi_rele_path(vpkt->vpkt_path); + vpkt->vpkt_path = NULL; + } +} + +/* + * Function name : vhci_scsi_sync_pkt() + * + * Return Values : none + */ +/*ARGSUSED*/ +static void +vhci_scsi_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt) +{ + struct vhci_pkt *vpkt = (struct vhci_pkt *)pkt->pkt_ha_private; + + ASSERT(vpkt != NULL); + if (vpkt->vpkt_hba_pkt) { + scsi_sync_pkt(vpkt->vpkt_hba_pkt); + } +} + +/* + * routine for reset notification setup, to register or cancel. + */ +static int +vhci_scsi_reset_notify(struct scsi_address *ap, int flag, + void (*callback)(caddr_t), caddr_t arg) +{ + struct scsi_vhci *vhci = ADDR2VHCI(ap); + return (scsi_hba_reset_notify_setup(ap, flag, callback, arg, + &vhci->vhci_mutex, &vhci->vhci_reset_notify_listf)); +} + +static int +vhci_scsi_get_name_bus_addr(struct scsi_device *sd, + char *name, int len, int bus_addr) +{ + dev_info_t *cdip; + char *guid; + scsi_vhci_lun_t *vlun; + + ASSERT(sd != NULL); + ASSERT(name != NULL); + + cdip = sd->sd_dev; + + ASSERT(cdip != NULL); + + if (mdi_component_is_client(cdip, NULL) != MDI_SUCCESS) { + name[0] = '\0'; + return (1); + } + + if (ddi_prop_lookup_string(DDI_DEV_T_ANY, cdip, PROPFLAGS, + MDI_CLIENT_GUID_PROP, &guid) != DDI_SUCCESS) { + name[0] = '\0'; + return (1); + } + + vlun = ADDR2VLUN(&sd->sd_address); + if (bus_addr && vlun && vlun->svl_fops_name) { + /* report the guid and the name of the failover module */ + (void) snprintf(name, len, "g%s %s", guid, vlun->svl_fops_name); + } else { + /* report the guid */ + (void) snprintf(name, len, "g%s", guid); + } + + ddi_prop_free(guid); + return (1); +} + +static int +vhci_scsi_get_bus_addr(struct scsi_device *sd, char *name, int len) +{ + return (vhci_scsi_get_name_bus_addr(sd, name, len, 1)); +} + +static int +vhci_scsi_get_name(struct scsi_device *sd, char *name, int len) +{ + return (vhci_scsi_get_name_bus_addr(sd, name, len, 0)); +} + +/* + * Return a pointer to the guid part of the devnm. + * devnm format is "nodename@busaddr", busaddr format is "gGUID". + */ +static char * +vhci_devnm_to_guid(char *devnm) +{ + char *cp = devnm; + + if (devnm == NULL) + return (NULL); + + while (*cp != '\0' && *cp != '@') + cp++; + if (*cp == '@' && *(cp + 1) == 'g') + return (cp + 2); + return (NULL); +} + +static int +vhci_bind_transport(struct scsi_address *ap, struct vhci_pkt *vpkt, int flags, + int (*func)(caddr_t)) +{ + struct scsi_vhci *vhci = ADDR2VHCI(ap); + dev_info_t *cdip = ADDR2DIP(ap); + mdi_pathinfo_t *pip = NULL; + mdi_pathinfo_t *npip = NULL; + scsi_vhci_priv_t *svp = NULL; + struct scsi_device *psd = NULL; + struct scsi_address *address = NULL; + struct scsi_pkt *pkt = NULL; + int rval = -1; + int pgr_sema_held = 0; + int held; + int mps_flag = MDI_SELECT_ONLINE_PATH; + struct scsi_vhci_lun *vlun; + time_t tnow; + + vlun = ADDR2VLUN(ap); + ASSERT(vlun != 0); + + if ((vpkt->vpkt_tgt_pkt->pkt_cdbp[0] == SCMD_PROUT) && + (((vpkt->vpkt_tgt_pkt->pkt_cdbp[1] & 0x1f) == + VHCI_PROUT_REGISTER) || + ((vpkt->vpkt_tgt_pkt->pkt_cdbp[1] & 0x1f) == + VHCI_PROUT_R_AND_IGNORE))) { + if (!sema_tryp(&vlun->svl_pgr_sema)) + return (TRAN_BUSY); + pgr_sema_held = 1; + if (vlun->svl_first_path != NULL) { + rval = mdi_select_path(cdip, NULL, + MDI_SELECT_ONLINE_PATH | MDI_SELECT_STANDBY_PATH, + NULL, &pip); + if ((rval != MDI_SUCCESS) || (pip == NULL)) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_bind_transport: path select fail\n")); + } else { + npip = pip; + do { + if (npip == vlun->svl_first_path) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_bind_transport: " + "valid first path 0x%p\n", + (void *) + vlun->svl_first_path)); + pip = vlun->svl_first_path; + goto bind_path; + } + pip = npip; + rval = mdi_select_path(cdip, NULL, + MDI_SELECT_ONLINE_PATH | + MDI_SELECT_STANDBY_PATH, + pip, &npip); + mdi_rele_path(pip); + } while ((rval == MDI_SUCCESS) && + (npip != NULL)); + } + } + + if (vlun->svl_first_path) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_bind_transport: invalid first path 0x%p\n", + (void *)vlun->svl_first_path)); + vlun->svl_first_path = NULL; + } + } else if (vpkt->vpkt_tgt_pkt->pkt_cdbp[0] == SCMD_PRIN) { + if ((vpkt->vpkt_state & VHCI_PKT_THRU_TASKQ) == 0) { + if (!sema_tryp(&vlun->svl_pgr_sema)) + return (TRAN_BUSY); + } + pgr_sema_held = 1; + } + + /* + * If the path is already bound for PKT_PARTIAL_DMA case, + * try to use the same path. + */ + if ((vpkt->vpkt_flags & CFLAG_DMA_PARTIAL) && vpkt->vpkt_path) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_bind_transport: PKT_PARTIAL_DMA " + "vpkt 0x%p, path 0x%p\n", + (void *)vpkt, (void *)vpkt->vpkt_path)); + pip = vpkt->vpkt_path; + goto bind_path; + } + + /* + * If reservation is active bind the transport directly to the pip + * with the reservation. + */ + if (vpkt->vpkt_hba_pkt == NULL) { + if (vlun->svl_flags & VLUN_RESERVE_ACTIVE_FLG) { + if (MDI_PI_IS_ONLINE(vlun->svl_resrv_pip)) { + pip = vlun->svl_resrv_pip; + mdi_hold_path(pip); + vlun->svl_waiting_for_activepath = 0; + rval = MDI_SUCCESS; + goto bind_path; + } else { + if (pgr_sema_held) { + sema_v(&vlun->svl_pgr_sema); + } + return (TRAN_BUSY); + } + } +try_again: + rval = mdi_select_path(cdip, vpkt->vpkt_tgt_init_bp, 0, NULL, + &pip); + if (rval == MDI_BUSY) { + if (pgr_sema_held) { + sema_v(&vlun->svl_pgr_sema); + } + return (TRAN_BUSY); + } else if (rval == MDI_DEVI_ONLINING) { + /* + * if we are here then we are in the midst of + * an attach/probe of the client device. + * We attempt to bind to ONLINE path if available, + * else it is OK to bind to a STANDBY path (instead + * of triggering a failover) because IO associated + * with attach/probe (eg. INQUIRY, block 0 read) + * are completed by targets even on passive paths + * If no ONLINE paths available, it is important + * to set svl_waiting_for_activepath for two + * reasons: (1) avoid sense analysis in the + * "external failure detection" codepath in + * vhci_intr(). Failure to do so will result in + * infinite loop (unless an ONLINE path becomes + * available at some point) (2) avoid + * unnecessary failover (see "---Waiting For Active + * Path---" comment below). + */ + VHCI_DEBUG(1, (CE_NOTE, NULL, "!%p in onlining " + "state\n", (void *)cdip)); + pip = NULL; + rval = mdi_select_path(cdip, vpkt->vpkt_tgt_init_bp, + mps_flag, NULL, &pip); + if ((rval != MDI_SUCCESS) || (pip == NULL)) { + if (vlun->svl_waiting_for_activepath == 0) { + vlun->svl_waiting_for_activepath = 1; + vlun->svl_wfa_time = ddi_get_time(); + } + mps_flag |= MDI_SELECT_STANDBY_PATH; + rval = mdi_select_path(cdip, + vpkt->vpkt_tgt_init_bp, + mps_flag, NULL, &pip); + if ((rval != MDI_SUCCESS) || (pip == NULL)) { + if (pgr_sema_held) { + sema_v(&vlun->svl_pgr_sema); + } + return (TRAN_FATAL_ERROR); + } + goto bind_path; + } + } else if (rval == MDI_FAILURE) { + if (pgr_sema_held) { + sema_v(&vlun->svl_pgr_sema); + } + return (TRAN_FATAL_ERROR); + } + + if ((pip == NULL) || (rval == MDI_NOPATH)) { + while (vlun->svl_waiting_for_activepath) { + /* + * ---Waiting For Active Path--- + * This device was discovered across a + * passive path; lets wait for a little + * bit, hopefully an active path will + * show up obviating the need for a + * failover + */ + tnow = ddi_get_time(); + if (tnow - vlun->svl_wfa_time >= 60) { + vlun->svl_waiting_for_activepath = 0; + } else { + drv_usecwait(1000); + if (vlun->svl_waiting_for_activepath + == 0) { + /* + * an active path has come + * online! + */ + goto try_again; + } + } + } + VHCI_HOLD_LUN(vlun, VH_NOSLEEP, held); + if (!held) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "!Lun not held\n")); + if (pgr_sema_held) { + sema_v(&vlun->svl_pgr_sema); + } + return (TRAN_BUSY); + } + /* + * now that the LUN is stable, one last check + * to make sure no other changes sneaked in + * (like a path coming online or a + * failover initiated by another thread) + */ + pip = NULL; + rval = mdi_select_path(cdip, vpkt->vpkt_tgt_init_bp, + 0, NULL, &pip); + if (pip != NULL) { + VHCI_RELEASE_LUN(vlun); + vlun->svl_waiting_for_activepath = 0; + goto bind_path; + } + + /* + * Check if there is an ONLINE path OR a STANDBY path + * available. If none is available, do not attempt + * to do a failover, just return a fatal error at this + * point. + */ + npip = NULL; + rval = mdi_select_path(cdip, NULL, + (MDI_SELECT_ONLINE_PATH | MDI_SELECT_STANDBY_PATH), + NULL, &npip); + if ((npip == NULL) || (rval != MDI_SUCCESS)) { + /* + * No paths available, jus return FATAL error. + */ + VHCI_RELEASE_LUN(vlun); + if (pgr_sema_held) { + sema_v(&vlun->svl_pgr_sema); + } + return (TRAN_FATAL_ERROR); + } + mdi_rele_path(npip); + VHCI_DEBUG(1, (CE_NOTE, NULL, "!invoking " + "mdi_failover\n")); + rval = mdi_failover(vhci->vhci_dip, cdip, + MDI_FAILOVER_ASYNC); + if (rval == MDI_FAILURE) { + VHCI_RELEASE_LUN(vlun); + if (pgr_sema_held) { + sema_v(&vlun->svl_pgr_sema); + } + return (TRAN_FATAL_ERROR); + } else if (rval == MDI_BUSY) { + VHCI_RELEASE_LUN(vlun); + if (pgr_sema_held) { + sema_v(&vlun->svl_pgr_sema); + } + return (TRAN_BUSY); + } else { + if (pgr_sema_held) { + sema_v(&vlun->svl_pgr_sema); + } + return (TRAN_BUSY); + } + } + vlun->svl_waiting_for_activepath = 0; +bind_path: + vpkt->vpkt_path = pip; + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(pip); + ASSERT(svp != NULL); + + psd = svp->svp_psd; + ASSERT(psd != NULL); + address = &psd->sd_address; + } else { + pkt = vpkt->vpkt_hba_pkt; + address = &pkt->pkt_address; + } + + /* + * For PKT_PARTIAL_DMA case, call pHCI's scsi_init_pkt whenever + * target driver calls vhci_scsi_init_pkt. + */ + if ((vpkt->vpkt_flags & CFLAG_DMA_PARTIAL) && + vpkt->vpkt_path && vpkt->vpkt_hba_pkt) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_bind_transport: PKT_PARTIAL_DMA " + "vpkt 0x%p, path 0x%p hba_pkt 0x%p\n", + (void *)vpkt, (void *)vpkt->vpkt_path, (void *)pkt)); + pkt = vpkt->vpkt_hba_pkt; + address = &pkt->pkt_address; + } + + if (pkt == NULL || (vpkt->vpkt_flags & CFLAG_DMA_PARTIAL)) { + pkt = scsi_init_pkt(address, pkt, + vpkt->vpkt_tgt_init_bp, vpkt->vpkt_tgt_init_cdblen, + vpkt->vpkt_tgt_init_scblen, + vpkt->vpkt_tgt_init_privlen, flags, func, NULL); + + if (pkt == NULL) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "!bind transport: 0x%p 0x%p 0x%p\n", + (void *)vhci, (void *)psd, (void *)vpkt)); + if ((vpkt->vpkt_hba_pkt == NULL) && vpkt->vpkt_path) { + MDI_PI_ERRSTAT(vpkt->vpkt_path, + MDI_PI_TRANSERR); + mdi_rele_path(vpkt->vpkt_path); + vpkt->vpkt_path = NULL; + } + if (pgr_sema_held) { + sema_v(&vlun->svl_pgr_sema); + } + /* + * Looks like a fatal error. + * May be device disappeared underneath. + * Give another chance to target driver for a retry to + * get another path. + */ + return (TRAN_BUSY); + } + } + + pkt->pkt_private = vpkt; + vpkt->vpkt_hba_pkt = pkt; + return (TRAN_ACCEPT); +} + + +/*PRINTFLIKE3*/ +void +vhci_log(int level, dev_info_t *dip, const char *fmt, ...) +{ + char buf[256]; + va_list ap; + + va_start(ap, fmt); + (void) vsprintf(buf, fmt, ap); + va_end(ap); + + scsi_log(dip, "scsi_vhci", level, buf); +} + +/* do a PGR out with the information we've saved away */ +static int +vhci_do_prout(scsi_vhci_priv_t *svp) +{ + + struct scsi_pkt *new_pkt; + struct buf *bp; + scsi_vhci_lun_t *vlun; + int rval, retry, nr_retry, ua_retry; + struct scsi_extended_sense *sns; + + bp = getrbuf(KM_SLEEP); + bp->b_flags = B_WRITE; + bp->b_resid = 0; + + VHCI_INCR_PATH_CMDCOUNT(svp); + vlun = svp->svp_svl; + + new_pkt = scsi_init_pkt(&svp->svp_psd->sd_address, NULL, bp, + CDB_GROUP1, sizeof (struct scsi_arq_status), 0, 0, + SLEEP_FUNC, NULL); + if (new_pkt == NULL) { + VHCI_DECR_PATH_CMDCOUNT(svp); + freerbuf(bp); + cmn_err(CE_WARN, "!vhci_do_prout: scsi_init_pkt failed"); + return (0); + } + mutex_enter(&vlun->svl_mutex); + bp->b_un.b_addr = (caddr_t)&vlun->svl_prout; + bp->b_bcount = vlun->svl_bcount; + bcopy(vlun->svl_cdb, new_pkt->pkt_cdbp, + sizeof (vlun->svl_cdb)); + new_pkt->pkt_time = vlun->svl_time; + mutex_exit(&vlun->svl_mutex); + new_pkt->pkt_flags = FLAG_NOINTR; + + ua_retry = nr_retry = retry = 0; +again: + rval = vhci_do_scsi_cmd(new_pkt); + if (rval != 1) { + if ((new_pkt->pkt_reason == CMD_CMPLT) && + (SCBP_C(new_pkt) == STATUS_CHECK) && + (new_pkt->pkt_state & STATE_ARQ_DONE)) { + sns = &(((struct scsi_arq_status *)(uintptr_t) + (new_pkt->pkt_scbp))->sts_sensedata); + if ((sns->es_key == KEY_UNIT_ATTENTION) || + (sns->es_key == KEY_NOT_READY)) { + int max_retry; + struct scsi_failover_ops *fops; + fops = vlun->svl_fops; + rval = (*fops->sfo_analyze_sense) + (svp->svp_psd, sns, + vlun->svl_fops_ctpriv); + if (rval == SCSI_SENSE_NOT_READY) { + max_retry = vhci_prout_not_ready_retry; + retry = nr_retry++; + delay(1*drv_usectohz(1000000)); + } else { + /* chk for state change and update */ + if (rval == SCSI_SENSE_STATE_CHANGED) { + int held; + VHCI_HOLD_LUN(vlun, + VH_NOSLEEP, held); + if (!held) { + rval = TRAN_BUSY; + } else { + /* chk for alua first */ + vhci_update_pathstates( + (void *)vlun); + } + } + retry = ua_retry++; + max_retry = VHCI_MAX_PGR_RETRIES; + } + if (retry < max_retry) { + VHCI_DEBUG(4, (CE_WARN, NULL, + "!vhci_do_prout retry 0x%x " + "(0x%x 0x%x 0x%x)", + SCBP_C(new_pkt), + new_pkt->pkt_cdbp[0], + new_pkt->pkt_cdbp[1], + new_pkt->pkt_cdbp[2])); + goto again; + } + rval = 0; + VHCI_DEBUG(4, (CE_WARN, NULL, + "!vhci_do_prout 0x%x " + "(0x%x 0x%x 0x%x)", + SCBP_C(new_pkt), + new_pkt->pkt_cdbp[0], + new_pkt->pkt_cdbp[1], + new_pkt->pkt_cdbp[2])); + } else if (sns->es_key == KEY_ILLEGAL_REQUEST) + rval = VHCI_PGR_ILLEGALOP; + } + } else { + rval = 1; + } + scsi_destroy_pkt(new_pkt); + VHCI_DECR_PATH_CMDCOUNT(svp); + freerbuf(bp); + return (rval); +} + +static void +vhci_run_cmd(void *arg) +{ + struct scsi_pkt *pkt = (struct scsi_pkt *)arg; + struct scsi_pkt *tpkt; + scsi_vhci_priv_t *svp; + mdi_pathinfo_t *pip, *npip; + scsi_vhci_lun_t *vlun; + dev_info_t *cdip; + scsi_vhci_priv_t *nsvp; + int fail = 0; + int rval; + struct vhci_pkt *vpkt; + uchar_t cdb_1; + vhci_prout_t *prout; + + vpkt = (struct vhci_pkt *)pkt->pkt_private; + tpkt = vpkt->vpkt_tgt_pkt; + pip = vpkt->vpkt_path; + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(pip); + if (svp == NULL) { + tpkt->pkt_reason = CMD_TRAN_ERR; + tpkt->pkt_statistics = STAT_ABORTED; + goto done; + } + vlun = svp->svp_svl; + prout = &vlun->svl_prout; + if (SCBP_C(pkt) != STATUS_GOOD) + fail++; + cdip = vlun->svl_dip; + pip = npip = NULL; + rval = mdi_select_path(cdip, NULL, + MDI_SELECT_ONLINE_PATH|MDI_SELECT_STANDBY_PATH, NULL, &npip); + if ((rval != MDI_SUCCESS) || (npip == NULL)) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_run_cmd: no path! 0x%p\n", (void *)svp)); + tpkt->pkt_reason = CMD_TRAN_ERR; + tpkt->pkt_statistics = STAT_ABORTED; + goto done; + } + + cdb_1 = vlun->svl_cdb[1]; + vlun->svl_cdb[1] &= 0xe0; + vlun->svl_cdb[1] |= VHCI_PROUT_R_AND_IGNORE; + + do { + nsvp = (scsi_vhci_priv_t *) + mdi_pi_get_vhci_private(npip); + if (nsvp == NULL) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_run_cmd: no " + "client priv! 0x%p offlined?\n", + (void *)npip)); + goto next_path; + } + if (vlun->svl_first_path == npip) { + goto next_path; + } else { + if (vhci_do_prout(nsvp) != 1) + fail++; + } +next_path: + pip = npip; + rval = mdi_select_path(cdip, NULL, + MDI_SELECT_ONLINE_PATH|MDI_SELECT_STANDBY_PATH, + pip, &npip); + mdi_rele_path(pip); + } while ((rval == MDI_SUCCESS) && (npip != NULL)); + + vlun->svl_cdb[1] = cdb_1; + + if (fail) { + VHCI_DEBUG(4, (CE_WARN, NULL, "%s%d: key registration failed, " + "couldn't be replicated on all paths", + ddi_driver_name(cdip), ddi_get_instance(cdip))); + vhci_print_prout_keys(vlun, "vhci_run_cmd: "); + + if (SCBP_C(pkt) != STATUS_GOOD) { + tpkt->pkt_reason = CMD_TRAN_ERR; + tpkt->pkt_statistics = STAT_ABORTED; + } + } else { + vlun->svl_pgr_active = 1; + vhci_print_prout_keys(vlun, "vhci_run_cmd: before bcopy:"); + + bcopy((const void *)prout->service_key, + (void *)prout->active_service_key, MHIOC_RESV_KEY_SIZE); + bcopy((const void *)prout->res_key, + (void *)prout->active_res_key, MHIOC_RESV_KEY_SIZE); + + vhci_print_prout_keys(vlun, "vhci_run_cmd: after bcopy:"); + } +done: + if (SCBP_C(pkt) == STATUS_GOOD) + vlun->svl_first_path = NULL; + + if (svp) + VHCI_DECR_PATH_CMDCOUNT(svp); + + if ((vpkt->vpkt_flags & CFLAG_DMA_PARTIAL) == 0) { + scsi_destroy_pkt(pkt); + vpkt->vpkt_hba_pkt = NULL; + if (vpkt->vpkt_path) { + mdi_rele_path(vpkt->vpkt_path); + vpkt->vpkt_path = NULL; + } + } + + sema_v(&vlun->svl_pgr_sema); + /* + * The PROUT commands are not included in the automatic retry + * mechanism, therefore, vpkt_org_vpkt should never be set here. + */ + ASSERT(vpkt->vpkt_org_vpkt == NULL); + if (tpkt->pkt_comp) + (*tpkt->pkt_comp)(tpkt); + +} + +/* + * Get the keys registered with this target. Since we will have + * registered the same key with multiple initiators, strip out + * any duplicate keys. + * + * The pointers which will be used to filter the registered keys from + * the device will be stored in filter_prin and filter_pkt. If the + * allocation length of the buffer was sufficient for the number of + * parameter data bytes available to be returned by the device then the + * key filtering will use the keylist returned from the original + * request. If the allocation length of the buffer was not sufficient, + * then the filtering will use the keylist returned from the request + * that is resent below. + * + * If the device returns an additional length field that is greater than + * the allocation length of the buffer, then allocate a new buffer which + * can accommodate the number of parameter data bytes available to be + * returned. Resend the scsi PRIN command, filter out the duplicate + * keys and return as many of the unique keys found that was originally + * requested and set the additional length field equal to the data bytes + * of unique reservation keys available to be returned. + * + * If the device returns an additional length field that is less than or + * equal to the allocation length of the buffer, then all the available + * keys registered were returned by the device. Filter out the + * duplicate keys and return all of the unique keys found and set the + * additional length field equal to the data bytes of the reservation + * keys to be returned. + */ +static int +vhci_do_prin(struct vhci_pkt **vpkt) +{ + scsi_vhci_priv_t *svp = (scsi_vhci_priv_t *) + mdi_pi_get_vhci_private((*vpkt)->vpkt_path); + vhci_prin_readkeys_t *prin; + scsi_vhci_lun_t *vlun = svp->svp_svl; + struct scsi_vhci *vhci = + ADDR2VHCI(&((*vpkt)->vpkt_tgt_pkt->pkt_address)); + + struct buf *new_bp = NULL; + struct scsi_pkt *new_pkt = NULL; + struct vhci_pkt *new_vpkt = NULL; + int hdr_len = 0; + int rval = VHCI_CMD_CMPLT; + uint32_t prin_length = 0; + uint32_t svl_prin_length = 0; + + prin = (vhci_prin_readkeys_t *) + bp_mapin_common((*vpkt)->vpkt_tgt_init_bp, VM_NOSLEEP); + + if (prin != NULL) { + prin_length = BE_32(prin->length); + } + + if (prin == NULL) { + VHCI_DEBUG(5, (CE_WARN, NULL, + "vhci_do_prin: bp_mapin_common failed.")); + rval = VHCI_CMD_ERROR; + } else { + /* + * According to SPC-3r22, sec 4.3.4.6: "If the amount of + * information to be transferred exceeds the maximum value + * that the ALLOCATION LENGTH field is capable of specifying, + * the device server shall...terminate the command with CHECK + * CONDITION status". The ALLOCATION LENGTH field of the + * PERSISTENT RESERVE IN command is 2 bytes. We should never + * get here with an ADDITIONAL LENGTH greater than 0xFFFF + * so if we do, then it is an error! + */ + + hdr_len = sizeof (prin->length) + sizeof (prin->generation); + + if ((prin_length + hdr_len) > 0xFFFF) { + VHCI_DEBUG(5, (CE_NOTE, NULL, + "vhci_do_prin: Device returned invalid " + "length 0x%x\n", prin_length)); + rval = VHCI_CMD_ERROR; + } + } + + /* + * If prin->length is greater than the byte count allocated in the + * original buffer, then resend the request with enough buffer + * allocated to get all of the available registered keys. + */ + if (rval != VHCI_CMD_ERROR) { + if (((*vpkt)->vpkt_tgt_init_bp->b_bcount - hdr_len) < + prin_length) { + if ((*vpkt)->vpkt_org_vpkt == NULL) { + new_pkt = vhci_create_retry_pkt(*vpkt); + if (new_pkt != NULL) { + new_vpkt = TGTPKT2VHCIPKT(new_pkt); + + /* + * This is the buf with buffer pointer + * where the prin readkeys will be + * returned from the device + */ + new_bp = scsi_alloc_consistent_buf( + &svp->svp_psd->sd_address, + NULL, (prin_length + hdr_len), + ((*vpkt)->vpkt_tgt_init_bp-> + b_flags & (B_READ | B_WRITE)), + NULL_FUNC, NULL); + if (new_bp != NULL) { + if (new_bp->b_un.b_addr != + NULL) { + + new_bp->b_bcount = + prin_length + + hdr_len; + + new_pkt->pkt_cdbp[7] = + (uchar_t)(new_bp-> + b_bcount >> 8); + new_pkt->pkt_cdbp[8] = + (uchar_t)new_bp-> + b_bcount; + + rval = VHCI_CMD_RETRY; + } else { + rval = VHCI_CMD_ERROR; + } + } else { + rval = VHCI_CMD_ERROR; + } + } else { + rval = VHCI_CMD_ERROR; + } + } else { + rval = VHCI_CMD_ERROR; + } + } + } + + if (rval == VHCI_CMD_RETRY) { + new_vpkt->vpkt_tgt_init_bp = new_bp; + + /* + * Release the old path because it does not matter which path + * this command is sent down. This allows the normal bind + * transport mechanism to be used. + */ + if ((*vpkt)->vpkt_path != NULL) { + mdi_rele_path((*vpkt)->vpkt_path); + (*vpkt)->vpkt_path = NULL; + } + + /* + * Dispatch the retry command + */ + if (taskq_dispatch(vhci->vhci_taskq, vhci_dispatch_scsi_start, + (void *) new_vpkt, KM_NOSLEEP) == NULL) { + rval = VHCI_CMD_ERROR; + } else { + /* + * If we return VHCI_CMD_RETRY, that means the caller + * is going to bail and wait for the reissued command + * to complete. In that case, we need to decrement + * the path command count right now. In any other + * case, it'll be decremented by the caller. + */ + VHCI_DECR_PATH_CMDCOUNT(svp); + } + } + + if ((rval != VHCI_CMD_ERROR) && (rval != VHCI_CMD_RETRY)) { + int new, old; + int data_len = 0; + + data_len = prin_length / MHIOC_RESV_KEY_SIZE; + VHCI_DEBUG(4, (CE_NOTE, NULL, "vhci_do_prin: %d keys read\n", + data_len)); + +#ifdef DEBUG + VHCI_DEBUG(5, (CE_NOTE, NULL, "vhci_do_prin: from storage\n")); + if (vhci_debug == 5) + vhci_print_prin_keys(prin, data_len); + VHCI_DEBUG(5, (CE_NOTE, NULL, + "vhci_do_prin: MPxIO old keys:\n")); + if (vhci_debug == 5) + vhci_print_prin_keys(&vlun->svl_prin, data_len); +#endif + + /* + * Filter out all duplicate keys returned from the device + * We know that we use a different key for every host, so we + * can simply strip out duplicates. Otherwise we would need to + * do more bookkeeping to figure out which keys to strip out. + */ + + new = 0; + + if (data_len > 0) { + vlun->svl_prin.keylist[0] = prin->keylist[0]; + new++; + } + + for (old = 1; old < data_len; old++) { + int j; + int match = 0; + for (j = 0; j < new; j++) { + if (bcmp(&prin->keylist[old], + &vlun->svl_prin.keylist[j], + sizeof (mhioc_resv_key_t)) == 0) { + match = 1; + break; + } + } + if (!match) { + vlun->svl_prin.keylist[new] = + prin->keylist[old]; + new++; + } + } + + vlun->svl_prin.generation = prin->generation; + svl_prin_length = new * MHIOC_RESV_KEY_SIZE; + vlun->svl_prin.length = BE_32(svl_prin_length); + + /* + * If we arrived at this point after issuing a retry, make sure + * that we put everything back the way it originally was so + * that the target driver can complete the command correctly. + */ + if ((*vpkt)->vpkt_org_vpkt != NULL) { + new_bp = (*vpkt)->vpkt_tgt_init_bp; + + scsi_free_consistent_buf(new_bp); + + *vpkt = vhci_sync_retry_pkt(*vpkt); + + /* + * Make sure the original buffer is mapped into kernel + * space before we try to copy the filtered keys into + * it. + */ + prin = (vhci_prin_readkeys_t *)bp_mapin_common( + (*vpkt)->vpkt_tgt_init_bp, VM_NOSLEEP); + } + + /* + * Now copy the desired number of prin keys into the original + * target buffer. + */ + if (svl_prin_length <= + ((*vpkt)->vpkt_tgt_init_bp->b_bcount - hdr_len)) { + /* + * It is safe to return all of the available unique + * keys + */ + bcopy(&vlun->svl_prin, prin, svl_prin_length + hdr_len); + } else { + /* + * Not all of the available keys were requested by the + * original command. + */ + bcopy(&vlun->svl_prin, prin, + (*vpkt)->vpkt_tgt_init_bp->b_bcount); + } +#ifdef DEBUG + VHCI_DEBUG(5, (CE_NOTE, NULL, + "vhci_do_prin: To Application:\n")); + if (vhci_debug == 5) + vhci_print_prin_keys(prin, new); + VHCI_DEBUG(5, (CE_NOTE, NULL, + "vhci_do_prin: MPxIO new keys:\n")); + if (vhci_debug == 5) + vhci_print_prin_keys(&vlun->svl_prin, new); +#endif + } + + if (rval == VHCI_CMD_ERROR) { + /* + * If we arrived at this point after issuing a + * retry, make sure that we put everything back + * the way it originally was so that ssd can + * complete the command correctly. + */ + + if ((*vpkt)->vpkt_org_vpkt != NULL) { + new_bp = (*vpkt)->vpkt_tgt_init_bp; + if (new_bp != NULL) { + scsi_free_consistent_buf(new_bp); + } + + new_vpkt = *vpkt; + *vpkt = (*vpkt)->vpkt_org_vpkt; + + vhci_scsi_destroy_pkt(&svp->svp_psd->sd_address, + new_vpkt->vpkt_tgt_pkt); + } + + /* + * Mark this command completion as having an error so that + * ssd will retry the command. + */ + + (*vpkt)->vpkt_tgt_pkt->pkt_reason = CMD_ABORTED; + (*vpkt)->vpkt_tgt_pkt->pkt_statistics |= STAT_ABORTED; + + rval = VHCI_CMD_CMPLT; + } + + /* + * Make sure that the semaphore is only released once. + */ + if (rval == VHCI_CMD_CMPLT) { + sema_v(&vlun->svl_pgr_sema); + } + + return (rval); +} + +static void +vhci_intr(struct scsi_pkt *pkt) +{ + struct vhci_pkt *vpkt = (struct vhci_pkt *)pkt->pkt_private; + struct scsi_pkt *tpkt; + scsi_vhci_priv_t *svp; + scsi_vhci_lun_t *vlun; + int rval, held; + struct scsi_failover_ops *fops; + struct scsi_extended_sense *sns; + mdi_pathinfo_t *lpath; + static char *timeout_err = "Command Timeout"; + static char *parity_err = "Parity Error"; + char *err_str = NULL; + dev_info_t *vdip, *cdip, *pdip; + char *cpath, *dpath; + + ASSERT(vpkt != NULL); + tpkt = vpkt->vpkt_tgt_pkt; + ASSERT(tpkt != NULL); + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(vpkt->vpkt_path); + ASSERT(svp != NULL); + vlun = svp->svp_svl; + ASSERT(vlun != NULL); + lpath = vpkt->vpkt_path; + + /* + * sync up the target driver's pkt with the pkt that + * we actually used + */ + *(tpkt->pkt_scbp) = *(pkt->pkt_scbp); + tpkt->pkt_resid = pkt->pkt_resid; + tpkt->pkt_state = pkt->pkt_state; + tpkt->pkt_statistics = pkt->pkt_statistics; + tpkt->pkt_reason = pkt->pkt_reason; + + if (pkt->pkt_cdbp[0] == SCMD_PROUT && + ((pkt->pkt_cdbp[1] & 0x1f) == VHCI_PROUT_REGISTER) || + ((pkt->pkt_cdbp[1] & 0x1f) == VHCI_PROUT_R_AND_IGNORE)) { + if ((SCBP_C(pkt) != STATUS_GOOD) || + (pkt->pkt_reason != CMD_CMPLT)) { + sema_v(&vlun->svl_pgr_sema); + } + } else if (pkt->pkt_cdbp[0] == SCMD_PRIN) { + if (pkt->pkt_reason != CMD_CMPLT || + (SCBP_C(pkt) != STATUS_GOOD)) { + sema_v(&vlun->svl_pgr_sema); + } + } + + switch (pkt->pkt_reason) { + case CMD_CMPLT: + /* + * cmd completed successfully, check for scsi errors + */ + switch (*(pkt->pkt_scbp)) { + case STATUS_CHECK: + if (pkt->pkt_state & STATE_ARQ_DONE) { + sns = &(((struct scsi_arq_status *)(uintptr_t) + (pkt->pkt_scbp))->sts_sensedata); + fops = vlun->svl_fops; + ASSERT(fops != NULL); + VHCI_DEBUG(4, (CE_NOTE, NULL, "vhci_intr: " + "Received sns key %x esc %x escq %x\n", + sns->es_key, sns->es_add_code, + sns->es_qual_code)); + + if (vlun->svl_waiting_for_activepath == 1) { + /* + * if we are here it means we are + * in the midst of a probe/attach + * through a passive path; this + * case is exempt from sense analysis + * for detection of ext. failover + * because that would unnecessarily + * increase attach time. + */ + bcopy(pkt->pkt_scbp, tpkt->pkt_scbp, + vpkt->vpkt_tgt_init_scblen); + break; + } + if (sns->es_add_code == VHCI_SCSI_PERR) { + /* + * parity error + */ + err_str = parity_err; + bcopy(pkt->pkt_scbp, tpkt->pkt_scbp, + vpkt->vpkt_tgt_init_scblen); + break; + } + rval = (*fops->sfo_analyze_sense) + (svp->svp_psd, sns, vlun->svl_fops_ctpriv); + if ((rval == SCSI_SENSE_NOFAILOVER) || + (rval == SCSI_SENSE_UNKNOWN) || + (rval == SCSI_SENSE_NOT_READY)) { + bcopy(pkt->pkt_scbp, tpkt->pkt_scbp, + vpkt->vpkt_tgt_init_scblen); + break; + } else if (rval == SCSI_SENSE_STATE_CHANGED) { + struct scsi_vhci *vhci; + vhci = ADDR2VHCI(&tpkt->pkt_address); + VHCI_HOLD_LUN(vlun, VH_NOSLEEP, held); + if (!held) { + /* + * looks like some other thread + * has already detected this + * condition + */ + tpkt->pkt_state &= + ~STATE_ARQ_DONE; + *(tpkt->pkt_scbp) = + STATUS_BUSY; + break; + } + (void) taskq_dispatch( + vhci->vhci_update_pathstates_taskq, + vhci_update_pathstates, + (void *)vlun, KM_SLEEP); + } else { + /* + * externally initiated failover + * has occurred or is in progress + */ + VHCI_HOLD_LUN(vlun, VH_NOSLEEP, held); + if (!held) { + /* + * looks like some other thread + * has already detected this + * condition + */ + tpkt->pkt_state &= + ~STATE_ARQ_DONE; + *(tpkt->pkt_scbp) = + STATUS_BUSY; + break; + } else { + rval = vhci_handle_ext_fo + (pkt, rval); + if (rval == BUSY_RETURN) { + tpkt->pkt_state &= + ~STATE_ARQ_DONE; + *(tpkt->pkt_scbp) = + STATUS_BUSY; + break; + } + bcopy(pkt->pkt_scbp, + tpkt->pkt_scbp, + vpkt->vpkt_tgt_init_scblen); + break; + } + } + } + break; + + /* + * If this is a good SCSI-II RELEASE cmd completion then restore + * the load balancing policy and reset VLUN_RESERVE_ACTIVE_FLG. + * If this is a good SCSI-II RESERVE cmd completion then set + * VLUN_RESERVE_ACTIVE_FLG. + */ + case STATUS_GOOD: + if ((pkt->pkt_cdbp[0] == SCMD_RELEASE) || + (pkt->pkt_cdbp[0] == SCMD_RELEASE_G1)) { + (void) mdi_set_lb_policy(vlun->svl_dip, + vlun->svl_lb_policy_save); + vlun->svl_flags &= ~VLUN_RESERVE_ACTIVE_FLG; + VHCI_DEBUG(1, (CE_WARN, NULL, + "!vhci_intr: vlun 0x%p release path 0x%p", + (void *)vlun, (void *)vpkt->vpkt_path)); + } + + if ((pkt->pkt_cdbp[0] == SCMD_RESERVE) || + (pkt->pkt_cdbp[0] == SCMD_RESERVE_G1)) { + vlun->svl_flags |= VLUN_RESERVE_ACTIVE_FLG; + vlun->svl_resrv_pip = vpkt->vpkt_path; + VHCI_DEBUG(1, (CE_WARN, NULL, + "!vhci_intr: vlun 0x%p reserved path 0x%p", + (void *)vlun, (void *)vpkt->vpkt_path)); + } + break; + + case STATUS_RESERVATION_CONFLICT: + VHCI_DEBUG(1, (CE_WARN, NULL, + "!vhci_intr: vlun 0x%p " + "reserve conflict on path 0x%p", + (void *)vlun, (void *)vpkt->vpkt_path)); + /* FALLTHROUGH */ + default: + break; + } + + /* + * Update I/O completion statistics for the path + */ + mdi_pi_kstat_iosupdate(vpkt->vpkt_path, vpkt->vpkt_tgt_init_bp); + + /* + * Command completed successfully, release the dma binding and + * destroy the transport side of the packet. + */ + if ((pkt->pkt_cdbp[0] == SCMD_PROUT) && + (((pkt->pkt_cdbp[1] & 0x1f) == VHCI_PROUT_REGISTER) || + ((pkt->pkt_cdbp[1] & 0x1f) == + VHCI_PROUT_R_AND_IGNORE))) { + if (SCBP_C(pkt) == STATUS_GOOD) { + ASSERT(vlun->svl_taskq); + svp->svp_last_pkt_reason = pkt->pkt_reason; + (void) taskq_dispatch(vlun->svl_taskq, + vhci_run_cmd, pkt, KM_SLEEP); + return; + } + } + if ((SCBP_C(pkt) == STATUS_GOOD) && + (pkt->pkt_cdbp[0] == SCMD_PRIN) && + vpkt->vpkt_tgt_init_bp) { + /* + * If the action (value in byte 1 of the cdb) is zero, + * we're reading keys, and that's the only condition + * where we need to be concerned with filtering keys + * and potential retries. Otherwise, we simply signal + * the semaphore and move on. + */ + if (pkt->pkt_cdbp[1] == 0) { + /* + * If this is the completion of an internal + * retry then we need to make sure that the + * pkt and tpkt pointers are readjusted so + * the calls to scsi_destroy_pkt and pkt_comp + * below work * correctly. + */ + if (vpkt->vpkt_org_vpkt != NULL) { + pkt = vpkt->vpkt_org_vpkt->vpkt_hba_pkt; + tpkt = vpkt->vpkt_org_vpkt-> + vpkt_tgt_pkt; + + /* + * If this command was issued through + * the taskq then we need to clear + * this flag for proper processing in + * the case of a retry from the target + * driver. + */ + vpkt->vpkt_state &= + ~VHCI_PKT_THRU_TASKQ; + } + + /* + * if vhci_do_prin returns VHCI_CMD_CMPLT then + * vpkt will contain the address of the + * original vpkt + */ + if (vhci_do_prin(&vpkt) == VHCI_CMD_RETRY) { + /* + * The command has been resent to get + * all the keys from the device. Don't + * complete the command with ssd until + * the retry completes. + */ + return; + } + } else { + sema_v(&vlun->svl_pgr_sema); + } + } + + break; + + case CMD_TIMEOUT: + if ((pkt->pkt_statistics & + (STAT_BUS_RESET|STAT_DEV_RESET|STAT_ABORTED)) == 0) { + + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!scsi vhci timeout invoked\n")); + + (void) vhci_recovery_reset(vlun, &pkt->pkt_address, + FALSE, VHCI_DEPTH_ALL); + } + MDI_PI_ERRSTAT(lpath, MDI_PI_TRANSERR); + tpkt->pkt_statistics |= STAT_ABORTED; + err_str = timeout_err; + break; + + case CMD_TRAN_ERR: + /* + * This status is returned if the transport has sent the cmd + * down the link to the target and then some error occurs. + * In case of SCSI-II RESERVE cmd, we don't know if the + * reservation been accepted by the target or not, so we need + * to clear the reservation. + */ + if ((pkt->pkt_cdbp[0] == SCMD_RESERVE) || + (pkt->pkt_cdbp[0] == SCMD_RESERVE_G1)) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_intr received" + " cmd_tran_err for scsi-2 reserve cmd\n")); + if (!vhci_recovery_reset(vlun, &pkt->pkt_address, + TRUE, VHCI_DEPTH_TARGET)) { + VHCI_DEBUG(1, (CE_WARN, NULL, + "!vhci_intr cmd_tran_err reset failed!")); + } + } + break; + + case CMD_DEV_GONE: + tpkt->pkt_reason = CMD_CMPLT; + tpkt->pkt_state = STATE_GOT_BUS | + STATE_GOT_TARGET | STATE_SENT_CMD | + STATE_GOT_STATUS; + *(tpkt->pkt_scbp) = STATUS_BUSY; + break; + + default: + break; + } + + /* + * SCSI-II RESERVE cmd has been serviced by the lower layers clear + * the flag so the lun is not QUIESCED any longer. + * Also clear the VHCI_PKT_THRU_TASKQ flag, to ensure that if this pkt + * is retried, a taskq shall again be dispatched to service it. Else + * it may lead to a system hang if the retry is within interrupt + * context. + */ + if ((pkt->pkt_cdbp[0] == SCMD_RESERVE) || + (pkt->pkt_cdbp[0] == SCMD_RESERVE_G1)) { + vlun->svl_flags &= ~VLUN_QUIESCED_FLG; + vpkt->vpkt_state &= ~VHCI_PKT_THRU_TASKQ; + } + + /* + * vpkt_org_vpkt should always be NULL here if the retry command + * has been successfully processed. If vpkt_org_vpkt != NULL at + * this point, it is an error so restore the original vpkt and + * return an error to the target driver so it can retry the + * command as appropriate. + */ + if (vpkt->vpkt_org_vpkt != NULL) { + struct vhci_pkt *new_vpkt = vpkt; + vpkt = vpkt->vpkt_org_vpkt; + + vhci_scsi_destroy_pkt(&svp->svp_psd->sd_address, + new_vpkt->vpkt_tgt_pkt); + + /* + * Mark this command completion as having an error so that + * ssd will retry the command. + */ + vpkt->vpkt_tgt_pkt->pkt_reason = CMD_ABORTED; + vpkt->vpkt_tgt_pkt->pkt_statistics |= STAT_ABORTED; + + pkt = vpkt->vpkt_hba_pkt; + tpkt = vpkt->vpkt_tgt_pkt; + } + + if ((err_str != NULL) && (pkt->pkt_reason != + svp->svp_last_pkt_reason)) { + cdip = vlun->svl_dip; + pdip = mdi_pi_get_phci(vpkt->vpkt_path); + vdip = ddi_get_parent(cdip); + cpath = kmem_alloc(MAXPATHLEN, KM_SLEEP); + dpath = kmem_alloc(MAXPATHLEN, KM_SLEEP); + vhci_log(CE_WARN, vdip, "!%s (%s%d): %s on path %s (%s%d)", + ddi_pathname(cdip, cpath), ddi_driver_name(cdip), + ddi_get_instance(cdip), err_str, + ddi_pathname(pdip, dpath), ddi_driver_name(pdip), + ddi_get_instance(pdip)); + kmem_free(cpath, MAXPATHLEN); + kmem_free(dpath, MAXPATHLEN); + } + svp->svp_last_pkt_reason = pkt->pkt_reason; + VHCI_DECR_PATH_CMDCOUNT(svp); + + /* + * For PARTIAL_DMA, vhci should not free the path. + * Target driver will call into vhci_scsi_dmafree or + * destroy pkt to release this path. + */ + if ((vpkt->vpkt_flags & CFLAG_DMA_PARTIAL) == 0) { + scsi_destroy_pkt(pkt); + vpkt->vpkt_hba_pkt = NULL; + if (vpkt->vpkt_path) { + mdi_rele_path(vpkt->vpkt_path); + vpkt->vpkt_path = NULL; + } + } + + if (tpkt->pkt_comp) { + (*tpkt->pkt_comp)(tpkt); + } +} + +/* + * two possibilities: (1) failover has completed + * or (2) is in progress; update our path states for + * the former case; for the latter case, + * initiate a scsi_watch request to + * determine when failover completes - vlun is HELD + * until failover completes; BUSY is returned to upper + * layer in both the cases + */ +static int +vhci_handle_ext_fo(struct scsi_pkt *pkt, int fostat) +{ + struct vhci_pkt *vpkt = (struct vhci_pkt *)pkt->pkt_private; + struct scsi_pkt *tpkt; + scsi_vhci_priv_t *svp; + scsi_vhci_lun_t *vlun; + struct scsi_vhci *vhci; + scsi_vhci_swarg_t *swarg; + char *path; + + ASSERT(vpkt != NULL); + tpkt = vpkt->vpkt_tgt_pkt; + ASSERT(tpkt != NULL); + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(vpkt->vpkt_path); + ASSERT(svp != NULL); + vlun = svp->svp_svl; + ASSERT(vlun != NULL); + ASSERT(VHCI_LUN_IS_HELD(vlun)); + + vhci = ADDR2VHCI(&tpkt->pkt_address); + + if (fostat == SCSI_SENSE_INACTIVE) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!Failover " + "detected for %s; updating path states...\n", + vlun->svl_lun_wwn)); + /* + * set the vlun flag to indicate to the task that the target + * port group needs updating + */ + vlun->svl_flags |= VLUN_UPDATE_TPG; + (void) taskq_dispatch(vhci->vhci_update_pathstates_taskq, + vhci_update_pathstates, (void *)vlun, KM_SLEEP); + } else { + path = kmem_alloc(MAXPATHLEN, KM_SLEEP); + vhci_log(CE_NOTE, ddi_get_parent(vlun->svl_dip), + "!%s (%s%d): Waiting for externally initiated failover " + "to complete", ddi_pathname(vlun->svl_dip, path), + ddi_driver_name(vlun->svl_dip), + ddi_get_instance(vlun->svl_dip)); + kmem_free(path, MAXPATHLEN); + swarg = kmem_alloc(sizeof (*swarg), KM_NOSLEEP); + if (swarg == NULL) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_handle_ext_fo: " + "request packet allocation for %s failed....\n", + vlun->svl_lun_wwn)); + VHCI_RELEASE_LUN(vlun); + return (PKT_RETURN); + } + swarg->svs_svp = svp; + swarg->svs_tos = ddi_get_time(); + swarg->svs_pi = vpkt->vpkt_path; + swarg->svs_release_lun = 0; + swarg->svs_done = 0; + /* + * place a hold on the path...we don't want it to + * vanish while scsi_watch is in progress + */ + mdi_hold_path(vpkt->vpkt_path); + svp->svp_sw_token = scsi_watch_request_submit(svp->svp_psd, + VHCI_FOWATCH_INTERVAL, SENSE_LENGTH, vhci_efo_watch_cb, + (caddr_t)swarg); + } + return (BUSY_RETURN); +} + +/* + * vhci_efo_watch_cb: + * Callback from scsi_watch request to check the failover status. + * Completion is either due to successful failover or timeout. + * Upon successful completion, vhci_update_path_states is called. + * For timeout condition, vhci_efo_done is called. + * Always returns 0 to scsi_watch to keep retrying till vhci_efo_done + * terminates this request properly in a separate thread. + */ + +static int +vhci_efo_watch_cb(caddr_t arg, struct scsi_watch_result *resultp) +{ + struct scsi_status *statusp = resultp->statusp; + struct scsi_extended_sense *sensep = resultp->sensep; + struct scsi_pkt *pkt = resultp->pkt; + scsi_vhci_swarg_t *swarg; + scsi_vhci_priv_t *svp; + scsi_vhci_lun_t *vlun; + struct scsi_vhci *vhci; + dev_info_t *vdip; + int rval, updt_paths; + + swarg = (scsi_vhci_swarg_t *)(uintptr_t)arg; + svp = swarg->svs_svp; + if (swarg->svs_done) { + /* + * Already completed failover or timedout. + * Waiting for vhci_efo_done to terminate this scsi_watch. + */ + return (0); + } + + ASSERT(svp != NULL); + vlun = svp->svp_svl; + ASSERT(vlun != NULL); + ASSERT(VHCI_LUN_IS_HELD(vlun)); + vlun->svl_efo_update_path = 0; + vdip = ddi_get_parent(vlun->svl_dip); + vhci = ddi_get_soft_state(vhci_softstate, + ddi_get_instance(vdip)); + + updt_paths = 0; + + if (pkt->pkt_reason != CMD_CMPLT) { + if ((ddi_get_time() - swarg->svs_tos) >= VHCI_EXTFO_TIMEOUT) { + swarg->svs_release_lun = 1; + goto done; + } + return (0); + } + if (*((unsigned char *)statusp) == STATUS_CHECK) { + rval = (*(vlun->svl_fops->sfo_analyze_sense)) + (svp->svp_psd, sensep, vlun->svl_fops_ctpriv); + switch (rval) { + /* + * Only update path states in case path is definitely + * inactive, or no failover occurred. For all other + * check conditions continue pinging. A unexpected + * check condition shouldn't cause pinging to complete + * prematurely. + */ + case SCSI_SENSE_INACTIVE: + case SCSI_SENSE_NOFAILOVER: + updt_paths = 1; + break; + default: + if ((ddi_get_time() - swarg->svs_tos) + >= VHCI_EXTFO_TIMEOUT) { + swarg->svs_release_lun = 1; + goto done; + } + return (0); + } + } else if (*((unsigned char *)statusp) == + STATUS_RESERVATION_CONFLICT) { + updt_paths = 1; + } else if ((*((unsigned char *)statusp)) & + (STATUS_BUSY | STATUS_QFULL)) { + return (0); + } + if ((*((unsigned char *)statusp) == STATUS_GOOD) || + (updt_paths == 1)) { + /* + * we got here because we had detected an + * externally initiated failover; things + * have settled down now, so let's + * start up a task to update the + * path states and target port group + */ + vlun->svl_efo_update_path = 1; + swarg->svs_done = 1; + vlun->svl_swarg = swarg; + vlun->svl_flags |= VLUN_UPDATE_TPG; + (void) taskq_dispatch(vhci->vhci_update_pathstates_taskq, + vhci_update_pathstates, (void *)vlun, + KM_SLEEP); + return (0); + } + if ((ddi_get_time() - swarg->svs_tos) >= VHCI_EXTFO_TIMEOUT) { + swarg->svs_release_lun = 1; + goto done; + } + return (0); +done: + swarg->svs_done = 1; + (void) taskq_dispatch(vhci->vhci_taskq, + vhci_efo_done, (void *)swarg, KM_SLEEP); + return (0); +} + +/* + * vhci_efo_done: + * cleanly terminates scsi_watch and free up resources. + * Called as taskq function in vhci_efo_watch_cb for EFO timeout condition + * or by vhci_update_path_states invoked during external initiated + * failover completion. + */ +static void +vhci_efo_done(void *arg) +{ + scsi_vhci_lun_t *vlun; + scsi_vhci_swarg_t *swarg = (scsi_vhci_swarg_t *)arg; + scsi_vhci_priv_t *svp = swarg->svs_svp; + ASSERT(svp); + + vlun = svp->svp_svl; + ASSERT(vlun); + + /* Wait for clean termination of scsi_watch */ + (void) scsi_watch_request_terminate(svp->svp_sw_token, + SCSI_WATCH_TERMINATE_WAIT); + svp->svp_sw_token = NULL; + + /* release path and freeup resources to indicate failover completion */ + mdi_rele_path(swarg->svs_pi); + if (swarg->svs_release_lun) { + VHCI_RELEASE_LUN(vlun); + } + kmem_free((void *)swarg, sizeof (*swarg)); +} + +/* + * Update the path states + * vlun should be HELD when this is invoked. + * Calls vhci_efo_done to cleanup resources allocated for EFO. + */ +void +vhci_update_pathstates(void *arg) +{ + mdi_pathinfo_t *pip, *npip; + dev_info_t *dip, *pdip; + struct scsi_failover_ops *fo; + struct scsi_vhci_priv *svp; + struct scsi_device *psd; + struct scsi_path_opinfo opinfo; + char *pclass, *tptr; + struct scsi_vhci_lun *vlun = (struct scsi_vhci_lun *)arg; + int sps; /* mdi_select_path() status */ + char *cpath, *dpath; + struct scsi_vhci *vhci; + struct scsi_pkt *pkt; + struct buf *bp; + int reserve_conflict = 0; + + ASSERT(VHCI_LUN_IS_HELD(vlun)); + dip = vlun->svl_dip; + pip = npip = NULL; + + vhci = ddi_get_soft_state(vhci_softstate, + ddi_get_instance(ddi_get_parent(dip))); + + sps = mdi_select_path(dip, NULL, (MDI_SELECT_ONLINE_PATH | + MDI_SELECT_STANDBY_PATH), NULL, &npip); + if ((npip == NULL) || (sps != MDI_SUCCESS)) { + goto done; + } + + fo = vlun->svl_fops; + do { + pip = npip; + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(pip); + psd = svp->svp_psd; + if ((*fo->sfo_path_get_opinfo)(psd, &opinfo, + vlun->svl_fops_ctpriv) != 0) { + sps = mdi_select_path(dip, NULL, + (MDI_SELECT_ONLINE_PATH | MDI_SELECT_STANDBY_PATH), + pip, &npip); + mdi_rele_path(pip); + continue; + } + + if (mdi_prop_lookup_string(pip, "path-class", &pclass) != + MDI_SUCCESS) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_update_pathstates: prop lookup failed for " + "path 0x%p\n", (void *)pip)); + sps = mdi_select_path(dip, NULL, + (MDI_SELECT_ONLINE_PATH | MDI_SELECT_STANDBY_PATH), + pip, &npip); + mdi_rele_path(pip); + continue; + } + + /* + * Need to update the "path-class" property + * value in the device tree if different + * from the existing value. + */ + if (strcmp(pclass, opinfo.opinfo_path_attr) != 0) { + (void) mdi_prop_update_string(pip, "path-class", + opinfo.opinfo_path_attr); + } + + /* + * Only change the state if needed. i.e. Don't call + * mdi_pi_set_state to ONLINE a path if its already + * ONLINE. Same for STANDBY paths. + */ + + if ((opinfo.opinfo_path_state == SCSI_PATH_ACTIVE || + opinfo.opinfo_path_state == SCSI_PATH_ACTIVE_NONOPT)) { + if (!(MDI_PI_IS_ONLINE(pip))) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_update_pathstates: marking path" + " 0x%p as ONLINE\n", (void *)pip)); + pdip = mdi_pi_get_phci(pip); + cpath = kmem_alloc(MAXPATHLEN, KM_SLEEP); + dpath = kmem_alloc(MAXPATHLEN, KM_SLEEP); + vhci_log(CE_NOTE, ddi_get_parent(dip), "!%s" + " (%s%d): path %s (%s%d) target address %s" + " is now ONLINE because of" + " an externally initiated failover", + ddi_pathname(dip, cpath), + ddi_driver_name(dip), + ddi_get_instance(dip), + ddi_pathname(pdip, dpath), + ddi_driver_name(pdip), + ddi_get_instance(pdip), + mdi_pi_get_addr(pip)); + kmem_free(cpath, MAXPATHLEN); + kmem_free(dpath, MAXPATHLEN); + mdi_pi_set_state(pip, + MDI_PATHINFO_STATE_ONLINE); + mdi_pi_set_preferred(pip, + opinfo.opinfo_preferred); + tptr = kmem_alloc(strlen + (opinfo.opinfo_path_attr)+1, KM_SLEEP); + (void) strlcpy(tptr, opinfo.opinfo_path_attr, + (strlen(opinfo.opinfo_path_attr)+1)); + mutex_enter(&vlun->svl_mutex); + if (vlun->svl_active_pclass != NULL) { + kmem_free(vlun->svl_active_pclass, + strlen(vlun->svl_active_pclass)+1); + } + vlun->svl_active_pclass = tptr; + if (vlun->svl_waiting_for_activepath) { + vlun->svl_waiting_for_activepath = 0; + } + mutex_exit(&vlun->svl_mutex); + /* Check for Reservation Conflict */ + bp = scsi_alloc_consistent_buf( + &svp->svp_psd->sd_address, + (struct buf *)NULL, DEV_BSIZE, B_READ, + NULL, NULL); + if (!bp) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "vhci_update_pathstates: " + "!No resources (buf)\n")); + mdi_rele_path(pip); + goto done; + } + pkt = scsi_init_pkt(&svp->svp_psd->sd_address, + NULL, bp, CDB_GROUP1, + sizeof (struct scsi_arq_status), 0, + PKT_CONSISTENT, NULL, NULL); + if (pkt) { + (void) scsi_setup_cdb((union scsi_cdb *) + (uintptr_t)pkt->pkt_cdbp, + SCMD_READ, 1, 1, 0); + pkt->pkt_time = 3*30; + pkt->pkt_flags = FLAG_NOINTR; + if ((scsi_transport(pkt) == + TRAN_ACCEPT) && (pkt->pkt_reason + == CMD_CMPLT) && (SCBP_C(pkt) == + STATUS_RESERVATION_CONFLICT)) { + reserve_conflict = 1; + } + scsi_destroy_pkt(pkt); + } + scsi_free_consistent_buf(bp); + } else if (MDI_PI_IS_ONLINE(pip)) { + if (strcmp(pclass, opinfo.opinfo_path_attr) + != 0) { + mdi_pi_set_preferred(pip, + opinfo.opinfo_preferred); + mutex_enter(&vlun->svl_mutex); + if (vlun->svl_active_pclass == NULL || + strcmp(opinfo.opinfo_path_attr, + vlun->svl_active_pclass) != 0) { + mutex_exit(&vlun->svl_mutex); + tptr = kmem_alloc(strlen + (opinfo.opinfo_path_attr)+1, + KM_SLEEP); + (void) strlcpy(tptr, + opinfo.opinfo_path_attr, + (strlen + (opinfo.opinfo_path_attr) + +1)); + mutex_enter(&vlun->svl_mutex); + } else { + /* + * No need to update + * svl_active_pclass + */ + tptr = NULL; + mutex_exit(&vlun->svl_mutex); + } + if (tptr) { + if (vlun->svl_active_pclass + != NULL) { + kmem_free(vlun-> + svl_active_pclass, + strlen(vlun-> + svl_active_pclass) + +1); + } + vlun->svl_active_pclass = tptr; + mutex_exit(&vlun->svl_mutex); + } + } + } + } else if ((opinfo.opinfo_path_state == SCSI_PATH_INACTIVE) && + !(MDI_PI_IS_STANDBY(pip))) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_update_pathstates: marking path" + " 0x%p as STANDBY\n", (void *)pip)); + pdip = mdi_pi_get_phci(pip); + cpath = kmem_alloc(MAXPATHLEN, KM_SLEEP); + dpath = kmem_alloc(MAXPATHLEN, KM_SLEEP); + vhci_log(CE_NOTE, ddi_get_parent(dip), "!%s" + " (%s%d): path %s (%s%d) target address %s" + " is now STANDBY because of" + " an externally initiated failover", + ddi_pathname(dip, cpath), + ddi_driver_name(dip), + ddi_get_instance(dip), + ddi_pathname(pdip, dpath), + ddi_driver_name(pdip), + ddi_get_instance(pdip), + mdi_pi_get_addr(pip)); + kmem_free(cpath, MAXPATHLEN); + kmem_free(dpath, MAXPATHLEN); + mdi_pi_set_state(pip, + MDI_PATHINFO_STATE_STANDBY); + mdi_pi_set_preferred(pip, + opinfo.opinfo_preferred); + mutex_enter(&vlun->svl_mutex); + if (vlun->svl_active_pclass != NULL) { + if (strcmp(vlun->svl_active_pclass, + opinfo.opinfo_path_attr) == 0) { + kmem_free(vlun-> + svl_active_pclass, + strlen(vlun-> + svl_active_pclass)+1); + vlun->svl_active_pclass = NULL; + } + } + mutex_exit(&vlun->svl_mutex); + } + (void) mdi_prop_free(pclass); + sps = mdi_select_path(dip, NULL, + (MDI_SELECT_ONLINE_PATH | MDI_SELECT_STANDBY_PATH), + pip, &npip); + mdi_rele_path(pip); + + } while ((npip != NULL) && (sps == MDI_SUCCESS)); + + /* + * Check to see if this vlun has an active SCSI-II RESERVE. If so + * clear the reservation by sending a reset, so the host doesn't + * receive a reservation conflict. + * Reset VLUN_RESERVE_ACTIVE_FLG for this vlun. Also notify ssd + * of the reset, explicitly. + */ + if (vlun->svl_flags & VLUN_RESERVE_ACTIVE_FLG) { + if (reserve_conflict && (vlun->svl_xlf_capable == 0)) { + (void) vhci_recovery_reset(vlun, + &svp->svp_psd->sd_address, FALSE, + VHCI_DEPTH_TARGET); + } + vlun->svl_flags &= ~VLUN_RESERVE_ACTIVE_FLG; + mutex_enter(&vhci->vhci_mutex); + scsi_hba_reset_notify_callback(&vhci->vhci_mutex, + &vhci->vhci_reset_notify_listf); + mutex_exit(&vhci->vhci_mutex); + } + if (vlun->svl_flags & VLUN_UPDATE_TPG) { + /* + * Update the AccessState of related MP-API TPGs + */ + (void) vhci_mpapi_update_tpg_acc_state_for_lu(vhci, vlun); + vlun->svl_flags &= ~VLUN_UPDATE_TPG; + } +done: + if (vlun->svl_efo_update_path) { + vlun->svl_efo_update_path = 0; + vhci_efo_done(vlun->svl_swarg); + vlun->svl_swarg = 0; + } + VHCI_RELEASE_LUN(vlun); +} + +/* ARGSUSED */ +static int +vhci_pathinfo_init(dev_info_t *vdip, mdi_pathinfo_t *pip, int flags) +{ + scsi_hba_tran_t *hba = NULL; + struct scsi_device *psd = NULL; + scsi_vhci_lun_t *vlun = NULL; + dev_info_t *pdip = NULL; + dev_info_t *tgt_dip; + struct scsi_vhci *vhci; + char *guid; + scsi_vhci_priv_t *svp = NULL; + int rval = MDI_FAILURE; + int vlun_alloced = 0; + + ASSERT(vdip != NULL); + ASSERT(pip != NULL); + + vhci = ddi_get_soft_state(vhci_softstate, ddi_get_instance(vdip)); + ASSERT(vhci != NULL); + + pdip = mdi_pi_get_phci(pip); + ASSERT(pdip != NULL); + + hba = ddi_get_driver_private(pdip); + ASSERT(hba != NULL); + + tgt_dip = mdi_pi_get_client(pip); + ASSERT(tgt_dip != NULL); + + if (ddi_prop_lookup_string(DDI_DEV_T_ANY, tgt_dip, PROPFLAGS, + MDI_CLIENT_GUID_PROP, &guid) != DDI_SUCCESS) { + VHCI_DEBUG(1, (CE_WARN, NULL, + "vhci_pathinfo_init: lun guid property failed")); + goto failure; + } + + vlun = vhci_lun_lookup_alloc(tgt_dip, guid, &vlun_alloced); + ddi_prop_free(guid); + + vlun->svl_dip = tgt_dip; + + svp = kmem_zalloc(sizeof (*svp), KM_SLEEP); + svp->svp_svl = vlun; + + vlun->svl_lb_policy_save = mdi_get_lb_policy(tgt_dip); + mutex_init(&svp->svp_mutex, NULL, MUTEX_DRIVER, NULL); + cv_init(&svp->svp_cv, NULL, CV_DRIVER, NULL); + + psd = kmem_zalloc(sizeof (*psd), KM_SLEEP); + mutex_init(&psd->sd_mutex, NULL, MUTEX_DRIVER, NULL); + + /* + * Clone transport structure if requested, so + * Self enumerating HBAs always need to use cloning + */ + + if (hba->tran_hba_flags & SCSI_HBA_TRAN_CLONE) { + scsi_hba_tran_t *clone = + kmem_alloc(sizeof (scsi_hba_tran_t), KM_SLEEP); + bcopy(hba, clone, sizeof (scsi_hba_tran_t)); + hba = clone; + hba->tran_sd = psd; + } else { + ASSERT(hba->tran_sd == NULL); + } + psd->sd_dev = tgt_dip; + psd->sd_address.a_hba_tran = hba; + psd->sd_private = (caddr_t)pip; + svp->svp_psd = psd; + mdi_pi_set_vhci_private(pip, (caddr_t)svp); + + /* + * call hba's target init entry point if it exists + */ + if (hba->tran_tgt_init != NULL) { + if ((rval = (*hba->tran_tgt_init)(pdip, tgt_dip, + hba, psd)) != DDI_SUCCESS) { + VHCI_DEBUG(1, (CE_WARN, pdip, + "!vhci_pathinfo_init: tran_tgt_init failed for " + "path=0x%p rval=%x", (void *)pip, rval)); + goto failure; + } + } + + svp->svp_new_path = 1; + + psd->sd_inq = NULL; + + VHCI_DEBUG(4, (CE_NOTE, NULL, "!vhci_pathinfo_init: path:%p\n", + (void *)pip)); + return (MDI_SUCCESS); + +failure: + if (psd) { + mutex_destroy(&psd->sd_mutex); + kmem_free(psd, sizeof (*psd)); + } + if (svp) { + mdi_pi_set_vhci_private(pip, NULL); + mutex_destroy(&svp->svp_mutex); + cv_destroy(&svp->svp_cv); + kmem_free(svp, sizeof (*svp)); + } + if (hba && hba->tran_hba_flags & SCSI_HBA_TRAN_CLONE) + kmem_free(hba, sizeof (scsi_hba_tran_t)); + + if (vlun_alloced) + vhci_lun_free(tgt_dip); + + return (rval); +} + +/* ARGSUSED */ +static int +vhci_pathinfo_uninit(dev_info_t *vdip, mdi_pathinfo_t *pip, int flags) +{ + scsi_hba_tran_t *hba = NULL; + struct scsi_device *psd = NULL; + dev_info_t *pdip = NULL; + dev_info_t *cdip = NULL; + scsi_vhci_priv_t *svp = NULL; + + ASSERT(vdip != NULL); + ASSERT(pip != NULL); + + pdip = mdi_pi_get_phci(pip); + ASSERT(pdip != NULL); + + cdip = mdi_pi_get_client(pip); + ASSERT(cdip != NULL); + + hba = ddi_get_driver_private(pdip); + ASSERT(hba != NULL); + + vhci_mpapi_set_path_state(vdip, pip, MP_DRVR_PATH_STATE_REMOVED); + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(pip); + if (svp == NULL) { + /* path already freed. Nothing to do. */ + return (MDI_SUCCESS); + } + + psd = svp->svp_psd; + ASSERT(psd != NULL); + + if (hba->tran_hba_flags & SCSI_HBA_TRAN_CLONE) { + hba = psd->sd_address.a_hba_tran; + ASSERT(hba->tran_hba_flags & SCSI_HBA_TRAN_CLONE); + ASSERT(hba->tran_sd == psd); + } else { + ASSERT(hba->tran_sd == NULL); + } + + if (hba->tran_tgt_free != NULL) { + (*hba->tran_tgt_free) (pdip, cdip, hba, psd); + } + mutex_destroy(&psd->sd_mutex); + if (hba->tran_hba_flags & SCSI_HBA_TRAN_CLONE) { + kmem_free(hba, sizeof (*hba)); + } + + mdi_pi_set_vhci_private(pip, NULL); + kmem_free((caddr_t)psd, sizeof (*psd)); + + mutex_destroy(&svp->svp_mutex); + cv_destroy(&svp->svp_cv); + kmem_free((caddr_t)svp, sizeof (*svp)); + + /* + * If this is the last path to the client, + * then free up the vlun as well. + */ + if (mdi_client_get_path_count(cdip) == 1) { + vhci_lun_free(cdip); + } + + VHCI_DEBUG(4, (CE_NOTE, NULL, "!vhci_pathinfo_uninit: path=0x%p\n", + (void *)pip)); + return (MDI_SUCCESS); +} + +/* ARGSUSED */ +static int +vhci_pathinfo_state_change(dev_info_t *vdip, mdi_pathinfo_t *pip, + mdi_pathinfo_state_t state, uint32_t ext_state, int flags) +{ + int rval = MDI_SUCCESS; + scsi_vhci_priv_t *svp; + scsi_vhci_lun_t *vlun; + int held; + int op = (flags & 0xf00) >> 8; + struct scsi_vhci *vhci; + + vhci = ddi_get_soft_state(vhci_softstate, ddi_get_instance(vdip)); + + if (flags & MDI_EXT_STATE_CHANGE) { + /* + * We do not want to issue any commands down the path in case + * sync flag is set. Lower layers might not be ready to accept + * any I/O commands. + */ + if (op == DRIVER_DISABLE) + return (MDI_SUCCESS); + + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(pip); + if (svp == NULL) { + return (MDI_FAILURE); + } + vlun = svp->svp_svl; + + if (flags & MDI_BEFORE_STATE_CHANGE) { + /* + * Hold the LUN. + */ + VHCI_HOLD_LUN(vlun, VH_SLEEP, held); + if (flags & MDI_DISABLE_OP) { + /* + * Issue scsi reset if it happens to be + * reserved path. + */ + if (vlun->svl_flags & VLUN_RESERVE_ACTIVE_FLG) { + /* + * if reservation pending on + * this path, dont' mark the + * path busy + */ + if (op == DRIVER_DISABLE_TRANSIENT) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_pathinfo" + "_state_change (pip:%p): " + " reservation: fail busy\n", + (void *)pip)); + return (MDI_FAILURE); + } + if (pip == vlun->svl_resrv_pip) { + if (vhci_recovery_reset( + svp->svp_svl, + &svp->svp_psd->sd_address, + TRUE, + VHCI_DEPTH_TARGET) == 0) { + VHCI_DEBUG(1, + (CE_NOTE, NULL, + "!vhci_pathinfo" + "_state_change " + " (pip:%p): " + "reset failed, " + "give up!\n", + (void *)pip)); + } + vlun->svl_flags &= + ~VLUN_RESERVE_ACTIVE_FLG; + } + } + } else if (flags & MDI_ENABLE_OP) { + if (((vhci->vhci_conf_flags & + VHCI_CONF_FLAGS_AUTO_FAILBACK) == + VHCI_CONF_FLAGS_AUTO_FAILBACK) && + MDI_PI_IS_USER_DISABLE(pip) && + MDI_PI_IS_STANDBY(pip)) { + struct scsi_failover_ops *fo; + char *best_pclass, *pclass = NULL; + int best_class, rv; + /* + * Failback if enabling a standby path + * and it is the primary class or + * preferred class + */ + best_class = mdi_pi_get_preferred(pip); + if (best_class == 0) { + /* + * if not preferred - compare + * path-class with class + */ + fo = vlun->svl_fops; + (*fo->sfo_pathclass_next)(NULL, + &best_pclass, + vlun->svl_fops_ctpriv); + pclass = NULL; + rv = mdi_prop_lookup_string(pip, + "path-class", &pclass); + if (rv != MDI_SUCCESS || + pclass == NULL) { + vhci_log(CE_NOTE, vdip, + "!path-class " + " lookup " + "failed. rv: %d" + "class: %p", rv, + (void *)pclass); + } else if (strncmp(pclass, + best_pclass, + strlen(best_pclass)) == 0) { + best_class = 1; + } + if (rv == MDI_SUCCESS && + pclass != NULL) { + rv = mdi_prop_free( + pclass); + if (rv != + DDI_PROP_SUCCESS) { + vhci_log( + CE_NOTE, + vdip, + "!path-" + "class" + " free" + " failed" + " rv: %d" + " class: " + "%p", + rv, + (void *) + pclass); + } + } + } + if (best_class == 1) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "preferred path: %p " + "USER_DISABLE->USER_ENABLE " + "transition for lun %s\n", + (void *)pip, + vlun->svl_lun_wwn)); + (void) taskq_dispatch( + vhci->vhci_taskq, + vhci_initiate_auto_failback, + (void *) vlun, KM_SLEEP); + } + } + /* + * if PGR is active, revalidate key and + * register on this path also, if key is + * still valid + */ + sema_p(&vlun->svl_pgr_sema); + if (vlun->svl_pgr_active) + (void) + vhci_pgr_validate_and_register(svp); + sema_v(&vlun->svl_pgr_sema); + /* + * Inform target driver about any + * reservations to be reinstated if target + * has dropped reservation during the busy + * period. + */ + mutex_enter(&vhci->vhci_mutex); + scsi_hba_reset_notify_callback( + &vhci->vhci_mutex, + &vhci->vhci_reset_notify_listf); + mutex_exit(&vhci->vhci_mutex); + } + } + if (flags & MDI_AFTER_STATE_CHANGE) { + if (flags & MDI_ENABLE_OP) { + mutex_enter(&vhci_global_mutex); + cv_broadcast(&vhci_cv); + mutex_exit(&vhci_global_mutex); + } + if (vlun->svl_setcap_done) { + (void) vhci_pHCI_cap(&svp->svp_psd->sd_address, + "sector-size", vlun->svl_sector_size, + 1, pip); + } + + /* + * Release the LUN + */ + VHCI_RELEASE_LUN(vlun); + + /* + * Path transition is complete. + * Run callback to indicate target driver to + * retry to prevent IO starvation. + */ + if (scsi_callback_id != 0) { + ddi_run_callback(&scsi_callback_id); + } + } + } else { + switch (state) { + case MDI_PATHINFO_STATE_ONLINE: + rval = vhci_pathinfo_online(vdip, pip, flags); + break; + + case MDI_PATHINFO_STATE_OFFLINE: + rval = vhci_pathinfo_offline(vdip, pip, flags); + break; + + default: + break; + } + /* + * Path transition is complete. + * Run callback to indicate target driver to + * retry to prevent IO starvation. + */ + if ((rval == MDI_SUCCESS) && (scsi_callback_id != 0)) { + ddi_run_callback(&scsi_callback_id); + } + return (rval); + } + + return (MDI_SUCCESS); +} + +/* + * Parse the mpxio load balancing options. The datanameptr + * will point to a string containing the load-balance-options value. + * The load-balance-options value will be a property that + * defines the load-balance algorithm and any arguments to that + * algorithm. + * For example: + * device-type-mpxio-options-list= + * "device-type=SUN SENA", "load-balance-options=logical-block-options" + * "device-type=SUN SE6920", "round-robin-options"; + * logical-block-options="load-balance=logical-block", "region-size=15"; + * round-robin-options="load-balance=round-robin"; + * + * If the load-balance is not defined the load balance algorithm will + * default to the global setting. There will be default values assigned + * to the arguments (region-size=18) and if an argument is one + * that is not known, it will be ignored. + */ +static void +vhci_parse_mpxio_lb_options(dev_info_t *dip, dev_info_t *cdip, + caddr_t datanameptr) +{ + char *dataptr, *next_entry; + caddr_t config_list = NULL; + int config_list_len = 0, list_len = 0; + int region_size = -1; + client_lb_t load_balance; + + if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, datanameptr, + (caddr_t)&config_list, &config_list_len) != DDI_PROP_SUCCESS) { + return; + } + + list_len = config_list_len; + next_entry = config_list; + while (config_list_len > 0) { + dataptr = next_entry; + + if (strncmp(mdi_load_balance, dataptr, + strlen(mdi_load_balance)) == 0) { + /* get the load-balance scheme */ + dataptr += strlen(mdi_load_balance) + 1; + if (strcmp(dataptr, LOAD_BALANCE_PROP_RR) == 0) { + (void) mdi_set_lb_policy(cdip, LOAD_BALANCE_RR); + load_balance = LOAD_BALANCE_RR; + } else if (strcmp(dataptr, + LOAD_BALANCE_PROP_LBA) == 0) { + (void) mdi_set_lb_policy(cdip, + LOAD_BALANCE_LBA); + load_balance = LOAD_BALANCE_LBA; + } else if (strcmp(dataptr, + LOAD_BALANCE_PROP_NONE) == 0) { + (void) mdi_set_lb_policy(cdip, + LOAD_BALANCE_NONE); + load_balance = LOAD_BALANCE_NONE; + } + } else if (strncmp(dataptr, LOGICAL_BLOCK_REGION_SIZE, + strlen(LOGICAL_BLOCK_REGION_SIZE)) == 0) { + int i = 0; + char *ptr; + char *tmp; + + tmp = dataptr + (strlen(LOGICAL_BLOCK_REGION_SIZE) + 1); + /* check for numeric value */ + for (ptr = tmp; i < strlen(tmp); i++, ptr++) { + if (!isdigit(*ptr)) { + cmn_err(CE_WARN, + "Illegal region size: %s." + " Setting to default value: %d", + tmp, + LOAD_BALANCE_DEFAULT_REGION_SIZE); + region_size = + LOAD_BALANCE_DEFAULT_REGION_SIZE; + break; + } + } + if (i >= strlen(tmp)) { + region_size = stoi(&tmp); + } + (void) mdi_set_lb_region_size(cdip, region_size); + } + config_list_len -= (strlen(next_entry) + 1); + next_entry += strlen(next_entry) + 1; + } +#ifdef DEBUG + if ((region_size >= 0) && (load_balance != LOAD_BALANCE_LBA)) { + VHCI_DEBUG(1, (CE_NOTE, dip, + "!vhci_parse_mpxio_lb_options: region-size: %d" + "only valid for load-balance=logical-block\n", + region_size)); + } +#endif + if ((region_size == -1) && (load_balance == LOAD_BALANCE_LBA)) { + VHCI_DEBUG(1, (CE_NOTE, dip, + "!vhci_parse_mpxio_lb_options: No region-size" + " defined load-balance=logical-block." + " Default to: %d\n", LOAD_BALANCE_DEFAULT_REGION_SIZE)); + (void) mdi_set_lb_region_size(cdip, + LOAD_BALANCE_DEFAULT_REGION_SIZE); + } + if (list_len > 0) { + kmem_free(config_list, list_len); + } +} + +/* + * Parse the device-type-mpxio-options-list looking for the key of + * "load-balance-options". If found, parse the load balancing options. + * Check the comment of the vhci_get_device_type_mpxio_options() + * for the device-type-mpxio-options-list. + */ +static void +vhci_parse_mpxio_options(dev_info_t *dip, dev_info_t *cdip, + caddr_t datanameptr, int list_len) +{ + char *dataptr; + int len; + + /* + * get the data list + */ + dataptr = datanameptr; + len = 0; + while (len < list_len && + strncmp(dataptr, DEVICE_TYPE_STR, strlen(DEVICE_TYPE_STR)) + != 0) { + if (strncmp(dataptr, LOAD_BALANCE_OPTIONS, + strlen(LOAD_BALANCE_OPTIONS)) == 0) { + len += strlen(LOAD_BALANCE_OPTIONS) + 1; + dataptr += strlen(LOAD_BALANCE_OPTIONS) + 1; + vhci_parse_mpxio_lb_options(dip, cdip, dataptr); + } + len += strlen(dataptr) + 1; + dataptr += strlen(dataptr) + 1; + } +} + +/* + * Check the inquriy string returned from the device wiith the device-type + * Check for the existence of the device-type-mpxio-options-list and + * if found parse the list checking for a match with the device-type + * value and the inquiry string returned from the device. If a match + * is found, parse the mpxio options list. The format of the + * device-type-mpxio-options-list is: + * device-type-mpxio-options-list= + * "device-type=SUN SENA", "load-balance-options=logical-block-options" + * "device-type=SUN SE6920", "round-robin-options"; + * logical-block-options="load-balance=logical-block", "region-size=15"; + * round-robin-options="load-balance=round-robin"; + */ +void +vhci_get_device_type_mpxio_options(dev_info_t *dip, dev_info_t *cdip, + struct scsi_device *devp) +{ + + caddr_t config_list = NULL; + caddr_t vidptr, datanameptr; + int vidlen, dupletlen = 0; + int config_list_len = 0, len; + struct scsi_inquiry *inq = devp->sd_inq; + + /* + * look up the device-type-mpxio-options-list and walk thru + * the list compare the vendor ids of the earlier inquiry command and + * with those vids in the list if there is a match, lookup + * the mpxio-options value + */ + if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, + MPXIO_OPTIONS_LIST, + (caddr_t)&config_list, &config_list_len) == DDI_PROP_SUCCESS) { + + /* + * Compare vids in each duplet - if it matches, + * parse the mpxio options list. + */ + for (len = config_list_len, vidptr = config_list; len > 0; + len -= dupletlen) { + + dupletlen = 0; + + if (strlen(vidptr) != 0 && + strncmp(vidptr, DEVICE_TYPE_STR, + strlen(DEVICE_TYPE_STR)) == 0) { + /* point to next duplet */ + datanameptr = vidptr + strlen(vidptr) + 1; + /* add len of this duplet */ + dupletlen += strlen(vidptr) + 1; + /* get to device type */ + vidptr += strlen(DEVICE_TYPE_STR) + 1; + vidlen = strlen(vidptr); + if ((vidlen != 0) && + bcmp(inq->inq_vid, vidptr, vidlen) == 0) { + vhci_parse_mpxio_options(dip, cdip, + datanameptr, len - dupletlen); + break; + } + /* get to next duplet */ + vidptr += strlen(vidptr) + 1; + } + /* get to the next device-type */ + while (len - dupletlen > 0 && + strlen(vidptr) != 0 && + strncmp(vidptr, DEVICE_TYPE_STR, + strlen(DEVICE_TYPE_STR)) != 0) { + dupletlen += strlen(vidptr) + 1; + vidptr += strlen(vidptr) + 1; + } + } + if (config_list_len > 0) { + kmem_free(config_list, config_list_len); + } + } +} + +static int +vhci_update_pathinfo(struct scsi_device *psd, mdi_pathinfo_t *pip, + struct scsi_failover_ops *fo, + scsi_vhci_lun_t *vlun, + struct scsi_vhci *vhci) +{ + struct scsi_path_opinfo opinfo; + char *pclass, *best_pclass; + + if ((*fo->sfo_path_get_opinfo)(psd, &opinfo, + vlun->svl_fops_ctpriv) != 0) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_update_pathinfo: " + "Failed to get operation info for path:%p\n", (void *)pip)); + return (MDI_FAILURE); + } + /* set the xlf capable flag in the vlun for future use */ + vlun->svl_xlf_capable = opinfo.opinfo_xlf_capable; + (void) mdi_prop_update_string(pip, "path-class", + opinfo.opinfo_path_attr); + + pclass = opinfo.opinfo_path_attr; + if (opinfo.opinfo_path_state == SCSI_PATH_ACTIVE) { + mutex_enter(&vlun->svl_mutex); + if (vlun->svl_active_pclass != NULL) { + if (strcmp(vlun->svl_active_pclass, pclass) != 0) { + mutex_exit(&vlun->svl_mutex); + /* + * Externally initiated failover has happened; + * force the path state to be STANDBY/ONLINE, + * next IO will trigger failover and thus + * sync-up the pathstates. Reason we don't + * sync-up immediately by invoking + * vhci_update_pathstates() is because it + * needs a VHCI_HOLD_LUN() and we don't + * want to block here. + * + * Further, if the device is an ALUA device, + * then failure to exactly match 'pclass' and + * 'svl_active_pclass'(as is the case here) + * indicates that the currently active path + * is a 'non-optimized' path - which means + * that 'svl_active_pclass' needs to be + * replaced with opinfo.opinfo_path_state + * value. + */ + + if (SCSI_FAILOVER_IS_TPGS(vlun->svl_fops)) { + char *tptr; + + /* + * The device is ALUA compliant. The + * state need to be changed to online + * rather than standby state which is + * done typically for a asymmetric + * device that is non ALUA compliant. + */ + mdi_pi_set_state(pip, + MDI_PATHINFO_STATE_ONLINE); + tptr = kmem_alloc(strlen + (opinfo.opinfo_path_attr)+1, + KM_SLEEP); + (void) strlcpy(tptr, + opinfo.opinfo_path_attr, + (strlen(opinfo.opinfo_path_attr) + +1)); + mutex_enter(&vlun->svl_mutex); + kmem_free(vlun->svl_active_pclass, + strlen(vlun->svl_active_pclass)+1); + vlun->svl_active_pclass = tptr; + mutex_exit(&vlun->svl_mutex); + } else { + /* + * Non ALUA device case. + */ + mdi_pi_set_state(pip, + MDI_PATHINFO_STATE_STANDBY); + } + vlun->svl_fo_support = opinfo.opinfo_mode; + mdi_pi_set_preferred(pip, + opinfo.opinfo_preferred); + return (MDI_SUCCESS); + } + } else { + char *tptr; + + /* + * lets release the mutex before we try to + * allocate since the potential to sleep is + * possible. + */ + mutex_exit(&vlun->svl_mutex); + tptr = kmem_alloc(strlen(pclass)+1, KM_SLEEP); + (void) strlcpy(tptr, pclass, (strlen(pclass)+1)); + mutex_enter(&vlun->svl_mutex); + vlun->svl_active_pclass = tptr; + } + mutex_exit(&vlun->svl_mutex); + mdi_pi_set_state(pip, MDI_PATHINFO_STATE_ONLINE); + vlun->svl_waiting_for_activepath = 0; + } else if (opinfo.opinfo_path_state == SCSI_PATH_ACTIVE_NONOPT) { + mutex_enter(&vlun->svl_mutex); + if (vlun->svl_active_pclass == NULL) { + char *tptr; + + mutex_exit(&vlun->svl_mutex); + tptr = kmem_alloc(strlen(pclass)+1, KM_SLEEP); + (void) strlcpy(tptr, pclass, (strlen(pclass)+1)); + mutex_enter(&vlun->svl_mutex); + vlun->svl_active_pclass = tptr; + } + mutex_exit(&vlun->svl_mutex); + mdi_pi_set_state(pip, MDI_PATHINFO_STATE_ONLINE); + vlun->svl_waiting_for_activepath = 0; + } else if (opinfo.opinfo_path_state == SCSI_PATH_INACTIVE) { + mutex_enter(&vlun->svl_mutex); + if (vlun->svl_active_pclass != NULL) { + if (strcmp(vlun->svl_active_pclass, pclass) == 0) { + mutex_exit(&vlun->svl_mutex); + /* + * externally initiated failover has happened; + * force state to ONLINE (see comment above) + */ + mdi_pi_set_state(pip, + MDI_PATHINFO_STATE_ONLINE); + vlun->svl_fo_support = opinfo.opinfo_mode; + mdi_pi_set_preferred(pip, + opinfo.opinfo_preferred); + return (MDI_SUCCESS); + } + } + mutex_exit(&vlun->svl_mutex); + mdi_pi_set_state(pip, MDI_PATHINFO_STATE_STANDBY); + + /* + * Initiate auto-failback, if enabled, for path if path-state + * is transitioning from OFFLINE->STANDBY and pathclass is the + * prefered pathclass for this storage. + * NOTE: In case where opinfo_path_state is SCSI_PATH_ACTIVE + * (above), where the pi state is set to STANDBY, we don't + * initiate auto-failback as the next IO shall take care of. + * this. See comment above. + */ + (*fo->sfo_pathclass_next)(NULL, &best_pclass, + vlun->svl_fops_ctpriv); + if (((vhci->vhci_conf_flags & VHCI_CONF_FLAGS_AUTO_FAILBACK) == + VHCI_CONF_FLAGS_AUTO_FAILBACK) && + ((strcmp(pclass, best_pclass) == 0) || + mdi_pi_get_preferred(pip) == 1) && + ((MDI_PI_OLD_STATE(pip) == MDI_PATHINFO_STATE_OFFLINE)|| + (MDI_PI_OLD_STATE(pip) == MDI_PATHINFO_STATE_INIT))) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "%s pathclass path: %p" + " OFFLINE->STANDBY transition for lun %s\n", + best_pclass, (void *)pip, vlun->svl_lun_wwn)); + (void) taskq_dispatch(vhci->vhci_taskq, + vhci_initiate_auto_failback, (void *) vlun, + KM_SLEEP); + } + } + vlun->svl_fo_support = opinfo.opinfo_mode; + mdi_pi_set_preferred(pip, opinfo.opinfo_preferred); + + VHCI_DEBUG(8, (CE_NOTE, NULL, "vhci_update_pathinfo: opinfo_rev = %x," + " opinfo_path_state = %x opinfo_preferred = %x, opinfo_mode = %x\n", + opinfo.opinfo_rev, opinfo.opinfo_path_state, + opinfo.opinfo_preferred, opinfo.opinfo_mode)); + + return (MDI_SUCCESS); +} + +/* + * Form the kstat name and and call mdi_pi_kstat_create() + */ +void +vhci_kstat_create_pathinfo(mdi_pathinfo_t *pip) +{ + dev_info_t *tgt_dip; + dev_info_t *pdip; + char *guid; + char *target_port, *target_port_dup; + char ks_name[KSTAT_STRLEN]; + uint_t pid; + int by_id; + mod_hash_val_t hv; + + + /* return if we have already allocated kstats */ + if (mdi_pi_kstat_exists(pip)) + return; + + /* + * We need instance numbers to create a kstat name, return if we don't + * have instance numbers assigned yet. + */ + tgt_dip = mdi_pi_get_client(pip); + pdip = mdi_pi_get_phci(pip); + if ((ddi_get_instance(tgt_dip) == -1) || (ddi_get_instance(pdip) == -1)) + return; + + /* + * A path oriented kstat has a ks_name of the form: + * + * <client-driver><instance>.t<pid>.<pHCI-driver><instance> + * + * We maintain a bidirectional 'target-port' to <pid> map, + * called targetmap. All pathinfo nodes with the same + * 'target-port' map to the same <pid>. The iostat(1M) code, + * when parsing a path oriented kstat name, uses the <pid> as + * a SCSI_VHCI_GET_TARGET_LONGNAME ioctl argument in order + * to get the 'target-port'. For KSTAT_FLAG_PERSISTENT kstats, + * this ioctl needs to translate a <pid> to a 'target-port' + * even after all pathinfo nodes associated with the + * 'target-port' have been destroyed. This is needed to support + * consistent first-iteration activity-since-boot iostat(1M) + * output. Because of this requirement, the mapping can't be + * based on pathinfo information in a devinfo snapshot. + */ + + /* determine 'target-port' */ + if (mdi_prop_lookup_string(pip, + "target-port", &target_port) == MDI_SUCCESS) { + target_port_dup = i_ddi_strdup(target_port, KM_SLEEP); + (void) mdi_prop_free(target_port); + by_id = 1; + } else { + /* + * If the pHCI did not set up 'target-port' on this + * pathinfo node, assume that our client is the only + * one with paths to the device by using the guid + * value as the 'target-port'. Since no other client + * will have the same guid, no other client will use + * the same <pid>. NOTE: a client with an instance + * number always has a guid. + */ + (void) ddi_prop_lookup_string(DDI_DEV_T_ANY, tgt_dip, + PROPFLAGS, MDI_CLIENT_GUID_PROP, &guid); + target_port_dup = i_ddi_strdup(guid, KM_SLEEP); + ddi_prop_free(guid); + + /* + * For this type of mapping we don't want the + * <id> -> 'target-port' mapping to be made. This + * will cause the SCSI_VHCI_GET_TARGET_LONGNAME ioctl + * to fail, and the iostat(1M) long '-n' output will + * still use the <pid>. We do this because we just + * made up the 'target-port' using the guid, and we + * don't want to expose that fact in iostat output. + */ + by_id = 0; + } + + /* find/establish <pid> given 'target-port' */ + mutex_enter(&vhci_targetmap_mutex); + if (mod_hash_find(vhci_targetmap_byport, + (mod_hash_key_t)target_port_dup, &hv) == 0) { + pid = (int)(intptr_t)hv; /* mapping exists */ + } else { + pid = vhci_targetmap_pid++; /* new mapping */ + + (void) mod_hash_insert(vhci_targetmap_byport, + (mod_hash_key_t)target_port_dup, + (mod_hash_val_t)(intptr_t)pid); + if (by_id) { + (void) mod_hash_insert(vhci_targetmap_bypid, + (mod_hash_key_t)(uintptr_t)pid, + (mod_hash_val_t)(uintptr_t)target_port_dup); + } + target_port_dup = NULL; /* owned by hash */ + } + mutex_exit(&vhci_targetmap_mutex); + + /* form kstat name */ + (void) snprintf(ks_name, KSTAT_STRLEN, "%s%d.t%d.%s%d", + ddi_driver_name(tgt_dip), ddi_get_instance(tgt_dip), + pid, ddi_driver_name(pdip), ddi_get_instance(pdip)); + + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_path_online: path:%p " + "kstat %s: pid %x <-> port %s\n", (void *)pip, + ks_name, pid, target_port_dup)); + if (target_port_dup) + kmem_free(target_port_dup, strlen(target_port_dup) + 1); + + /* call mdi to create kstats with the name we built */ + (void) mdi_pi_kstat_create(pip, ks_name); +} + +/* ARGSUSED */ +static int +vhci_pathinfo_online(dev_info_t *vdip, mdi_pathinfo_t *pip, int flags) +{ + scsi_hba_tran_t *hba = NULL; + struct scsi_device *psd = NULL; + scsi_vhci_lun_t *vlun = NULL; + dev_info_t *pdip = NULL; + dev_info_t *tgt_dip; + struct scsi_vhci *vhci; + char *guid; + struct scsi_failover *sf; + struct scsi_failover_ops *sfo; + char *override; + scsi_vhci_priv_t *svp = NULL; + struct buf *bp; + struct scsi_address *ap; + struct scsi_pkt *pkt; + int rval = MDI_FAILURE; + uint_t inq_size = VHCI_STD_INQ_SIZE; + mpapi_item_list_t *list_ptr; + mpapi_lu_data_t *ld; + + ASSERT(vdip != NULL); + ASSERT(pip != NULL); + + vhci = ddi_get_soft_state(vhci_softstate, ddi_get_instance(vdip)); + ASSERT(vhci != NULL); + + pdip = mdi_pi_get_phci(pip); + hba = ddi_get_driver_private(pdip); + ASSERT(hba != NULL); + + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(pip); + ASSERT(svp != NULL); + + tgt_dip = mdi_pi_get_client(pip); + ASSERT(tgt_dip != NULL); + if (ddi_prop_lookup_string(DDI_DEV_T_ANY, tgt_dip, PROPFLAGS, + MDI_CLIENT_GUID_PROP, &guid) != DDI_SUCCESS) { + VHCI_DEBUG(1, (CE_WARN, NULL, "vhci_path_online: lun guid " + "property failed")); + goto failure; + } + + vlun = vhci_lun_lookup(tgt_dip); + ASSERT(vlun != NULL); + + ddi_prop_free(guid); + + vlun->svl_dip = mdi_pi_get_client(pip); + ASSERT(vlun->svl_dip != NULL); + + psd = svp->svp_psd; + ASSERT(psd != NULL); + + /* + * For INQUIRY response buffer size, we use VHCI_STD_INQ_SIZE(128bytes) + * instead of SUN_INQSIZE(48bytes) which is used in sd layer. This is + * because we could get the Vendor specific parameters(present 97th + * byte onwards) which are required to process Vendor specific data + * based on array type. + * This INQUIRY buffer is freed in vhci_pathinfo_offline but NEVER + * in a different layer like sd/phci transport. In other words, vhci + * maintains its own copy of scsi_device and scsi_inquiry data on a + * per-path basis. + */ + if (psd->sd_inq == NULL) { + psd->sd_inq = (struct scsi_inquiry *) + kmem_zalloc(inq_size, KM_SLEEP); + } + + tgt_dip = psd->sd_dev; + ASSERT(tgt_dip != NULL); + + /* + * do inquiry to pass into probe routine; this + * will avoid each probe routine doing scsi inquiry + */ + bp = getrbuf(KM_SLEEP); + bp->b_un.b_addr = (caddr_t)psd->sd_inq; + bp->b_flags = B_READ; + bp->b_bcount = inq_size; + bp->b_resid = 0; + + ap = &psd->sd_address; + pkt = scsi_init_pkt(ap, NULL, bp, CDB_GROUP0, + sizeof (struct scsi_arq_status), 0, 0, SLEEP_FUNC, NULL); + if (pkt == NULL) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_pathinfo_online: " + "Inquiry init_pkt failed :%p\n", (void *)pip)); + rval = MDI_FAILURE; + goto failure; + } + pkt->pkt_cdbp[0] = SCMD_INQUIRY; + pkt->pkt_cdbp[4] = (uchar_t)inq_size; + pkt->pkt_time = 60; + + rval = vhci_do_scsi_cmd(pkt); + scsi_destroy_pkt(pkt); + freerbuf(bp); + if (rval == 0) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_pathinfo_online: " + "Failover Inquiry failed path:%p rval:%x\n", + (void *)pip, rval)); + rval = MDI_FAILURE; + goto failure; + } + + /* + * Determine if device is supported under scsi_vhci, and select + * failover module. + * + * See if there is a scsi_vhci.conf file override for this devices's + * VID/PID. The following values can be returned: + * + * NULL If the NULL is returned then there is no scsi_vhci.conf + * override. For NULL, we determine the failover_ops for + * this device by checking the sfo_device_probe entry + * point for each 'fops' module, in order. + * + * NOTE: Correct operation may depend on module ordering + * of 'specific' (failover modules that are completely + * VID/PID table based) to 'generic' (failover modules + * that based on T10 standards like TPGS). Currently, + * the value of 'ddi-forceload' in scsi_vhci.conf is used + * to establish the module list and probe order. + * + * "NONE" If value "NONE" is returned then there is a + * scsi_vhci.conf VID/PID override to indicate the device + * should not be supported under scsi_vhci (even if there + * is an 'fops' module supporting the device). + * + * "<other>" If another value is returned then that value is the + * name of the 'fops' module that should be used. + */ + sfo = NULL; /* "NONE" */ + override = scsi_get_device_type_string( + "scsi-vhci-failover-override", vdip, psd); + + if (override == NULL) { + /* NULL: default: select based on sfo_device_probe results */ + for (sf = scsi_failover_table; sf->sf_mod; sf++) { + if ((sf->sf_sfo == NULL) || + ((*sf->sf_sfo->sfo_device_probe) (psd, + psd->sd_inq, &vlun->svl_fops_ctpriv) == + SFO_DEVICE_PROBE_PHCI)) + continue; + + /* found failover module, supported under scsi_vhci */ + sfo = sf->sf_sfo; + vlun->svl_fops_name = + i_ddi_strdup(sfo->sfo_name, KM_SLEEP); + break; + } + } else if (strcmp(override, "NONE") && strcmp(override, "none")) { + /* !"NONE": select based on driver.conf specified name */ + for (sf = scsi_failover_table, sfo = NULL; sf->sf_mod; sf++) { + if ((sf->sf_sfo == NULL) || + (sf->sf_sfo->sfo_name == NULL) || + strcmp(override, sf->sf_sfo->sfo_name)) + continue; + + /* found failover module, supported under scsi_vhci */ + sfo = sf->sf_sfo; + vlun->svl_fops_name = kmem_alloc(strlen("conf ") + + strlen(sfo->sfo_name) + 1, KM_SLEEP); + (void) sprintf(vlun->svl_fops_name, "conf %s", + sfo->sfo_name); + break; + } + } + if (override) + kmem_free(override, strlen(override) + 1); + + if (sfo == NULL) { + /* no failover module - device not supported */ + VHCI_DEBUG(1, (CE_NOTE, vhci->vhci_dip, + "!vhci_pathinfo_online: dev (path 0x%p) not " + "supported\n", (void *)pip)); + vlun->svl_not_supported = 1; + rval = MDI_NOT_SUPPORTED; + goto done; + } + + /* failover supported for device - save failover_ops in vlun */ + vlun->svl_fops = sfo; + + /* + * Obtain the device-type based mpxio options as specified in + * scsi_vhci.conf file. + * + * NOTE: currently, the end result is a call to + * mdi_set_lb_region_size(). + */ + vhci_get_device_type_mpxio_options(vdip, tgt_dip, psd); + + /* + * if PGR is active, revalidate key and register on this path also, + * if key is still valid + */ + sema_p(&vlun->svl_pgr_sema); + if (vlun->svl_pgr_active) { + rval = vhci_pgr_validate_and_register(svp); + if (rval != 1) { + rval = MDI_FAILURE; + sema_v(&vlun->svl_pgr_sema); + goto failure; + } + } + sema_v(&vlun->svl_pgr_sema); + + if (svp->svp_new_path) { + /* + * Last chance to perform any cleanup operations on this + * new path before making this path completely online. + */ + svp->svp_new_path = 0; + + /* + * If scsi_vhci knows the lun is alread RESERVE'd, + * then skip the issue of RELEASE on new path. + */ + if ((vlun->svl_flags & VLUN_RESERVE_ACTIVE_FLG) == 0) { + /* + * Issue SCSI-2 RELEASE only for the first time on + * a new path just in case the host rebooted and + * a reservation is still pending on this path. + * IBM Shark storage does not clear RESERVE upon + * host reboot. + */ + ap = &psd->sd_address; + pkt = scsi_init_pkt(ap, NULL, NULL, CDB_GROUP0, + sizeof (struct scsi_arq_status), 0, 0, + SLEEP_FUNC, NULL); + if (pkt == NULL) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_pathinfo_online: " + "Release init_pkt failed :%p\n", + (void *)pip)); + rval = MDI_FAILURE; + goto failure; + } + pkt->pkt_cdbp[0] = SCMD_RELEASE; + pkt->pkt_time = 60; + + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_path_online: path:%p " + "Issued SCSI-2 RELEASE\n", (void *)pip)); + + /* Ignore the return value */ + (void) vhci_do_scsi_cmd(pkt); + scsi_destroy_pkt(pkt); + } + } + + rval = vhci_update_pathinfo(psd, pip, sfo, vlun, vhci); + if (rval == MDI_FAILURE) { + goto failure; + } + + /* Initialize MP-API data */ + vhci_update_mpapi_data(vhci, vlun, pip); + + /* + * MP-API also needs the Inquiry data to be maintained in the + * mp_vendor_prop_t structure, so find the lun and update its + * structure with this data. + */ + list_ptr = (mpapi_item_list_t *)vhci_get_mpapi_item(vhci, NULL, + MP_OBJECT_TYPE_MULTIPATH_LU, (void *)vlun); + ld = (mpapi_lu_data_t *)list_ptr->item->idata; + if (ld != NULL) { + bcopy(psd->sd_inq->inq_vid, ld->prop.prodInfo.vendor, 8); + bcopy(psd->sd_inq->inq_pid, ld->prop.prodInfo.product, 16); + bcopy(psd->sd_inq->inq_revision, ld->prop.prodInfo.revision, 4); + } else { + VHCI_DEBUG(1, (CE_WARN, NULL, "!vhci_pathinfo_online: " + "mpapi_lu_data_t is NULL")); + } + + /* create kstats for path */ + vhci_kstat_create_pathinfo(pip); + +done: + mutex_enter(&vhci_global_mutex); + cv_broadcast(&vhci_cv); + mutex_exit(&vhci_global_mutex); + + if (vlun->svl_setcap_done) { + (void) vhci_pHCI_cap(ap, "sector-size", + vlun->svl_sector_size, 1, pip); + } + + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_path_online: path:%p\n", + (void *)pip)); + +failure: + if ((rval != MDI_SUCCESS) && psd->sd_inq) { + kmem_free((caddr_t)psd->sd_inq, inq_size); + psd->sd_inq = (struct scsi_inquiry *)NULL; + } + return (rval); +} + +/* + * path offline handler. Release all bindings that will not be + * released by the normal packet transport/completion code path. + * Since we don't (presently) keep any bindings alive outside of + * the in-transport packets (which will be released on completion) + * there is not much to do here. + */ +/* ARGSUSED */ +static int +vhci_pathinfo_offline(dev_info_t *vdip, mdi_pathinfo_t *pip, int flags) +{ + scsi_hba_tran_t *hba = NULL; + struct scsi_device *psd = NULL; + dev_info_t *pdip = NULL; + dev_info_t *cdip = NULL; + scsi_vhci_priv_t *svp = NULL; + uint_t inq_size = VHCI_STD_INQ_SIZE; + + ASSERT(vdip != NULL); + ASSERT(pip != NULL); + + pdip = mdi_pi_get_phci(pip); + ASSERT(pdip != NULL); + if (pdip == NULL) { + VHCI_DEBUG(1, (CE_WARN, vdip, "Invalid path 0x%p: NULL " + "phci dip", (void *)pip)); + return (MDI_FAILURE); + } + + cdip = mdi_pi_get_client(pip); + ASSERT(cdip != NULL); + if (cdip == NULL) { + VHCI_DEBUG(1, (CE_WARN, vdip, "Invalid path 0x%p: NULL " + "client dip", (void *)pip)); + return (MDI_FAILURE); + } + + hba = ddi_get_driver_private(pdip); + ASSERT(hba != NULL); + + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(pip); + if (svp == NULL) { + /* + * mdi_pathinfo node in INIT state can have vHCI private + * information set to null + */ + VHCI_DEBUG(1, (CE_NOTE, vdip, "!vhci_pathinfo_offline: " + "svp is NULL for pip 0x%p\n", (void *)pip)); + return (MDI_SUCCESS); + } + + psd = svp->svp_psd; + ASSERT(psd != NULL); + + mutex_enter(&svp->svp_mutex); + + VHCI_DEBUG(1, (CE_NOTE, vdip, "!vhci_pathinfo_offline: " + "%d cmds pending on path: 0x%p\n", svp->svp_cmds, (void *)pip)); + while (svp->svp_cmds != 0) { + if (cv_timedwait(&svp->svp_cv, &svp->svp_mutex, + ddi_get_lbolt() + + drv_usectohz(vhci_path_quiesce_timeout * 1000000)) == -1) { + /* + * The timeout time reached without the condition + * being signaled. + */ + VHCI_DEBUG(1, (CE_NOTE, vdip, "!vhci_pathinfo_offline: " + "Timeout reached on path 0x%p without the cond\n", + (void *)pip)); + VHCI_DEBUG(1, (CE_NOTE, vdip, "!vhci_pathinfo_offline: " + "%d cmds still pending on path: 0x%p\n", + svp->svp_cmds, (void *)pip)); + break; + } + } + mutex_exit(&svp->svp_mutex); + + /* + * Check to see if this vlun has an active SCSI-II RESERVE. And this + * is the pip for the path that has been reserved. + * If so clear the reservation by sending a reset, so the host will not + * get a reservation conflict. Reset the flag VLUN_RESERVE_ACTIVE_FLG + * for this lun. Also a reset notify is sent to the target driver + * just in case the POR check condition is cleared by some other layer + * in the stack. + */ + if (svp->svp_svl->svl_flags & VLUN_RESERVE_ACTIVE_FLG) { + if (pip == svp->svp_svl->svl_resrv_pip) { + if (vhci_recovery_reset(svp->svp_svl, + &svp->svp_psd->sd_address, TRUE, + VHCI_DEPTH_TARGET) == 0) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_pathinfo_offline (pip:%p):" + "reset failed, retrying\n", (void *)pip)); + delay(1*drv_usectohz(1000000)); + if (vhci_recovery_reset(svp->svp_svl, + &svp->svp_psd->sd_address, TRUE, + VHCI_DEPTH_TARGET) == 0) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_pathinfo_offline " + "(pip:%p): reset failed, " + "giving up!\n", (void *)pip)); + } + } + svp->svp_svl->svl_flags &= ~VLUN_RESERVE_ACTIVE_FLG; + } + } + + mdi_pi_set_state(pip, MDI_PATHINFO_STATE_OFFLINE); + if (psd->sd_inq) { + kmem_free((caddr_t)psd->sd_inq, inq_size); + psd->sd_inq = (struct scsi_inquiry *)NULL; + } + vhci_mpapi_set_path_state(vdip, pip, MP_DRVR_PATH_STATE_REMOVED); + + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_pathinfo_offline: offlined path 0x%p\n", (void *)pip)); + return (MDI_SUCCESS); +} + + +/* + * routine for SCSI VHCI IOCTL implementation. + */ +/* ARGSUSED */ +static int +vhci_ctl(dev_t dev, int cmd, intptr_t data, int mode, cred_t *credp, int *rval) +{ + struct scsi_vhci *vhci; + dev_info_t *vdip; + mdi_pathinfo_t *pip; + int instance, held; + int retval = 0; + caddr_t phci_path = NULL, client_path = NULL; + caddr_t paddr = NULL; + sv_iocdata_t ioc; + sv_iocdata_t *pioc = &ioc; + sv_switch_to_cntlr_iocdata_t iocsc; + sv_switch_to_cntlr_iocdata_t *piocsc = &iocsc; + caddr_t s; + scsi_vhci_lun_t *vlun; + struct scsi_failover_ops *fo; + char *pclass; + + /* Check for validity of vhci structure */ + vhci = ddi_get_soft_state(vhci_softstate, MINOR2INST(getminor(dev))); + if (vhci == NULL) { + return (ENXIO); + } + + mutex_enter(&vhci->vhci_mutex); + if ((vhci->vhci_state & VHCI_STATE_OPEN) == 0) { + mutex_exit(&vhci->vhci_mutex); + return (ENXIO); + } + mutex_exit(&vhci->vhci_mutex); + + /* Get the vhci dip */ + vdip = vhci->vhci_dip; + ASSERT(vdip != NULL); + instance = ddi_get_instance(vdip); + + /* Allocate memory for getting parameters from userland */ + phci_path = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + client_path = kmem_zalloc(MAXPATHLEN, KM_SLEEP); + paddr = kmem_zalloc(MAXNAMELEN, KM_SLEEP); + + /* + * Set a local variable indicating the ioctl name. Used for + * printing debug strings. + */ + switch (cmd) { + case SCSI_VHCI_GET_CLIENT_MULTIPATH_INFO: + s = "GET_CLIENT_MULTIPATH_INFO"; + break; + + case SCSI_VHCI_GET_PHCI_MULTIPATH_INFO: + s = "GET_PHCI_MULTIPATH_INFO"; + break; + + case SCSI_VHCI_GET_CLIENT_NAME: + s = "GET_CLIENT_NAME"; + break; + + case SCSI_VHCI_PATH_ONLINE: + s = "PATH_ONLINE"; + break; + + case SCSI_VHCI_PATH_OFFLINE: + s = "PATH_OFFLINE"; + break; + + case SCSI_VHCI_PATH_STANDBY: + s = "PATH_STANDBY"; + break; + + case SCSI_VHCI_PATH_TEST: + s = "PATH_TEST"; + break; + + case SCSI_VHCI_SWITCH_TO_CNTLR: + s = "SWITCH_TO_CNTLR"; + break; + case SCSI_VHCI_PATH_DISABLE: + s = "PATH_DISABLE"; + break; + case SCSI_VHCI_PATH_ENABLE: + s = "PATH_ENABLE"; + break; + + case SCSI_VHCI_GET_TARGET_LONGNAME: + s = "GET_TARGET_LONGNAME"; + break; + +#ifdef DEBUG + case SCSI_VHCI_CONFIGURE_PHCI: + s = "CONFIGURE_PHCI"; + break; + + case SCSI_VHCI_UNCONFIGURE_PHCI: + s = "UNCONFIGURE_PHCI"; + break; +#endif + + default: + s = "Unknown"; + vhci_log(CE_NOTE, vdip, + "!vhci%d: ioctl %x (unsupported ioctl)", instance, cmd); + retval = ENOTSUP; + break; + } + if (retval != 0) { + goto end; + } + + VHCI_DEBUG(6, (CE_WARN, vdip, "!vhci%d: ioctl <%s>", instance, s)); + + /* + * Get IOCTL parameters from userland + */ + switch (cmd) { + case SCSI_VHCI_GET_CLIENT_MULTIPATH_INFO: + case SCSI_VHCI_GET_PHCI_MULTIPATH_INFO: + case SCSI_VHCI_GET_CLIENT_NAME: + case SCSI_VHCI_PATH_ONLINE: + case SCSI_VHCI_PATH_OFFLINE: + case SCSI_VHCI_PATH_STANDBY: + case SCSI_VHCI_PATH_TEST: + case SCSI_VHCI_PATH_DISABLE: + case SCSI_VHCI_PATH_ENABLE: + case SCSI_VHCI_GET_TARGET_LONGNAME: +#ifdef DEBUG + case SCSI_VHCI_CONFIGURE_PHCI: + case SCSI_VHCI_UNCONFIGURE_PHCI: +#endif + retval = vhci_get_iocdata((const void *)data, pioc, mode, s); + break; + + case SCSI_VHCI_SWITCH_TO_CNTLR: + retval = vhci_get_iocswitchdata((const void *)data, piocsc, + mode, s); + break; + } + if (retval != 0) { + goto end; + } + + + /* + * Process the IOCTL + */ + switch (cmd) { + case SCSI_VHCI_GET_CLIENT_MULTIPATH_INFO: + { + uint_t num_paths; /* Num paths to client dev */ + sv_path_info_t *upibuf = NULL; /* To keep userland values */ + sv_path_info_t *kpibuf = NULL; /* Kernel data for ioctls */ + dev_info_t *cdip; /* Client device dip */ + + if (pioc->ret_elem == NULL) { + retval = EINVAL; + break; + } + + /* Get client device path from user land */ + if (vhci_ioc_get_client_path(pioc, client_path, mode, s)) { + retval = EFAULT; + break; + } + + VHCI_DEBUG(6, (CE_WARN, vdip, "!vhci_ioctl: ioctl <%s> " + "client <%s>", s, client_path)); + + /* Get number of paths to this client device */ + if ((cdip = mdi_client_path2devinfo(vdip, client_path)) + == NULL) { + retval = ENXIO; + VHCI_DEBUG(1, (CE_WARN, NULL, "!vhci_ioctl: ioctl <%s> " + "client dip doesn't exist. invalid path <%s>", + s, client_path)); + break; + } + num_paths = mdi_client_get_path_count(cdip); + + if (ddi_copyout(&num_paths, pioc->ret_elem, + sizeof (num_paths), mode)) { + VHCI_DEBUG(1, (CE_WARN, NULL, "!vhci_ioctl: ioctl <%s> " + "num_paths copyout failed", s)); + retval = EFAULT; + break; + } + + /* If user just wanted num_paths, then return */ + if (pioc->buf_elem == 0 || pioc->ret_buf == NULL || + num_paths == 0) { + break; + } + + /* Set num_paths to value as much as can be sent to userland */ + if (num_paths > pioc->buf_elem) { + num_paths = pioc->buf_elem; + } + + /* Allocate memory and get userland pointers */ + if (vhci_ioc_alloc_pathinfo(&upibuf, &kpibuf, num_paths, + pioc, mode, s) != 0) { + retval = EFAULT; + break; + } + ASSERT(upibuf != NULL); + ASSERT(kpibuf != NULL); + + /* + * Get the path information and send it to userland. + */ + if (vhci_get_client_path_list(cdip, kpibuf, num_paths) + != MDI_SUCCESS) { + retval = ENXIO; + vhci_ioc_free_pathinfo(upibuf, kpibuf, num_paths); + break; + } + + if (vhci_ioc_send_pathinfo(upibuf, kpibuf, num_paths, + pioc, mode, s)) { + retval = EFAULT; + vhci_ioc_free_pathinfo(upibuf, kpibuf, num_paths); + break; + } + + /* Free the memory allocated for path information */ + vhci_ioc_free_pathinfo(upibuf, kpibuf, num_paths); + break; + } + + case SCSI_VHCI_GET_PHCI_MULTIPATH_INFO: + { + uint_t num_paths; /* Num paths to client dev */ + sv_path_info_t *upibuf = NULL; /* To keep userland values */ + sv_path_info_t *kpibuf = NULL; /* Kernel data for ioctls */ + dev_info_t *pdip; /* PHCI device dip */ + + if (pioc->ret_elem == NULL) { + retval = EINVAL; + break; + } + + /* Get PHCI device path from user land */ + if (vhci_ioc_get_phci_path(pioc, phci_path, mode, s)) { + retval = EFAULT; + break; + } + + VHCI_DEBUG(6, (CE_WARN, vdip, + "!vhci_ioctl: ioctl <%s> phci <%s>", s, phci_path)); + + /* Get number of devices associated with this PHCI device */ + if ((pdip = mdi_phci_path2devinfo(vdip, phci_path)) == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "!vhci_ioctl: ioctl <%s> " + "phci dip doesn't exist. invalid path <%s>", + s, phci_path)); + retval = ENXIO; + break; + } + + num_paths = mdi_phci_get_path_count(pdip); + + if (ddi_copyout(&num_paths, pioc->ret_elem, + sizeof (num_paths), mode)) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_ioctl: ioctl <%s> " + "num_paths copyout failed", s)); + retval = EFAULT; + break; + } + + /* If user just wanted num_paths, then return */ + if (pioc->buf_elem == 0 || pioc->ret_buf == NULL || + num_paths == 0) { + break; + } + + /* Set num_paths to value as much as can be sent to userland */ + if (num_paths > pioc->buf_elem) { + num_paths = pioc->buf_elem; + } + + /* Allocate memory and get userland pointers */ + if (vhci_ioc_alloc_pathinfo(&upibuf, &kpibuf, num_paths, + pioc, mode, s) != 0) { + retval = EFAULT; + break; + } + ASSERT(upibuf != NULL); + ASSERT(kpibuf != NULL); + + /* + * Get the path information and send it to userland. + */ + if (vhci_get_phci_path_list(pdip, kpibuf, num_paths) + != MDI_SUCCESS) { + retval = ENXIO; + vhci_ioc_free_pathinfo(upibuf, kpibuf, num_paths); + break; + } + + if (vhci_ioc_send_pathinfo(upibuf, kpibuf, num_paths, + pioc, mode, s)) { + retval = EFAULT; + vhci_ioc_free_pathinfo(upibuf, kpibuf, num_paths); + break; + } + + /* Free the memory allocated for path information */ + vhci_ioc_free_pathinfo(upibuf, kpibuf, num_paths); + break; + } + + case SCSI_VHCI_GET_CLIENT_NAME: + { + dev_info_t *cdip, *pdip; + + /* Get PHCI path and device address from user land */ + if (vhci_ioc_get_phci_path(pioc, phci_path, mode, s) || + vhci_ioc_get_paddr(pioc, paddr, mode, s)) { + retval = EFAULT; + break; + } + + VHCI_DEBUG(6, (CE_WARN, vdip, "!vhci_ioctl: ioctl <%s> " + "phci <%s>, paddr <%s>", s, phci_path, paddr)); + + /* Get the PHCI dip */ + if ((pdip = mdi_phci_path2devinfo(vdip, phci_path)) == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "!vhci_ioctl: ioctl <%s> " + "phci dip doesn't exist. invalid path <%s>", + s, phci_path)); + retval = ENXIO; + break; + } + + if ((pip = mdi_pi_find(pdip, NULL, paddr)) == NULL) { + VHCI_DEBUG(1, (CE_WARN, vdip, "!vhci_ioctl: ioctl <%s> " + "pathinfo doesn't exist. invalid device addr", s)); + retval = ENXIO; + break; + } + + /* Get the client device pathname and send to userland */ + cdip = mdi_pi_get_client(pip); + vhci_ioc_devi_to_path(cdip, client_path); + + VHCI_DEBUG(6, (CE_WARN, vdip, "!vhci_ioctl: ioctl <%s> " + "client <%s>", s, client_path)); + + if (vhci_ioc_send_client_path(client_path, pioc, mode, s)) { + retval = EFAULT; + break; + } + break; + } + + case SCSI_VHCI_PATH_ONLINE: + case SCSI_VHCI_PATH_OFFLINE: + case SCSI_VHCI_PATH_STANDBY: + case SCSI_VHCI_PATH_TEST: + { + dev_info_t *pdip; /* PHCI dip */ + + /* Get PHCI path and device address from user land */ + if (vhci_ioc_get_phci_path(pioc, phci_path, mode, s) || + vhci_ioc_get_paddr(pioc, paddr, mode, s)) { + retval = EFAULT; + break; + } + + VHCI_DEBUG(6, (CE_WARN, vdip, "!vhci_ioctl: ioctl <%s> " + "phci <%s>, paddr <%s>", s, phci_path, paddr)); + + /* Get the PHCI dip */ + if ((pdip = mdi_phci_path2devinfo(vdip, phci_path)) == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "!vhci_ioctl: ioctl <%s> " + "phci dip doesn't exist. invalid path <%s>", + s, phci_path)); + retval = ENXIO; + break; + } + + if ((pip = mdi_pi_find(pdip, NULL, paddr)) == NULL) { + VHCI_DEBUG(1, (CE_WARN, vdip, "!vhci_ioctl: ioctl <%s> " + "pathinfo doesn't exist. invalid device addr", s)); + retval = ENXIO; + break; + } + + VHCI_DEBUG(6, (CE_WARN, vdip, "!vhci_ioctl: ioctl <%s> " + "Calling MDI function to change device state", s)); + + switch (cmd) { + case SCSI_VHCI_PATH_ONLINE: + retval = mdi_pi_online(pip, 0); + break; + + case SCSI_VHCI_PATH_OFFLINE: + retval = mdi_pi_offline(pip, 0); + break; + + case SCSI_VHCI_PATH_STANDBY: + retval = mdi_pi_standby(pip, 0); + break; + + case SCSI_VHCI_PATH_TEST: + break; + } + break; + } + + case SCSI_VHCI_SWITCH_TO_CNTLR: + { + dev_info_t *cdip; + struct scsi_device *devp; + + /* Get the client device pathname */ + if (ddi_copyin(piocsc->client, client_path, + MAXPATHLEN, mode)) { + VHCI_DEBUG(2, (CE_WARN, vdip, "!vhci_ioctl: ioctl <%s> " + "client_path copyin failed", s)); + retval = EFAULT; + break; + } + + /* Get the path class to which user wants to switch */ + if (ddi_copyin(piocsc->class, paddr, MAXNAMELEN, mode)) { + VHCI_DEBUG(2, (CE_WARN, vdip, "!vhci_ioctl: ioctl <%s> " + "controller_class copyin failed", s)); + retval = EFAULT; + break; + } + + /* Perform validity checks */ + if ((cdip = mdi_client_path2devinfo(vdip, + client_path)) == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "!vhci_ioctl: ioctl <%s> " + "client dip doesn't exist. invalid path <%s>", + s, client_path)); + retval = ENXIO; + break; + } + + VHCI_DEBUG(6, (CE_WARN, vdip, "!vhci_ioctl: Calling MDI func " + "to switch controller")); + VHCI_DEBUG(6, (CE_WARN, vdip, "!vhci_ioctl: client <%s> " + "class <%s>", client_path, paddr)); + + if (strcmp(paddr, PCLASS_PRIMARY) && + strcmp(paddr, PCLASS_SECONDARY)) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_ioctl: ioctl <%s> " + "invalid path class <%s>", s, paddr)); + retval = ENXIO; + break; + } + + devp = ddi_get_driver_private(cdip); + if (devp == NULL) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_ioctl: ioctl <%s> " + "invalid scsi device <%s>", s, client_path)); + retval = ENXIO; + break; + } + vlun = ADDR2VLUN(&devp->sd_address); + ASSERT(vlun); + + /* + * Checking to see if device has only one pclass, PRIMARY. + * If so this device doesn't support failovers. Assumed + * that the devices with one pclass is PRIMARY, as thats the + * case today. If this is not true and in future other + * symmetric devices are supported with other pclass, this + * IOCTL shall have to be overhauled anyways as now the only + * arguments it accepts are PRIMARY and SECONDARY. + */ + fo = vlun->svl_fops; + if ((*fo->sfo_pathclass_next)(PCLASS_PRIMARY, &pclass, + vlun->svl_fops_ctpriv)) { + retval = ENOTSUP; + break; + } + + VHCI_HOLD_LUN(vlun, VH_SLEEP, held); + mutex_enter(&vlun->svl_mutex); + if (vlun->svl_active_pclass != NULL) { + if (strcmp(vlun->svl_active_pclass, paddr) == 0) { + mutex_exit(&vlun->svl_mutex); + retval = EALREADY; + VHCI_RELEASE_LUN(vlun); + break; + } + } + mutex_exit(&vlun->svl_mutex); + /* Call mdi function to cause a switch over */ + retval = mdi_failover(vdip, cdip, MDI_FAILOVER_SYNC); + if (retval == MDI_SUCCESS) { + retval = 0; + } else if (retval == MDI_BUSY) { + retval = EBUSY; + } else { + retval = EIO; + } + VHCI_RELEASE_LUN(vlun); + break; + } + + case SCSI_VHCI_PATH_ENABLE: + case SCSI_VHCI_PATH_DISABLE: + { + dev_info_t *cdip, *pdip; + + /* + * Get client device path from user land + */ + if (vhci_ioc_get_client_path(pioc, client_path, mode, s)) { + retval = EFAULT; + break; + } + + /* + * Get Phci device path from user land + */ + if (vhci_ioc_get_phci_path(pioc, phci_path, mode, s)) { + retval = EFAULT; + break; + } + + /* + * Get the devinfo for the Phci. + */ + if ((pdip = mdi_phci_path2devinfo(vdip, phci_path)) == NULL) { + VHCI_DEBUG(1, (CE_WARN, NULL, "!vhci_ioctl: ioctl <%s> " + "phci dip doesn't exist. invalid path <%s>", + s, phci_path)); + retval = ENXIO; + break; + } + + /* + * If the client path is set to /scsi_vhci then we need + * to do the operation on all the clients so set cdip to NULL. + * Else, try to get the client dip. + */ + if (strcmp(client_path, "/scsi_vhci") == 0) { + cdip = NULL; + } else { + if ((cdip = mdi_client_path2devinfo(vdip, + client_path)) == NULL) { + retval = ENXIO; + VHCI_DEBUG(1, (CE_WARN, NULL, + "!vhci_ioctl: ioctl <%s> client dip " + "doesn't exist. invalid path <%s>", + s, client_path)); + break; + } + } + + if (cmd == SCSI_VHCI_PATH_ENABLE) + retval = mdi_pi_enable(cdip, pdip, USER_DISABLE); + else + retval = mdi_pi_disable(cdip, pdip, USER_DISABLE); + + break; + } + + case SCSI_VHCI_GET_TARGET_LONGNAME: + { + uint_t pid = pioc->buf_elem; + char *target_port; + mod_hash_val_t hv; + + /* targetmap lookup of 'target-port' by <pid> */ + if (mod_hash_find(vhci_targetmap_bypid, + (mod_hash_key_t)(uintptr_t)pid, &hv) != 0) { + /* + * NOTE: failure to find the mapping is OK for guid + * based 'target-port' values. + */ + VHCI_DEBUG(3, (CE_WARN, NULL, "!vhci_ioctl: ioctl <%s> " + "targetport mapping doesn't exist: pid %d", + s, pid)); + retval = ENXIO; + break; + } + + /* copyout 'target-port' result */ + target_port = (char *)hv; + if (copyoutstr(target_port, pioc->addr, MAXNAMELEN, NULL)) { + VHCI_DEBUG(1, (CE_WARN, NULL, "!vhci_ioctl: ioctl <%s> " + "targetport copyout failed: len: %d", + s, (int)strlen(target_port))); + retval = EFAULT; + } + break; + } + +#ifdef DEBUG + case SCSI_VHCI_CONFIGURE_PHCI: + { + dev_info_t *pdip; + + /* Get PHCI path and device address from user land */ + if (vhci_ioc_get_phci_path(pioc, phci_path, mode, s)) { + retval = EFAULT; + break; + } + + VHCI_DEBUG(6, (CE_WARN, vdip, "!vhci_ioctl: ioctl <%s> " + "phci <%s>", s, phci_path)); + + /* Get the PHCI dip */ + if ((pdip = e_ddi_hold_devi_by_path(phci_path, 0)) == NULL) { + VHCI_DEBUG(3, (CE_WARN, NULL, "!vhci_ioctl: ioctl <%s> " + "phci dip doesn't exist. invalid path <%s>", + s, phci_path)); + retval = ENXIO; + break; + } + + if (ndi_devi_config(pdip, + NDI_DEVFS_CLEAN|NDI_DEVI_PERSIST) != NDI_SUCCESS) { + retval = EIO; + } + + ddi_release_devi(pdip); + break; + } + + case SCSI_VHCI_UNCONFIGURE_PHCI: + { + dev_info_t *pdip; + + /* Get PHCI path and device address from user land */ + if (vhci_ioc_get_phci_path(pioc, phci_path, mode, s)) { + retval = EFAULT; + break; + } + + VHCI_DEBUG(6, (CE_WARN, vdip, "!vhci_ioctl: ioctl <%s> " + "phci <%s>", s, phci_path)); + + /* Get the PHCI dip */ + if ((pdip = e_ddi_hold_devi_by_path(phci_path, 0)) == NULL) { + VHCI_DEBUG(3, (CE_WARN, NULL, "!vhci_ioctl: ioctl <%s> " + "phci dip doesn't exist. invalid path <%s>", + s, phci_path)); + retval = ENXIO; + break; + } + + if (ndi_devi_unconfig(pdip, + NDI_DEVI_REMOVE|NDI_DEVFS_CLEAN) != NDI_SUCCESS) { + retval = EBUSY; + } + + ddi_release_devi(pdip); + break; + } +#endif + } + +end: + /* Free the memory allocated above */ + if (phci_path != NULL) { + kmem_free(phci_path, MAXPATHLEN); + } + if (client_path != NULL) { + kmem_free(client_path, MAXPATHLEN); + } + if (paddr != NULL) { + kmem_free(paddr, MAXNAMELEN); + } + return (retval); +} + +/* + * devctl IOCTL support for client device DR + */ +/* ARGSUSED */ +int +vhci_devctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, + int *rvalp) +{ + dev_info_t *self; + dev_info_t *child; + scsi_hba_tran_t *hba; + struct devctl_iocdata *dcp; + struct scsi_vhci *vhci; + int rv = 0; + int retval = 0; + scsi_vhci_priv_t *svp; + mdi_pathinfo_t *pip; + + if ((vhci = ddi_get_soft_state(vhci_softstate, + MINOR2INST(getminor(dev)))) == NULL) + return (ENXIO); + + /* + * check if :devctl minor device has been opened + */ + mutex_enter(&vhci->vhci_mutex); + if ((vhci->vhci_state & VHCI_STATE_OPEN) == 0) { + mutex_exit(&vhci->vhci_mutex); + return (ENXIO); + } + mutex_exit(&vhci->vhci_mutex); + + self = vhci->vhci_dip; + hba = ddi_get_driver_private(self); + if (hba == NULL) + return (ENXIO); + + /* + * We can use the generic implementation for these ioctls + */ + switch (cmd) { + case DEVCTL_DEVICE_GETSTATE: + case DEVCTL_DEVICE_ONLINE: + case DEVCTL_DEVICE_OFFLINE: + case DEVCTL_DEVICE_REMOVE: + case DEVCTL_BUS_GETSTATE: + return (ndi_devctl_ioctl(self, cmd, arg, mode, 0)); + } + + /* + * read devctl ioctl data + */ + if (ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS) + return (EFAULT); + + switch (cmd) { + + case DEVCTL_DEVICE_RESET: + /* + * lookup and hold child device + */ + if ((child = ndi_devi_find(self, ndi_dc_getname(dcp), + ndi_dc_getaddr(dcp))) == NULL) { + rv = ENXIO; + break; + } + retval = mdi_select_path(child, NULL, + (MDI_SELECT_ONLINE_PATH | MDI_SELECT_STANDBY_PATH), + NULL, &pip); + if ((retval != MDI_SUCCESS) || (pip == NULL)) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_ioctl:" + "Unable to get a path, dip 0x%p", (void *)child)); + rv = ENXIO; + break; + } + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(pip); + if (vhci_recovery_reset(svp->svp_svl, + &svp->svp_psd->sd_address, TRUE, + VHCI_DEPTH_TARGET) == 0) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_ioctl(pip:%p): " + "reset failed\n", (void *)pip)); + rv = ENXIO; + } + mdi_rele_path(pip); + break; + + case DEVCTL_BUS_QUIESCE: + case DEVCTL_BUS_UNQUIESCE: + case DEVCTL_BUS_RESET: + case DEVCTL_BUS_RESETALL: +#ifdef DEBUG + case DEVCTL_BUS_CONFIGURE: + case DEVCTL_BUS_UNCONFIGURE: +#endif + rv = ENOTSUP; + break; + + default: + rv = ENOTTY; + } /* end of outer switch */ + + ndi_dc_freehdl(dcp); + return (rv); +} + +/* + * Routine to get the PHCI pathname from ioctl structures in userland + */ +/* ARGSUSED */ +static int +vhci_ioc_get_phci_path(sv_iocdata_t *pioc, caddr_t phci_path, + int mode, caddr_t s) +{ + int retval = 0; + + if (ddi_copyin(pioc->phci, phci_path, MAXPATHLEN, mode)) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_ioc_get_phci: ioctl <%s> " + "phci_path copyin failed", s)); + retval = EFAULT; + } + return (retval); + +} + + +/* + * Routine to get the Client device pathname from ioctl structures in userland + */ +/* ARGSUSED */ +static int +vhci_ioc_get_client_path(sv_iocdata_t *pioc, caddr_t client_path, + int mode, caddr_t s) +{ + int retval = 0; + + if (ddi_copyin(pioc->client, client_path, MAXPATHLEN, mode)) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_ioc_get_client: " + "ioctl <%s> client_path copyin failed", s)); + retval = EFAULT; + } + return (retval); +} + + +/* + * Routine to get physical device address from ioctl structure in userland + */ +/* ARGSUSED */ +static int +vhci_ioc_get_paddr(sv_iocdata_t *pioc, caddr_t paddr, int mode, caddr_t s) +{ + int retval = 0; + + if (ddi_copyin(pioc->addr, paddr, MAXNAMELEN, mode)) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_ioc_get_paddr: " + "ioctl <%s> device addr copyin failed", s)); + retval = EFAULT; + } + return (retval); +} + + +/* + * Routine to send client device pathname to userland. + */ +/* ARGSUSED */ +static int +vhci_ioc_send_client_path(caddr_t client_path, sv_iocdata_t *pioc, + int mode, caddr_t s) +{ + int retval = 0; + + if (ddi_copyout(client_path, pioc->client, MAXPATHLEN, mode)) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_ioc_send_client: " + "ioctl <%s> client_path copyout failed", s)); + retval = EFAULT; + } + return (retval); +} + + +/* + * Routine to translated dev_info pointer (dip) to device pathname. + */ +static void +vhci_ioc_devi_to_path(dev_info_t *dip, caddr_t path) +{ + (void) ddi_pathname(dip, path); +} + + +/* + * vhci_get_phci_path_list: + * get information about devices associated with a + * given PHCI device. + * + * Return Values: + * path information elements + */ +int +vhci_get_phci_path_list(dev_info_t *pdip, sv_path_info_t *pibuf, + uint_t num_elems) +{ + uint_t count, done; + mdi_pathinfo_t *pip; + sv_path_info_t *ret_pip; + int status; + size_t prop_size; + int circular; + + /* + * Get the PHCI structure and retrieve the path information + * from the GUID hash table. + */ + + ret_pip = pibuf; + count = 0; + + ndi_devi_enter(pdip, &circular); + + done = (count >= num_elems); + pip = mdi_get_next_client_path(pdip, NULL); + while (pip && !done) { + mdi_pi_lock(pip); + (void) ddi_pathname(mdi_pi_get_phci(pip), + ret_pip->device.ret_phci); + (void) strcpy(ret_pip->ret_addr, mdi_pi_get_addr(pip)); + (void) mdi_pi_get_state2(pip, &ret_pip->ret_state, + &ret_pip->ret_ext_state); + + status = mdi_prop_size(pip, &prop_size); + if (status == MDI_SUCCESS && ret_pip->ret_prop.ret_buf_size) { + *ret_pip->ret_prop.ret_buf_size = (uint_t)prop_size; + } + +#ifdef DEBUG + if (status != MDI_SUCCESS) { + VHCI_DEBUG(2, (CE_WARN, NULL, + "!vhci_get_phci_path_list: " + "phci <%s>, prop size failure 0x%x", + ret_pip->device.ret_phci, status)); + } +#endif /* DEBUG */ + + + if (status == MDI_SUCCESS && ret_pip->ret_prop.buf && + prop_size && ret_pip->ret_prop.buf_size >= prop_size) { + status = mdi_prop_pack(pip, + &ret_pip->ret_prop.buf, + ret_pip->ret_prop.buf_size); + +#ifdef DEBUG + if (status != MDI_SUCCESS) { + VHCI_DEBUG(2, (CE_WARN, NULL, + "!vhci_get_phci_path_list: " + "phci <%s>, prop pack failure 0x%x", + ret_pip->device.ret_phci, status)); + } +#endif /* DEBUG */ + } + + mdi_pi_unlock(pip); + pip = mdi_get_next_client_path(pdip, pip); + ret_pip++; + count++; + done = (count >= num_elems); + } + + ndi_devi_exit(pdip, circular); + + return (MDI_SUCCESS); +} + + +/* + * vhci_get_client_path_list: + * get information about various paths associated with a + * given client device. + * + * Return Values: + * path information elements + */ +int +vhci_get_client_path_list(dev_info_t *cdip, sv_path_info_t *pibuf, + uint_t num_elems) +{ + uint_t count, done; + mdi_pathinfo_t *pip; + sv_path_info_t *ret_pip; + int status; + size_t prop_size; + int circular; + + ret_pip = pibuf; + count = 0; + + ndi_devi_enter(cdip, &circular); + + done = (count >= num_elems); + pip = mdi_get_next_phci_path(cdip, NULL); + while (pip && !done) { + mdi_pi_lock(pip); + (void) ddi_pathname(mdi_pi_get_phci(pip), + ret_pip->device.ret_phci); + (void) strcpy(ret_pip->ret_addr, mdi_pi_get_addr(pip)); + (void) mdi_pi_get_state2(pip, &ret_pip->ret_state, + &ret_pip->ret_ext_state); + + status = mdi_prop_size(pip, &prop_size); + if (status == MDI_SUCCESS && ret_pip->ret_prop.ret_buf_size) { + *ret_pip->ret_prop.ret_buf_size = (uint_t)prop_size; + } + +#ifdef DEBUG + if (status != MDI_SUCCESS) { + VHCI_DEBUG(2, (CE_WARN, NULL, + "!vhci_get_client_path_list: " + "phci <%s>, prop size failure 0x%x", + ret_pip->device.ret_phci, status)); + } +#endif /* DEBUG */ + + + if (status == MDI_SUCCESS && ret_pip->ret_prop.buf && + prop_size && ret_pip->ret_prop.buf_size >= prop_size) { + status = mdi_prop_pack(pip, + &ret_pip->ret_prop.buf, + ret_pip->ret_prop.buf_size); + +#ifdef DEBUG + if (status != MDI_SUCCESS) { + VHCI_DEBUG(2, (CE_WARN, NULL, + "!vhci_get_client_path_list: " + "phci <%s>, prop pack failure 0x%x", + ret_pip->device.ret_phci, status)); + } +#endif /* DEBUG */ + } + + mdi_pi_unlock(pip); + pip = mdi_get_next_phci_path(cdip, pip); + ret_pip++; + count++; + done = (count >= num_elems); + } + + ndi_devi_exit(cdip, circular); + + return (MDI_SUCCESS); +} + + +/* + * Routine to get ioctl argument structure from userland. + */ +/* ARGSUSED */ +static int +vhci_get_iocdata(const void *data, sv_iocdata_t *pioc, int mode, caddr_t s) +{ + int retval = 0; + +#ifdef _MULTI_DATAMODEL + switch (ddi_model_convert_from(mode & FMODELS)) { + case DDI_MODEL_ILP32: + { + sv_iocdata32_t ioc32; + + if (ddi_copyin(data, &ioc32, sizeof (ioc32), mode)) { + retval = EFAULT; + break; + } + pioc->client = (caddr_t)(uintptr_t)ioc32.client; + pioc->phci = (caddr_t)(uintptr_t)ioc32.phci; + pioc->addr = (caddr_t)(uintptr_t)ioc32.addr; + pioc->buf_elem = (uint_t)ioc32.buf_elem; + pioc->ret_buf = (sv_path_info_t *)(uintptr_t)ioc32.ret_buf; + pioc->ret_elem = (uint_t *)(uintptr_t)ioc32.ret_elem; + break; + } + + case DDI_MODEL_NONE: + if (ddi_copyin(data, pioc, sizeof (*pioc), mode)) { + retval = EFAULT; + break; + } + break; + } +#else /* _MULTI_DATAMODEL */ + if (ddi_copyin(data, pioc, sizeof (*pioc), mode)) { + retval = EFAULT; + } +#endif /* _MULTI_DATAMODEL */ + +#ifdef DEBUG + if (retval) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_get_ioc: cmd <%s> " + "iocdata copyin failed", s)); + } +#endif + + return (retval); +} + + +/* + * Routine to get the ioctl argument for ioctl causing controller switchover. + */ +/* ARGSUSED */ +static int +vhci_get_iocswitchdata(const void *data, sv_switch_to_cntlr_iocdata_t *piocsc, + int mode, caddr_t s) +{ + int retval = 0; + +#ifdef _MULTI_DATAMODEL + switch (ddi_model_convert_from(mode & FMODELS)) { + case DDI_MODEL_ILP32: + { + sv_switch_to_cntlr_iocdata32_t ioc32; + + if (ddi_copyin(data, &ioc32, sizeof (ioc32), mode)) { + retval = EFAULT; + break; + } + piocsc->client = (caddr_t)(uintptr_t)ioc32.client; + piocsc->class = (caddr_t)(uintptr_t)ioc32.class; + break; + } + + case DDI_MODEL_NONE: + if (ddi_copyin(data, piocsc, sizeof (*piocsc), mode)) { + retval = EFAULT; + } + break; + } +#else /* _MULTI_DATAMODEL */ + if (ddi_copyin(data, piocsc, sizeof (*piocsc), mode)) { + retval = EFAULT; + } +#endif /* _MULTI_DATAMODEL */ + +#ifdef DEBUG + if (retval) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_get_ioc: cmd <%s> " + "switch_to_cntlr_iocdata copyin failed", s)); + } +#endif + + return (retval); +} + + +/* + * Routine to allocate memory for the path information structures. + * It allocates two chunks of memory - one for keeping userland + * pointers/values for path information and path properties, second for + * keeping allocating kernel memory for path properties. These path + * properties are finally copied to userland. + */ +/* ARGSUSED */ +static int +vhci_ioc_alloc_pathinfo(sv_path_info_t **upibuf, sv_path_info_t **kpibuf, + uint_t num_paths, sv_iocdata_t *pioc, int mode, caddr_t s) +{ + sv_path_info_t *pi; + uint_t bufsize; + int retval = 0; + int index; + + /* Allocate memory */ + *upibuf = (sv_path_info_t *) + kmem_zalloc(sizeof (sv_path_info_t) * num_paths, KM_SLEEP); + ASSERT(*upibuf != NULL); + *kpibuf = (sv_path_info_t *) + kmem_zalloc(sizeof (sv_path_info_t) * num_paths, KM_SLEEP); + ASSERT(*kpibuf != NULL); + + /* + * Get the path info structure from the user space. + * We are interested in the following fields: + * - user size of buffer for per path properties. + * - user address of buffer for path info properties. + * - user pointer for returning actual buffer size + * Keep these fields in the 'upibuf' structures. + * Allocate buffer for per path info properties in kernel + * structure ('kpibuf'). + * Size of these buffers will be equal to the size of buffers + * in the user space. + */ +#ifdef _MULTI_DATAMODEL + switch (ddi_model_convert_from(mode & FMODELS)) { + case DDI_MODEL_ILP32: + { + sv_path_info32_t *src; + sv_path_info32_t pi32; + + src = (sv_path_info32_t *)pioc->ret_buf; + pi = (sv_path_info_t *)*upibuf; + for (index = 0; index < num_paths; index++, src++, pi++) { + if (ddi_copyin(src, &pi32, sizeof (pi32), mode)) { + retval = EFAULT; + break; + } + + pi->ret_prop.buf_size = + (uint_t)pi32.ret_prop.buf_size; + pi->ret_prop.ret_buf_size = + (uint_t *)(uintptr_t)pi32.ret_prop.ret_buf_size; + pi->ret_prop.buf = + (caddr_t)(uintptr_t)pi32.ret_prop.buf; + } + break; + } + + case DDI_MODEL_NONE: + if (ddi_copyin(pioc->ret_buf, *upibuf, + sizeof (sv_path_info_t) * num_paths, mode)) { + retval = EFAULT; + } + break; + } +#else /* _MULTI_DATAMODEL */ + if (ddi_copyin(pioc->ret_buf, *upibuf, + sizeof (sv_path_info_t) * num_paths, mode)) { + retval = EFAULT; + } +#endif /* _MULTI_DATAMODEL */ + + if (retval != 0) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_alloc_path_info: " + "ioctl <%s> normal: path_info copyin failed", s)); + kmem_free(*upibuf, sizeof (sv_path_info_t) * num_paths); + kmem_free(*kpibuf, sizeof (sv_path_info_t) * num_paths); + *upibuf = NULL; + *kpibuf = NULL; + return (retval); + } + + /* + * Allocate memory for per path properties. + */ + for (index = 0, pi = *kpibuf; index < num_paths; index++, pi++) { + bufsize = (*upibuf)[index].ret_prop.buf_size; + + if (bufsize && bufsize <= SV_PROP_MAX_BUF_SIZE) { + pi->ret_prop.buf_size = bufsize; + pi->ret_prop.buf = (caddr_t) + kmem_zalloc(bufsize, KM_SLEEP); + ASSERT(pi->ret_prop.buf != NULL); + } else { + pi->ret_prop.buf_size = 0; + pi->ret_prop.buf = NULL; + } + + if ((*upibuf)[index].ret_prop.ret_buf_size != NULL) { + pi->ret_prop.ret_buf_size = (uint_t *)kmem_zalloc( + sizeof (*pi->ret_prop.ret_buf_size), KM_SLEEP); + ASSERT(pi->ret_prop.ret_buf_size != NULL); + } else { + pi->ret_prop.ret_buf_size = NULL; + } + } + + return (0); +} + + +/* + * Routine to free memory for the path information structures. + * This is the memory which was allocated earlier. + */ +/* ARGSUSED */ +static void +vhci_ioc_free_pathinfo(sv_path_info_t *upibuf, sv_path_info_t *kpibuf, + uint_t num_paths) +{ + sv_path_info_t *pi; + int index; + + /* Free memory for per path properties */ + for (index = 0, pi = kpibuf; index < num_paths; index++, pi++) { + if (pi->ret_prop.ret_buf_size != NULL) { + kmem_free(pi->ret_prop.ret_buf_size, + sizeof (*pi->ret_prop.ret_buf_size)); + } + + if (pi->ret_prop.buf != NULL) { + kmem_free(pi->ret_prop.buf, pi->ret_prop.buf_size); + } + } + + /* Free memory for path info structures */ + kmem_free(upibuf, sizeof (sv_path_info_t) * num_paths); + kmem_free(kpibuf, sizeof (sv_path_info_t) * num_paths); +} + + +/* + * Routine to copy path information and path properties to userland. + */ +/* ARGSUSED */ +static int +vhci_ioc_send_pathinfo(sv_path_info_t *upibuf, sv_path_info_t *kpibuf, + uint_t num_paths, sv_iocdata_t *pioc, int mode, caddr_t s) +{ + int retval = 0, index; + sv_path_info_t *upi_ptr; + sv_path_info32_t *upi32_ptr; + +#ifdef _MULTI_DATAMODEL + switch (ddi_model_convert_from(mode & FMODELS)) { + case DDI_MODEL_ILP32: + goto copy_32bit; + + case DDI_MODEL_NONE: + goto copy_normal; + } +#else /* _MULTI_DATAMODEL */ + + goto copy_normal; + +#endif /* _MULTI_DATAMODEL */ + +copy_normal: + + /* + * Copy path information and path properties to user land. + * Pointer fields inside the path property structure were + * saved in the 'upibuf' structure earlier. + */ + upi_ptr = pioc->ret_buf; + for (index = 0; index < num_paths; index++) { + if (ddi_copyout(kpibuf[index].device.ret_ct, + upi_ptr[index].device.ret_ct, MAXPATHLEN, mode)) { + retval = EFAULT; + break; + } + + if (ddi_copyout(kpibuf[index].ret_addr, + upi_ptr[index].ret_addr, MAXNAMELEN, mode)) { + retval = EFAULT; + break; + } + + if (ddi_copyout(&kpibuf[index].ret_state, + &upi_ptr[index].ret_state, sizeof (kpibuf[index].ret_state), + mode)) { + retval = EFAULT; + break; + } + + if (ddi_copyout(&kpibuf[index].ret_ext_state, + &upi_ptr[index].ret_ext_state, + sizeof (kpibuf[index].ret_ext_state), mode)) { + retval = EFAULT; + break; + } + + if ((kpibuf[index].ret_prop.ret_buf_size != NULL) && + ddi_copyout(kpibuf[index].ret_prop.ret_buf_size, + upibuf[index].ret_prop.ret_buf_size, + sizeof (*upibuf[index].ret_prop.ret_buf_size), mode)) { + retval = EFAULT; + break; + } + + if ((kpibuf[index].ret_prop.buf != NULL) && + ddi_copyout(kpibuf[index].ret_prop.buf, + upibuf[index].ret_prop.buf, + upibuf[index].ret_prop.buf_size, mode)) { + retval = EFAULT; + break; + } + } + +#ifdef DEBUG + if (retval) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_get_ioc: ioctl <%s> " + "normal: path_info copyout failed", s)); + } +#endif + + return (retval); + +copy_32bit: + /* + * Copy path information and path properties to user land. + * Pointer fields inside the path property structure were + * saved in the 'upibuf' structure earlier. + */ + upi32_ptr = (sv_path_info32_t *)pioc->ret_buf; + for (index = 0; index < num_paths; index++) { + if (ddi_copyout(kpibuf[index].device.ret_ct, + upi32_ptr[index].device.ret_ct, MAXPATHLEN, mode)) { + retval = EFAULT; + break; + } + + if (ddi_copyout(kpibuf[index].ret_addr, + upi32_ptr[index].ret_addr, MAXNAMELEN, mode)) { + retval = EFAULT; + break; + } + + if (ddi_copyout(&kpibuf[index].ret_state, + &upi32_ptr[index].ret_state, + sizeof (kpibuf[index].ret_state), mode)) { + retval = EFAULT; + break; + } + + if (ddi_copyout(&kpibuf[index].ret_ext_state, + &upi32_ptr[index].ret_ext_state, + sizeof (kpibuf[index].ret_ext_state), mode)) { + retval = EFAULT; + break; + } + if ((kpibuf[index].ret_prop.ret_buf_size != NULL) && + ddi_copyout(kpibuf[index].ret_prop.ret_buf_size, + upibuf[index].ret_prop.ret_buf_size, + sizeof (*upibuf[index].ret_prop.ret_buf_size), mode)) { + retval = EFAULT; + break; + } + + if ((kpibuf[index].ret_prop.buf != NULL) && + ddi_copyout(kpibuf[index].ret_prop.buf, + upibuf[index].ret_prop.buf, + upibuf[index].ret_prop.buf_size, mode)) { + retval = EFAULT; + break; + } + } + +#ifdef DEBUG + if (retval) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_get_ioc: ioctl <%s> " + "normal: path_info copyout failed", s)); + } +#endif + + return (retval); +} + + +/* + * vhci_failover() + * This routine expects VHCI_HOLD_LUN before being invoked. It can be invoked + * as MDI_FAILOVER_ASYNC or MDI_FAILOVER_SYNC. For Asynchronous failovers + * this routine shall VHCI_RELEASE_LUN on exiting. For synchronous failovers + * it is the callers responsibility to release lun. + */ + +/* ARGSUSED */ +static int +vhci_failover(dev_info_t *vdip, dev_info_t *cdip, int flags) +{ + char *guid; + scsi_vhci_lun_t *vlun = NULL; + struct scsi_vhci *vhci; + mdi_pathinfo_t *pip, *npip; + char *s_pclass, *pclass1, *pclass2, *pclass; + char active_pclass_copy[255], *active_pclass_ptr; + char *ptr1, *ptr2; + mdi_pathinfo_state_t pi_state; + uint32_t pi_ext_state; + scsi_vhci_priv_t *svp; + struct scsi_device *sd; + struct scsi_failover_ops *sfo; + int sps; /* mdi_select_path() status */ + int activation_done = 0; + int rval, retval = MDI_FAILURE; + int reserve_pending, check_condition, UA_condition; + struct scsi_pkt *pkt; + struct buf *bp; + + vhci = ddi_get_soft_state(vhci_softstate, ddi_get_instance(vdip)); + sd = ddi_get_driver_private(cdip); + vlun = ADDR2VLUN(&sd->sd_address); + ASSERT(vlun != 0); + ASSERT(VHCI_LUN_IS_HELD(vlun)); + guid = vlun->svl_lun_wwn; + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_failover(1): guid %s\n", guid)); + vhci_log(CE_NOTE, vdip, "!Initiating failover for device %s " + "(GUID %s)", ddi_node_name(cdip), guid); + + /* + * Lets maintain a local copy of the vlun->svl_active_pclass + * for the rest of the processing. Accessing the field + * directly in the loop below causes loop logic to break + * especially when the field gets updated by other threads + * update path status etc and causes 'paths are not currently + * available' condition to be declared prematurely. + */ + mutex_enter(&vlun->svl_mutex); + if (vlun->svl_active_pclass != NULL) { + (void) strlcpy(active_pclass_copy, vlun->svl_active_pclass, + sizeof (active_pclass_copy)); + active_pclass_ptr = &active_pclass_copy[0]; + mutex_exit(&vlun->svl_mutex); + if (vhci_quiesce_paths(vdip, cdip, vlun, guid, + active_pclass_ptr) != 0) { + retval = MDI_FAILURE; + } + } else { + /* + * can happen only when the available path to device + * discovered is a STANDBY path. + */ + mutex_exit(&vlun->svl_mutex); + active_pclass_copy[0] = '\0'; + active_pclass_ptr = NULL; + } + + sfo = vlun->svl_fops; + ASSERT(sfo != NULL); + pclass1 = s_pclass = active_pclass_ptr; + VHCI_DEBUG(1, (CE_NOTE, NULL, "!(%s)failing over from %s\n", guid, + (s_pclass == NULL ? "<none>" : s_pclass))); + +next_pathclass: + + rval = (*sfo->sfo_pathclass_next)(pclass1, &pclass2, + vlun->svl_fops_ctpriv); + if (rval == ENOENT) { + if (s_pclass == NULL) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_failover(4)(%s): " + "failed, no more pathclasses\n", guid)); + goto done; + } else { + (*sfo->sfo_pathclass_next)(NULL, &pclass2, + vlun->svl_fops_ctpriv); + } + } else if (rval == EINVAL) { + vhci_log(CE_NOTE, vdip, "!Failover operation failed for " + "device %s (GUID %s): Invalid path-class %s", + ddi_node_name(cdip), guid, + ((pclass1 == NULL) ? "<none>" : pclass1)); + goto done; + } + if ((s_pclass != NULL) && (strcmp(pclass2, s_pclass) == 0)) { + /* + * paths are not currently available + */ + vhci_log(CE_NOTE, vdip, "!Failover path currently unavailable" + " for device %s (GUID %s)", + ddi_node_name(cdip), guid); + goto done; + } + pip = npip = NULL; + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_failover(5.2)(%s): considering " + "%s as failover destination\n", guid, pclass2)); + sps = mdi_select_path(cdip, NULL, MDI_SELECT_STANDBY_PATH, NULL, &npip); + if ((npip == NULL) || (sps != MDI_SUCCESS)) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_failover(%s): no " + "STANDBY paths found (status:%x)!\n", guid, sps)); + pclass1 = pclass2; + goto next_pathclass; + } + do { + pclass = NULL; + if ((mdi_prop_lookup_string(npip, "path-class", + &pclass) != MDI_SUCCESS) || (strcmp(pclass2, + pclass) != 0)) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_failover(5.5)(%s): skipping path " + "%p(%s)...\n", guid, (void *)npip, pclass)); + pip = npip; + sps = mdi_select_path(cdip, NULL, + MDI_SELECT_STANDBY_PATH, pip, &npip); + mdi_rele_path(pip); + (void) mdi_prop_free(pclass); + continue; + } + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(npip); + + /* + * Issue READ at non-zer block on this STANDBY path. + * Purple returns + * 1. RESERVATION_CONFLICT if reservation is pending + * 2. POR check condition if it reset happened. + * 2. failover Check Conditions if one is already in progress. + */ + reserve_pending = 0; + check_condition = 0; + UA_condition = 0; + + bp = scsi_alloc_consistent_buf(&svp->svp_psd->sd_address, + (struct buf *)NULL, DEV_BSIZE, B_READ, NULL, NULL); + if (!bp) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "vhci_failover !No resources (buf)\n")); + mdi_rele_path(npip); + goto done; + } + pkt = scsi_init_pkt(&svp->svp_psd->sd_address, NULL, bp, + CDB_GROUP1, sizeof (struct scsi_arq_status), 0, + PKT_CONSISTENT, NULL, NULL); + if (pkt) { + (void) scsi_setup_cdb((union scsi_cdb *)(uintptr_t) + pkt->pkt_cdbp, SCMD_READ, 1, 1, 0); + pkt->pkt_flags = FLAG_NOINTR; +check_path_again: + pkt->pkt_time = 3*30; + if (scsi_transport(pkt) == TRAN_ACCEPT) { + switch (pkt->pkt_reason) { + case CMD_CMPLT: + switch (SCBP_C(pkt)) { + case STATUS_GOOD: + /* Already failed over */ + activation_done = 1; + break; + case STATUS_RESERVATION_CONFLICT: + reserve_pending = 1; + break; + case STATUS_CHECK: + check_condition = 1; + break; + } + } + } + if (check_condition && + (pkt->pkt_state & STATE_ARQ_DONE)) { + struct scsi_extended_sense *sns = + &(((struct scsi_arq_status *)(uintptr_t) + (pkt->pkt_scbp))->sts_sensedata); + if (sns->es_key == KEY_UNIT_ATTENTION && + sns->es_add_code == 0x29) { + /* Already failed over */ + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_failover(7)(%s): " + "path 0x%p POR UA condition\n", + guid, (void *)npip)); + if (UA_condition == 0) { + UA_condition = 1; + goto check_path_again; + } + } else { + activation_done = 0; + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_failover(%s): path 0x%p " + "unhandled chkcond %x %x %x\n", + guid, (void *)npip, sns->es_key, + sns->es_add_code, + sns->es_qual_code)); + } + } + scsi_destroy_pkt(pkt); + } + scsi_free_consistent_buf(bp); + + if (activation_done) { + mdi_rele_path(npip); + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_failover(7)(%s): " + "path 0x%p already failedover\n", guid, + (void *)npip)); + break; + } + if (reserve_pending && (vlun->svl_xlf_capable == 0)) { + (void) vhci_recovery_reset(vlun, + &svp->svp_psd->sd_address, + FALSE, VHCI_DEPTH_ALL); + } + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_failover(6)(%s): " + "activating path 0x%p(psd:%p)\n", guid, (void *)npip, + (void *)svp->svp_psd)); + if ((*sfo->sfo_path_activate)(svp->svp_psd, pclass2, + vlun->svl_fops_ctpriv) == 0) { + activation_done = 1; + mdi_rele_path(npip); + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_failover(7)(%s): " + "path 0x%p successfully activated\n", guid, + (void *)npip)); + break; + } + pip = npip; + sps = mdi_select_path(cdip, NULL, MDI_SELECT_STANDBY_PATH, + pip, &npip); + mdi_rele_path(pip); + } while ((npip != NULL) && (sps == MDI_SUCCESS)); + if (activation_done == 0) { + pclass1 = pclass2; + goto next_pathclass; + } + + /* + * if we are here, we have succeeded in activating path npip of + * pathclass pclass2; let us validate all paths of pclass2 by + * "ping"-ing each one and mark the good ones ONLINE + * Also, set the state of the paths belonging to the previously + * active pathclass to STANDBY + */ + pip = npip = NULL; + sps = mdi_select_path(cdip, NULL, (MDI_SELECT_ONLINE_PATH | + MDI_SELECT_STANDBY_PATH | MDI_SELECT_USER_DISABLE_PATH), + NULL, &npip); + if (npip == NULL || sps != MDI_SUCCESS) { + VHCI_DEBUG(1, (CE_NOTE, NULL, "!Failover operation failed for " + "device %s (GUID %s): paths may be busy\n", + ddi_node_name(cdip), guid)); + goto done; + } + do { + (void) mdi_pi_get_state2(npip, &pi_state, &pi_ext_state); + if (mdi_prop_lookup_string(npip, "path-class", &pclass) + != MDI_SUCCESS) { + pip = npip; + sps = mdi_select_path(cdip, NULL, + (MDI_SELECT_ONLINE_PATH | + MDI_SELECT_STANDBY_PATH | + MDI_SELECT_USER_DISABLE_PATH), + pip, &npip); + mdi_rele_path(pip); + continue; + } + if (strcmp(pclass, pclass2) == 0) { + if (pi_state == MDI_PATHINFO_STATE_STANDBY) { + svp = (scsi_vhci_priv_t *) + mdi_pi_get_vhci_private(npip); + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_failover(8)(%s): " + "pinging path 0x%p\n", + guid, (void *)npip)); + if ((*sfo->sfo_path_ping)(svp->svp_psd, + vlun->svl_fops_ctpriv) == 1) { + mdi_pi_set_state(npip, + MDI_PATHINFO_STATE_ONLINE); + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_failover(9)(%s): " + "path 0x%p ping successful, " + "marked online\n", guid, + (void *)npip)); + MDI_PI_ERRSTAT(npip, MDI_PI_FAILTO); + } + } + } else if ((s_pclass != NULL) && (strcmp(pclass, s_pclass) + == 0)) { + if (pi_state == MDI_PATHINFO_STATE_ONLINE) { + mdi_pi_set_state(npip, + MDI_PATHINFO_STATE_STANDBY); + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_failover(10)(%s): path 0x%p marked " + "STANDBY\n", guid, (void *)npip)); + MDI_PI_ERRSTAT(npip, MDI_PI_FAILFROM); + } + } + (void) mdi_prop_free(pclass); + pip = npip; + sps = mdi_select_path(cdip, NULL, (MDI_SELECT_ONLINE_PATH | + MDI_SELECT_STANDBY_PATH|MDI_SELECT_USER_DISABLE_PATH), + pip, &npip); + mdi_rele_path(pip); + } while ((npip != NULL) && (sps == MDI_SUCCESS)); + + /* + * Update the AccessState of related MP-API TPGs + */ + (void) vhci_mpapi_update_tpg_acc_state_for_lu(vhci, vlun); + + vhci_log(CE_NOTE, vdip, "!Failover operation completed successfully " + "for device %s (GUID %s): failed over from %s to %s", + ddi_node_name(cdip), guid, ((s_pclass == NULL) ? "<none>" : + s_pclass), pclass2); + ptr1 = kmem_alloc(strlen(pclass2)+1, KM_SLEEP); + (void) strlcpy(ptr1, pclass2, (strlen(pclass2)+1)); + mutex_enter(&vlun->svl_mutex); + ptr2 = vlun->svl_active_pclass; + vlun->svl_active_pclass = ptr1; + mutex_exit(&vlun->svl_mutex); + if (ptr2) { + kmem_free(ptr2, strlen(ptr2)+1); + } + mutex_enter(&vhci->vhci_mutex); + scsi_hba_reset_notify_callback(&vhci->vhci_mutex, + &vhci->vhci_reset_notify_listf); + /* All reservations are cleared upon these resets. */ + vlun->svl_flags &= ~VLUN_RESERVE_ACTIVE_FLG; + mutex_exit(&vhci->vhci_mutex); + VHCI_DEBUG(1, (CE_NOTE, NULL, "!vhci_failover(11): DONE! Active " + "pathclass for %s is now %s\n", guid, pclass2)); + retval = MDI_SUCCESS; + +done: + if (flags == MDI_FAILOVER_ASYNC) { + VHCI_RELEASE_LUN(vlun); + VHCI_DEBUG(6, (CE_NOTE, NULL, "!vhci_failover(12): DONE! " + "releasing lun, as failover was ASYNC\n")); + } else { + VHCI_DEBUG(6, (CE_NOTE, NULL, "!vhci_failover(12): DONE! " + "NOT releasing lun, as failover was SYNC\n")); + } + return (retval); +} + +/* + * vhci_client_attached is called after the successful attach of a + * client devinfo node. + */ +static void +vhci_client_attached(dev_info_t *cdip) +{ + mdi_pathinfo_t *pip; + int circular; + + /* + * At this point the client has attached and it's instance number is + * valid, so we can set up kstats. We need to do this here because it + * is possible for paths to go online prior to client attach, in which + * case the call to vhci_kstat_create_pathinfo in vhci_pathinfo_online + * was a noop. + */ + ndi_devi_enter(cdip, &circular); + for (pip = mdi_get_next_phci_path(cdip, NULL); pip; + pip = mdi_get_next_phci_path(cdip, pip)) + vhci_kstat_create_pathinfo(pip); + ndi_devi_exit(cdip, circular); +} + +/* + * quiesce all of the online paths + */ +static int +vhci_quiesce_paths(dev_info_t *vdip, dev_info_t *cdip, scsi_vhci_lun_t *vlun, + char *guid, char *active_pclass_ptr) +{ + scsi_vhci_priv_t *svp; + char *s_pclass = NULL; + mdi_pathinfo_t *npip, *pip; + int sps; + + /* quiesce currently active paths */ + s_pclass = NULL; + pip = npip = NULL; + sps = mdi_select_path(cdip, NULL, MDI_SELECT_ONLINE_PATH, NULL, &npip); + if ((npip == NULL) || (sps != MDI_SUCCESS)) { + return (1); + } + do { + if (mdi_prop_lookup_string(npip, "path-class", + &s_pclass) != MDI_SUCCESS) { + mdi_rele_path(npip); + vhci_log(CE_NOTE, vdip, "!Failover operation failed " + "for device %s (GUID %s) due to an internal " + "error", ddi_node_name(cdip), guid); + return (1); + } + if (strcmp(s_pclass, active_pclass_ptr) == 0) { + /* + * quiesce path. Free s_pclass since + * we don't need it anymore + */ + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_failover(2)(%s): failing over " + "from %s; quiescing path %p\n", + guid, s_pclass, (void *)npip)); + (void) mdi_prop_free(s_pclass); + svp = (scsi_vhci_priv_t *) + mdi_pi_get_vhci_private(npip); + if (svp == NULL) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_failover(2.5)(%s): no " + "client priv! %p offlined?\n", + guid, (void *)npip)); + pip = npip; + sps = mdi_select_path(cdip, NULL, + MDI_SELECT_ONLINE_PATH, pip, &npip); + mdi_rele_path(pip); + continue; + } + if (scsi_abort(&svp->svp_psd->sd_address, NULL) + == 0) { + (void) vhci_recovery_reset(vlun, + &svp->svp_psd->sd_address, FALSE, + VHCI_DEPTH_TARGET); + } + mutex_enter(&svp->svp_mutex); + if (svp->svp_cmds == 0) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_failover(3)(%s):" + "quiesced path %p\n", guid, (void *)npip)); + } else { + while (svp->svp_cmds != 0) { + cv_wait(&svp->svp_cv, &svp->svp_mutex); + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!vhci_failover(3.cv)(%s):" + "quiesced path %p\n", guid, + (void *)npip)); + } + } + mutex_exit(&svp->svp_mutex); + } else { + /* + * make sure we freeup the memory + */ + (void) mdi_prop_free(s_pclass); + } + pip = npip; + sps = mdi_select_path(cdip, NULL, MDI_SELECT_ONLINE_PATH, + pip, &npip); + mdi_rele_path(pip); + } while ((npip != NULL) && (sps == MDI_SUCCESS)); + return (0); +} + +static struct scsi_vhci_lun * +vhci_lun_lookup(dev_info_t *tgt_dip) +{ + return ((struct scsi_vhci_lun *) + mdi_client_get_vhci_private(tgt_dip)); +} + +static struct scsi_vhci_lun * +vhci_lun_lookup_alloc(dev_info_t *tgt_dip, char *guid, int *didalloc) +{ + struct scsi_vhci_lun *svl; + + if (svl = vhci_lun_lookup(tgt_dip)) { + return (svl); + } + + svl = kmem_zalloc(sizeof (*svl), KM_SLEEP); + svl->svl_lun_wwn = kmem_zalloc(strlen(guid)+1, KM_SLEEP); + (void) strcpy(svl->svl_lun_wwn, guid); + mutex_init(&svl->svl_mutex, NULL, MUTEX_DRIVER, NULL); + cv_init(&svl->svl_cv, NULL, CV_DRIVER, NULL); + sema_init(&svl->svl_pgr_sema, 1, NULL, SEMA_DRIVER, NULL); + svl->svl_waiting_for_activepath = 1; + svl->svl_sector_size = 1; + mdi_client_set_vhci_private(tgt_dip, svl); + *didalloc = 1; + VHCI_DEBUG(1, (CE_NOTE, NULL, + "vhci_lun_lookup_alloc: guid %s vlun 0x%p\n", + guid, (void *)svl)); + return (svl); +} + +static void +vhci_lun_free(dev_info_t *tgt_dip) +{ + struct scsi_vhci_lun *dvlp; + char *guid; + + dvlp = (struct scsi_vhci_lun *) + mdi_client_get_vhci_private(tgt_dip); + ASSERT(dvlp != NULL); + + mdi_client_set_vhci_private(tgt_dip, NULL); + + guid = dvlp->svl_lun_wwn; + ASSERT(guid != NULL); + VHCI_DEBUG(4, (CE_NOTE, NULL, "!vhci_lun_free: %s\n", guid)); + + mutex_enter(&dvlp->svl_mutex); + if (dvlp->svl_active_pclass != NULL) { + kmem_free(dvlp->svl_active_pclass, + strlen(dvlp->svl_active_pclass)+1); + } + dvlp->svl_active_pclass = NULL; + mutex_exit(&dvlp->svl_mutex); + + if (dvlp->svl_lun_wwn != NULL) { + kmem_free(dvlp->svl_lun_wwn, strlen(dvlp->svl_lun_wwn)+1); + } + dvlp->svl_lun_wwn = NULL; + + if (dvlp->svl_fops_name) { + kmem_free(dvlp->svl_fops_name, strlen(dvlp->svl_fops_name)+1); + } + dvlp->svl_fops_name = NULL; + + if (dvlp->svl_flags & VLUN_TASK_D_ALIVE_FLG) + taskq_destroy(dvlp->svl_taskq); + + mutex_destroy(&dvlp->svl_mutex); + cv_destroy(&dvlp->svl_cv); + sema_destroy(&dvlp->svl_pgr_sema); + kmem_free(dvlp, sizeof (*dvlp)); +} + + +int +vhci_do_scsi_cmd(struct scsi_pkt *pkt) +{ + int err = 0; + int retry_cnt = 0; + struct scsi_extended_sense *sns; + +retry: + err = scsi_poll(pkt); + if (err) { + if (pkt->pkt_cdbp[0] == SCMD_RELEASE) { + if (SCBP_C(pkt) == STATUS_RESERVATION_CONFLICT) { + VHCI_DEBUG(1, (CE_NOTE, NULL, + "!v_s_do_s_c: RELEASE conflict\n")); + return (0); + } + } + if (retry_cnt++ < 3) { + VHCI_DEBUG(1, (CE_WARN, NULL, + "!v_s_do_s_c:retry packet 0x%p " + "status 0x%x reason %s", + (void *)pkt, SCBP_C(pkt), + scsi_rname(pkt->pkt_reason))); + if ((pkt->pkt_reason == CMD_CMPLT) && + (SCBP_C(pkt) == STATUS_CHECK) && + (pkt->pkt_state & STATE_ARQ_DONE)) { + sns = &(((struct scsi_arq_status *)(uintptr_t) + (pkt->pkt_scbp))->sts_sensedata); + VHCI_DEBUG(1, (CE_WARN, NULL, + "!v_s_do_s_c:retry " + "packet 0x%p sense data %s", (void *)pkt, + scsi_sname(sns->es_key))); + } + goto retry; + } + VHCI_DEBUG(1, (CE_WARN, NULL, + "!v_s_do_s_c: failed transport 0x%p 0x%x", + (void *)pkt, SCBP_C(pkt))); + return (0); + } + + switch (pkt->pkt_reason) { + case CMD_TIMEOUT: + VHCI_DEBUG(1, (CE_WARN, NULL, "!pkt timed " + "out (pkt 0x%p)", (void *)pkt)); + return (0); + case CMD_CMPLT: + switch (SCBP_C(pkt)) { + case STATUS_GOOD: + break; + case STATUS_CHECK: + if (pkt->pkt_state & STATE_ARQ_DONE) { + sns = &((( + struct scsi_arq_status *) + (uintptr_t) + (pkt->pkt_scbp))-> + sts_sensedata); + if ((sns->es_key == + KEY_UNIT_ATTENTION) || + (sns->es_key == + KEY_NOT_READY)) { + /* + * clear unit attn. + */ + + VHCI_DEBUG(1, + (CE_WARN, NULL, + "!v_s_do_s_c: " + "retry " + "packet 0x%p sense " + "data %s", + (void *)pkt, + scsi_sname + (sns->es_key))); + goto retry; + } + VHCI_DEBUG(4, (CE_WARN, NULL, + "!ARQ while " + "transporting " + "(pkt 0x%p)", + (void *)pkt)); + return (0); + } + return (0); + default: + VHCI_DEBUG(1, (CE_WARN, NULL, + "!Bad status returned " + "(pkt 0x%p, status %x)", + (void *)pkt, SCBP_C(pkt))); + return (0); + } + break; + case CMD_INCOMPLETE: + case CMD_RESET: + case CMD_ABORTED: + case CMD_TRAN_ERR: + if (retry_cnt++ < 1) { + VHCI_DEBUG(1, (CE_WARN, NULL, + "!v_s_do_s_c: retry packet 0x%p %s", + (void *)pkt, scsi_rname(pkt->pkt_reason))); + goto retry; + } + /* FALLTHROUGH */ + default: + VHCI_DEBUG(1, (CE_WARN, NULL, "!pkt did not " + "complete successfully (pkt 0x%p," + "reason %x)", (void *)pkt, pkt->pkt_reason)); + return (0); + } + return (1); +} + +static int +vhci_quiesce_lun(struct scsi_vhci_lun *vlun) +{ + mdi_pathinfo_t *pip, *spip; + dev_info_t *cdip; + struct scsi_vhci_priv *svp; + mdi_pathinfo_state_t pstate; + uint32_t p_ext_state; + int circular; + + cdip = vlun->svl_dip; + pip = spip = NULL; + ndi_devi_enter(cdip, &circular); + pip = mdi_get_next_phci_path(cdip, NULL); + while (pip != NULL) { + (void) mdi_pi_get_state2(pip, &pstate, &p_ext_state); + if (pstate != MDI_PATHINFO_STATE_ONLINE) { + spip = pip; + pip = mdi_get_next_phci_path(cdip, spip); + continue; + } + mdi_hold_path(pip); + ndi_devi_exit(cdip, circular); + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(pip); + mutex_enter(&svp->svp_mutex); + while (svp->svp_cmds != 0) { + if (cv_timedwait(&svp->svp_cv, &svp->svp_mutex, + ddi_get_lbolt() + drv_usectohz + (vhci_path_quiesce_timeout * 1000000)) == -1) { + mutex_exit(&svp->svp_mutex); + mdi_rele_path(pip); + VHCI_DEBUG(1, (CE_WARN, NULL, + "Quiesce of lun is not successful " + "vlun: 0x%p.", (void *)vlun)); + return (0); + } + } + mutex_exit(&svp->svp_mutex); + ndi_devi_enter(cdip, &circular); + spip = pip; + pip = mdi_get_next_phci_path(cdip, spip); + mdi_rele_path(spip); + } + ndi_devi_exit(cdip, circular); + return (1); +} + +static int +vhci_pgr_validate_and_register(scsi_vhci_priv_t *svp) +{ + scsi_vhci_lun_t *vlun; + vhci_prout_t *prout; + int rval, success; + mdi_pathinfo_t *pip, *npip; + scsi_vhci_priv_t *osvp; + dev_info_t *cdip; + uchar_t cdb_1; + uchar_t temp_res_key[MHIOC_RESV_KEY_SIZE]; + + + /* + * see if there are any other paths available; if none, + * then there is nothing to do. + */ + cdip = svp->svp_svl->svl_dip; + rval = mdi_select_path(cdip, NULL, MDI_SELECT_ONLINE_PATH | + MDI_SELECT_STANDBY_PATH, NULL, &pip); + if ((rval != MDI_SUCCESS) || (pip == NULL)) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "%s%d: vhci_pgr_validate_and_register: first path\n", + ddi_driver_name(cdip), ddi_get_instance(cdip))); + return (1); + } + + vlun = svp->svp_svl; + prout = &vlun->svl_prout; + ASSERT(vlun->svl_pgr_active != 0); + + /* + * When the path was busy/offlined, some other host might have + * cleared this key. Validate key on some other path first. + * If it fails, return failure. + */ + + npip = pip; + pip = NULL; + success = 0; + + /* Save the res key */ + bcopy((const void *)prout->res_key, + (void *)temp_res_key, MHIOC_RESV_KEY_SIZE); + + /* + * Sometimes CDB from application can be a Register_And_Ignore. + * Instead of validation, this cdb would result in force registration. + * Convert it to normal cdb for validation. + * After that be sure to restore the cdb. + */ + cdb_1 = vlun->svl_cdb[1]; + vlun->svl_cdb[1] &= 0xe0; + + do { + osvp = (scsi_vhci_priv_t *) + mdi_pi_get_vhci_private(npip); + if (osvp == NULL) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_pgr_validate_and_register: no " + "client priv! 0x%p offlined?\n", + (void *)npip)); + goto next_path_1; + } + + if (osvp == svp) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_pgr_validate_and_register: same svp 0x%p" + " npip 0x%p vlun 0x%p\n", + (void *)svp, (void *)npip, (void *)vlun)); + goto next_path_1; + } + + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_pgr_validate_and_register: First validate on" + " osvp 0x%p being done. vlun 0x%p thread 0x%p Before bcopy" + " cdb1 %x\n", (void *)osvp, (void *)vlun, + (void *)curthread, vlun->svl_cdb[1])); + vhci_print_prout_keys(vlun, "v_pgr_val_reg: before bcopy:"); + + bcopy((const void *)prout->service_key, + (void *)prout->res_key, MHIOC_RESV_KEY_SIZE); + + VHCI_DEBUG(4, (CE_WARN, NULL, "vlun 0x%p After bcopy", + (void *)vlun)); + vhci_print_prout_keys(vlun, "v_pgr_val_reg: after bcopy: "); + + rval = vhci_do_prout(osvp); + if (rval == 1) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "%s%d: vhci_pgr_validate_and_register: key" + " validated thread 0x%p\n", ddi_driver_name(cdip), + ddi_get_instance(cdip), (void *)curthread)); + pip = npip; + success = 1; + break; + } else { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_pgr_validate_and_register: First validation" + " on osvp 0x%p failed %x\n", (void *)osvp, rval)); + vhci_print_prout_keys(vlun, "v_pgr_val_reg: failed:"); + } + + /* + * Try other paths + */ +next_path_1: + pip = npip; + rval = mdi_select_path(cdip, NULL, + MDI_SELECT_ONLINE_PATH|MDI_SELECT_STANDBY_PATH, + pip, &npip); + mdi_rele_path(pip); + } while ((rval == MDI_SUCCESS) && (npip != NULL)); + + + /* Be sure to restore original cdb */ + vlun->svl_cdb[1] = cdb_1; + + /* Restore the res_key */ + bcopy((const void *)temp_res_key, + (void *)prout->res_key, MHIOC_RESV_KEY_SIZE); + + /* + * If key could not be registered on any path for the first time, + * return success as online should still continue. + */ + if (success == 0) { + return (1); + } + + ASSERT(pip != NULL); + + /* + * Force register on new path + */ + cdb_1 = vlun->svl_cdb[1]; /* store the cdb */ + + vlun->svl_cdb[1] &= 0xe0; + vlun->svl_cdb[1] |= VHCI_PROUT_R_AND_IGNORE; + + vhci_print_prout_keys(vlun, "v_pgr_val_reg: keys before bcopy: "); + + bcopy((const void *)prout->active_service_key, + (void *)prout->service_key, MHIOC_RESV_KEY_SIZE); + bcopy((const void *)prout->active_res_key, + (void *)prout->res_key, MHIOC_RESV_KEY_SIZE); + + vhci_print_prout_keys(vlun, "v_pgr_val_reg:keys after bcopy: "); + + rval = vhci_do_prout(svp); + vlun->svl_cdb[1] = cdb_1; /* restore the cdb */ + if (rval != 1) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_pgr_validate_and_register: register on new" + " path 0x%p svp 0x%p failed %x\n", + (void *)pip, (void *)svp, rval)); + vhci_print_prout_keys(vlun, "v_pgr_val_reg: reg failed: "); + mdi_rele_path(pip); + return (0); + } + + if (bcmp(prout->service_key, zero_key, MHIOC_RESV_KEY_SIZE) == 0) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_pgr_validate_and_register: zero service key\n")); + mdi_rele_path(pip); + return (rval); + } + + /* + * While the key was force registered, some other host might have + * cleared the key. Re-validate key on another pre-existing path + * before declaring success. + */ + npip = pip; + pip = NULL; + + /* + * Sometimes CDB from application can be Register and Ignore. + * Instead of validation, it would result in force registration. + * Convert it to normal cdb for validation. + * After that be sure to restore the cdb. + */ + cdb_1 = vlun->svl_cdb[1]; + vlun->svl_cdb[1] &= 0xe0; + success = 0; + + do { + osvp = (scsi_vhci_priv_t *) + mdi_pi_get_vhci_private(npip); + if (osvp == NULL) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_pgr_validate_and_register: no " + "client priv! 0x%p offlined?\n", + (void *)npip)); + goto next_path_2; + } + + if (osvp == svp) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_pgr_validate_and_register: same osvp 0x%p" + " npip 0x%p vlun 0x%p\n", + (void *)svp, (void *)npip, (void *)vlun)); + goto next_path_2; + } + + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_pgr_validate_and_register: Re-validation on" + " osvp 0x%p being done. vlun 0x%p Before bcopy cdb1 %x\n", + (void *)osvp, (void *)vlun, vlun->svl_cdb[1])); + vhci_print_prout_keys(vlun, "v_pgr_val_reg: before bcopy: "); + + bcopy((const void *)prout->service_key, + (void *)prout->res_key, MHIOC_RESV_KEY_SIZE); + + vhci_print_prout_keys(vlun, "v_pgr_val_reg: after bcopy: "); + + rval = vhci_do_prout(osvp); + if (rval == 1) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "%s%d: vhci_pgr_validate_and_register: key" + " validated thread 0x%p\n", ddi_driver_name(cdip), + ddi_get_instance(cdip), (void *)curthread)); + pip = npip; + success = 1; + break; + } else { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_pgr_validate_and_register: Re-validation on" + " osvp 0x%p failed %x\n", (void *)osvp, rval)); + vhci_print_prout_keys(vlun, + "v_pgr_val_reg: reval failed: "); + } + + /* + * Try other paths + */ +next_path_2: + pip = npip; + rval = mdi_select_path(cdip, NULL, + MDI_SELECT_ONLINE_PATH|MDI_SELECT_STANDBY_PATH, + pip, &npip); + mdi_rele_path(pip); + } while ((rval == MDI_SUCCESS) && (npip != NULL)); + + /* Be sure to restore original cdb */ + vlun->svl_cdb[1] = cdb_1; + + if (success == 1) { + /* Successfully validated registration */ + mdi_rele_path(pip); + return (1); + } + + VHCI_DEBUG(4, (CE_WARN, NULL, "key validation failed")); + + /* + * key invalid, back out by registering key value of 0 + */ + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_pgr_validate_and_register: backout on" + " svp 0x%p being done\n", (void *)svp)); + vhci_print_prout_keys(vlun, "v_pgr_val_reg: before bcopy: "); + + bcopy((const void *)prout->service_key, (void *)prout->res_key, + MHIOC_RESV_KEY_SIZE); + bzero((void *)prout->service_key, MHIOC_RESV_KEY_SIZE); + + vhci_print_prout_keys(vlun, "v_pgr_val_reg: before bcopy: "); + + /* + * Get a new path + */ + rval = mdi_select_path(cdip, NULL, MDI_SELECT_ONLINE_PATH | + MDI_SELECT_STANDBY_PATH, NULL, &pip); + if ((rval != MDI_SUCCESS) || (pip == NULL)) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "%s%d: vhci_pgr_validate_and_register: no valid pip\n", + ddi_driver_name(cdip), ddi_get_instance(cdip))); + return (0); + } + + if ((rval = vhci_do_prout(svp)) != 1) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_pgr_validate_and_register: backout on" + " svp 0x%p failed\n", (void *)svp)); + vhci_print_prout_keys(vlun, "backout failed"); + + VHCI_DEBUG(4, (CE_WARN, NULL, + "%s%d: vhci_pgr_validate_and_register: key" + " validation and backout failed", ddi_driver_name(cdip), + ddi_get_instance(cdip))); + if (rval == VHCI_PGR_ILLEGALOP) { + VHCI_DEBUG(4, (CE_WARN, NULL, + "%s%d: vhci_pgr_validate_and_register: key" + " already cleared", ddi_driver_name(cdip), + ddi_get_instance(cdip))); + rval = 1; + } else + rval = 0; + } else { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "%s%d: vhci_pgr_validate_and_register: key" + " validation failed, key backed out\n", + ddi_driver_name(cdip), ddi_get_instance(cdip))); + vhci_print_prout_keys(vlun, "v_pgr_val_reg: key backed out: "); + } + mdi_rele_path(pip); + + return (rval); +} + +/* + * taskq routine to dispatch a scsi cmd to vhci_scsi_start. This ensures + * that vhci_scsi_start is not called in interrupt context. + * As the upper layer gets TRAN_ACCEPT when the command is dispatched, we + * need to complete the command if something goes wrong. + */ +static void +vhci_dispatch_scsi_start(void *arg) +{ + struct vhci_pkt *vpkt = (struct vhci_pkt *)arg; + struct scsi_pkt *tpkt = vpkt->vpkt_tgt_pkt; + int rval = TRAN_BUSY; + + VHCI_DEBUG(6, (CE_NOTE, NULL, "!vhci_dispatch_scsi_start: sending" + " scsi-2 reserve for 0x%p\n", + (void *)ADDR2DIP(&(vpkt->vpkt_tgt_pkt->pkt_address)))); + + /* + * To prevent the taskq from being called recursively we set the + * the VHCI_PKT_THRU_TASKQ bit in the vhci_pkt_states. + */ + vpkt->vpkt_state |= VHCI_PKT_THRU_TASKQ; + + /* + * Wait for the transport to get ready to send packets + * and if it times out, it will return something other than + * TRAN_BUSY. The vhci_reserve_delay may want to + * get tuned for other transports and is therefore a global. + * Using delay since this routine is called by taskq dispatch + * and not called during interrupt context. + */ + while ((rval = vhci_scsi_start(&(vpkt->vpkt_tgt_pkt->pkt_address), + vpkt->vpkt_tgt_pkt)) == TRAN_BUSY) { + delay(drv_usectohz(vhci_reserve_delay)); + } + + switch (rval) { + case TRAN_ACCEPT: + return; + + default: + /* + * This pkt shall be retried, and to ensure another taskq + * is dispatched for it, clear the VHCI_PKT_THRU_TASKQ + * flag. + */ + vpkt->vpkt_state &= ~VHCI_PKT_THRU_TASKQ; + + /* Ensure that the pkt is retried without a reset */ + tpkt->pkt_reason = CMD_ABORTED; + tpkt->pkt_statistics |= STAT_ABORTED; + VHCI_DEBUG(1, (CE_WARN, NULL, "!vhci_dispatch_scsi_start: " + "TRAN_rval %d returned for dip 0x%p", rval, + (void *)ADDR2DIP(&(vpkt->vpkt_tgt_pkt->pkt_address)))); + break; + } + + /* + * vpkt_org_vpkt should always be NULL here if the retry command + * has been successfully dispatched. If vpkt_org_vpkt != NULL at + * this point, it is an error so restore the original vpkt and + * return an error to the target driver so it can retry the + * command as appropriate. + */ + if (vpkt->vpkt_org_vpkt != NULL) { + struct vhci_pkt *new_vpkt = vpkt; + scsi_vhci_priv_t *svp = (scsi_vhci_priv_t *) + mdi_pi_get_vhci_private(vpkt->vpkt_path); + + vpkt = vpkt->vpkt_org_vpkt; + + vpkt->vpkt_tgt_pkt->pkt_reason = tpkt->pkt_reason; + vpkt->vpkt_tgt_pkt->pkt_statistics = tpkt->pkt_statistics; + + vhci_scsi_destroy_pkt(&svp->svp_psd->sd_address, + new_vpkt->vpkt_tgt_pkt); + + tpkt = vpkt->vpkt_tgt_pkt; + } + + if (tpkt->pkt_comp) { + (*tpkt->pkt_comp)(tpkt); + } +} + +static void +vhci_initiate_auto_failback(void *arg) +{ + struct scsi_vhci_lun *vlun = (struct scsi_vhci_lun *)arg; + dev_info_t *vdip, *cdip; + int held; + + cdip = vlun->svl_dip; + vdip = ddi_get_parent(cdip); + + VHCI_HOLD_LUN(vlun, VH_SLEEP, held); + + /* + * Perform a final check to see if the active path class is indeed + * not the prefered path class. As in the time the auto failback + * was dispatched, an external failover could have been detected. + * [Some other host could have detected this condition and triggered + * the auto failback before]. + * In such a case if we go ahead with failover we will be negating the + * whole purpose of auto failback. + */ + mutex_enter(&vlun->svl_mutex); + if (vlun->svl_active_pclass != NULL) { + char *best_pclass; + struct scsi_failover_ops *fo; + + fo = vlun->svl_fops; + + (*fo->sfo_pathclass_next)(NULL, &best_pclass, + vlun->svl_fops_ctpriv); + if (strcmp(vlun->svl_active_pclass, best_pclass) == 0) { + mutex_exit(&vlun->svl_mutex); + VHCI_RELEASE_LUN(vlun); + VHCI_DEBUG(1, (CE_NOTE, NULL, "Not initiating " + "auto failback for %s as %s pathclass already " + "active.\n", vlun->svl_lun_wwn, best_pclass)); + return; + } + } + mutex_exit(&vlun->svl_mutex); + if (mdi_failover(vdip, vlun->svl_dip, MDI_FAILOVER_SYNC) + == MDI_SUCCESS) { + vhci_log(CE_NOTE, vdip, "!Auto failback operation " + "succeeded for device %s (GUID %s)", + ddi_node_name(cdip), vlun->svl_lun_wwn); + } else { + vhci_log(CE_NOTE, vdip, "!Auto failback operation " + "failed for device %s (GUID %s)", + ddi_node_name(cdip), vlun->svl_lun_wwn); + } + VHCI_RELEASE_LUN(vlun); +} + +#ifdef DEBUG +static void +vhci_print_prin_keys(vhci_prin_readkeys_t *prin, int numkeys) +{ + uchar_t index = 0; + char buf[100]; + + VHCI_DEBUG(5, (CE_NOTE, NULL, "num keys %d\n", numkeys)); + + while (index < numkeys) { + bcopy(&prin->keylist[index], buf, MHIOC_RESV_KEY_SIZE); + VHCI_DEBUG(5, (CE_NOTE, NULL, + "%02x%02x%02x%02x%02x%02x%02x%02x\t", + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], + buf[7])); + index++; + } +} +#endif + +static void +vhci_print_prout_keys(scsi_vhci_lun_t *vlun, char *msg) +{ + int i; + vhci_prout_t *prout; + char buf1[4*MHIOC_RESV_KEY_SIZE + 1]; + char buf2[4*MHIOC_RESV_KEY_SIZE + 1]; + char buf3[4*MHIOC_RESV_KEY_SIZE + 1]; + char buf4[4*MHIOC_RESV_KEY_SIZE + 1]; + + prout = &vlun->svl_prout; + + for (i = 0; i < MHIOC_RESV_KEY_SIZE; i++) + (void) sprintf(&buf1[4*i], "[%02x]", prout->res_key[i]); + for (i = 0; i < MHIOC_RESV_KEY_SIZE; i++) + (void) sprintf(&buf2[(4*i)], "[%02x]", prout->service_key[i]); + for (i = 0; i < MHIOC_RESV_KEY_SIZE; i++) + (void) sprintf(&buf3[4*i], "[%02x]", prout->active_res_key[i]); + for (i = 0; i < MHIOC_RESV_KEY_SIZE; i++) + (void) sprintf(&buf4[4*i], "[%02x]", + prout->active_service_key[i]); + + /* Printing all in one go. Otherwise it will jumble up */ + VHCI_DEBUG(5, (CE_CONT, NULL, "%s vlun 0x%p, thread 0x%p\n" + "res_key: : %s\n" + "service_key : %s\n" + "active_res_key : %s\n" + "active_service_key: %s\n", + msg, (void *)vlun, (void *)curthread, buf1, buf2, buf3, buf4)); +} + +/* + * Called from vhci_scsi_start to update the pHCI pkt with target packet. + */ +static void +vhci_update_pHCI_pkt(struct vhci_pkt *vpkt, struct scsi_pkt *pkt) +{ + + ASSERT(vpkt->vpkt_hba_pkt); + + vpkt->vpkt_hba_pkt->pkt_flags = pkt->pkt_flags; + vpkt->vpkt_hba_pkt->pkt_flags |= FLAG_NOQUEUE; + + if ((vpkt->vpkt_hba_pkt->pkt_flags & FLAG_NOINTR) || + MDI_PI_IS_SUSPENDED(vpkt->vpkt_path)) { + /* + * Polled Command is requested or HBA is in + * suspended state + */ + vpkt->vpkt_hba_pkt->pkt_flags |= FLAG_NOINTR; + vpkt->vpkt_hba_pkt->pkt_comp = NULL; + } else { + vpkt->vpkt_hba_pkt->pkt_comp = vhci_intr; + } + vpkt->vpkt_hba_pkt->pkt_time = pkt->pkt_time; + bcopy(pkt->pkt_cdbp, vpkt->vpkt_hba_pkt->pkt_cdbp, + vpkt->vpkt_tgt_init_cdblen); + vpkt->vpkt_hba_pkt->pkt_resid = pkt->pkt_resid; + + /* Re-initialize the following pHCI packet state information */ + vpkt->vpkt_hba_pkt->pkt_state = 0; + vpkt->vpkt_hba_pkt->pkt_statistics = 0; + vpkt->vpkt_hba_pkt->pkt_reason = 0; +} + +static int +vhci_scsi_bus_power(dev_info_t *parent, void *impl_arg, pm_bus_power_op_t op, + void *arg, void *result) +{ + int ret = DDI_SUCCESS; + + /* + * Generic processing in MPxIO framework + */ + ret = mdi_bus_power(parent, impl_arg, op, arg, result); + + switch (ret) { + case MDI_SUCCESS: + ret = DDI_SUCCESS; + break; + case MDI_FAILURE: + ret = DDI_FAILURE; + break; + default: + break; + } + + return (ret); +} + +static int +vhci_pHCI_cap(struct scsi_address *ap, char *cap, int val, int whom, + mdi_pathinfo_t *pip) +{ + dev_info_t *cdip; + mdi_pathinfo_t *npip = NULL; + scsi_vhci_priv_t *svp = NULL; + struct scsi_address *pap = NULL; + scsi_hba_tran_t *hba = NULL; + int sps; + int mps_flag; + int rval = 0; + + mps_flag = (MDI_SELECT_ONLINE_PATH | MDI_SELECT_STANDBY_PATH); + if (pip) { + /* + * If the call is from vhci_pathinfo_state_change, + * then this path was busy and is becoming ready to accept IO. + */ + ASSERT(ap != NULL); + hba = ap->a_hba_tran; + ASSERT(hba != NULL); + rval = scsi_ifsetcap(ap, cap, val, whom); + + VHCI_DEBUG(2, (CE_NOTE, NULL, + "!vhci_pHCI_cap: only on path %p, ap %p, rval %x\n", + (void *)pip, (void *)ap, rval)); + + return (rval); + } + + /* + * Set capability on all the pHCIs. + * If any path is busy, then the capability would be set by + * vhci_pathinfo_state_change. + */ + + cdip = ADDR2DIP(ap); + ASSERT(cdip != NULL); + sps = mdi_select_path(cdip, NULL, mps_flag, NULL, &pip); + if ((sps != MDI_SUCCESS) || (pip == NULL)) { + VHCI_DEBUG(2, (CE_WARN, NULL, + "!vhci_pHCI_cap: Unable to get a path, dip 0x%p", + (void *)cdip)); + return (0); + } + +again: + svp = (scsi_vhci_priv_t *)mdi_pi_get_vhci_private(pip); + if (svp == NULL) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_pHCI_cap: " + "priv is NULL, pip 0x%p", (void *)pip)); + mdi_rele_path(pip); + return (rval); + } + + if (svp->svp_psd == NULL) { + VHCI_DEBUG(2, (CE_WARN, NULL, "!vhci_pHCI_cap: " + "psd is NULL, pip 0x%p, svp 0x%p", + (void *)pip, (void *)svp)); + mdi_rele_path(pip); + return (rval); + } + + pap = &svp->svp_psd->sd_address; + ASSERT(pap != NULL); + hba = pap->a_hba_tran; + ASSERT(hba != NULL); + + if (hba->tran_setcap != NULL) { + rval = scsi_ifsetcap(pap, cap, val, whom); + + VHCI_DEBUG(2, (CE_NOTE, NULL, + "!vhci_pHCI_cap: path %p, ap %p, rval %x\n", + (void *)pip, (void *)ap, rval)); + + /* + * Select next path and issue the setcap, repeat + * until all paths are exhausted + */ + sps = mdi_select_path(cdip, NULL, mps_flag, pip, &npip); + if ((sps != MDI_SUCCESS) || (npip == NULL)) { + mdi_rele_path(pip); + return (1); + } + mdi_rele_path(pip); + pip = npip; + goto again; + } + mdi_rele_path(pip); + return (rval); +} + +static int +vhci_scsi_bus_config(dev_info_t *pdip, uint_t flags, ddi_bus_config_op_t op, + void *arg, dev_info_t **child) +{ + char *guid; + + if (op == BUS_CONFIG_ONE || op == BUS_UNCONFIG_ONE) + guid = vhci_devnm_to_guid((char *)arg); + else + guid = NULL; + + if (mdi_vhci_bus_config(pdip, flags, op, arg, child, guid) + == MDI_SUCCESS) + return (NDI_SUCCESS); + else + return (NDI_FAILURE); +} + +/* + * Take the original vhci_pkt, create a duplicate of the pkt for resending + * as though it originated in ssd. + */ +static struct scsi_pkt * +vhci_create_retry_pkt(struct vhci_pkt *vpkt) +{ + struct vhci_pkt *new_vpkt = NULL; + struct scsi_pkt *pkt = NULL; + + scsi_vhci_priv_t *svp = (scsi_vhci_priv_t *) + mdi_pi_get_vhci_private(vpkt->vpkt_path); + + /* + * Ensure consistent data at completion time by setting PKT_CONSISTENT + */ + pkt = vhci_scsi_init_pkt(&svp->svp_psd->sd_address, pkt, + vpkt->vpkt_tgt_init_bp, vpkt->vpkt_tgt_init_cdblen, + vpkt->vpkt_tgt_init_scblen, vpkt->vpkt_tgt_init_privlen, + PKT_CONSISTENT, + NULL_FUNC, NULL); + if (pkt != NULL) { + new_vpkt = TGTPKT2VHCIPKT(pkt); + + pkt->pkt_address = vpkt->vpkt_tgt_pkt->pkt_address; + pkt->pkt_flags = vpkt->vpkt_tgt_pkt->pkt_flags; + pkt->pkt_time = vpkt->vpkt_tgt_pkt->pkt_time; + pkt->pkt_comp = vpkt->vpkt_tgt_pkt->pkt_comp; + + pkt->pkt_resid = 0; + pkt->pkt_statistics = 0; + pkt->pkt_reason = 0; + + bcopy(vpkt->vpkt_tgt_pkt->pkt_cdbp, + pkt->pkt_cdbp, vpkt->vpkt_tgt_init_cdblen); + + /* + * Save a pointer to the original vhci_pkt + */ + new_vpkt->vpkt_org_vpkt = vpkt; + } + + return (pkt); +} + +/* + * Copy the successful completion information from the hba packet into + * the original target pkt from the upper layer. Returns the original + * vpkt and destroys the new vpkt from the internal retry. + */ +static struct vhci_pkt * +vhci_sync_retry_pkt(struct vhci_pkt *vpkt) +{ + struct vhci_pkt *ret_vpkt = NULL; + struct scsi_pkt *tpkt = NULL; + struct scsi_pkt *hba_pkt = NULL; + scsi_vhci_priv_t *svp = (scsi_vhci_priv_t *) + mdi_pi_get_vhci_private(vpkt->vpkt_path); + + ASSERT(vpkt->vpkt_org_vpkt != NULL); + VHCI_DEBUG(0, (CE_NOTE, NULL, "vhci_sync_retry_pkt: Retry pkt " + "completed successfully!\n")); + + ret_vpkt = vpkt->vpkt_org_vpkt; + tpkt = ret_vpkt->vpkt_tgt_pkt; + hba_pkt = vpkt->vpkt_hba_pkt; + + /* + * Copy the good status into the target driver's packet + */ + *(tpkt->pkt_scbp) = *(hba_pkt->pkt_scbp); + tpkt->pkt_resid = hba_pkt->pkt_resid; + tpkt->pkt_state = hba_pkt->pkt_state; + tpkt->pkt_statistics = hba_pkt->pkt_statistics; + tpkt->pkt_reason = hba_pkt->pkt_reason; + + /* + * Destroy the internally created vpkt for the retry + */ + vhci_scsi_destroy_pkt(&svp->svp_psd->sd_address, + vpkt->vpkt_tgt_pkt); + + return (ret_vpkt); +} + +/* restart the request sense request */ +static void +vhci_uscsi_restart_sense(void *arg) +{ + struct buf *rqbp; + struct buf *bp; + struct scsi_pkt *rqpkt = (struct scsi_pkt *)arg; + mp_uscsi_cmd_t *mp_uscmdp; + + VHCI_DEBUG(4, (CE_WARN, NULL, + "vhci_uscsi_restart_sense: enter: rqpkt: %p", (void *)rqpkt)); + + if (scsi_transport(rqpkt) != TRAN_ACCEPT) { + /* if it fails - need to wakeup the original command */ + mp_uscmdp = rqpkt->pkt_private; + bp = mp_uscmdp->cmdbp; + rqbp = mp_uscmdp->rqbp; + ASSERT(mp_uscmdp && bp && rqbp); + scsi_free_consistent_buf(rqbp); + scsi_destroy_pkt(rqpkt); + bp->b_resid = bp->b_bcount; + bioerror(bp, EIO); + biodone(bp); + } +} + +/* + * auto-rqsense is not enabled so we have to retrieve the request sense + * manually. + */ +static int +vhci_uscsi_send_sense(struct scsi_pkt *pkt, mp_uscsi_cmd_t *mp_uscmdp) +{ + struct buf *rqbp, *cmdbp; + struct scsi_pkt *rqpkt; + int rval = 0; + + cmdbp = mp_uscmdp->cmdbp; + ASSERT(cmdbp != NULL); + + VHCI_DEBUG(4, (CE_WARN, NULL, + "vhci_uscsi_send_sense: enter: bp: %p pkt: %p scmd: %p", + (void *)cmdbp, (void *)pkt, (void *)mp_uscmdp)); + /* set up the packet information and cdb */ + if ((rqbp = scsi_alloc_consistent_buf(mp_uscmdp->ap, NULL, + SENSE_LENGTH, B_READ, NULL, NULL)) == NULL) { + return (-1); + } + + if ((rqpkt = scsi_init_pkt(mp_uscmdp->ap, NULL, rqbp, + CDB_GROUP0, 1, 0, PKT_CONSISTENT, NULL, NULL)) == NULL) { + scsi_free_consistent_buf(rqbp); + return (-1); + } + + (void) scsi_setup_cdb((union scsi_cdb *)(intptr_t)rqpkt->pkt_cdbp, + SCMD_REQUEST_SENSE, 0, SENSE_LENGTH, 0); + + mp_uscmdp->rqbp = rqbp; + rqbp->b_private = mp_uscmdp; + rqpkt->pkt_flags |= FLAG_SENSING; + rqpkt->pkt_time = 60; + rqpkt->pkt_comp = vhci_uscsi_iodone; + rqpkt->pkt_private = mp_uscmdp; + + /* get her done */ + switch (scsi_transport(rqpkt)) { + case TRAN_ACCEPT: + VHCI_DEBUG(1, (CE_NOTE, NULL, "vhci_uscsi_send_sense: " + "transport accepted.")); + break; + case TRAN_BUSY: + VHCI_DEBUG(1, (CE_NOTE, NULL, "vhci_uscsi_send_sense: " + "transport busy, setting timeout.")); + vhci_restart_timeid = timeout(vhci_uscsi_restart_sense, rqpkt, + (drv_usectohz(5 * 1000000))); + break; + default: + VHCI_DEBUG(1, (CE_NOTE, NULL, "vhci_uscsi_send_sense: " + "transport failed")); + scsi_free_consistent_buf(rqbp); + scsi_destroy_pkt(rqpkt); + rval = -1; + } + + return (rval); +} + +/* + * done routine for the mpapi uscsi command - this is behaving as though + * FLAG_DIAGNOSE is set meaning there are no retries except for a manual + * request sense. + */ +void +vhci_uscsi_iodone(struct scsi_pkt *pkt) +{ + struct buf *bp; + mp_uscsi_cmd_t *mp_uscmdp; + struct uscsi_cmd *uscmdp; + struct scsi_arq_status *arqstat; + int err; + + mp_uscmdp = (mp_uscsi_cmd_t *)pkt->pkt_private; + uscmdp = mp_uscmdp->uscmdp; + bp = mp_uscmdp->cmdbp; + ASSERT(bp != NULL); + VHCI_DEBUG(4, (CE_WARN, NULL, + "vhci_uscsi_iodone: enter: bp: %p pkt: %p scmd: %p", + (void *)bp, (void *)pkt, (void *)mp_uscmdp)); + /* Save the status and the residual into the uscsi_cmd struct */ + uscmdp->uscsi_status = ((*(pkt)->pkt_scbp) & STATUS_MASK); + uscmdp->uscsi_resid = bp->b_resid; + + /* return on a very successful command */ + if (pkt->pkt_reason == CMD_CMPLT && + SCBP_C(pkt) == 0 && ((pkt->pkt_flags & FLAG_SENSING) == 0) && + pkt->pkt_resid == 0) { + mdi_pi_kstat_iosupdate(mp_uscmdp->pip, bp); + scsi_destroy_pkt(pkt); + biodone(bp); + return; + } + VHCI_DEBUG(4, (CE_NOTE, NULL, "iodone: reason=0x%x " + " pkt_resid=%ld pkt_state: 0x%x b_count: %ld b_resid: %ld", + pkt->pkt_reason, pkt->pkt_resid, + pkt->pkt_state, bp->b_bcount, bp->b_resid)); + + err = EIO; + + arqstat = (struct scsi_arq_status *)(intptr_t)(pkt->pkt_scbp); + if (pkt->pkt_reason != CMD_CMPLT) { + /* + * The command did not complete. + */ + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_uscsi_iodone: command did not complete." + " reason: %x flag: %x", pkt->pkt_reason, pkt->pkt_flags)); + if (pkt->pkt_flags & FLAG_SENSING) { + MDI_PI_ERRSTAT(mp_uscmdp->pip, MDI_PI_TRANSERR); + } else if (pkt->pkt_reason == CMD_TIMEOUT) { + MDI_PI_ERRSTAT(mp_uscmdp->pip, MDI_PI_HARDERR); + err = ETIMEDOUT; + } + } else if (pkt->pkt_state & STATE_ARQ_DONE && mp_uscmdp->arq_enabled) { + /* + * The auto-rqsense happened, and the packet has a filled-in + * scsi_arq_status structure, pointed to by pkt_scbp. + */ + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_uscsi_iodone: received auto-requested sense")); + if (uscmdp->uscsi_flags & USCSI_RQENABLE) { + /* get the amount of data to copy into rqbuf */ + int rqlen = SENSE_LENGTH - arqstat->sts_rqpkt_resid; + rqlen = min(((int)uscmdp->uscsi_rqlen), rqlen); + uscmdp->uscsi_rqresid = uscmdp->uscsi_rqlen - rqlen; + uscmdp->uscsi_rqstatus = + *((char *)&arqstat->sts_rqpkt_status); + if (uscmdp->uscsi_rqbuf && uscmdp->uscsi_rqlen && + rqlen != 0) { + bcopy(&(arqstat->sts_sensedata), + uscmdp->uscsi_rqbuf, rqlen); + } + mdi_pi_kstat_iosupdate(mp_uscmdp->pip, bp); + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_uscsi_iodone: ARQ " + "uscsi_rqstatus=0x%x uscsi_rqresid=%d rqlen: %d " + "xfer: %d rqpkt_resid: %d\n", + uscmdp->uscsi_rqstatus, uscmdp->uscsi_rqresid, + uscmdp->uscsi_rqlen, rqlen, + arqstat->sts_rqpkt_resid)); + } + } else if (pkt->pkt_flags & FLAG_SENSING) { + struct buf *rqbp; + struct scsi_status *rqstatus; + + rqstatus = (struct scsi_status *)pkt->pkt_scbp; + /* a manual request sense was done - get the information */ + if (uscmdp->uscsi_flags & USCSI_RQENABLE) { + int rqlen = SENSE_LENGTH - pkt->pkt_resid; + + rqbp = mp_uscmdp->rqbp; + /* get the amount of data to copy into rqbuf */ + rqlen = min(((int)uscmdp->uscsi_rqlen), rqlen); + uscmdp->uscsi_rqresid = uscmdp->uscsi_rqlen - rqlen; + uscmdp->uscsi_rqstatus = *((char *)rqstatus); + if (uscmdp->uscsi_rqlen && uscmdp->uscsi_rqbuf) { + bcopy(rqbp->b_un.b_addr, uscmdp->uscsi_rqbuf, + rqlen); + } + MDI_PI_ERRSTAT(mp_uscmdp->pip, MDI_PI_TRANSERR); + scsi_free_consistent_buf(rqbp); + } + VHCI_DEBUG(4, (CE_NOTE, NULL, "vhci_uscsi_iodone: FLAG_SENSING" + "uscsi_rqstatus=0x%x uscsi_rqresid=%d\n", + uscmdp->uscsi_rqstatus, uscmdp->uscsi_rqresid)); + } else { + struct scsi_status *status = + (struct scsi_status *)pkt->pkt_scbp; + /* + * Command completed and we're not getting sense. Check for + * errors and decide what to do next. + */ + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_uscsi_iodone: command appears complete: reason: %x", + pkt->pkt_reason)); + if (status->sts_chk) { + /* need to manually get the request sense */ + if (vhci_uscsi_send_sense(pkt, mp_uscmdp) == 0) { + scsi_destroy_pkt(pkt); + return; + } + } else { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_chk_err: appears complete")); + err = 0; + mdi_pi_kstat_iosupdate(mp_uscmdp->pip, bp); + if (pkt->pkt_resid) { + bp->b_resid += pkt->pkt_resid; + } + } + } + + if (err) { + if (bp->b_resid == 0) + bp->b_resid = bp->b_bcount; + bioerror(bp, err); + bp->b_flags |= B_ERROR; + } + + scsi_destroy_pkt(pkt); + biodone(bp); + + VHCI_DEBUG(4, (CE_WARN, NULL, "vhci_uscsi_iodone: exit")); +} + +/* + * start routine for the mpapi uscsi command + */ +int +vhci_uscsi_iostart(struct buf *bp) +{ + struct scsi_pkt *pkt; + struct uscsi_cmd *uscmdp; + mp_uscsi_cmd_t *mp_uscmdp; + int stat_size, rval; + int retry = 0; + + ASSERT(bp->b_private != NULL); + + mp_uscmdp = (mp_uscsi_cmd_t *)bp->b_private; + uscmdp = mp_uscmdp->uscmdp; + if (uscmdp->uscsi_flags & USCSI_RQENABLE) { + stat_size = SENSE_LENGTH; + } else { + stat_size = 1; + } + + pkt = scsi_init_pkt(mp_uscmdp->ap, NULL, bp, uscmdp->uscsi_cdblen, + stat_size, 0, 0, SLEEP_FUNC, NULL); + if (pkt == NULL) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_uscsi_iostart: rval: EINVAL")); + bp->b_resid = bp->b_bcount; + uscmdp->uscsi_resid = bp->b_bcount; + bioerror(bp, EINVAL); + biodone(bp); + return (EINVAL); + } + + pkt->pkt_time = uscmdp->uscsi_timeout; + bcopy(uscmdp->uscsi_cdb, pkt->pkt_cdbp, (size_t)uscmdp->uscsi_cdblen); + pkt->pkt_comp = vhci_uscsi_iodone; + pkt->pkt_private = mp_uscmdp; + if (uscmdp->uscsi_flags & USCSI_SILENT) + pkt->pkt_flags |= FLAG_SILENT; + if (uscmdp->uscsi_flags & USCSI_ISOLATE) + pkt->pkt_flags |= FLAG_ISOLATE; + if (uscmdp->uscsi_flags & USCSI_DIAGNOSE) + pkt->pkt_flags |= FLAG_DIAGNOSE; + if (uscmdp->uscsi_flags & USCSI_RENEGOT) { + pkt->pkt_flags |= FLAG_RENEGOTIATE_WIDE_SYNC; + } + VHCI_DEBUG(4, (CE_WARN, NULL, + "vhci_uscsi_iostart: ap: %p pkt: %p pcdbp: %p uscmdp: %p" + " ucdbp: %p pcdblen: %d bp: %p count: %ld pip: %p" + " stat_size: %d", + (void *)mp_uscmdp->ap, (void *)pkt, (void *)pkt->pkt_cdbp, + (void *)uscmdp, (void *)uscmdp->uscsi_cdb, pkt->pkt_cdblen, + (void *)bp, bp->b_bcount, (void *)mp_uscmdp->pip, stat_size)); + + while (((rval = scsi_transport(pkt)) == TRAN_BUSY) && + retry < vhci_uscsi_retry_count) { + delay(drv_usectohz(vhci_uscsi_delay)); + retry++; + } + if (retry >= vhci_uscsi_retry_count) { + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_uscsi_iostart: tran_busy - retry: %d", retry)); + } + switch (rval) { + case TRAN_ACCEPT: + rval = 0; + break; + + default: + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_uscsi_iostart: rval: %d count: %ld res: %ld", + rval, bp->b_bcount, bp->b_resid)); + bp->b_resid = bp->b_bcount; + uscmdp->uscsi_resid = bp->b_bcount; + bioerror(bp, EIO); + scsi_destroy_pkt(pkt); + biodone(bp); + rval = EIO; + MDI_PI_ERRSTAT(mp_uscmdp->pip, MDI_PI_TRANSERR); + break; + } + VHCI_DEBUG(4, (CE_NOTE, NULL, + "vhci_uscsi_iostart: exit: rval: %d", rval)); + return (rval); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/common/io/scsi/adapters/scsi_vhci/scsi_vhci.conf Fri Aug 10 17:00:37 2007 -0700 @@ -0,0 +1,92 @@ +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# +#pragma ident "%Z%%M% %I% %E% SMI" +# +name="scsi_vhci" class="root"; + +# +# Load balancing global configuration: setting load-balance="none" will cause +# all I/O to a given device (which supports multipath I/O) to occur via one +# path. Setting load-balance="round-robin" will cause each path to the device +# to be used in turn. +# +load-balance="round-robin"; + +# +# Automatic failback configuration +# possible values are auto-failback="enable" or auto-failback="disable" +auto-failback="enable"; + +#BEGIN: FAILOVER_MODULE_BLOCK (DO NOT MOVE OR DELETE) +# +# Declare scsi_vhci failover module paths with 'ddi-forceload' so that +# they get loaded early enough to be available for scsi_vhci root use. +# +# NOTE: Correct operation depends on the value of 'ddi-forceload', this +# value should not be changed. The ordering of entries is from +# most-specific failover modules (with a "probe" implementation that is +# completely VID/PID table based), to most generic (failover modules that +# are based on T10 standards like TPGS). By convention the last part of a +# failover module path, after "/scsi_vhci_", is called the +# "failover-module-name", which begins with "f_" (like "f_asym_sun"). The +# "failover-module-name" is also used in the override mechanism below. +ddi-forceload = + "misc/scsi_vhci/scsi_vhci_f_asym_sun", + "misc/scsi_vhci/scsi_vhci_f_asym_lsi", + "misc/scsi_vhci/scsi_vhci_f_asym_emc", + "misc/scsi_vhci/scsi_vhci_f_sym_emc", + "misc/scsi_vhci/scsi_vhci_f_sym", + "misc/scsi_vhci/scsi_vhci_f_tpgs"; + +# +# For a device that has a GUID, discovered on a pHCI with mpxio enabled, vHCI +# access also depends on one of the scsi_vhci failover modules accepting the +# device. The default way this occurs is by a failover module's "probe" +# implementation (sfo_device_probe) indicating the device is supported under +# scsi_vhci. To override this default probe-oriented configuration in +# order to +# +# 1) establish support for a device not currently accepted under scsi_vhci +# +# or 2) override the module selected by "probe" +# +# or 3) disable scsi_vhci support for a device +# +# you can add a 'scsi-vhci-failover-override' tuple, as documented in +# scsi_get_device_type_string(9F). For each tuple, the first part provides +# basic device identity information (vid/pid) and the second part selects +# the failover module by "failover-module-name". If you want to disable +# scsi_vhci support for a device, use the special failover-module-name "NONE". +# Currently, for each failover-module-name in 'scsi-vhci-failover-override' +# (except "NONE") there needs to be a +# "misc/scsi_vhci/scsi_vhci_<failover-module-name>" in 'ddi-forceload' above. +# +# " 111111" +# "012345670123456789012345", "failover-module-name" or "NONE" +# "|-VID--||-----PID------|", +# scsi-vhci-failover-override = +# "STK FLEXLINE 400", "f_asym_lsi", +# "SUN T4", "f_tpgs", +# "CME XIRTEMMYS", "NONE"; +# +#END: FAILOVER_MODULE_BLOCK (DO NOT MOVE OR DELETE)
--- a/usr/src/uts/common/io/scsi/impl/scsi_subr.c Fri Aug 10 11:19:25 2007 -0700 +++ b/usr/src/uts/common/io/scsi/impl/scsi_subr.c Fri Aug 10 17:00:37 2007 -0700 @@ -115,7 +115,7 @@ * every second as this status takes a while to change. */ for (busy_count = 0; busy_count < (pkt->pkt_time * SEC_TO_CSEC); - busy_count++) { + busy_count++) { int rc; int poll_delay; @@ -310,9 +310,8 @@ local.b_flags = B_READ; local.b_bcount = datalen; pkt = (*tran->tran_init_pkt) (ap, NULL, &local, - cdblen, statuslen, 0, PKT_CONSISTENT, - (func == SLEEP_FUNC) ? SLEEP_FUNC : NULL_FUNC, - NULL); + cdblen, statuslen, 0, PKT_CONSISTENT, + (func == SLEEP_FUNC) ? SLEEP_FUNC : NULL_FUNC, NULL); if (!pkt) { i_ddi_mem_free(local.b_un.b_addr, NULL); if (func != NULL_FUNC) { @@ -1181,21 +1180,22 @@ (void) sprintf(&buf1[strlen(buf1)], "ASC: 0x%x (%s)", asc, scsi_asc_ascq_name(asc, ascq, - tmpbuf, asc_list)); + tmpbuf, asc_list)); buflen = strlen(buf1); if (buflen < SCSI_ERRMSG_COLUMN_LEN) { - pad[SCSI_ERRMSG_COLUMN_LEN - buflen] = '\0'; - (void) sprintf(&buf1[buflen], - "%s ASCQ: 0x%x", pad, ascq); + pad[SCSI_ERRMSG_COLUMN_LEN - buflen] = + '\0'; + (void) sprintf(&buf1[buflen], + "%s ASCQ: 0x%x", pad, ascq); } else { - (void) sprintf(&buf1[buflen], " ASCQ: 0x%x", - ascq); + (void) sprintf(&buf1[buflen], + " ASCQ: 0x%x", ascq); } impl_scsi_log(dev, - label, CE_CONT, "%s\n", buf1); - impl_scsi_log(dev, label, CE_CONT, - "FRU: 0x%x (%s)\n", - fru_code, buf); + label, CE_CONT, "%s\n", buf1); + impl_scsi_log(dev, + label, CE_CONT, "FRU: 0x%x (%s)\n", + fru_code, buf); mutex_exit(&scsi_log_mutex); return; } @@ -1215,7 +1215,7 @@ struct scsi_key_strings *cmdlist, struct scsi_extended_sense *sensep) { scsi_vu_errmsg(devp, pkt, label, severity, blkno, - err_blkno, cmdlist, sensep, NULL, NULL); + err_blkno, cmdlist, sensep, NULL, NULL); } /*PRINTFLIKE4*/ @@ -1265,8 +1265,8 @@ if (level == CE_PANIC || level == CE_WARN || level == CE_NOTE) { (void) sprintf(name, "%s (%s%d):\n", - ddi_pathname(dev, scsi_log_buffer), - label, ddi_get_instance(dev)); + ddi_pathname(dev, scsi_log_buffer), + label, ddi_get_instance(dev)); } else if (level >= (uint_t)SCSI_DEBUG) { (void) sprintf(name, "%s%d:", label, ddi_get_instance(dev)); @@ -1299,83 +1299,120 @@ case CE_WARN: case CE_PANIC: if (boot_only) { - cmn_err(level, "?%s\t%s", name, - &scsi_log_buffer[1]); + cmn_err(level, "?%s\t%s", name, &scsi_log_buffer[1]); } else if (console_only) { - cmn_err(level, "^%s\t%s", name, - &scsi_log_buffer[1]); + cmn_err(level, "^%s\t%s", name, &scsi_log_buffer[1]); } else if (log_only) { - cmn_err(level, "!%s\t%s", name, - &scsi_log_buffer[1]); + cmn_err(level, "!%s\t%s", name, &scsi_log_buffer[1]); } else { - cmn_err(level, "%s\t%s", name, - scsi_log_buffer); + cmn_err(level, "%s\t%s", name, scsi_log_buffer); } break; case (uint_t)SCSI_DEBUG: default: - cmn_err(CE_CONT, "^DEBUG: %s\t%s", name, - scsi_log_buffer); + cmn_err(CE_CONT, "^DEBUG: %s\t%s", name, scsi_log_buffer); break; } } -int -scsi_get_device_type_scsi_options(dev_info_t *dip, - struct scsi_device *devp, int default_scsi_options) +/* + * Lookup the 'prop_name' string array property and walk thru its list of + * tuple values looking for a tuple who's VID/PID string (first part of tuple) + * matches the inquiry VID/PID information for the scsi_device. On a match, + * return a duplicate of the second part of the tuple. If no match is found, + * return NULL. On non-NULL return, caller is responsible for freeing return + * result via: + * kmem_free(string, strlen(string) + 1); + * + * This interface can either be used directly, or indirectly by + * scsi_get_device_type_scsi_options. + */ +char * +scsi_get_device_type_string(char *prop_name, + dev_info_t *dip, struct scsi_device *devp) { + struct scsi_inquiry *inq = devp->sd_inq; + char **tuples; + uint_t ntuples; + int i; + char *tvp; /* tuple vid/pid */ + char *trs; /* tuple return string */ + int tvp_len; - caddr_t config_list = NULL; - int options = default_scsi_options; - struct scsi_inquiry *inq = devp->sd_inq; - caddr_t vidptr, datanameptr; - int vidlen, dupletlen; - int config_list_len, len; + /* if we have no inquiry data then we can't do this */ + if (inq == NULL) + return (NULL); /* - * look up the device-type-scsi-options-list and walk thru - * the list - * compare the vendor ids of the earlier inquiry command and - * with those vids in the list - * if there is a match, lookup the scsi-options value + * So that we can establish a 'prop_name' for all instances of a + * device in the system in a single place if needed (via options.conf), + * we loop going up to the root ourself. This way root lookup does + * *not* specify DDI_PROP_DONTPASS, and the code will look on the + * options node. */ - if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, - "device-type-scsi-options-list", - (caddr_t)&config_list, &config_list_len) == DDI_PROP_SUCCESS) { + do { + if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, dip, + (ddi_get_parent(dip) ? DDI_PROP_DONTPASS : 0) | + DDI_PROP_NOTPROM, prop_name, &tuples, &ntuples) == + DDI_PROP_SUCCESS) { + + /* loop over tuples */ + for (i = 0; i < (ntuples/2); i++) { + /* split into vid/pid and return-string */ + tvp = tuples[i * 2]; + trs = tuples[(i * 2) + 1]; + tvp_len = strlen(tvp); - /* - * Compare vids in each duplet - if it matches, get value for - * dataname and then lookup scsi_options - * dupletlen is calculated later. - */ - for (len = config_list_len, vidptr = config_list; len > 0; - vidptr += dupletlen, len -= dupletlen) { + /* check for vid/pid match */ + if ((tvp_len == 0) || + bcmp(tvp, inq->inq_vid, tvp_len)) + continue; /* no match */ + + /* match, dup return-string */ + trs = i_ddi_strdup(trs, KM_SLEEP); + ddi_prop_free(tuples); + return (trs); + } + ddi_prop_free(tuples); + } - vidlen = strlen(vidptr); - datanameptr = vidptr + vidlen + 1; + /* climb up to root one step at a time */ + dip = ddi_get_parent(dip); + } while (dip); + + return (NULL); +} - if ((vidlen != 0) && - bcmp(inq->inq_vid, vidptr, vidlen) == 0) { - /* - * get the data list - */ - options = ddi_prop_get_int(DDI_DEV_T_ANY, - dip, 0, - datanameptr, default_scsi_options); - break; - } - dupletlen = vidlen + strlen(datanameptr) + 2; - } - kmem_free(config_list, config_list_len); +/* + * The 'device-type-scsi-options' mechanism can be used to establish a device + * specific scsi_options value for a particular device. This mechanism uses + * paired strings ("vendor_info", "options_property_name") from the string + * array "device-type-scsi-options" definition. A bcmp of the vendor info is + * done against the inquiry data (inq_vid). Here is an example of use: + * + * device-type-scsi-options-list = + * "FOOLCO Special x1000", "foolco-scsi-options", + * "FOOLCO Special y1000", "foolco-scsi-options"; + * foolco-scsi-options = 0xXXXXXXXX; + */ +int +scsi_get_device_type_scsi_options(dev_info_t *dip, + struct scsi_device *devp, int options) +{ + char *string; + + if ((string = scsi_get_device_type_string( + "device-type-scsi-options-list", dip, devp)) != NULL) { + options = ddi_prop_get_int(DDI_DEV_T_ANY, dip, 0, + string, options); + kmem_free(string, strlen(string) + 1); } - return (options); } /* * Functions for format-neutral sense data functions */ - int scsi_validate_sense(uint8_t *sense_buffer, int sense_buf_len, int *flags) { @@ -1396,9 +1433,9 @@ */ if ((es->es_class != CLASS_EXTENDED_SENSE) || ((es->es_code != CODE_FMT_FIXED_CURRENT) && - (es->es_code != CODE_FMT_FIXED_DEFERRED) && - (es->es_code != CODE_FMT_DESCR_CURRENT) && - (es->es_code != CODE_FMT_DESCR_DEFERRED))) { + (es->es_code != CODE_FMT_FIXED_DEFERRED) && + (es->es_code != CODE_FMT_DESCR_CURRENT) && + (es->es_code != CODE_FMT_DESCR_DEFERRED))) { /* * Sense data (if there's actually anything here) is not * in a format we can handle). @@ -1411,7 +1448,7 @@ */ if ((flags != NULL) && ((es->es_code == CODE_FMT_FIXED_DEFERRED) || - (es->es_code == CODE_FMT_DESCR_DEFERRED))) { + (es->es_code == CODE_FMT_DESCR_DEFERRED))) { *flags |= SNS_BUF_DEFERRED; } @@ -1427,7 +1464,7 @@ */ if ((sense_buf_len < MIN_FIXED_SENSE_LEN) || ((es->es_add_len + ADDL_SENSE_ADJUST) < - MIN_FIXED_SENSE_LEN)) { + MIN_FIXED_SENSE_LEN)) { result = SENSE_UNUSABLE; } else { /* @@ -1436,7 +1473,7 @@ */ if ((flags != NULL) && (sense_buf_len < - (es->es_add_len + ADDL_SENSE_ADJUST))) { + (es->es_add_len + ADDL_SENSE_ADJUST))) { *flags |= SNS_BUF_OVERFLOW; } @@ -1458,7 +1495,7 @@ */ if ((flags != NULL) && (sense_buf_len < - (ds->ds_addl_sense_length + sizeof (*ds)))) { + (ds->ds_addl_sense_length + sizeof (*ds)))) { *flags |= SNS_BUF_OVERFLOW; } @@ -1686,7 +1723,7 @@ isd = (struct scsi_information_sense_descr *) scsi_find_sense_descr(sense_buffer, sense_buf_len, - DESCR_INFORMATION); + DESCR_INFORMATION); if (isd) { *information = SCSI_READ64(isd->isd_information); @@ -1728,7 +1765,7 @@ c = (struct scsi_cmd_specific_sense_descr *) scsi_find_sense_descr(sense_buffer, sense_buf_len, - DESCR_COMMAND_SPECIFIC); + DESCR_COMMAND_SPECIFIC); if (c) { valid = 1;
--- a/usr/src/uts/common/os/modctl.c Fri Aug 10 11:19:25 2007 -0700 +++ b/usr/src/uts/common/os/modctl.c Fri Aug 10 17:00:37 2007 -0700 @@ -807,7 +807,8 @@ ASSERT(constraints != NULL); ASSERT(len > 0); - for (i = 0, p = constraints; strlen(p) > 0; i++, p += strlen(p) + 1); + for (i = 0, p = constraints; strlen(p) > 0; i++, p += strlen(p) + 1) + ; n = i; @@ -1518,7 +1519,7 @@ break; case MODEVENTS_POST_EVENT: error = log_usr_sysevent((sysevent_t *)a2, (uint32_t)a3, - (sysevent_id_t *)a4); + (sysevent_id_t *)a4); break; case MODEVENTS_REGISTER_EVENT: error = log_sysevent_register((char *)a2, (char *)a3, @@ -1568,7 +1569,7 @@ LOCK_DEV_OPS(&dnp->dn_lock); if (strcmp(mp->mp_minorname, "*") == 0) { wildmp = ((is_clone == 0) ? - &dnp->dn_mperm_wild : &dnp->dn_mperm_clone); + &dnp->dn_mperm_wild : &dnp->dn_mperm_clone); if (*wildmp) freemp = *wildmp; *wildmp = NULL; @@ -1597,7 +1598,7 @@ } else { if (moddebug & MODDEBUG_MINORPERM) { cmn_err(CE_CONT, MP_NO_MINOR, - drvname, mp->mp_minorname); + drvname, mp->mp_minorname); } } @@ -1624,7 +1625,7 @@ LOCK_DEV_OPS(&dnp->dn_lock); if (strcmp(mp->mp_minorname, "*") == 0) { wildmp = ((is_clone == 0) ? - &dnp->dn_mperm_wild : &dnp->dn_mperm_clone); + &dnp->dn_mperm_wild : &dnp->dn_mperm_clone); if (*wildmp) freemp = *wildmp; *wildmp = mp; @@ -1869,7 +1870,7 @@ for (pe = list_head(&wargs->wa_pathlist); pe != NULL; pe = list_next(&wargs->wa_pathlist, pe)) { err = devfs_remdrv_cleanup((const char *)pe->pe_dir, - (const char *)pe->pe_nodename); + (const char *)pe->pe_nodename); if (rval == 0) rval = err; } @@ -2199,12 +2200,12 @@ case MODGETDEVFSPATH_MI_LEN: /* sizeof path nm of (major,instance) */ error = modctl_devfspath_mi_len((major_t)a1, (int)a2, - (uint_t *)a3); + (uint_t *)a3); break; case MODGETDEVFSPATH_MI: /* get path name of (major,instance) */ error = modctl_devfspath_mi((major_t)a1, (int)a2, - (uint_t)a3, (char *)a4); + (uint_t)a3, (char *)a4); break; @@ -2243,7 +2244,7 @@ case MODGETDEVPOLICY: /* get device policy */ error = devpolicy_get((int *)a1, (size_t)a2, - (devplcysys_t *)a3); + (devplcysys_t *)a3); break; case MODALLOCPRIV: @@ -3026,7 +3027,7 @@ /*CONSTANTCONDITION*/ while (1) { printf("Name of system file [%s]: ", - systemfile ? systemfile : "/dev/null"); + systemfile ? systemfile : "/dev/null"); console_gets(s0, sizeof (s0)); @@ -3072,7 +3073,7 @@ mod_sysctl(SYS_CHECK_EXCLUDE, mp->mod_filename) != 0) { if (moddebug & MODDEBUG_LOADMSG) { printf(mod_excl_msg, mp->mod_filename, - mp->mod_modname); + mp->mod_modname); } return (ENXIO); } @@ -3103,10 +3104,10 @@ mp->mod_loadcnt++; if (moddebug & MODDEBUG_LOADMSG) { printf(load_msg, mp->mod_filename, mp->mod_id, - (void *)((struct module *)mp->mod_mp)->text, - (void *)((struct module *)mp->mod_mp)->data, - ((struct module *)mp->mod_mp)->text_size, - ((struct module *)mp->mod_mp)->data_size); + (void *)((struct module *)mp->mod_mp)->text, + (void *)((struct module *)mp->mod_mp)->data, + ((struct module *)mp->mod_mp)->text_size, + ((struct module *)mp->mod_mp)->data_size); } /* @@ -3179,7 +3180,7 @@ if (moddebug & MODDEBUG_LOADMSG) printf(unload_msg, mp->mod_modname, - mp->mod_id, mp->mod_loadcnt); + mp->mod_id, mp->mod_loadcnt); /* * If mod_ref is not zero, it means some modules might still refer @@ -3271,7 +3272,7 @@ if (moddebug & MODDEBUG_LOADMSG) printf("installing %s, module id %d.\n", - mp->mod_modname, mp->mod_id); + mp->mod_modname, mp->mod_id); ASSERT(mp->mod_mp != NULL); if (mod_install_requisites(mp) != 0) { @@ -3286,11 +3287,11 @@ if (moddebug & MODDEBUG_ERRMSG) { printf("init '%s' id %d loaded @ 0x%p/0x%p size %lu/%lu\n", - mp->mod_filename, mp->mod_id, - (void *)((struct module *)mp->mod_mp)->text, - (void *)((struct module *)mp->mod_mp)->data, - ((struct module *)mp->mod_mp)->text_size, - ((struct module *)mp->mod_mp)->data_size); + mp->mod_filename, mp->mod_id, + (void *)((struct module *)mp->mod_mp)->text, + (void *)((struct module *)mp->mod_mp)->data, + ((struct module *)mp->mod_mp)->text_size, + ((struct module *)mp->mod_mp)->data_size); } func = (int (*)())kobj_lookup(mp->mod_mp, "_init"); @@ -3574,9 +3575,9 @@ */ if (mod_uninstall_interval) { ticks = ddi_get_lbolt() + - drv_usectohz(mod_uninstall_interval * 1000000); + drv_usectohz(mod_uninstall_interval * 1000000); (void) cv_timedwait(&mod_uninstall_cv, - &mod_uninstall_lock, ticks); + &mod_uninstall_lock, ticks); } else { cv_wait(&mod_uninstall_cv, &mod_uninstall_lock); } @@ -4025,7 +4026,7 @@ mod_make_requisite(dep, on_mod); } else if (moddebug & MODDEBUG_ERRMSG) { printf("error processing %s on which module %s depends\n", - on, dep->mod_modname); + on, dep->mod_modname); } return (on_mod); } @@ -4069,7 +4070,7 @@ #define popchar(p, c) \ c = *p++; \ if (c == 0) \ - return (0); + return (0) int gmatch(const char *s, const char *p) @@ -4085,7 +4086,7 @@ switch (c) { case '\\': /* skip to quoted character */ - popchar(p, c) + popchar(p, c); /*FALLTHRU*/ default: @@ -4127,14 +4128,14 @@ notflag = 1; p++; } - popchar(p, c) + popchar(p, c); do { if (c == '-' && lc && *p != ']') { /* test sc against range [c1-c2] */ - popchar(p, c) + popchar(p, c); if (c == '\\') { - popchar(p, c) + popchar(p, c); } if (notflag) { @@ -4148,7 +4149,7 @@ /* keep going, may get a match next */ } else if (c == '\\') { /* skip to quoted character */ - popchar(p, c) + popchar(p, c); } lc = c; if (notflag) { @@ -4158,7 +4159,7 @@ } else if (sc == lc) { ok++; } - popchar(p, c) + popchar(p, c); } while (c != ']'); /* recurse on remainder of string */ @@ -4383,10 +4384,8 @@ if ((modname == NULL) || (mode != KRTLD_MODE_FIRST)) goto out; - /* find optional first '/' in modname */ - mod = strchr(modname, '/'); - if (mod != strrchr(modname, '/')) - goto out; /* only one '/' is legal */ + /* find last '/' in modname */ + mod = strrchr(modname, '/'); if (mod) { /* for subdir string without modification to argument */
--- a/usr/src/uts/common/os/swapgeneric.c Fri Aug 10 11:19:25 2007 -0700 +++ b/usr/src/uts/common/os/swapgeneric.c Fri Aug 10 17:00:37 2007 -0700 @@ -238,10 +238,10 @@ if (error) { cmn_err(CE_CONT, "Cannot remount root on %s fstype %s\n", - rootfs.bo_name, rootfs.bo_fstype); + rootfs.bo_name, rootfs.bo_fstype); } else { cmn_err(CE_CONT, "?root remounted on %s fstype %s\n", - rootfs.bo_name, rootfs.bo_fstype); + rootfs.bo_name, rootfs.bo_fstype); } return (error); } @@ -305,7 +305,7 @@ { struct vfssw *vsw; char *this; - char *name; + char *name; int err; /* ONC_PLUS EXTRACT END */ int i, proplen, dhcacklen; @@ -685,8 +685,16 @@ static int load_boot_driver(char *drv) { - char *drvname; - major_t major; + char *drvname; + major_t major; +#ifdef sparc + struct devnames *dnp; + ddi_prop_t *propp; + char *module; + char *dir, *mf; + int plen; + int mlen; +#endif /* sparc */ if ((major = ddi_name_to_major(drv)) == (major_t)-1) { cmn_err(CE_CONT, "%s: no major number\n", drv); @@ -710,6 +718,49 @@ return (-1); } +#ifdef sparc + /* + * NOTE: this can be removed when newboot-sparc is delivered. + * + * Check to see if the driver had a 'ddi-forceload' global driver.conf + * property to identify additional modules that need to be loaded. + * The driver still needs to use ddi_modopen() to open these modules, + * but the 'ddi-forceload' property allows the modules to be loaded + * into memory prior to lights-out, so that driver ddi_modopen() + * calls during lights-out (when mounting root) will work correctly. + * Use of 'ddi-forceload' is only required for drivers involved in + * getting root mounted. + */ + dnp = &devnamesp[major]; + if (dnp->dn_global_prop_ptr && dnp->dn_global_prop_ptr->prop_list && + ((propp = i_ddi_prop_search(DDI_DEV_T_ANY, + "ddi-forceload", DDI_PROP_TYPE_STRING, + &dnp->dn_global_prop_ptr->prop_list)) != NULL)) { + + module = (char *)propp->prop_val; + plen = propp->prop_len; + while (plen > 0) { + mlen = strlen(module); + mf = strrchr(module, '/'); + if (mf) { + dir = module; + *mf++ = '\0'; /* '/' -> '\0' */ + } else { + dir = "misc"; + mf = module; + } + if (modloadonly(dir, mf) == -1) + cmn_err(CE_CONT, + "misc/%s: can't load module\n", mf); + if (mf != module) + *(mf - 1) = '/'; /* '\0' -> '/' */ + + module += mlen + 1; + plen -= mlen + 1; + } + } +#endif /* sparc */ + return (0); } @@ -800,7 +851,7 @@ *p = 0; BMDPRINTF(("load_bootpath_drivers: parent=%s leaf=%s\n", - bootpath, leaf)); + bootpath, leaf)); dip = path_to_devinfo(pathcopy); if (leaf) { @@ -815,7 +866,7 @@ if (dip == NULL) { cmn_err(CE_WARN, "can't bind driver for boot path <%s>", - bootpath); + bootpath); kmem_free(pathcopy, pathcopy_len); return (NULL); } @@ -884,7 +935,7 @@ BMDPRINTF(("load_boot_platform_modules: %s\n", drv)); } else { BMDPRINTF(("load_boot_platform_modules: %s -> %s\n", - drv, drvname)); + drv, drvname)); } #endif /* DEBUG */
--- a/usr/src/uts/common/sys/scsi/adapters/scsi_vhci.h Fri Aug 10 11:19:25 2007 -0700 +++ b/usr/src/uts/common/sys/scsi/adapters/scsi_vhci.h Fri Aug 10 17:00:37 2007 -0700 @@ -72,6 +72,7 @@ #define SCBP_C(pkt) ((*(pkt)->pkt_scbp) & STATUS_MASK) int vhci_do_scsi_cmd(struct scsi_pkt *); +/*PRINTFLIKE3*/ void vhci_log(int, dev_info_t *, const char *, ...); /* @@ -200,12 +201,6 @@ #define VHCI_SCSI_CDB_SIZE 16 #define VHCI_SCSI_SCB_SIZE (sizeof (struct scsi_arq_status)) -#define SCSI_OPTIONS_VHCI_MASK 0x7000000 -#define SCSI_OPTIONS_DISABLE_DEV_TYPE 0x7000000 -#define SCSI_OPTIONS_SYMMETRIC 0x1000000 -#define SCSI_OPTIONS_ASYMMETRIC 0x2000000 -#define SCSI_OPTIONS_SUN_T3_ASYMMETRIC 0x3000000 - /* * flag to determine failover support */ @@ -215,15 +210,6 @@ #define SCSI_BOTH_FAILOVER \ (SCSI_IMPLICIT_FAILOVER | SCSI_EXPLICIT_FAILOVER) -#define SCSI_OPTIONS_VHCI(n) ((n) & SCSI_OPTIONS_VHCI_MASK) - -typedef struct vhci_dev_vidpid_entry { - char vid[10]; - int vid_len; - char pid[20]; - int pid_len; -} vhci_dev_vidpid_entry_t; - struct scsi_vhci_swarg; typedef struct vhci_prin_readkeys { @@ -269,7 +255,7 @@ struct vhci_pkt { struct scsi_pkt *vpkt_tgt_pkt; - mdi_pathinfo_t *vpkt_path; /* path pkt bound to */ + mdi_pathinfo_t *vpkt_path; /* path pkt bound to */ /* * pHCI packet that does the actual work. @@ -319,8 +305,14 @@ */ client_lb_t svl_lb_policy_save; + /* + * Failover ops and ops name selected for the lun. + */ struct scsi_failover_ops *svl_fops; + char *svl_fops_name; + void *svl_fops_ctpriv; + struct scsi_vhci_lun *svl_hash_next; char *svl_lun_wwn; @@ -365,7 +357,6 @@ int svl_xlf_capable; /* XLF implementation */ int svl_sector_size; int svl_setcap_done; - uint16_t svl_fo_type; /* failover type */ uint16_t svl_fo_support; /* failover mode */ } scsi_vhci_lun_t; @@ -533,70 +524,168 @@ int sfo_rev; /* + * failover module name, begins with "f_" + */ + char *sfo_name; + + /* + * devices supported by failover module + * + * NOTE: this is an aproximation, sfo_device_probe has the final say. + */ + char **sfo_devices; + + /* + * initialize the failover module + */ + void (*sfo_init)(); + + /* * identify device */ int (*sfo_device_probe)( struct scsi_device *sd, - struct scsi_inquiry *stdinq, - void **ctpriv); + struct scsi_inquiry *stdinq, + void **ctpriv); /* * housekeeping (free memory etc alloc'ed during probe */ void (*sfo_device_unprobe)( - struct scsi_device *sd, - void *ctpriv); + struct scsi_device *sd, + void *ctpriv); /* * bring a path ONLINE (ie make it ACTIVE) */ int (*sfo_path_activate)( - struct scsi_device *sd, - char *pathclass, - void *ctpriv); + struct scsi_device *sd, + char *pathclass, + void *ctpriv); /* * inverse of above */ - int (*sfo_path_deactivate)( - struct scsi_device *sd, - char *pathclass, - void *ctpriv); + int (*sfo_path_deactivate)( + struct scsi_device *sd, + char *pathclass, + void *ctpriv); /* * returns operational characteristics of path */ int (*sfo_path_get_opinfo)( - struct scsi_device *sd, + struct scsi_device *sd, struct scsi_path_opinfo *opinfo, - void *ctpriv); + void *ctpriv); /* * verify path is operational */ int (*sfo_path_ping)( - struct scsi_device *sd, - void *ctpriv); + struct scsi_device *sd, + void *ctpriv); /* * analyze SENSE data to detect externally initiated * failovers */ int (*sfo_analyze_sense)( - struct scsi_device *sd, - struct scsi_extended_sense *sense, - void *ctpriv); + struct scsi_device *sd, + struct scsi_extended_sense *sense, + void *ctpriv); /* * return the next pathclass in order of preference * eg. "secondary" comes after "primary" */ int (*sfo_pathclass_next)( - char *cur, - char **nxt, - void *ctpriv); + char *cur, + char **nxt, + void *ctpriv); }; +/* + * Names of (too) 'well-known' failover ops. + * NOTE: consumers of these names should look for a better way... + */ +#define SFO_NAME_SYM "f_sym" +#define SFO_NAME_TPGS "f_tpgs" +#define SCSI_FAILOVER_IS_SYM(sfo) \ + ((sfo) ? (strcmp((sfo)->sfo_name, SFO_NAME_SYM) == 0) : 0) +#define SCSI_FAILOVER_IS_TPGS(sfo) \ + ((sfo) ? (strcmp((sfo)->sfo_name, SFO_NAME_TPGS) == 0) : 0) + +/* + * Macro to provide plumbing for basic failover module + */ +#define _SCSI_FAILOVER_OP(sfo_name, local_name, ops_name, vers) \ + static struct modlmisc modlmisc = { \ + &mod_miscops, sfo_name " " vers \ + }; \ + static struct modlinkage modlinkage = { \ + MODREV_1, (void *)&modlmisc, NULL \ + }; \ + int _init() \ + { \ + return (mod_install(&modlinkage)); \ + } \ + int _fini() \ + { \ + return (mod_remove(&modlinkage)); \ + } \ + int _info(struct modinfo *modinfop) \ + { \ + return (mod_info(&modlinkage, modinfop)); \ + } \ + static int local_name##_device_probe( \ + struct scsi_device *, \ + struct scsi_inquiry *, void **); \ + static void local_name##_device_unprobe( \ + struct scsi_device *, void *); \ + static int local_name##_path_activate( \ + struct scsi_device *, char *, void *); \ + static int local_name##_path_deactivate( \ + struct scsi_device *, char *, void *); \ + static int local_name##_path_get_opinfo( \ + struct scsi_device *, \ + struct scsi_path_opinfo *, void *); \ + static int local_name##_path_ping( \ + struct scsi_device *, void *); \ + static int local_name##_analyze_sense( \ + struct scsi_device *, \ + struct scsi_extended_sense *, void *); \ + static int local_name##_pathclass_next( \ + char *, char **, void *); \ + struct scsi_failover_ops ops_name##_failover_ops = { \ + SFO_REV, \ + sfo_name, \ + local_name##_dev_table, \ + NULL, \ + local_name##_device_probe, \ + local_name##_device_unprobe, \ + local_name##_path_activate, \ + local_name##_path_deactivate, \ + local_name##_path_get_opinfo, \ + local_name##_path_ping, \ + local_name##_analyze_sense, \ + local_name##_pathclass_next \ + } + +#ifdef lint +#define SCSI_FAILOVER_OP(sfo_name, local_name, vers) \ + _SCSI_FAILOVER_OP(sfo_name, local_name, local_name, vers) +#else /* lint */ +#define SCSI_FAILOVER_OP(sfo_name, local_name, vers) \ + _SCSI_FAILOVER_OP(sfo_name, local_name, scsi_vhci, vers) +#endif /* lint */ + +/* + * Return values for sfo_device_probe + */ +#define SFO_DEVICE_PROBE_VHCI 1 /* supported under scsi_vhci */ +#define SFO_DEVICE_PROBE_PHCI 0 /* not supported under scsi_vhci */ + /* return values for sfo_analyze_sense() */ #define SCSI_SENSE_NOFAILOVER 0 #define SCSI_SENSE_FAILOVER_INPROG 1
--- a/usr/src/uts/common/sys/scsi/impl/transport.h Fri Aug 10 11:19:25 2007 -0700 +++ b/usr/src/uts/common/sys/scsi/impl/transport.h Fri Aug 10 17:00:37 2007 -0700 @@ -336,6 +336,11 @@ struct scsi_device *sd, int (*callback)(void)); +char *scsi_get_device_type_string( + char *prop_name, + dev_info_t *hba_dip, + struct scsi_device *devp); + extern int scsi_get_device_type_scsi_options( dev_info_t *hba_dip, struct scsi_device *devp,
--- a/usr/src/uts/intel/Makefile.intel.shared Fri Aug 10 11:19:25 2007 -0700 +++ b/usr/src/uts/intel/Makefile.intel.shared Fri Aug 10 17:00:37 2007 -0700 @@ -371,7 +371,7 @@ # Machine Specific Driver Modules (/kernel/drv): # DRV_KMODS += options -$(CLOSED_BUILD)CLOSED_DRV_KMODS += scsi_vhci +DRV_KMODS += scsi_vhci # # PCMCIA specific module(s) @@ -550,10 +550,16 @@ MISC_KMODS += tem MISC_KMODS += tlimod MISC_KMODS += usba usba10 usbs49_fw +MISC_KMODS += scsi_vhci_f_sym +MISC_KMODS += scsi_vhci_f_tpgs +MISC_KMODS += scsi_vhci_f_asym_sun $(CLOSED_BUILD)CLOSED_MISC_KMODS += amsrc1 $(CLOSED_BUILD)CLOSED_MISC_KMODS += klmmod klmops $(CLOSED_BUILD)CLOSED_MISC_KMODS += phx +$(CLOSED_BUILD)CLOSED_MISC_KMODS += scsi_vhci_f_asym_lsi +$(CLOSED_BUILD)CLOSED_MISC_KMODS += scsi_vhci_f_asym_emc +$(CLOSED_BUILD)CLOSED_MISC_KMODS += scsi_vhci_f_sym_emc # # Software Cryptographic Providers (/kernel/crypto):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/scsi_vhci/Makefile Fri Aug 10 17:00:37 2007 -0700 @@ -0,0 +1,89 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +#pragma ident "%Z%%M% %I% %E% SMI" +# +# This makefile drives the production of SCSI vHCI Driver +# intel architecture dependent +# + +# +# Paths to the base of the uts directory trees +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = scsi_vhci +OBJECTS = $(SCSI_VHCI_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(SCSI_VHCI_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE) +CONF_SRCDIR = $(UTSBASE)/common/io/scsi/adapters/scsi_vhci + +# +# Include common rules. +# +include $(UTSBASE)/intel/Makefile.intel + +# +# Define targets. +# +ALL_TARGET = $(BINARY) $(SRC_CONFILE) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE) + +# +# Note dependancy on misc/scsi. +# +LDFLAGS += -dy -N"misc/scsi" + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/intel/Makefile.targ
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/scsi_vhci_f_asym_sun/Makefile Fri Aug 10 17:00:37 2007 -0700 @@ -0,0 +1,88 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +#pragma ident "%Z%%M% %I% %E% SMI" +# +# This makefile drives the production of misc/scsi_vhci/scsi_vhci_f_asym_sun +# intel architecture dependent +# + +# +# Paths to the base of the uts directory trees +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = scsi_vhci_f_asym_sun +OBJECTS = $(SCSI_VHCI_F_ASYM_SUN_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(SCSI_VHCI_F_ASYM_SUN_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_SCSI_VHCI_DIR)/$(MODULE) + +# +# Include common rules. +# +include $(UTSBASE)/intel/Makefile.intel + +# +# Define targets. +# +ALL_TARGET = $(BINARY) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) + +# +# Note dependancy on misc/scsi. +# +LDFLAGS += -dy -N"misc/scsi" -N"drv/scsi_vhci" + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/intel/Makefile.targ
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/scsi_vhci_f_sym/Makefile Fri Aug 10 17:00:37 2007 -0700 @@ -0,0 +1,88 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +#pragma ident "%Z%%M% %I% %E% SMI" +# +# This makefile drives the production of misc/scsi_vhci/scsi_vhci_f_sym +# intel architecture dependent module +# + +# +# Paths to the base of the uts directory trees +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = scsi_vhci_f_sym +OBJECTS = $(SCSI_VHCI_F_SYM_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(SCSI_VHCI_F_SYM_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_SCSI_VHCI_DIR)/$(MODULE) + +# +# Include common rules. +# +include $(UTSBASE)/intel/Makefile.intel + +# +# Define targets. +# +ALL_TARGET = $(BINARY) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) + +# +# Note dependancy on misc/scsi. +# +LDFLAGS += -dy -N"misc/scsi" -N"drv/scsi_vhci" + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/intel/Makefile.targ
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/intel/scsi_vhci_f_tpgs/Makefile Fri Aug 10 17:00:37 2007 -0700 @@ -0,0 +1,88 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +#pragma ident "%Z%%M% %I% %E% SMI" +# +# This makefile drives the production of misc/scsi_vhci/scsi_vhci_f_tpgs +# intel architecture dependent +# + +# +# Paths to the base of the uts directory trees +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = scsi_vhci_f_tpgs +OBJECTS = $(SCSI_VHCI_F_TPGS_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(SCSI_VHCI_F_TPGS_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_SCSI_VHCI_DIR)/$(MODULE) + +# +# Include common rules. +# +include $(UTSBASE)/intel/Makefile.intel + +# +# Define targets. +# +ALL_TARGET = $(BINARY) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) + +# +# Note dependancy on misc/scsi. +# +LDFLAGS += -dy -N"misc/scsi" -N"drv/scsi_vhci" + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/intel/Makefile.targ
--- a/usr/src/uts/sparc/Makefile.sparc.shared Fri Aug 10 11:19:25 2007 -0700 +++ b/usr/src/uts/sparc/Makefile.sparc.shared Fri Aug 10 17:00:37 2007 -0700 @@ -286,11 +286,11 @@ DRV_KMODS += socal DRV_KMODS += sgen DRV_KMODS += dad +DRV_KMODS += scsi_vhci $(CLOSED_BUILD)CLOSED_DRV_KMODS += audioens $(CLOSED_BUILD)CLOSED_DRV_KMODS += audiovia823x $(CLOSED_BUILD)CLOSED_DRV_KMODS += ifp -$(CLOSED_BUILD)CLOSED_DRV_KMODS += scsi_vhci $(CLOSED_BUILD)CLOSED_DRV_KMODS += uata $(CLOSED_BUILD)CLOSED_DRV_KMODS += usbser_edge @@ -385,10 +385,14 @@ MISC_KMODS += cmlb MISC_KMODS += tem MISC_KMODS += pcicfg.e fcodem fcpci +MISC_KMODS += scsi_vhci_f_sym scsi_vhci_f_tpgs scsi_vhci_f_asym_sun $(CLOSED_BUILD)CLOSED_MISC_KMODS += amsrc1 $(CLOSED_BUILD)CLOSED_MISC_KMODS += klmmod klmops $(CLOSED_BUILD)CLOSED_MISC_KMODS += phx +$(CLOSED_BUILD)CLOSED_MISC_KMODS += scsi_vhci_f_asym_lsi +$(CLOSED_BUILD)CLOSED_MISC_KMODS += scsi_vhci_f_asym_emc +$(CLOSED_BUILD)CLOSED_MISC_KMODS += scsi_vhci_f_sym_emc # # Software Cryptographic Providers (/kernel/crypto):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/sparc/scsi_vhci/Makefile Fri Aug 10 17:00:37 2007 -0700 @@ -0,0 +1,89 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +#pragma ident "%Z%%M% %I% %E% SMI" +# +# This makefile drives the production of SCSI vHCI Driver +# sparc architecture dependent +# + +# +# Paths to the base of the uts directory trees +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = scsi_vhci +OBJECTS = $(SCSI_VHCI_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(SCSI_VHCI_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE) +CONF_SRCDIR = $(UTSBASE)/common/io/scsi/adapters/scsi_vhci + +# +# Include common rules. +# +include $(UTSBASE)/sparc/Makefile.sparc + +# +# Define targets. +# +ALL_TARGET = $(BINARY) $(SRC_CONFILE) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE) + +# +# Note dependancy on misc/scsi. +# +LDFLAGS += -dy -N"misc/scsi" + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/sparc/Makefile.targ
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/sparc/scsi_vhci_f_asym_sun/Makefile Fri Aug 10 17:00:37 2007 -0700 @@ -0,0 +1,88 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +#pragma ident "%Z%%M% %I% %E% SMI" +# +# This makefile drives the production of misc/scsi_vhci/scsi_vhci_f_asym_sun +# sparc architecture dependent +# + +# +# Paths to the base of the uts directory trees +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = scsi_vhci_f_asym_sun +OBJECTS = $(SCSI_VHCI_F_ASYM_SUN_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(SCSI_VHCI_F_ASYM_SUN_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_SCSI_VHCI_DIR)/$(MODULE) + +# +# Include common rules. +# +include $(UTSBASE)/sparc/Makefile.sparc + +# +# Define targets. +# +ALL_TARGET = $(BINARY) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) + +# +# Note dependancy on misc/scsi. +# +LDFLAGS += -dy -N"misc/scsi" -N"drv/scsi_vhci" + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/sparc/Makefile.targ
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/sparc/scsi_vhci_f_sym/Makefile Fri Aug 10 17:00:37 2007 -0700 @@ -0,0 +1,88 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +#pragma ident "%Z%%M% %I% %E% SMI" +# +# This makefile drives the production of misc/scsi_vhci/scsi_vhci_f_sym +# sparc architecture dependent +# + +# +# Paths to the base of the uts directory trees +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = scsi_vhci_f_sym +OBJECTS = $(SCSI_VHCI_F_SYM_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(SCSI_VHCI_F_SYM_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_SCSI_VHCI_DIR)/$(MODULE) + +# +# Include common rules. +# +include $(UTSBASE)/sparc/Makefile.sparc + +# +# Define targets. +# +ALL_TARGET = $(BINARY) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) + +# +# Note dependancy on misc/scsi. +# +LDFLAGS += -dy -N"misc/scsi" -N"drv/scsi_vhci" + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/sparc/Makefile.targ
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/usr/src/uts/sparc/scsi_vhci_f_tpgs/Makefile Fri Aug 10 17:00:37 2007 -0700 @@ -0,0 +1,88 @@ +# +# 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 2007 Sun Microsystems, Inc. All rights reserved. +# Use is subject to license terms. +# + +# +#pragma ident "%Z%%M% %I% %E% SMI" +# +# This makefile drives the production of misc/scsi_vhci/scsi_vhci_f_tpgs +# sparc architecture dependent +# + +# +# Paths to the base of the uts directory trees +# +UTSBASE = ../.. + +# +# Define the module and object file sets. +# +MODULE = scsi_vhci_f_tpgs +OBJECTS = $(SCSI_VHCI_F_TPGS_OBJS:%=$(OBJS_DIR)/%) +LINTS = $(SCSI_VHCI_F_TPGS_OBJS:%.o=$(LINTS_DIR)/%.ln) +ROOTMODULE = $(ROOT_SCSI_VHCI_DIR)/$(MODULE) + +# +# Include common rules. +# +include $(UTSBASE)/sparc/Makefile.sparc + +# +# Define targets. +# +ALL_TARGET = $(BINARY) +LINT_TARGET = $(MODULE).lint +INSTALL_TARGET = $(BINARY) $(ROOTMODULE) + +# +# Note dependancy on misc/scsi. +# +LDFLAGS += -dy -N"misc/scsi" -N"drv/scsi_vhci" + +# +# Default build targets. +# +.KEEP_STATE: + +def: $(DEF_DEPS) + +all: $(ALL_DEPS) + +clean: $(CLEAN_DEPS) + +clobber: $(CLOBBER_DEPS) + +lint: $(LINT_DEPS) + +modlintlib: $(MODLINTLIB_DEPS) + +clean.lint: $(CLEAN_LINT_DEPS) + +install: $(INSTALL_DEPS) + +# +# Include common targets. +# +include $(UTSBASE)/sparc/Makefile.targ