summaryrefslogtreecommitdiff
path: root/usr/src
diff options
context:
space:
mode:
authorPeter Dunlap <Peter.Dunlap@Sun.COM>2008-10-30 15:49:07 -0600
committerPeter Dunlap <Peter.Dunlap@Sun.COM>2008-10-30 15:49:07 -0600
commita6d42e7d71324c5193c3b94d57d96ba2925d52e1 (patch)
treeb99a1d06464c8dcb9ae0462623f708a380a27d2c /usr/src
parent2d6aa547f868d751d00f1f2e70171d9dc9317af1 (diff)
downloadillumos-joyent-a6d42e7d71324c5193c3b94d57d96ba2925d52e1.tar.gz
PSARC 2008/587 iSCSI Port Provider for COMSTAR
6702584 Need iSCSI port provider for COMSTAR
Diffstat (limited to 'usr/src')
-rw-r--r--usr/src/Makefile.lint3
-rw-r--r--usr/src/cmd/Makefile3
-rw-r--r--usr/src/cmd/Makefile.cmd1
-rw-r--r--usr/src/cmd/iscsitsvc/Makefile72
-rw-r--r--usr/src/cmd/iscsitsvc/iscsi-target.xml110
-rw-r--r--usr/src/cmd/iscsitsvc/iscsitsvc.c249
-rw-r--r--usr/src/cmd/itadm/Makefile43
-rw-r--r--usr/src/cmd/itadm/itadm.c2093
-rw-r--r--usr/src/cmd/mdb/Makefile.common1
-rw-r--r--usr/src/cmd/mdb/common/modules/idm/idm.c2480
-rw-r--r--usr/src/cmd/mdb/intel/amd64/idm/Makefile38
-rw-r--r--usr/src/cmd/mdb/intel/ia32/idm/Makefile37
-rw-r--r--usr/src/cmd/mdb/sparc/v9/idm/Makefile38
-rw-r--r--usr/src/common/iscsi/base64.c255
-rw-r--r--usr/src/common/iscsit/iscsit_common.c1590
-rw-r--r--usr/src/lib/Makefile3
-rw-r--r--usr/src/lib/libiscsit/Makefile56
-rw-r--r--usr/src/lib/libiscsit/Makefile.com66
-rw-r--r--usr/src/lib/libiscsit/amd64/Makefile32
-rw-r--r--usr/src/lib/libiscsit/common/libiscsit.c1889
-rw-r--r--usr/src/lib/libiscsit/common/libiscsit.h747
-rw-r--r--usr/src/lib/libiscsit/common/llib-liscsit30
-rw-r--r--usr/src/lib/libiscsit/common/mapfile-vers79
-rw-r--r--usr/src/lib/libiscsit/i386/Makefile31
-rw-r--r--usr/src/lib/libiscsit/sparc/Makefile29
-rw-r--r--usr/src/lib/libiscsit/sparcv9/Makefile32
-rw-r--r--usr/src/pkgdefs/Makefile4
-rw-r--r--usr/src/pkgdefs/SUNWiscsidmr/Makefile37
-rw-r--r--usr/src/pkgdefs/SUNWiscsidmr/pkginfo.tmpl49
-rw-r--r--usr/src/pkgdefs/SUNWiscsidmr/prototype_com46
-rw-r--r--usr/src/pkgdefs/SUNWiscsidmr/prototype_i38654
-rw-r--r--usr/src/pkgdefs/SUNWiscsidmr/prototype_sparc51
-rw-r--r--usr/src/pkgdefs/SUNWiscsidmu/Makefile35
-rw-r--r--usr/src/pkgdefs/SUNWiscsidmu/depend50
-rw-r--r--usr/src/pkgdefs/SUNWiscsidmu/pkginfo.tmpl49
-rw-r--r--usr/src/pkgdefs/SUNWiscsidmu/prototype_com47
-rw-r--r--usr/src/pkgdefs/SUNWiscsidmu/prototype_i38651
-rw-r--r--usr/src/pkgdefs/SUNWiscsidmu/prototype_sparc49
-rw-r--r--usr/src/pkgdefs/SUNWiscsitr/Makefile37
-rw-r--r--usr/src/pkgdefs/SUNWiscsitr/depend50
-rw-r--r--usr/src/pkgdefs/SUNWiscsitr/pkginfo.tmpl48
-rw-r--r--usr/src/pkgdefs/SUNWiscsitr/postinstall40
-rw-r--r--usr/src/pkgdefs/SUNWiscsitr/preremove36
-rw-r--r--usr/src/pkgdefs/SUNWiscsitr/prototype_com60
-rw-r--r--usr/src/pkgdefs/SUNWiscsitr/prototype_i38651
-rw-r--r--usr/src/pkgdefs/SUNWiscsitr/prototype_sparc49
-rw-r--r--usr/src/pkgdefs/SUNWiscsitu/Makefile35
-rw-r--r--usr/src/pkgdefs/SUNWiscsitu/depend49
-rw-r--r--usr/src/pkgdefs/SUNWiscsitu/pkginfo.tmpl48
-rw-r--r--usr/src/pkgdefs/SUNWiscsitu/prototype_com54
-rw-r--r--usr/src/pkgdefs/SUNWiscsitu/prototype_i38651
-rw-r--r--usr/src/pkgdefs/SUNWiscsitu/prototype_sparc51
-rw-r--r--usr/src/pkgdefs/etc/exception_list_i38615
-rw-r--r--usr/src/pkgdefs/etc/exception_list_sparc15
-rw-r--r--usr/src/uts/common/Makefile.files13
-rw-r--r--usr/src/uts/common/Makefile.rules28
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/iscsit.c2480
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/iscsit.conf27
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/iscsit.h799
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.c751
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.h34
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.c261
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.h160
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.c2531
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.h109
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/iscsit_login.c2534
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiusauth.c183
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiuspacket.c390
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/iscsit_sess.c792
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/iscsit_text.c324
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/iscsit_tgt.c2054
-rw-r--r--usr/src/uts/common/io/comstar/port/iscsit/radius_auth.h71
-rw-r--r--usr/src/uts/common/io/idm/idm.c2131
-rw-r--r--usr/src/uts/common/io/idm/idm_conn_sm.c1480
-rw-r--r--usr/src/uts/common/io/idm/idm_impl.c1023
-rw-r--r--usr/src/uts/common/io/idm/idm_so.c2525
-rw-r--r--usr/src/uts/common/io/idm/idm_text.c1709
-rw-r--r--usr/src/uts/common/sys/Makefile25
-rw-r--r--usr/src/uts/common/sys/Makefile.syshdrs11
-rw-r--r--usr/src/uts/common/sys/idm/idm.h445
-rw-r--r--usr/src/uts/common/sys/idm/idm_conn_sm.h269
-rw-r--r--usr/src/uts/common/sys/idm/idm_impl.h479
-rw-r--r--usr/src/uts/common/sys/idm/idm_so.h110
-rw-r--r--usr/src/uts/common/sys/idm/idm_text.h198
-rw-r--r--usr/src/uts/common/sys/idm/idm_transport.h221
-rw-r--r--usr/src/uts/common/sys/iscsi_protocol.h45
-rw-r--r--usr/src/uts/common/sys/iscsit/chap.h94
-rw-r--r--usr/src/uts/common/sys/iscsit/iscsi_if.h651
-rw-r--r--usr/src/uts/common/sys/iscsit/iscsit_common.h292
-rw-r--r--usr/src/uts/common/sys/iscsit/isns_protocol.h281
-rw-r--r--usr/src/uts/common/sys/iscsit/radius_packet.h98
-rw-r--r--usr/src/uts/common/sys/iscsit/radius_protocol.h83
-rw-r--r--usr/src/uts/intel/Makefile.intel.shared2
-rw-r--r--usr/src/uts/intel/idm/Makefile89
-rw-r--r--usr/src/uts/intel/iscsit/Makefile96
-rw-r--r--usr/src/uts/sparc/Makefile.sparc.shared2
-rw-r--r--usr/src/uts/sparc/idm/Makefile87
-rw-r--r--usr/src/uts/sparc/iscsit/Makefile96
98 files changed, 37293 insertions, 8 deletions
diff --git a/usr/src/Makefile.lint b/usr/src/Makefile.lint
index 59fd87161f..6cffcc986f 100644
--- a/usr/src/Makefile.lint
+++ b/usr/src/Makefile.lint
@@ -166,7 +166,9 @@ COMMON_SUBDIRS = \
cmd/iscsi \
cmd/iscsiadm \
cmd/iscsid \
+ cmd/iscsitsvc \
cmd/isns \
+ cmd/itadm \
cmd/join \
cmd/kbd \
cmd/killall \
@@ -364,6 +366,7 @@ COMMON_SUBDIRS = \
lib/libipmp \
lib/libipp \
lib/libipsecutil \
+ lib/libiscsit \
lib/libiscsitgt \
lib/libkmf \
lib/libkstat \
diff --git a/usr/src/cmd/Makefile b/usr/src/cmd/Makefile
index af2b9c563e..927f5ca801 100644
--- a/usr/src/cmd/Makefile
+++ b/usr/src/cmd/Makefile
@@ -213,7 +213,9 @@ COMMON_SUBDIRS= \
itutools \
iscsiadm \
iscsid \
+ iscsitsvc \
isns \
+ itadm \
java \
join \
kbd \
@@ -604,6 +606,7 @@ MSGSUBDIRS= \
iscsiadm \
iscsid \
isns \
+ itadm \
join \
kbd \
krb5 \
diff --git a/usr/src/cmd/Makefile.cmd b/usr/src/cmd/Makefile.cmd
index eb2ef79e2a..ca394186ff 100644
--- a/usr/src/cmd/Makefile.cmd
+++ b/usr/src/cmd/Makefile.cmd
@@ -235,6 +235,7 @@ ROOTSVCSYSTEMFILESYSTEM= $(ROOTSVCSYSTEM)/filesystem
ROOTSVCSYSTEMSECURITY= $(ROOTSVCSYSTEM)/security
ROOTSVCNETWORK= $(ROOTVARSVCMANIFEST)/network
ROOTSVCNETWORKDNS= $(ROOTSVCNETWORK)/dns
+ROOTSVCNETWORKISCSI= $(ROOTSVCNETWORK)/iscsi
ROOTSVCNETWORKLDAP= $(ROOTSVCNETWORK)/ldap
ROOTSVCNETWORKNFS= $(ROOTSVCNETWORK)/nfs
ROOTSVCNETWORKNIS= $(ROOTSVCNETWORK)/nis
diff --git a/usr/src/cmd/iscsitsvc/Makefile b/usr/src/cmd/iscsitsvc/Makefile
new file mode 100644
index 0000000000..953ff80c77
--- /dev/null
+++ b/usr/src/cmd/iscsitsvc/Makefile
@@ -0,0 +1,72 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+PROG = iscsi-target
+
+include ../Makefile.cmd
+
+COMMONBASE = ../../common
+
+LOCAL_OBJS = iscsitsvc.o
+COMMON_OBJS = cmdparse.o
+LOCAL_SRCS = $(LOCAL_OBJS:%.o=%.c)
+COMMON_SRCS = $(COMMON_OBJS:%.o=$(COMMONBASE)/cmdparse/%.c)
+OBJS = $(LOCAL_OBJS) $(COMMON_OBJS)
+SRCS = $(LOCAL_SRCS) $(COMMON_SRCS)
+
+CPPFLAGS += -I$(COMMONBASE)/cmdparse
+
+MANIFEST = iscsi-target.xml
+SVCMETHOD = iscsi-target
+
+ROOTMANIFESTDIR = $(ROOTSVCNETWORKISCSI)
+$(ROOTSVCNETWORKISCSI)/iscsi-target.xml := OWNER = root
+$(ROOTSVCNETWORKISCSI)/iscsi-target.xml := GROUP = bin
+$(ROOTSVCNETWORKISCSI)/iscsi-target.xml := FILEMODE = 0444
+
+.KEEP_STATE:
+
+all: $(PROG)
+
+$(PROG): $(OBJS)
+ $(LINK.c) -o $@ $(OBJS) $(LDLIBS)
+ $(POST_PROCESS)
+
+install: all $(ROOTMANIFEST) $(ROOTSVCMETHOD)
+
+check: $(CHKMANIFEST)
+ $(CSTYLE) -pPc $(SRCS:%=%)
+
+cmdparse.o: $(COMMONBASE)/cmdparse/cmdparse.c
+ $(COMPILE.c) -o $@ $(COMMONBASE)/cmdparse/cmdparse.c
+ $(POST_PROCESS_O)
+
+clean:
+ $(RM) $(OBJS)
+
+lint: lint_SRCS
+
+include ../Makefile.targ
diff --git a/usr/src/cmd/iscsitsvc/iscsi-target.xml b/usr/src/cmd/iscsitsvc/iscsi-target.xml
new file mode 100644
index 0000000000..4598d27380
--- /dev/null
+++ b/usr/src/cmd/iscsitsvc/iscsi-target.xml
@@ -0,0 +1,110 @@
+<?xml version='1.0'?>
+<!DOCTYPE service_bundle SYSTEM '/usr/share/lib/xml/dtd/service_bundle.dtd.1'>
+
+<!--
+
+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 2008 Sun Microsystems, Inc. All rights reserved.
+Use is subject to license terms.
+
+Service manifests for iSCSI Target
+-->
+
+<!--
+ network/iscsi/target - Export iSCSI target services
+
+-->
+
+<service_bundle type='manifest' name='SUNWiscsitr:iscsi-target'>
+
+<service
+ name='network/iscsi/target'
+ type='service'
+ version='1'>
+
+ <create_default_instance enabled='false' />
+
+ <single_instance/>
+
+ <dependency name = 'network'
+ grouping='require_any'
+ restart_on='error'
+ type='service'>
+ <service_fmri value='svc:/milestone/network'/>
+ </dependency>
+
+ <dependency name = 'stmf'
+ grouping='require_all'
+ restart_on='none'
+ type='service'>
+ <service_fmri value='svc:/system/stmf:default'/>
+ </dependency>
+
+ <exec_method
+ type='method'
+ name='start'
+ exec='/lib/svc/method/iscsi-target start'
+ timeout_seconds='600'>
+ <method_context>
+ <method_credential
+ user='root'
+ group='root'
+ privileges='basic,sys_devices'
+ />
+ </method_context>
+ </exec_method>
+
+ <exec_method
+ type='method'
+ name='stop'
+ exec='/lib/svc/method/iscsi-target stop'
+ timeout_seconds='600'>
+ <method_context>
+ <method_credential
+ user='root'
+ group='root'
+ privileges='basic,sys_devices'
+ />
+ </method_context>
+ </exec_method>
+
+ <property_group name='startd' type='framework'>
+ <propval name='duration' type='astring'
+ value='transient' />
+ </property_group>
+
+ <stability value='Evolving' />
+
+ <template>
+ <common_name>
+ <loctext xml:lang='C'>
+ iscsi target
+ </loctext>
+ </common_name>
+ <documentation>
+ <manpage title='itadm' section='1M'
+ manpath='/usr/share/man' />
+ </documentation>
+ </template>
+
+</service>
+
+</service_bundle>
diff --git a/usr/src/cmd/iscsitsvc/iscsitsvc.c b/usr/src/cmd/iscsitsvc/iscsitsvc.c
new file mode 100644
index 0000000000..0c147d2d4e
--- /dev/null
+++ b/usr/src/cmd/iscsitsvc/iscsitsvc.c
@@ -0,0 +1,249 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stropts.h>
+#include <libintl.h>
+#include <errno.h>
+#include <time.h>
+#include <string.h>
+#include <assert.h>
+#include <getopt.h>
+#include <cmdparse.h>
+#include <signal.h>
+#include <pthread.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <sys/systeminfo.h>
+
+#include <libiscsit.h>
+#include <sys/iscsit/iscsit_common.h>
+
+static int it_enable(int, char **, cmdOptions_t *, void *);
+static int it_disable(int, char **, cmdOptions_t *, void *);
+
+/*
+ * MAJOR - This should only change when there is an incompatible change made
+ * to the interfaces or the output.
+ *
+ * MINOR - This should change whenever there is a new command or new feature
+ * with no incompatible change.
+ */
+#define VERSION_STRING_MAJOR "1"
+#define VERSION_STRING_MINOR "0"
+#define VERSION_STRING_MAX_LEN 10
+
+/* 10 ms sleep in nanoseconds */
+#define TEN_MS_NANOSLEEP 10000000
+
+/* tables set up based on cmdparse instructions */
+
+/* add new options here */
+optionTbl_t longOptions[] = {
+ {NULL, 0, 0, 0}
+};
+
+/*
+ * Add new subcommands here
+ */
+subCommandProps_t subcommands[] = {
+ {"start", it_enable, NULL, NULL, NULL, OPERAND_NONE, NULL},
+ {"stop", it_disable, NULL, NULL, NULL, OPERAND_NONE, NULL},
+ {NULL, 0, NULL, NULL, 0, NULL, 0, NULL}
+};
+
+/* globals */
+char *cmdName;
+
+/*
+ * Opens the iSCSI Target Node
+ *
+ * fd - Return the iscsit file descriptor
+ */
+static int
+it_open(int *fd)
+{
+
+ int ret = ITADM_SUCCESS;
+
+ *fd = open(ISCSIT_NODE, O_RDONLY);
+ if (*fd < 0) {
+ if (errno == EPERM) {
+ (void) fprintf(stdout, "open failed: EPERM");
+ ret = ITADM_PERM;
+ } else {
+ (void) fprintf(stdout, "open failed: INVALID");
+ ret = ITADM_INVALID;
+ }
+ }
+
+ return (ret);
+}
+
+/*
+ * Enables the iSCSI Target
+ */
+/*ARGSUSED*/
+static int
+it_enable(int operandLen, char *operands[], cmdOptions_t *options,
+ void *args)
+{
+ int ret;
+ int fd;
+ char buf[256];
+ uint32_t *buflenp;
+ char *fqhnp;
+ iscsit_hostinfo_t hostinfo;
+
+ (void) fprintf(stdout, "%s: %s\n", cmdName,
+ gettext("Requesting to enable iscsi target"));
+
+ bzero(buf, 256);
+ bzero(hostinfo.fqhn, sizeof (hostinfo.fqhn));
+
+ /* Open the iscsi target node */
+ if ((ret = it_open(&fd)) != ITADM_SUCCESS) {
+ (void) fprintf(stdout, "Unable to open device %s", ISCSIT_NODE);
+ return (ret);
+ }
+
+ (void) fprintf(stdout, "it_enable [fd=%d]\n", fd);
+ /* enable the iscsi target */
+ buflenp = (uint32_t *)((void *)&buf);
+ *buflenp = strlen("target_name") + 1;
+ (void) strncpy(buf + sizeof (uint32_t), "target_name",
+ 256 - sizeof (uint32_t));
+
+ fqhnp = &hostinfo.fqhn[0];
+
+ ret = sysinfo(SI_HOSTNAME, fqhnp, 256);
+
+ if ((ret != -1) && (ret < sizeof (hostinfo.fqhn))) {
+ fqhnp += ret;
+ hostinfo.length = ret;
+ hostinfo.fqhn[ret-1] = '.';
+ hostinfo.length += sysinfo(SI_SRPC_DOMAIN, fqhnp,
+ sizeof (hostinfo.fqhn) - ret);
+ }
+
+ (void) fprintf(stdout, "it_enable: fqhn = '%s'\n", hostinfo.fqhn);
+
+ if ((ret = ioctl(fd, ISCSIT_IOC_ENABLE_SVC, &hostinfo)) != 0) {
+ (void) fprintf(stdout, "Unable to issue ioctl: %d", errno);
+ return (ret);
+ }
+ return (ITADM_SUCCESS);
+}
+
+
+/*
+ * Disable the iSCSI target
+ */
+/* ARGSUSED */
+static int
+it_disable(int operandLen, char *operands[], cmdOptions_t *options,
+ void *args)
+{
+ int ret;
+ int fd;
+
+ (void) fprintf(stdout, "%s: %s\n", cmdName,
+ gettext("Requesting to disable iscsi target"));
+
+ /* Open the iscsi target node */
+ if ((ret = it_open(&fd)) != ITADM_SUCCESS) {
+ return (ret);
+ }
+
+ /* disable the iSCSI target */
+ if ((ret = ioctl(fd, ISCSIT_IOC_DISABLE_SVC, NULL)) != 0) {
+ return (ret);
+ }
+ return (ITADM_SUCCESS);
+}
+
+/*
+ * input:
+ * execFullName - exec name of program (argv[0])
+ *
+ * copied from usr/src/cmd/zoneadm/zoneadm.c in OS/Net
+ * (changed name to lowerCamelCase to keep consistent with this file)
+ *
+ * Returns:
+ * command name portion of execFullName
+ */
+static char *
+getExecBasename(char *execFullname)
+{
+ char *lastSlash, *execBasename;
+
+ /* guard against '/' at end of command invocation */
+ for (;;) {
+ lastSlash = strrchr(execFullname, '/');
+ if (lastSlash == NULL) {
+ execBasename = execFullname;
+ break;
+ } else {
+ execBasename = lastSlash + 1;
+ if (*execBasename == '\0') {
+ *lastSlash = '\0';
+ continue;
+ }
+ break;
+ }
+ }
+ return (execBasename);
+}
+
+int
+main(int argc, char *argv[])
+{
+ synTables_t synTables;
+ char versionString[VERSION_STRING_MAX_LEN];
+ int ret;
+ int funcRet;
+ void *subcommandArgs = NULL;
+
+ (void) setlocale(LC_ALL, "");
+ /* set global command name */
+ cmdName = getExecBasename(argv[0]);
+
+ (void) snprintf(versionString, VERSION_STRING_MAX_LEN, "%s.%s",
+ VERSION_STRING_MAJOR, VERSION_STRING_MINOR);
+ synTables.versionString = versionString;
+ synTables.longOptionTbl = &longOptions[0];
+ synTables.subCommandPropsTbl = &subcommands[0];
+
+ ret = cmdParse(argc, argv, synTables, subcommandArgs, &funcRet);
+ if (ret != 0) {
+ return (ret);
+ }
+
+ return (funcRet);
+} /* end main */
diff --git a/usr/src/cmd/itadm/Makefile b/usr/src/cmd/itadm/Makefile
new file mode 100644
index 0000000000..c15a271209
--- /dev/null
+++ b/usr/src/cmd/itadm/Makefile
@@ -0,0 +1,43 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+PROG= itadm
+
+include ../Makefile.cmd
+
+LDLIBS += -lgen -lsecdb -lscf -lstmf -lnvpair -liscsit
+
+.KEEP_STATE:
+
+all: $(PROG)
+
+install: all $(ROOTUSRSBINPROG)
+
+clean:
+
+lint: lint_PROG
+
+include ../Makefile.targ
diff --git a/usr/src/cmd/itadm/itadm.c b/usr/src/cmd/itadm/itadm.c
new file mode 100644
index 0000000000..60f31d3a0b
--- /dev/null
+++ b/usr/src/cmd/itadm/itadm.c
@@ -0,0 +1,2093 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+#include <strings.h>
+#include <ctype.h>
+#include <libnvpair.h>
+#include <libintl.h>
+#include <libgen.h>
+#include <pwd.h>
+#include <auth_attr.h>
+#include <secdb.h>
+#include <libscf.h>
+#include <limits.h>
+#include <locale.h>
+
+#include <libstmf.h>
+#include <libiscsit.h>
+
+/* what's this used for?? */
+#define ITADM_VERSION "1.0"
+
+/* SMF service info */
+#define ISCSIT_SVC "svc:/network/iscsi/target:default"
+
+#define STMF_STALE(ret) {\
+ if (ret == STMF_ERROR_PROV_DATA_STALE) {\
+ (void) fprintf(stderr, "%s\n",\
+ gettext("Configuration changed during processing. "\
+ "Check the configuration, then retry this command "\
+ "if appropriate."));\
+ }\
+}
+
+#define ITADM_CHKAUTH(sec) {\
+ if (!chkauthattr(sec, itadm_uname)) {\
+ (void) fprintf(stderr,\
+ gettext("Error, operation requires authorization %s"),\
+ sec);\
+ (void) fprintf(stderr, "\n");\
+ return (1);\
+ }\
+}
+
+static struct option itadm_long[] = {
+ {"alias", required_argument, NULL, 'l'},
+ {"auth-method", required_argument, NULL, 'a'},
+ {"chap-secret", no_argument, NULL, 's'},
+ {"chap-secret-file", required_argument, NULL, 'S'},
+ {"chap-user", required_argument, NULL, 'u'},
+ {"force", no_argument, NULL, 'f'},
+ {"help", no_argument, NULL, 'h'},
+ {"help", no_argument, NULL, '?'},
+ {"isns", required_argument, NULL, 'i'},
+ {"isns-server", required_argument, NULL, 'I'},
+ {"node-name", required_argument, NULL, 'n'},
+ {"radius-secret", no_argument, NULL, 'd'},
+ {"radius-secret-file", required_argument, NULL, 'D'},
+ {"radius-server", required_argument, NULL, 'r'},
+ {"tpg-tag", required_argument, NULL, 't'},
+ {"verbose", no_argument, NULL, 'v'},
+ {"version", no_argument, NULL, 'V'},
+ {NULL, 0, NULL, 0}
+};
+
+char c_tgt[] = "itadm create-target [-a radius|chap|none|default] [-s] \
+[-S chap-secret-path] [-u chap-username] [-n target-node-name] \
+[-l alias] [-t tpg-name[,tpg-name,...]]";
+
+static char m_tgt[] = "itadm modify-target [-a radius|chap|none|default] [-s] \
+[-S chap-secret-path] [-u chap-username] [-n new-target-node-name] \
+[-l alias] [-t tpg-name[,tpg-name,...]] target-node-name";
+
+static char d_tgt[] = "itadm delete-target [-f] target-node-name";
+
+static char l_tgt[] = "itadm list-target [-v] [target-node-name";
+
+static char c_tpg[] = "itadm create-tpg tpg-name IP-address[:port] \
+[IP-address[:port]] [...]";
+
+static char l_tpg[] = "itadm list-tpg [-v] [tpg-name]";
+
+static char d_tpg[] = "itadm delete-tpg [-f] tpg-name";
+
+static char c_ini[] = "itadm create-initiator [-s] [-S chap-secret-path] \
+[-u chap-username] initiator-node-name";
+
+static char m_ini[] = "itadm modify-initiator [-s] [-S chap-secret-path] \
+[-u chap-username] initiator-node-name";
+
+static char l_ini[] = "itadm list-initiator [-v] initiator-node-name";
+
+static char d_ini[] = "itadm delete-inititator initiator-node-name";
+
+static char m_def[] = "itadm modify-defaults [-a radius|chap|none] \
+[-r IP-address[:port]] [-d] [-D radius-secret-path] [-i enable|disable] \
+[-I IP-address[:port][,IP-adddress[:port]]]";
+
+static char l_def[] = "itadm list-defaults";
+
+
+/* keep the order of this enum in the same order as the 'subcmds' struct */
+typedef enum {
+ CREATE_TGT,
+ MODIFY_TGT,
+ DELETE_TGT,
+ LIST_TGT,
+ CREATE_TPG,
+ DELETE_TPG,
+ LIST_TPG,
+ CREATE_INI,
+ MODIFY_INI,
+ LIST_INI,
+ DELETE_INI,
+ MODIFY_DEF,
+ LIST_DEF,
+ NULL_SUBCMD /* must always be last! */
+} itadm_sub_t;
+
+typedef struct {
+ char *name;
+ char *shortopts;
+ char *usemsg;
+} itadm_subcmds_t;
+
+static itadm_subcmds_t subcmds[] = {
+ {"create-target", ":a:sS:u:n:l:t:h?", c_tgt},
+ {"modify-target", ":a:sS:u:n:l:t:h?", m_tgt},
+ {"delete-target", ":fh?", d_tgt},
+ {"list-target", ":vh?", l_tgt},
+ {"create-tpg", ":h?", c_tpg},
+ {"delete-tpg", ":fh?", d_tpg},
+ {"list-tpg", ":vh?", l_tpg},
+ {"create-initiator", ":sS:u:h?", c_ini},
+ {"modify-initiator", ":sS:u:h?", m_ini},
+ {"list-initiator", ":vh?", l_ini},
+ {"delete-initiator", ":h?", d_ini},
+ {"modify-defaults", ":a:r:dD:i:I:h?", m_def},
+ {"list-defaults", ":h?", l_def},
+ {NULL, ":h?", NULL},
+};
+
+/* used for checking if user is authorized */
+static char *itadm_uname = NULL;
+
+/* prototypes */
+static int
+itadm_get_password(nvlist_t *nvl, char *key, char *passfile,
+ char *phrase);
+
+static int
+itadm_opt_to_arr(nvlist_t *nvl, char *key, char *opt, uint32_t *num);
+
+static int
+create_target(char *tgt, nvlist_t *proplist);
+
+static int
+modify_target(char *tgt, char *new, nvlist_t *proplist);
+
+static int
+delete_target(char *tgt, boolean_t force);
+
+static int
+list_target(char *tgt, boolean_t verbose, boolean_t script);
+
+static int
+create_tpg(char *tpg, int addrc, char **addrs);
+
+static int
+list_tpg(char *tpg, boolean_t verbose, boolean_t script);
+
+static int
+delete_tpg(char *tpg, boolean_t force);
+
+static int
+modify_initiator(char *ini, nvlist_t *proplist, boolean_t create);
+
+static int
+list_initiator(char *ini, boolean_t verbose, boolean_t script);
+
+static int
+delete_initiator(char *ini);
+
+static int
+modify_defaults(nvlist_t *proplist);
+
+static int
+list_defaults(boolean_t script);
+
+static void
+tag_name_to_num(char *tagname, uint16_t *tagnum);
+
+/* prototype from iscsit_common.h */
+extern int
+sockaddr_to_str(struct sockaddr_storage *sa, char **addr);
+
+int
+main(int argc, char *argv[])
+{
+ int ret = 0;
+ int idx = NULL_SUBCMD;
+ char c;
+ int newargc = argc;
+ char **newargv = NULL;
+ char *objp;
+ int itind = 0;
+ nvlist_t *proplist = NULL;
+ boolean_t verbose = B_FALSE;
+ boolean_t scripting = B_FALSE;
+ boolean_t tbool;
+ char *targetname = NULL;
+ char *propname;
+ boolean_t force = B_FALSE;
+ struct passwd *pwd = NULL;
+ uint32_t count = 0;
+ char *smfstate = NULL;
+
+ (void) setlocale(LC_ALL, "");
+ (void) textdomain(TEXT_DOMAIN);
+
+ if (argc < 2) {
+ ret = 1;
+ goto usage_error;
+ }
+
+ for (idx = 0; subcmds[idx].name != NULL; idx++) {
+ if (strcmp(argv[1], subcmds[idx].name) == 0) {
+ break;
+ }
+ }
+
+
+ /* get the caller's user name for subsequent chkauthattr() calls */
+ pwd = getpwuid(getuid());
+ if (pwd == NULL) {
+ (void) fprintf(stderr, "%s\n",
+ gettext("Could not determine callers user name."));
+ return (1);
+ }
+
+ itadm_uname = strdup(pwd->pw_name);
+
+ /* increment past command & subcommand */
+ newargc--;
+ newargv = &(argv[1]);
+
+ ret = nvlist_alloc(&proplist, NV_UNIQUE_NAME, 0);
+ if (ret != 0) {
+ ret = errno;
+ (void) fprintf(stderr,
+ gettext("Could not allocate nvlist, errno = %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ ret = 1;
+ goto usage_error;
+ }
+
+ while ((ret == 0) && (newargv)) {
+ c = getopt_long(newargc, newargv, subcmds[idx].shortopts,
+ itadm_long, &itind);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 0:
+ /* flag set by getopt */
+ break;
+ case 'a':
+ ret = nvlist_add_string(proplist,
+ "auth", optarg);
+ break;
+ case 'd':
+ ret = itadm_get_password(proplist,
+ "radiussecret", NULL,
+ gettext("Enter RADIUS secret: "));
+ break;
+ case 'D':
+ ret = itadm_get_password(proplist,
+ "radiussecret", optarg, NULL);
+ break;
+ case 'f':
+ force = B_TRUE;
+ break;
+ case '?':
+ /*
+ * '?' is returned for both unrecognized
+ * options and if explicitly provided on
+ * the command line. The latter should
+ * be handled the same as -h.
+ */
+ if (strcmp(newargv[optind-1], "-?") != 0) {
+ (void) fprintf(stderr,
+ gettext("Unrecognized option %s"),
+ newargv[optind-1]);
+ (void) fprintf(stderr, "\n");
+ ret = 1;
+ }
+ goto usage_error;
+ case 'h':
+ goto usage_error;
+ case 'i':
+ if (strncmp(optarg, "enable", strlen(optarg))
+ == 0) {
+ tbool = B_TRUE;
+ } else if (strncmp(optarg, "disable",
+ strlen(optarg)) == 0) {
+ tbool = B_FALSE;
+ } else {
+ (void) fprintf(stderr, "%s\n",
+ gettext("invalid value for -i"));
+ ret = 1;
+ break;
+ }
+ ret = nvlist_add_boolean_value(proplist,
+ "isns", tbool);
+ break;
+ case 'I':
+ /* possibly multi-valued */
+ ret = itadm_opt_to_arr(proplist,
+ "isnsserver", optarg, &count);
+ if ((ret == 0) && (count > 8)) {
+ (void) fprintf(stderr, "%s\n",
+ gettext(
+ "Too many iSNS servers specified, "
+ "maximum of 8 allowed"));
+ ret = 1;
+ }
+ break;
+ case 'l':
+ ret = nvlist_add_string(proplist,
+ "alias", optarg);
+ break;
+ case 'n':
+ targetname = strdup(optarg);
+ if (targetname == NULL) {
+ ret = ENOMEM;
+ }
+ break;
+ case 'r':
+ ret = nvlist_add_string(proplist,
+ "radiusserver", optarg);
+ break;
+ case 's':
+ if ((idx == CREATE_TGT) ||
+ (idx == MODIFY_TGT)) {
+ propname = "targetchapsecret";
+ } else {
+ propname = "chapsecret";
+ }
+ ret = itadm_get_password(proplist,
+ propname, NULL,
+ gettext("Enter CHAP secret: "));
+ break;
+ case 'S':
+ if ((idx == CREATE_TGT) ||
+ (idx == MODIFY_TGT)) {
+ propname = "targetchapsecret";
+ } else {
+ propname = "chapsecret";
+ }
+ ret = itadm_get_password(proplist,
+ propname, optarg, NULL);
+ break;
+ case 't':
+ /* possibly multi-valued */
+ ret = itadm_opt_to_arr(proplist,
+ "tpg-tag", optarg, NULL);
+ break;
+ case 'u':
+ if ((idx == CREATE_TGT) ||
+ (idx == MODIFY_TGT)) {
+ propname = "targetchapuser";
+ } else {
+ propname = "chapuser";
+ }
+ ret = nvlist_add_string(proplist,
+ propname, optarg);
+ break;
+ case 'v':
+ verbose = B_TRUE;
+ break;
+ case ':':
+ (void) fprintf(stderr,
+ gettext("Option %s requires an operand."),
+ newargv[optind-1]);
+ (void) fprintf(stderr, "\n");
+
+ /* fall through to default */
+ default:
+ ret = 1;
+ break;
+ }
+ }
+
+ if (ret != 0) {
+ goto usage_error;
+ }
+
+ /* after getopt() to allow handling of -h option */
+ if ((itadm_sub_t)idx == NULL_SUBCMD) {
+ (void) fprintf(stderr, "%s\n",
+ gettext("Error, no subcommand specified"));
+ ret = 1;
+ goto usage_error;
+ }
+
+ /*
+ * some subcommands take multiple operands, so adjust now that
+ * getopt is complete
+ */
+ newargc -= optind;
+ if (newargc == 0) {
+ newargv = NULL;
+ objp = NULL;
+ } else {
+ newargv = &(newargv[optind]);
+ objp = newargv[0];
+ }
+
+ if (objp == NULL) {
+ switch ((itadm_sub_t)idx) {
+ case MODIFY_TGT:
+ case DELETE_TGT:
+ case CREATE_TPG:
+ case DELETE_TPG:
+ case CREATE_INI:
+ case MODIFY_INI:
+ case DELETE_INI:
+ /* These subcommands need operands */
+ ret = 1;
+ goto usage_error;
+ default:
+ break;
+ }
+ }
+
+ /*
+ * XXX - this should probably get pushed down to the library
+ * depending on the decision to allow/disallow configuratoin
+ * without the service running.
+ */
+ /*
+ * Make sure iSCSI target service is enabled before
+ * proceeding.
+ */
+ smfstate = smf_get_state(ISCSIT_SVC);
+ if (!smfstate ||
+ (strcmp(smfstate, SCF_STATE_STRING_ONLINE) != 0)) {
+ (void) fprintf(stderr, "%s\n",
+ gettext("The iSCSI target service must be online "
+ "before running this command."));
+ (void) fprintf(stderr,
+ gettext("Use 'svcadm enable -r %s'"), ISCSIT_SVC);
+ (void) fprintf(stderr, "\n");
+ (void) fprintf(stderr, "%s\n",
+ gettext("to enable the service and its prerequisite "
+ "services and/or"));
+ (void) fprintf(stderr,
+ gettext("'svcs -x %s' to determine why it is not online."),
+ ISCSIT_SVC);
+ (void) fprintf(stderr, "\n");
+
+ return (1);
+ }
+
+ switch ((itadm_sub_t)idx) {
+ case CREATE_TGT:
+ if (targetname) {
+ ret = create_target(targetname, proplist);
+ } else {
+ /*
+ * OK for objp to be NULL here. If the
+ * user did not specify a target name,
+ * one will be generated.
+ */
+ ret = create_target(objp, proplist);
+ }
+ break;
+ case MODIFY_TGT:
+ ret = modify_target(objp, targetname, proplist);
+ break;
+ case DELETE_TGT:
+ ret = delete_target(objp, force);
+ break;
+ case LIST_TGT:
+ ret = list_target(objp, verbose, scripting);
+ break;
+ case CREATE_TPG:
+ ret = create_tpg(objp, newargc - 1, &(newargv[1]));
+ break;
+ case DELETE_TPG:
+ ret = delete_tpg(objp, force);
+ break;
+ case LIST_TPG:
+ ret = list_tpg(objp, verbose, scripting);
+ break;
+ case CREATE_INI:
+ ret = modify_initiator(objp, proplist, B_TRUE);
+ break;
+ case MODIFY_INI:
+ ret = modify_initiator(objp, proplist, B_FALSE);
+ break;
+ case LIST_INI:
+ ret = list_initiator(objp, verbose, scripting);
+ break;
+ case DELETE_INI:
+ ret = delete_initiator(objp);
+ break;
+ case MODIFY_DEF:
+ ret = modify_defaults(proplist);
+ break;
+ case LIST_DEF:
+ ret = list_defaults(scripting);
+ break;
+ default:
+ ret = 1;
+ goto usage_error;
+ }
+
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("itadm %s failed with error %d"),
+ subcmds[idx].name, ret);
+ (void) fprintf(stderr, "\n");
+ }
+ return (ret);
+
+usage_error:
+ if (subcmds[idx].name) {
+ (void) printf("%s\n", gettext(subcmds[idx].usemsg));
+ } else {
+ /* overall usage */
+ (void) printf("%s\n\n", gettext("itadm usage:"));
+ for (idx = 0; subcmds[idx].name != NULL; idx++) {
+ if (!subcmds[idx].usemsg) {
+ continue;
+ }
+ (void) printf("\t%s\n", gettext(subcmds[idx].usemsg));
+ }
+ }
+
+ return (ret);
+}
+
+static int
+create_target(char *tgt, nvlist_t *proplist)
+{
+ int ret;
+ it_config_t *cfg = NULL;
+ it_tgt_t *tgtp;
+ char **tags = NULL;
+ uint32_t count = 0;
+ nvlist_t *errlist = NULL;
+ int i;
+ it_tpg_t *tpg = NULL;
+ uint16_t tagid = 0;
+ it_tpgt_t *tpgt;
+ char *sec = "solaris.smf.modify.stmf";
+
+ ITADM_CHKAUTH(sec);
+
+ if (tgt) {
+ /*
+ * validate input name - what are the rules for EUI
+ * and IQN values?
+ */
+ if ((strncmp(tgt, "eui.", 4) != 0) &&
+ (strncmp(tgt, "iqn.", 4) != 0)) {
+ (void) fprintf(stderr, gettext("Invalid name %s"),
+ tgt);
+ (void) fprintf(stderr, "\n");
+ return (EINVAL);
+ }
+ }
+
+ ret = it_config_load(&cfg);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error retrieving iSCSI target configuration: %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ ret = it_tgt_create(cfg, &tgtp, tgt);
+ if (ret != 0) {
+ if (ret == EFAULT) {
+ (void) fprintf(stderr,
+ gettext("Invalid iSCSI name %s"), tgt);
+ } else if (ret == EEXIST) {
+ (void) fprintf(stderr,
+ gettext("iSCSI target %s already configured"),
+ tgt);
+ } else {
+ (void) fprintf(stderr,
+ gettext("Error creating target: %d"), ret);
+ }
+ (void) fprintf(stderr, "\n");
+ goto done;
+ }
+
+ /* set the target portal group tags */
+ ret = nvlist_lookup_string_array(proplist, "tpg-tag", &tags,
+ &count);
+
+ if (ret == ENOENT) {
+ /* none specified. is this ok? */
+ ret = 0;
+ } else if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("internal error: %d"), ret);
+ (void) fprintf(stderr, "\n");
+ goto done;
+ }
+
+ /* special case, don't set any TPGs */
+ if (tags && (strcmp("default", tags[0]) == 0)) {
+ count = 0;
+ }
+
+ for (i = 0; i < count; i++) {
+ if (!tags[i]) {
+ continue;
+ }
+
+ /* see that all referenced groups are already defined */
+ tpg = cfg->config_tpg_list;
+ while (tpg != NULL) {
+ if (strcmp(tags[i], tpg->tpg_name) == 0) {
+ break;
+ }
+
+ tpg = tpg->tpg_next;
+ }
+ if (tpg == NULL) {
+ (void) fprintf(stderr,
+ gettext("Invalid tpg-tag %s, tag not defined"),
+ tags[i]);
+ (void) fprintf(stderr, "\n");
+ ret = 1;
+ goto done;
+ }
+
+ /* generate the tag number to use */
+ tag_name_to_num(tags[i], &tagid);
+
+ ret = it_tpgt_create(cfg, tgtp, &tpgt, tags[i], tagid);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Could not add target portal group"
+ "tag %s, error %d"), tags[i], ret);
+ (void) fprintf(stderr, "\n");
+ goto done;
+ }
+ tagid++;
+ }
+
+ /* remove the tags from the proplist before continuing */
+ if (tags) {
+ (void) nvlist_remove_all(proplist, "tpg-tag");
+ }
+
+ ret = it_tgt_setprop(cfg, tgtp, proplist, &errlist);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error setting target properties, %d"), ret);
+ (void) fprintf(stderr, "\n");
+ if (errlist) {
+ nvpair_t *nvp = NULL;
+ char *nn;
+ char *nv;
+
+ while ((nvp = nvlist_next_nvpair(errlist, nvp))
+ != NULL) {
+ nv = NULL;
+
+ nn = nvpair_name(nvp);
+ (void) nvpair_value_string(nvp, &nv);
+
+ if (nv != NULL) {
+ (void) fprintf(stderr, "\t%s: %s\n",
+ nn, nv);
+ }
+ }
+
+ nvlist_free(errlist);
+ }
+ goto done;
+ }
+
+ if (ret == 0) {
+ ret = it_config_commit(cfg);
+ STMF_STALE(ret);
+ }
+
+done:
+ if (ret == 0) {
+ (void) printf(gettext("Target %s successfully created"),
+ tgtp->tgt_name);
+ (void) printf("\n");
+ }
+
+ it_config_free(cfg);
+
+ return (ret);
+}
+
+int
+list_target(char *tgt, boolean_t verbose, boolean_t script)
+{
+ int ret;
+ it_config_t *cfg;
+ it_tgt_t *ptr;
+ boolean_t found = B_FALSE;
+ boolean_t first = B_TRUE;
+ boolean_t first_tag = B_TRUE;
+ char *gauth = "none";
+ char *galias = "-";
+ char *auth;
+ char *alias;
+ char *chapu;
+ char *chaps;
+ it_tpgt_t *tagp;
+ char *sec = "solaris.smf.read.stmf";
+ stmfDevid devid;
+ stmfSessionList *sess = NULL;
+ stmfTargetProperties props;
+ char *state;
+ int num_sessions;
+
+ ITADM_CHKAUTH(sec);
+
+ ret = it_config_load(&cfg);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error retrieving iSCSI target configuration: %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ ptr = cfg->config_tgt_list;
+
+ /* grab global defaults for auth, alias */
+ if (cfg->config_global_properties) {
+ (void) nvlist_lookup_string(cfg->config_global_properties,
+ "alias", &galias);
+ (void) nvlist_lookup_string(cfg->config_global_properties,
+ "auth", &gauth);
+ }
+
+ for (; ptr != NULL; ptr = ptr->tgt_next) {
+ if (found) {
+ break;
+ }
+
+ if (tgt) {
+ if (strcmp(tgt, ptr->tgt_name) != 0) {
+ continue;
+ } else {
+ found = B_TRUE;
+ }
+ }
+
+ state = "-";
+ num_sessions = 0;
+ sess = NULL;
+
+ /*
+ * make a best effort to retrieve target status and
+ * number of active sessions from STMF.
+ */
+ ret = stmfDevidFromIscsiName(ptr->tgt_name, &devid);
+ if (ret == STMF_STATUS_SUCCESS) {
+ ret = stmfGetTargetProperties(&devid, &props);
+ if (ret == STMF_STATUS_SUCCESS) {
+ if (props.status == STMF_TARGET_PORT_ONLINE) {
+ state = "online";
+ } else {
+ state = "offline";
+ }
+ }
+ }
+ if (ret == STMF_STATUS_SUCCESS) {
+ ret = stmfGetSessionList(&devid, &sess);
+ if (ret == STMF_STATUS_SUCCESS) {
+ num_sessions = sess->cnt;
+ free(sess);
+ }
+ }
+
+ /* reset ret so we don't return an error */
+ ret = 0;
+
+ if (!script && first) {
+ (void) printf("%-61s%-9s%-9s\n", "TARGET NAME",
+ "STATE", "SESSIONS");
+ first = B_FALSE;
+ }
+
+ if (!script) {
+ /*
+ * try not to let columns run into each other.
+ * Stick a tab after too-long fields.
+ * Lengths chosen are for the 'common' cases.
+ */
+ (void) printf("%-61s", ptr->tgt_name);
+ if (strlen(ptr->tgt_name) > 60) {
+ (void) printf("\t");
+ }
+ (void) printf("%-9s%-9d", state, num_sessions);
+ } else {
+ (void) printf("%s\t%s\t%d", ptr->tgt_name,
+ state, num_sessions);
+ }
+
+ if (!verbose) {
+ (void) printf("\n");
+ continue;
+ }
+
+ auth = gauth;
+ alias = galias;
+ chapu = "-";
+ chaps = "unset";
+
+ if (ptr->tgt_properties) {
+ (void) nvlist_lookup_string(ptr->tgt_properties,
+ "auth", &auth);
+ (void) nvlist_lookup_string(ptr->tgt_properties,
+ "alias", &alias);
+ if (nvlist_exists(ptr->tgt_properties,
+ "targetchapsecret")) {
+ chaps = "set";
+ }
+ (void) nvlist_lookup_string(ptr->tgt_properties,
+ "targetchapuser", &chapu);
+ }
+
+ if (!script) {
+ (void) printf("\n\t%-20s\t%s\n\t%-20s\t%s\n"
+ "\t%-20s\t%s\n\t%-20s\t%s\n\t%-20s\t",
+ "alias:", alias, "auth:", auth, "targetchapuser:",
+ chapu, "targetchapsecret:", chaps, "tpg-tags:");
+ } else {
+ (void) printf("\t%s\t%s\t%s\t%s\t",
+ alias, auth, chapu, chaps);
+ }
+
+ first_tag = B_TRUE;
+ tagp = ptr->tgt_tpgt_list;
+ for (; tagp != NULL; tagp = tagp->tpgt_next) {
+ if (!first_tag) {
+ (void) printf(",");
+ } else {
+ first_tag = B_FALSE;
+ }
+ (void) printf("%s", tagp->tpgt_tpg_name);
+ }
+
+ if (first_tag) {
+ /* didn't find any */
+ (void) printf("default");
+ }
+
+ (void) printf("\n");
+ }
+
+ if (tgt && (!found)) {
+ (void) fprintf(stderr,
+ gettext("Target %s not found!\n"), tgt);
+ (void) fprintf(stderr, "\n");
+ ret = 1;
+ }
+
+ it_config_free(cfg);
+
+ return (ret);
+}
+
+int
+delete_target(char *tgt, boolean_t force)
+{
+ int ret;
+ it_config_t *cfg;
+ it_tgt_t *ptr;
+ char *sec = "solaris.smf.modify.stmf";
+
+ ITADM_CHKAUTH(sec);
+
+ if (!tgt) {
+ (void) fprintf(stderr, "%s\n",
+ gettext("Error, no target specified"));
+ return (EINVAL);
+ }
+
+ ret = it_config_load(&cfg);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error retrieving iSCSI target configuration: %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ ptr = cfg->config_tgt_list;
+ while (ptr) {
+ if (strcmp(ptr->tgt_name, tgt) == 0) {
+ break;
+ }
+
+ ptr = ptr->tgt_next;
+ }
+
+ if (ptr) {
+ ret = it_tgt_delete(cfg, ptr, force);
+
+ if (ret != 0) {
+ if (ret == EBUSY) {
+ (void) fprintf(stderr,
+ gettext("The target is online or busy. "
+ "Use the -f (force) option, or "
+ "'stmfadm offline-target %s'"), tgt);
+ (void) fprintf(stderr, "\n");
+ }
+ }
+
+ if (ret == 0) {
+ ret = it_config_commit(cfg);
+ STMF_STALE(ret);
+ }
+ } else {
+ (void) fprintf(stderr,
+ gettext("Target %s not found"), tgt);
+ (void) fprintf(stderr, "\n");
+ ret = 1;
+ }
+
+ it_config_free(cfg);
+
+ return (ret);
+}
+
+static int
+modify_target(char *tgt, char *newname, nvlist_t *proplist)
+{
+ int ret;
+ it_config_t *cfg = NULL;
+ it_tgt_t *ptr = NULL;
+ it_tgt_t *tgtp;
+ char **tags = NULL;
+ uint32_t count = 0;
+ nvlist_t *errlist = NULL;
+ int i;
+ it_tpg_t *tpg = NULL;
+ uint16_t tagid;
+ it_tpgt_t *tpgt;
+ char *sec = "solaris.smf.modify.stmf";
+
+ ITADM_CHKAUTH(sec);
+
+ /* XXX: Do we need to offline anything here too? */
+
+ if (!tgt) {
+ (void) fprintf(stderr, "%s\n",
+ gettext("Error, no target specified"));
+ return (EINVAL);
+ }
+
+ ret = it_config_load(&cfg);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error retrieving iSCSI target configuration: %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ /*
+ * If newname is specified, ensure it is a valid name
+ */
+ if (newname) {
+ if (!validate_iscsi_name(newname)) {
+ (void) fprintf(stderr,
+ gettext("Invalid iSCSI name %s"), newname);
+ (void) fprintf(stderr, "\n");
+ return (1);
+ }
+ }
+
+ /*
+ * Loop through to verify that the target to be modified truly
+ * exists. If this target is to be renamed, ensure the new
+ * name is not already in use.
+ */
+ ptr = cfg->config_tgt_list;
+ while (ptr) {
+ if (newname && (strcmp(newname, ptr->tgt_name) == 0)) {
+ (void) fprintf(stderr,
+ gettext("A target with name %s already exists"),
+ newname);
+ (void) fprintf(stderr, "\n");
+ ret = 1;
+ goto done;
+ }
+
+ if (strcmp(ptr->tgt_name, tgt) == 0) {
+ tgtp = ptr;
+ }
+
+ ptr = ptr ->tgt_next;
+ }
+
+ if (!tgtp) {
+ (void) fprintf(stderr,
+ gettext("Target %s not found"), tgt);
+ (void) fprintf(stderr, "\n");
+ it_config_free(cfg);
+ return (EINVAL);
+ }
+
+ /* set the target portal group tags */
+ ret = nvlist_lookup_string_array(proplist, "tpg-tag", &tags,
+ &count);
+
+ if (ret == ENOENT) {
+ /* none specified. is this ok? */
+ ret = 0;
+ } else if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("internal error: %d"), ret);
+ (void) fprintf(stderr, "\n");
+ goto done;
+ }
+
+ /* special case, remove all explicit TPGs, and don't add any */
+ if (tags && (strcmp("default", tags[0]) == 0)) {
+ count = 0;
+ }
+
+ for (i = 0; i < count; i++) {
+ if (!tags[i]) {
+ continue;
+ }
+
+ /* see that all referenced groups are already defined */
+ tpg = cfg->config_tpg_list;
+ while (tpg != NULL) {
+ if (strcmp(tags[i], tpg->tpg_name) == 0) {
+ break;
+ }
+ tpg = tpg->tpg_next;
+ }
+ if (tpg == NULL) {
+ (void) fprintf(stderr,
+ gettext("Invalid tpg-name %s: not defined"),
+ tags[i]);
+ (void) fprintf(stderr, "\n");
+ ret = 1;
+ goto done;
+ }
+ }
+
+ /*
+ * don't recreate tags that are already associated,
+ * remove tags not requested.
+ */
+ if (tags) {
+ tpgt = tgtp->tgt_tpgt_list;
+ while (tpgt) {
+ for (i = 0; i < count; i++) {
+ if (!tags[i]) {
+ continue;
+ }
+
+ if (strcmp(tpgt->tpgt_tpg_name, tags[i])
+ == 0) {
+ /* non-null tags will be created */
+ tags[i] = NULL;
+ break;
+ }
+ }
+ if (i == count) {
+ /* one to remove */
+ it_tpgt_t *ptr = tpgt;
+
+ tpgt = ptr->tpgt_next;
+ it_tpgt_delete(cfg, tgtp, ptr);
+ } else {
+ tpgt = tpgt->tpgt_next;
+ }
+ }
+ }
+
+ /* see if there are any left to add */
+ for (i = 0; i < count; i++) {
+ if (!tags[i]) {
+ continue;
+ }
+
+ /* generate the tag number to use */
+ tag_name_to_num(tags[i], &tagid);
+
+ ret = it_tpgt_create(cfg, tgtp, &tpgt, tags[i], tagid);
+ if (ret != 0) {
+ if (ret == E2BIG) {
+ (void) fprintf(stderr, "%s\n",
+ gettext("Error, no portal tag available"));
+ } else {
+ (void) fprintf(stderr, gettext(
+ "Could not add target portal group"
+ " tag %s, error %d"), tags[i], ret);
+ (void) fprintf(stderr, "\n");
+ }
+ goto done;
+ }
+ }
+
+ /* remove the tags from the proplist before continuing */
+ (void) nvlist_remove_all(proplist, "tpg-tag");
+
+ /*
+ * Rename this target, if requested. Save the old name in
+ * the property list, so the kernel knows this is a renamed
+ * target, and not a new one.
+ */
+ if (newname && (strlen(newname) > 0)) {
+ ret = nvlist_add_string(proplist, "oldtargetname",
+ tgtp->tgt_name);
+ if (ret != 0) {
+ (void) fprintf(stderr, "%s\n",
+ gettext("Error renaming target."));
+ goto done;
+ }
+ (void) strlcpy(tgtp->tgt_name, newname,
+ sizeof (tgtp->tgt_name));
+ }
+
+ ret = it_tgt_setprop(cfg, tgtp, proplist, &errlist);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error setting target properties: %d"), ret);
+ (void) fprintf(stderr, "\n");
+ if (errlist) {
+ nvpair_t *nvp = NULL;
+ char *nn;
+ char *nv;
+
+ while ((nvp = nvlist_next_nvpair(errlist, nvp))
+ != NULL) {
+ nv = NULL;
+
+ nn = nvpair_name(nvp);
+ (void) nvpair_value_string(nvp, &nv);
+
+ if (nv != NULL) {
+ (void) fprintf(stderr, "\t%s: %s\n",
+ nn, nv);
+ }
+ }
+
+ nvlist_free(errlist);
+ }
+ goto done;
+ }
+
+ if (ret == 0) {
+ ret = it_config_commit(cfg);
+ STMF_STALE(ret);
+ }
+
+done:
+ if (ret == 0) {
+ (void) printf(gettext("Target %s successfully modified"),
+ tgtp->tgt_name);
+ (void) printf("\n");
+ }
+
+ it_config_free(cfg);
+
+ return (ret);
+}
+
+int
+create_tpg(char *tpg, int addrc, char **addrs)
+{
+ int ret;
+ it_config_t *cfg;
+ it_tpg_t *tpgp;
+ int count = 0;
+ it_portal_t *ptl;
+ char *sec = "solaris.smf.modify.stmf";
+
+ ITADM_CHKAUTH(sec);
+
+ if (!tpg) {
+ (void) fprintf(stderr, "%s\n",
+ gettext("Error, no target portal group specified"));
+ return (EINVAL);
+ }
+
+ if (strlen(tpg) > (MAX_TPG_NAMELEN - 1)) {
+ (void) fprintf(stderr,
+ gettext("Target Portal Group name must be no longer "
+ "than %d characters."), (MAX_TPG_NAMELEN - 1));
+ (void) fprintf(stderr, "\n");
+ return (EINVAL);
+ }
+
+ if (!addrs || (addrc <= 0)) {
+ (void) fprintf(stderr, "%s\n",
+ gettext("Error, no portal addresses specified"));
+ return (EINVAL);
+ }
+
+ ret = it_config_load(&cfg);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error retrieving iSCSI target configuration: %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ tpgp = cfg->config_tpg_list;
+ while (tpgp != NULL) {
+ if (strcmp(tpgp->tpg_name, tpg) == 0) {
+ (void) fprintf(stderr,
+ gettext("Target Portal Group %s already exists"),
+ tpg);
+ (void) fprintf(stderr, "\n");
+ it_config_free(cfg);
+ return (1);
+ }
+ tpgp = tpgp->tpg_next;
+ }
+
+ /*
+ * Create the portal group and first portal
+ */
+ ret = it_tpg_create(cfg, &tpgp, tpg, addrs[count]);
+ if (ret != 0) {
+ if (ret == EEXIST) {
+ (void) fprintf(stderr,
+ gettext("Portal %s already in use"),
+ addrs[count]);
+ (void) fprintf(stderr, "\n");
+ }
+ it_config_free(cfg);
+ return (ret);
+ }
+
+ /*
+ * Add the remaining portals
+ */
+ for (count = 1; count < addrc; count++) {
+ if (!addrs[count]) {
+ continue;
+ }
+
+ ret = it_portal_create(cfg, tpgp, &ptl, addrs[count]);
+ if (ret != 0) {
+ if (ret == EEXIST) {
+ (void) fprintf(stderr,
+ gettext("Portal %s already in use"),
+ addrs[count]);
+ (void) fprintf(stderr, "\n");
+ } else {
+ (void) fprintf(stderr,
+ gettext("Error adding portal %s: %d"),
+ addrs[count], ret);
+ (void) fprintf(stderr, "\n");
+ break;
+ }
+ }
+ }
+
+ if (ret == 0) {
+ ret = it_config_commit(cfg);
+ STMF_STALE(ret);
+ }
+
+ it_config_free(cfg);
+
+ return (ret);
+}
+
+static int
+list_tpg(char *tpg, boolean_t verbose, boolean_t script)
+{
+ int ret;
+ it_config_t *cfg;
+ it_tpg_t *ptr;
+ boolean_t found = B_FALSE;
+ it_portal_t *portal;
+ boolean_t first = B_TRUE;
+ boolean_t first_portal;
+ char *pstr;
+ char *sec = "solaris.smf.read.stmf";
+
+ ITADM_CHKAUTH(sec);
+
+ ret = it_config_load(&cfg);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error retrieving iSCSI target configuration: %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ ptr = cfg->config_tpg_list;
+
+ for (; ptr != NULL; ptr = ptr->tpg_next) {
+ if (found) {
+ break;
+ }
+
+ if (tpg) {
+ if (strcmp(tpg, ptr->tpg_name) != 0) {
+ continue;
+ } else {
+ found = B_TRUE;
+ }
+ }
+
+ if (!script && first) {
+ (void) printf("%-30s%-9s\n", "TARGET PORTAL GROUP",
+ "PORTAL COUNT");
+ first = B_FALSE;
+ }
+
+ if (!script) {
+ (void) printf("%-30s", ptr->tpg_name);
+ if (strlen(ptr->tpg_name) > 30) {
+ (void) printf("\t");
+ }
+ (void) printf("%-9d", ptr->tpg_portal_count);
+ } else {
+ (void) printf("%s\t%d", ptr->tpg_name,
+ ptr->tpg_portal_count);
+ }
+
+ if (!verbose) {
+ (void) printf("\n");
+ continue;
+ }
+
+ if (!script) {
+ (void) printf("\n portals:");
+ }
+
+ first_portal = B_TRUE;
+
+ portal = ptr->tpg_portal_list;
+ for (; portal != NULL; portal = portal->next) {
+ ret = sockaddr_to_str(&(portal->portal_addr), &pstr);
+ if (ret != 0) {
+ /* invalid addr? */
+ continue;
+ }
+ if (!first_portal) {
+ (void) printf(",");
+ } else {
+ (void) printf("\t");
+ first_portal = B_FALSE;
+ }
+
+ (void) printf("%s", pstr);
+ free(pstr);
+ }
+
+ if (first_portal) {
+ /* none found */
+ (void) printf("\t<none>");
+ }
+
+ (void) printf("\n");
+ }
+
+ if (tpg && (!found)) {
+ (void) fprintf(stderr,
+ gettext("Target Portal Group %s not found!\n"), tpg);
+ (void) fprintf(stderr, "\n");
+ ret = 1;
+ }
+
+ it_config_free(cfg);
+
+ return (ret);
+}
+
+static int
+delete_tpg(char *tpg, boolean_t force)
+{
+ int ret;
+ it_config_t *cfg;
+ it_tpg_t *ptpg = NULL;
+ char *sec = "solaris.smf.modify.stmf";
+
+ ITADM_CHKAUTH(sec);
+
+ if (!tpg) {
+ (void) fprintf(stderr, "%s\n",
+ gettext("Error, no target portal group specified"));
+ return (EINVAL);
+ }
+
+ ret = it_config_load(&cfg);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error retrieving iSCSI target configuration: %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ ptpg = cfg->config_tpg_list;
+ for (; ptpg != NULL; ptpg = ptpg->tpg_next) {
+ if (strcmp(tpg, ptpg->tpg_name) == 0) {
+ break;
+ }
+ }
+
+ if (!ptpg) {
+ (void) fprintf(stderr,
+ gettext("Target portal group %s does not exist."),
+ tpg);
+ (void) fprintf(stderr, "\n");
+ ret = 1;
+ } else {
+ ret = it_tpg_delete(cfg, ptpg, force);
+ if (ret == EBUSY) {
+ (void) fprintf(stderr, "%s\n",
+ gettext(
+ "Target portal group associated with one or more "
+ "targets. Cannot delete."));
+ }
+
+ if (ret == 0) {
+ ret = it_config_commit(cfg);
+ STMF_STALE(ret);
+ }
+ }
+
+ it_config_free(cfg);
+
+ return (ret);
+}
+
+static int
+modify_initiator(char *ini, nvlist_t *proplist, boolean_t create)
+{
+ int ret;
+ it_config_t *cfg;
+ it_ini_t *inip;
+ nvlist_t *errlist = NULL;
+ nvpair_t *nvp = NULL;
+ char *sec = "solaris.smf.modify.stmf";
+ boolean_t changed = B_TRUE;
+
+ ITADM_CHKAUTH(sec);
+
+ if (!ini) {
+ (void) fprintf(stderr, "%s\n",
+ gettext("Error, no initiator specified"));
+ return (EINVAL);
+ } else if (create) {
+ /*
+ * validate input name - what are the rules for EUI
+ * and IQN values?
+ */
+ if ((strncmp(ini, "eui.", 4) != 0) &&
+ (strncmp(ini, "iqn.", 4) != 0)) {
+ (void) fprintf(stderr, gettext("Invalid name %s"),
+ ini);
+ (void) fprintf(stderr, "\n");
+ return (EINVAL);
+ }
+ }
+
+ /*
+ * See if any properties were actually specified.
+ */
+ if (proplist) {
+ nvp = nvlist_next_nvpair(proplist, nvp);
+ }
+
+ if ((nvp == NULL) && !create) {
+ changed = B_FALSE;
+ }
+
+ /*
+ * If no properties, and this is really a modify op, verify
+ * that the requested initiator exists, but then don't do anything.
+ * Modifying non-existent is an error; doing nothing to a defined
+ * initiator is not.
+ */
+
+ ret = it_config_load(&cfg);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error retrieving iSCSI target configuration: %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ inip = cfg->config_ini_list;
+ while (inip) {
+ if (strcmp(inip->ini_name, ini) == 0) {
+ break;
+ }
+
+ inip = inip->ini_next;
+ }
+
+ if (create) {
+ if (inip) {
+ (void) fprintf(stderr,
+ gettext("Initiator %s already exists"),
+ inip->ini_name);
+ (void) fprintf(stderr, "\n");
+ ret = EINVAL;
+ } else {
+ ret = it_ini_create(cfg, &inip, ini);
+ if (ret != 0) {
+ if (ret == EFAULT) {
+ (void) fprintf(stderr,
+ gettext("Invalid iSCSI name %s"),
+ ini);
+ } else {
+ (void) fprintf(stderr,
+ gettext(
+ "Error creating initiator: %d"),
+ ret);
+ }
+ (void) fprintf(stderr, "\n");
+ }
+ }
+ } else if (!inip) {
+ ret = ENOENT;
+ (void) fprintf(stderr,
+ gettext("Error, initiator %s not found."),
+ ini);
+ (void) fprintf(stderr, "\n");
+ }
+
+ if ((ret == 0) && nvp) {
+ ret = it_ini_setprop(inip, proplist, &errlist);
+
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error setting initiator properties: %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ if (errlist) {
+ nvpair_t *nvp = NULL;
+ char *nn;
+ char *nv;
+
+ while ((nvp = nvlist_next_nvpair(errlist, nvp))
+ != NULL) {
+ nv = NULL;
+
+ nn = nvpair_name(nvp);
+ (void) nvpair_value_string(nvp, &nv);
+
+ if (nv != NULL) {
+ (void) fprintf(stderr,
+ "\t%s: %s\n", nn, nv);
+ }
+ }
+
+ nvlist_free(errlist);
+ }
+ }
+ }
+
+ if ((ret == 0) && changed) {
+ ret = it_config_commit(cfg);
+ STMF_STALE(ret);
+ }
+
+ it_config_free(cfg);
+
+ return (ret);
+}
+
+static int
+list_initiator(char *ini, boolean_t verbose, boolean_t script) /* ARGSUSED */
+{
+ int ret;
+ it_config_t *cfg;
+ it_ini_t *ptr;
+ boolean_t found = B_FALSE;
+ boolean_t first = B_TRUE;
+ char *isecret;
+ char *iuser;
+ char *sec = "solaris.smf.read.stmf";
+
+ ITADM_CHKAUTH(sec);
+
+ ret = it_config_load(&cfg);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error retrieving iSCSI target configuration: %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ ptr = cfg->config_ini_list;
+
+ for (; ptr != NULL; ptr = ptr->ini_next) {
+ isecret = "unset";
+ iuser = "<none>";
+
+ if (found) {
+ break;
+ }
+
+ if (ini) {
+ if (strcmp(ini, ptr->ini_name) != 0) {
+ continue;
+ } else {
+ found = B_TRUE;
+ }
+ }
+
+ if (ptr->ini_properties) {
+ if (nvlist_exists(ptr->ini_properties, "chapsecret")) {
+ isecret = "set";
+ }
+ (void) nvlist_lookup_string(ptr->ini_properties,
+ "chapuser", &iuser);
+
+ }
+
+ /* there's nothing to print for verbose yet */
+ if (!script && first) {
+ (void) printf("%-61s%-10s%-7s\n", "INITIATOR NAME",
+ "CHAPUSER", "SECRET");
+ first = B_FALSE;
+ }
+
+ if (!script) {
+ /*
+ * try not to let columns run into each other.
+ * Stick a tab after too-long fields.
+ * Lengths chosen are for the 'common' cases.
+ */
+ (void) printf("%-61s", ptr->ini_name);
+
+ if (strlen(ptr->ini_name) > 60) {
+ (void) printf("\t");
+ }
+
+ (void) printf("%-15s", iuser);
+ if (strlen(iuser) >= 15) {
+ (void) printf("\t");
+ }
+
+ (void) printf("%-4s", isecret);
+ } else {
+ (void) printf("%s\t%s\t%s", ptr->ini_name,
+ iuser, isecret);
+ }
+
+ (void) printf("\n");
+ }
+
+ if (ini && (!found)) {
+ (void) fprintf(stderr,
+ gettext("Initiator %s not found!"), ini);
+ (void) fprintf(stderr, "\n");
+ ret = 1;
+ }
+
+ it_config_free(cfg);
+
+ return (ret);
+}
+
+int
+delete_initiator(char *ini)
+{
+ int ret;
+ it_config_t *cfg;
+ it_ini_t *ptr;
+ char *sec = "solaris.smf.modify.stmf";
+
+ ITADM_CHKAUTH(sec);
+
+ if (!ini) {
+ (void) fprintf(stderr, "%s\n",
+ gettext("Error, no initiator specified"));
+ return (EINVAL);
+ }
+
+ ret = it_config_load(&cfg);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error retrieving iSCSI target configuration: %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ ptr = cfg->config_ini_list;
+ while (ptr) {
+ if (strcmp(ptr->ini_name, ini) == 0) {
+ break;
+ }
+
+ ptr = ptr->ini_next;
+ }
+
+ if (ptr) {
+ it_ini_delete(cfg, ptr);
+
+ ret = it_config_commit(cfg);
+ STMF_STALE(ret);
+ } else {
+ (void) fprintf(stderr,
+ gettext("Initiator %s not found"), ini);
+ (void) fprintf(stderr, "\n");
+ ret = 1;
+ }
+
+ return (ret);
+}
+
+static int
+modify_defaults(nvlist_t *proplist)
+{
+ int ret;
+ it_config_t *cfg;
+ nvlist_t *errlist = NULL;
+ nvpair_t *nvp = NULL;
+ char *sec = "solaris.smf.modify.stmf";
+
+ ITADM_CHKAUTH(sec);
+
+ if (proplist) {
+ /* make sure at least one property is specified */
+ nvp = nvlist_next_nvpair(proplist, nvp);
+ }
+
+ if (nvp == NULL) {
+ /* empty list */
+ (void) fprintf(stderr, "%s\n",
+ gettext("Error, no properties specified"));
+ return (EINVAL);
+ }
+
+ ret = it_config_load(&cfg);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error retrieving iSCSI target configuration: %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ ret = it_config_setprop(cfg, proplist, &errlist);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error setting global properties: %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ if (errlist) {
+ nvpair_t *nvp = NULL;
+ char *nn;
+ char *nv;
+
+ while ((nvp = nvlist_next_nvpair(errlist, nvp))
+ != NULL) {
+ nv = NULL;
+
+ nn = nvpair_name(nvp);
+ (void) nvpair_value_string(nvp, &nv);
+
+ if (nv != NULL) {
+ (void) fprintf(stderr, "\t%s: %s\n",
+ nn, nv);
+ }
+ }
+
+ nvlist_free(errlist);
+ }
+ }
+
+ if (ret == 0) {
+ ret = it_config_commit(cfg);
+ STMF_STALE(ret);
+ }
+
+ it_config_free(cfg);
+
+ return (ret);
+}
+
+static int
+list_defaults(boolean_t script)
+{
+ int ret;
+ it_config_t *cfg;
+ nvlist_t *nvl;
+ char *alias = "<none>";
+ char *auth = "<none>";
+ char *isns = "disabled";
+ char **isvrs = NULL;
+ uint32_t scount = 0;
+ char *rsvr = "<none>";
+ char *rsecret = "unset";
+ boolean_t val = B_FALSE;
+ int i;
+ char *sec = "solaris.smf.read.stmf";
+
+ ITADM_CHKAUTH(sec);
+
+ ret = it_config_load(&cfg);
+ if (ret != 0) {
+ (void) fprintf(stderr,
+ gettext("Error retrieving iSCSI target configuration: %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ nvl = cfg->config_global_properties;
+
+ /* look up all possible options */
+ (void) nvlist_lookup_string(nvl, "alias", &alias);
+ (void) nvlist_lookup_string(nvl, "auth", &auth);
+ (void) nvlist_lookup_boolean_value(nvl, "isns", &val);
+ if (val == B_TRUE) {
+ isns = "enabled";
+ }
+ (void) nvlist_lookup_string_array(nvl, "isnsserver", &isvrs,
+ &scount);
+ (void) nvlist_lookup_string(nvl, "radiusserver", &rsvr);
+ if (nvlist_exists(nvl, "radiussecret")) {
+ rsecret = "set";
+ }
+
+ if (!script) {
+ (void) printf("%s:\n\n",
+ gettext("iSCSI Target Default Properties"));
+ }
+
+ if (script) {
+ (void) printf("%s\t%s\t%s\t%s\t%s\t",
+ alias, auth, rsvr, rsecret, isns);
+ } else {
+ (void) printf("%-15s\t%s\n%-15s\t%s\n%-15s\t%s\n%-15s\t%s\n"
+ "%-15s\t%s\n%-15s\t",
+ "alias:", alias, "auth:", auth, "radiusserver:",
+ rsvr, "radiussecret:", rsecret, "isns:", isns,
+ "isnsserver:");
+ }
+
+ for (i = 0; i < scount; i++) {
+ if (!isvrs || !isvrs[i]) {
+ break;
+ }
+ if (i > 0) {
+ (void) printf(",");
+ }
+ (void) printf("%s", isvrs[i]);
+ }
+
+ if (i == 0) {
+ (void) printf("%s", "<none>");
+ }
+
+ (void) printf("\n");
+
+ it_config_free(cfg);
+
+ return (0);
+}
+
+static int
+itadm_get_password(nvlist_t *nvl, char *key, char *passfile,
+ char *phrase)
+{
+ int ret = 0;
+ char *pass;
+ char buf[1024];
+ int fd;
+ struct stat64 sbuf;
+ size_t rd;
+
+ if (!nvl || !key) {
+ return (EINVAL);
+ }
+
+ if (passfile) {
+ ret = stat64(passfile, &sbuf);
+ if ((ret != 0) || (!S_ISREG(sbuf.st_mode))) {
+ (void) fprintf(stderr,
+ gettext("Invalid secret file %s"),
+ passfile);
+ (void) fprintf(stderr, "\n");
+ return (EBADF);
+ }
+
+ fd = open64(passfile, O_RDONLY);
+ if (fd == -1) {
+ ret = errno;
+ (void) fprintf(stderr,
+ gettext("Could not open secret file %s, %d"),
+ passfile, ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ rd = read(fd, buf, sbuf.st_size);
+ (void) close(fd);
+
+ if (rd != sbuf.st_size) {
+ ret = EIO;
+ (void) fprintf(stderr,
+ gettext("Could not read secret file %s, %d"),
+ passfile, ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ /* ensure buf is properly terminated */
+ buf[rd] = '\0';
+
+ /* if last char is a newline, strip it off */
+ if (buf[rd - 1] == '\n') {
+ buf[rd - 1] = '\0';
+ }
+
+ /* validate length */
+ if ((strlen(buf) > 255) || (strlen(buf) < 12)) {
+ (void) fprintf(stderr, "%s\n",
+ gettext(
+ "Secret must be between 12 and 255 characters"));
+ return (EINVAL);
+ }
+ } else {
+ /* prompt for secret */
+ if (!phrase) {
+ return (EINVAL);
+ }
+
+ pass = getpassphrase(phrase);
+ if (!pass) {
+ ret = errno;
+ (void) fprintf(stderr,
+ gettext("Could not read secret, %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ /* validate length */
+ if ((strlen(pass) > 255) || (strlen(pass) < 12)) {
+ (void) fprintf(stderr, "%s\n",
+ gettext(
+ "Secret must be between 12 and 255 characters"));
+ return (EINVAL);
+ }
+
+ (void) strlcpy(buf, pass, sizeof (buf));
+
+ /* confirm entered secret */
+ pass = getpassphrase(gettext("Re-enter secret: "));
+ if (!pass) {
+ ret = errno;
+ (void) fprintf(stderr,
+ gettext("Could not read secret, %d"),
+ ret);
+ (void) fprintf(stderr, "\n");
+ return (ret);
+ }
+
+ if (strcmp(buf, pass) != 0) {
+ ret = EINVAL;
+ (void) fprintf(stderr, "%s\n",
+ gettext("Secret validation failed"));
+ return (ret);
+ }
+
+ }
+
+ ret = nvlist_add_string(nvl, key, buf);
+
+ return (ret);
+}
+
+static int
+itadm_opt_to_arr(nvlist_t *nvl, char *key, char *opt, uint32_t *num)
+{
+ int count;
+ char *bufp;
+ char **arr;
+
+ if (!opt || !key || !nvl) {
+ return (EINVAL);
+ }
+
+ bufp = opt;
+ count = 1;
+
+ for (;;) {
+ bufp = strchr(bufp, ',');
+ if (!bufp) {
+ break;
+ }
+ bufp++;
+ count++;
+ }
+
+ arr = calloc(count, sizeof (char *));
+ if (!arr) {
+ return (ENOMEM);
+ }
+
+ bufp = opt;
+ /* set delimiter to comma */
+ (void) bufsplit(",", 0, NULL);
+
+ /* split up that buf! */
+ (void) bufsplit(bufp, count, arr);
+
+ /* if requested, return the number of array members found */
+ if (num) {
+ *num = count;
+ }
+
+ return (nvlist_add_string_array(nvl, key, arr, count));
+}
+
+static void
+tag_name_to_num(char *tagname, uint16_t *tagnum)
+{
+ ulong_t id;
+ char *ptr = NULL;
+
+ if (!tagname || !tagnum) {
+ return;
+ }
+
+ *tagnum = 0;
+
+ id = strtoul(tagname, &ptr, 10);
+
+ /* Must be entirely numeric and in-range */
+ if (ptr && (*ptr != '\0')) {
+ return;
+ }
+
+ if ((id <= UINT16_MAX) && (id > 1)) {
+ *tagnum = (uint16_t)id;
+ }
+}
diff --git a/usr/src/cmd/mdb/Makefile.common b/usr/src/cmd/mdb/Makefile.common
index 855679b995..5677289bc9 100644
--- a/usr/src/cmd/mdb/Makefile.common
+++ b/usr/src/cmd/mdb/Makefile.common
@@ -62,6 +62,7 @@ COMMON_MODULES_KVM = \
genunix \
hook \
neti \
+ idm \
ii \
ip \
ipc \
diff --git a/usr/src/cmd/mdb/common/modules/idm/idm.c b/usr/src/cmd/mdb/common/modules/idm/idm.c
new file mode 100644
index 0000000000..210e90a84f
--- /dev/null
+++ b/usr/src/cmd/mdb/common/modules/idm/idm.c
@@ -0,0 +1,2480 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/mdb_modapi.h>
+#include <sys/cpuvar.h>
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/types.h>
+#include <sys/taskq.h>
+#include <sys/sysmacros.h>
+#include <sys/socket.h> /* networking stuff */
+#include <sys/strsubr.h> /* networking stuff */
+#include <sys/nvpair.h>
+#include <sys/sunldi.h>
+#include <sys/stmf.h>
+#include <sys/stmf_ioctl.h>
+#include <sys/portif.h>
+
+#define IDM_CONN_SM_STRINGS
+#define ISCSIT_TGT_SM_STRINGS
+#define ISCSIT_SESS_SM_STRINGS
+#define ISCSIT_LOGIN_SM_STRINGS
+#include <sys/idm/idm.h>
+#include <iscsit.h>
+#include <iscsit_isns.h>
+
+
+/*
+ * We want to be able to print multiple levels of object hierarchy with a
+ * single dcmd information, and preferably also exclude intermediate
+ * levels if desired. For example some of the target objects have the
+ * following relationship:
+ *
+ * target --> session --> connection --> task
+ *
+ * The session dcmd should allow the printing of all associated tasks for the
+ * sessions without printing all the associated connections. To accomplish
+ * this the following structure contains a bit for each object type. Dcmds
+ * should invoked the functions for child objects if any bits are set
+ * in iscsi_dcmd_ctrl_t but the functions for the child object should only
+ * print data if their associated bit is set.
+ *
+ * Each dcmd should provide an external interface with the standard MDB API
+ * and an internal interface that accepts iscsi_dcmd_ctrl_t. To display
+ * child objects the dcmd calls the internal interface for the child object
+ * directly. Dcmds invoked from the command line will, of course, call the
+ * external interface. See iscsi_conn() and iscsi_conn_impl().
+ */
+
+typedef struct {
+ union {
+ uint32_t idc_children;
+ struct {
+ uint32_t idc_tgt:1,
+ idc_tpgt:1,
+ idc_portal:1,
+ idc_sess:1,
+ idc_conn:1,
+ idc_print_ip:1,
+ idc_task:1,
+ idc_buffer:1,
+ idc_states:1,
+ idc_rc_audit:1,
+ idc_lun:1,
+ idc_hba:1;
+ } child;
+ } u;
+ boolean_t idc_ini;
+ boolean_t idc_tgt;
+ boolean_t idc_verbose;
+ boolean_t idc_header;
+ /*
+ * Our connection dcmd code works off the global connection lists
+ * in IDM since we want to know about connections even when they
+ * have not progressed to the point that they have an associated
+ * session. If we use "::iscsi_sess [-c]" then we only want to
+ * see connections associated with particular session. To avoid
+ * writing a separate set of code to print session-specific connection
+ * the session code should set the sessions kernel address in the
+ * following field. The connection code will then only print
+ * connections that match.
+ */
+ uintptr_t idc_assoc_session;
+} iscsi_dcmd_ctrl_t;
+
+static int iscsi_walk_all_sess(iscsi_dcmd_ctrl_t *idc);
+static int iscsi_walk_all_conn(iscsi_dcmd_ctrl_t *idc);
+static int iscsi_tgt_walk_cb(uintptr_t addr, const void *list_walker_data,
+ void *idc_void);
+static int iscsi_tpgt_walk_cb(uintptr_t addr, const void *list_walker_data,
+ void *idc_void);
+static int iscsi_tpg_walk_cb(uintptr_t addr, const void *list_walker_data,
+ void *idc_void);
+static int iscsi_portal_walk_cb(uintptr_t addr, const void *list_walker_data,
+ void *idc_void);
+static int iscsi_sess_walk_cb(uintptr_t addr, const void *list_walker_data,
+ void *idc_void);
+static int iscsi_conn_walk_cb(uintptr_t addr, const void *list_walker_data,
+ void *idc_void);
+static int iscsi_buffer_walk_cb(uintptr_t addr, const void *list_walker_data,
+ void *idc_void);
+static int iscsi_tgt_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc);
+static int iscsi_tpgt_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc);
+static int iscsi_tpg_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc);
+static int iscsi_portal_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc);
+static int iscsi_sess_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc);
+static int iscsi_conn_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc);
+static void iscsi_print_iscsit_conn_data(idm_conn_t *ict);
+static void iscsi_print_idm_conn_data(idm_conn_t *ict);
+static int iscsi_task_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc);
+static void iscsi_print_iscsit_task_data(idm_task_t *idt);
+static int iscsi_buffer_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc);
+static idm_conn_type_t idm_conn_type(uintptr_t addr);
+static int iscsi_i_task_impl(idm_task_t *idt, uintptr_t addr,
+ iscsi_dcmd_ctrl_t *idc);
+static int iscsi_refcnt_impl(uintptr_t addr);
+static int iscsi_sm_audit_impl(uintptr_t addr);
+static int iscsi_isns(uintptr_t addr, uint_t flags, int argc,
+ const mdb_arg_t *argv);
+
+static const char *iscsi_idm_conn_event(int event);
+static const char *iscsi_iscsit_tgt_event(int event);
+static const char *iscsi_iscsit_sess_event(int event);
+static const char *iscsi_iscsit_login_event(int event);
+static const char *iscsi_idm_conn_state(int state);
+static const char *iscsi_idm_task_state(int state);
+static const char *iscsi_iscsit_tgt_state(int state);
+static const char *iscsi_iscsit_sess_state(int state);
+static const char *iscsi_iscsit_login_state(int state);
+
+static void iscsi_format_timestamp(char *ts_str, int strlen,
+ timespec_t *ts);
+static char *inet_ntop(int af, const void *addr, char *buf, int addrlen);
+static void convert2ascii(char *, const in6_addr_t *);
+static int sa_to_str(struct sockaddr_storage *sa, char *addr);
+static int iscsi_isns_portal_cb(uintptr_t addr, const void *walker_data,
+ void *data);
+
+#define PORTAL_STR_LEN (INET6_ADDRSTRLEN + 7)
+
+/*
+ * ::iscsi_tgt [-scatgpbSRv]
+ *
+ * iscsi_tgt - Print out information associated with an iscsit target instance
+ *
+ * s Print associated session information
+ * c Print associated connection information
+ * a Print IP addresses with connection information
+ * t Print associated task information
+ * g Print associated TPG information
+ * p Print portals with TPG information
+ * b Print associated buffer information
+ * S Print recent state events and transitions
+ * R Print reference count audit data
+ * v Verbose output about the connection
+ */
+/*ARGSUSED*/
+static int
+iscsi_tgt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+{
+ iscsi_dcmd_ctrl_t idc;
+ int buffer = 0, task = 0, print_ip = 0;
+ int tpgt = 0, conn = 0, sess = 0, portal = 0;
+ int states = 0, rc_audit = 0;
+ uintptr_t iscsit_global_addr, avl_addr, list_addr;
+ GElf_Sym sym;
+
+ bzero(&idc, sizeof (idc));
+ if (mdb_getopts(argc, argv,
+ 'a', MDB_OPT_SETBITS, TRUE, &print_ip,
+ 'g', MDB_OPT_SETBITS, TRUE, &tpgt,
+ 's', MDB_OPT_SETBITS, TRUE, &sess,
+ 'c', MDB_OPT_SETBITS, TRUE, &conn,
+ 't', MDB_OPT_SETBITS, TRUE, &task,
+ 'b', MDB_OPT_SETBITS, TRUE, &buffer,
+ 'p', MDB_OPT_SETBITS, TRUE, &portal,
+ 'S', MDB_OPT_SETBITS, TRUE, &states,
+ 'R', MDB_OPT_SETBITS, TRUE, &rc_audit,
+ 'v', MDB_OPT_SETBITS, TRUE, &idc.idc_verbose,
+ NULL) != argc)
+ return (DCMD_USAGE);
+
+ idc.u.child.idc_tgt = 1;
+ idc.u.child.idc_print_ip = print_ip;
+ idc.u.child.idc_tpgt = tpgt;
+ idc.u.child.idc_portal = portal;
+ idc.u.child.idc_sess = sess;
+ idc.u.child.idc_conn = conn;
+ idc.u.child.idc_task = task;
+ idc.u.child.idc_buffer = buffer;
+ idc.u.child.idc_states = states;
+ idc.u.child.idc_rc_audit = rc_audit;
+
+ if (DCMD_HDRSPEC(flags))
+ idc.idc_header = 1;
+
+ /*
+ * If no address was specified on the command line, we
+ * print out all tgtions
+ */
+ if (!(flags & DCMD_ADDRSPEC)) {
+ if (mdb_lookup_by_name("iscsit_global", &sym) == -1) {
+ mdb_warn("failed to find symbol 'iscsit_global'");
+ return (DCMD_ERR);
+ }
+ iscsit_global_addr = (uintptr_t)sym.st_value;
+ avl_addr = iscsit_global_addr +
+ offsetof(iscsit_global_t, global_target_list);
+ if (mdb_pwalk("avl", iscsi_tgt_walk_cb, &idc, avl_addr) == -1) {
+ mdb_warn("avl walk failed for global target tree");
+ return (DCMD_ERR);
+ }
+ list_addr = iscsit_global_addr +
+ offsetof(iscsit_global_t, global_deleted_target_list);
+ if (mdb_pwalk("list", iscsi_tgt_walk_cb,
+ &idc, list_addr) == -1) {
+ mdb_warn("list walk failed for deleted target list");
+ return (DCMD_ERR);
+ }
+ return (DCMD_OK);
+ } else {
+ return (iscsi_tgt_impl(addr, &idc));
+ }
+ /*NOTREACHED*/
+}
+
+static int
+iscsi_tpg(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+{
+ iscsi_dcmd_ctrl_t idc;
+ uintptr_t iscsit_global_addr, avl_addr;
+ GElf_Sym sym;
+
+ bzero(&idc, sizeof (idc));
+ if (mdb_getopts(argc, argv,
+ 'v', MDB_OPT_SETBITS, TRUE, &idc.idc_verbose,
+ NULL) != argc)
+ return (DCMD_USAGE);
+
+ idc.u.child.idc_portal = 1; /* Always print portals */
+ if (DCMD_HDRSPEC(flags))
+ idc.idc_header = 1;
+
+ /*
+ * If no address was specified on the command line, we
+ * print out all tgtions
+ */
+ if (!(flags & DCMD_ADDRSPEC)) {
+ if (mdb_lookup_by_name("iscsit_global", &sym) == -1) {
+ mdb_warn("failed to find symbol 'iscsit_global'");
+ return (DCMD_ERR);
+ }
+ iscsit_global_addr = (uintptr_t)sym.st_value;
+ avl_addr = iscsit_global_addr +
+ offsetof(iscsit_global_t, global_tpg_list);
+ if (mdb_pwalk("avl", iscsi_tpg_walk_cb, &idc, avl_addr) == -1) {
+ mdb_warn("avl walk failed for global target tree");
+ return (DCMD_ERR);
+ }
+ return (DCMD_OK);
+ } else {
+ return (iscsi_tpg_impl(addr, &idc));
+ }
+ /*NOTREACHED*/
+}
+
+/*
+ * ::iscsi_sess [-bctvIT]
+ *
+ * iscsi_sess - Print out information associated with an iSCSI session
+ *
+ * I Print only initiator sessions
+ * T Print only target sessions
+ * c Print associated connection information
+ * a Print IP addresses with connection information
+ * t Print associated task information
+ * b Print associated buffer information
+ * S Print recent state events and transitions
+ * R Print reference count audit data
+ * v Verbose output about the connection
+ */
+/*ARGSUSED*/
+static int
+iscsi_sess(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+{
+ iscsi_dcmd_ctrl_t idc;
+ int buffer = 0, task = 0, conn = 0, print_ip = 0;
+ int states = 0, rc_audit = 0;
+
+ bzero(&idc, sizeof (idc));
+ if (mdb_getopts(argc, argv,
+ 'I', MDB_OPT_SETBITS, TRUE, &idc.idc_ini,
+ 'T', MDB_OPT_SETBITS, TRUE, &idc.idc_tgt,
+ 'a', MDB_OPT_SETBITS, TRUE, &print_ip,
+ 'c', MDB_OPT_SETBITS, TRUE, &conn,
+ 't', MDB_OPT_SETBITS, TRUE, &task,
+ 'b', MDB_OPT_SETBITS, TRUE, &buffer,
+ 'S', MDB_OPT_SETBITS, TRUE, &states,
+ 'R', MDB_OPT_SETBITS, TRUE, &rc_audit,
+ 'v', MDB_OPT_SETBITS, TRUE, &idc.idc_verbose,
+ NULL) != argc)
+ return (DCMD_USAGE);
+
+ idc.u.child.idc_sess = 1;
+ idc.u.child.idc_print_ip = print_ip;
+ idc.u.child.idc_conn = conn;
+ idc.u.child.idc_task = task;
+ idc.u.child.idc_buffer = buffer;
+ idc.u.child.idc_states = states;
+ idc.u.child.idc_rc_audit = rc_audit;
+ if (DCMD_HDRSPEC(flags))
+ idc.idc_header = 1;
+
+ /*
+ * If no address was specified on the command line, we
+ * print out all sessions
+ */
+ if (!(flags & DCMD_ADDRSPEC)) {
+ return (iscsi_walk_all_sess(&idc));
+ } else {
+ return (iscsi_sess_impl(addr, &idc));
+ }
+ /*NOTREACHED*/
+}
+
+
+
+/*
+ * ::iscsi_conn [-btvIT]
+ *
+ * iscsi_conn - Print out information associated with an iSCSI connection
+ *
+ * I Print only initiator connections
+ * T Print only target connections
+ * a Print IP addresses with connection information
+ * t Print associated task information
+ * b Print associated buffer information
+ * S Print recent state events and transitions
+ * R Print reference count audit data
+ * v Verbose output about the connection
+ */
+/*ARGSUSED*/
+static int
+iscsi_conn(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+{
+ iscsi_dcmd_ctrl_t idc;
+ int buffer = 0, task = 0, print_ip = 0;
+ int states = 0, rc_audit = 0;
+
+ bzero(&idc, sizeof (idc));
+ if (mdb_getopts(argc, argv,
+ 'I', MDB_OPT_SETBITS, TRUE, &idc.idc_ini,
+ 'T', MDB_OPT_SETBITS, TRUE, &idc.idc_tgt,
+ 'a', MDB_OPT_SETBITS, TRUE, &print_ip,
+ 't', MDB_OPT_SETBITS, TRUE, &task,
+ 'b', MDB_OPT_SETBITS, TRUE, &buffer,
+ 'S', MDB_OPT_SETBITS, TRUE, &states,
+ 'R', MDB_OPT_SETBITS, TRUE, &rc_audit,
+ 'v', MDB_OPT_SETBITS, TRUE, &idc.idc_verbose,
+ NULL) != argc)
+ return (DCMD_USAGE);
+
+ idc.u.child.idc_conn = 1;
+ idc.u.child.idc_print_ip = print_ip;
+ idc.u.child.idc_task = task;
+ idc.u.child.idc_buffer = buffer;
+ idc.u.child.idc_states = states;
+ idc.u.child.idc_rc_audit = rc_audit;
+ if (DCMD_HDRSPEC(flags))
+ idc.idc_header = 1;
+
+ /*
+ * If no address was specified on the command line, we
+ * print out all connections
+ */
+ if (!(flags & DCMD_ADDRSPEC)) {
+ return (iscsi_walk_all_conn(&idc));
+ } else {
+ return (iscsi_conn_impl(addr, &idc));
+ }
+ /*NOTREACHED*/
+}
+
+/*
+ * ::iscsi_task [-bv]
+ *
+ * iscsi_task - Print out information associated with an iSCSI task
+ *
+ * b Print associated buffer information
+ * S Print recent state events and transitions
+ * R Print reference count audit data
+ * v Verbose output about the connection
+ */
+/*ARGSUSED*/
+static int
+iscsi_task(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+{
+ iscsi_dcmd_ctrl_t idc;
+ int buffer = 0;
+ int states = 0, rc_audit = 0;
+
+ bzero(&idc, sizeof (idc));
+ if (mdb_getopts(argc, argv,
+ 'b', MDB_OPT_SETBITS, TRUE, &buffer,
+ 'S', MDB_OPT_SETBITS, TRUE, &states,
+ 'R', MDB_OPT_SETBITS, TRUE, &rc_audit,
+ 'v', MDB_OPT_SETBITS, TRUE, &idc.idc_verbose,
+ NULL) != argc)
+ return (DCMD_USAGE);
+
+ idc.u.child.idc_conn = 0;
+ idc.u.child.idc_task = 1;
+ idc.u.child.idc_buffer = buffer;
+ idc.u.child.idc_states = states;
+ idc.u.child.idc_rc_audit = rc_audit;
+ if (DCMD_HDRSPEC(flags))
+ idc.idc_header = 1;
+
+ /*
+ * If no address was specified on the command line, we
+ * print out all connections
+ */
+ if (!(flags & DCMD_ADDRSPEC)) {
+ return (iscsi_walk_all_conn(&idc));
+ } else {
+ return (iscsi_task_impl(addr, &idc));
+ }
+ /*NOTREACHED*/
+}
+
+/*
+ * ::iscsi_refcnt
+ *
+ * iscsi_refcnt - Dump an idm_refcnt_t structure
+ *
+ */
+/*ARGSUSED*/
+static int
+iscsi_refcnt(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+{
+ if (!(flags & DCMD_ADDRSPEC)) {
+ return (DCMD_ERR);
+ } else {
+ return (iscsi_refcnt_impl(addr));
+ }
+ /*NOTREACHED*/
+}
+
+/*
+ * ::iscsi_states
+ *
+ * iscsi_states - Dump events and state transitions recoreded in an
+ * idm_sm_audit_t structure
+ *
+ */
+/*ARGSUSED*/
+static int
+iscsi_states(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+{
+ if (!(flags & DCMD_ADDRSPEC)) {
+ return (DCMD_ERR);
+ } else {
+ return (iscsi_sm_audit_impl(addr));
+ }
+ /*NOTREACHED*/
+}
+
+static int
+iscsi_walk_all_sess(iscsi_dcmd_ctrl_t *idc)
+{
+ uintptr_t iscsit_global_addr;
+ uintptr_t avl_addr;
+ uintptr_t list_addr;
+ GElf_Sym sym;
+
+ /* Walk discovery sessions */
+ if (mdb_lookup_by_name("iscsit_global", &sym) == -1) {
+ mdb_warn("failed to find symbol 'iscsit_global'");
+ return (DCMD_ERR);
+ }
+ iscsit_global_addr = (uintptr_t)sym.st_value;
+ avl_addr = iscsit_global_addr +
+ offsetof(iscsit_global_t, global_discovery_sessions);
+ if (mdb_pwalk("avl", iscsi_sess_walk_cb, idc, avl_addr) == -1) {
+ mdb_warn("avl walk failed for discovery sessions");
+ return (DCMD_ERR);
+ }
+
+ /* Walk targets printing all session info */
+ avl_addr = iscsit_global_addr +
+ offsetof(iscsit_global_t, global_target_list);
+ if (mdb_pwalk("avl", iscsi_tgt_walk_cb, idc, avl_addr) == -1) {
+ mdb_warn("avl walk failed for target/session tree");
+ return (DCMD_ERR);
+ }
+
+ /* Walk deleting targets printing all session info */
+ list_addr = iscsit_global_addr +
+ offsetof(iscsit_global_t, global_deleted_target_list);
+ if (mdb_pwalk("list", iscsi_tgt_walk_cb, idc, list_addr) == -1) {
+ mdb_warn("list walk failed for deleted target list");
+ return (DCMD_ERR);
+ }
+
+ return (DCMD_OK);
+}
+
+static int
+iscsi_walk_all_conn(iscsi_dcmd_ctrl_t *idc)
+{
+ uintptr_t idm_global_addr;
+ uintptr_t list_addr;
+ GElf_Sym sym;
+
+ /* Walk initiator connections */
+ if (mdb_lookup_by_name("idm", &sym) == -1) {
+ mdb_warn("failed to find symbol 'idm'");
+ return (DCMD_ERR);
+ }
+ idm_global_addr = (uintptr_t)sym.st_value;
+ /* Walk connection list associated with the initiator */
+ list_addr = idm_global_addr + offsetof(idm_global_t, idm_ini_conn_list);
+ if (mdb_pwalk("list", iscsi_conn_walk_cb, idc, list_addr) == -1) {
+ mdb_warn("list walk failed for initiator connections");
+ return (DCMD_ERR);
+ }
+
+ /* Walk connection list associated with the target */
+ list_addr = idm_global_addr + offsetof(idm_global_t, idm_tgt_conn_list);
+ if (mdb_pwalk("list", iscsi_conn_walk_cb, idc, list_addr) == -1) {
+ mdb_warn("list walk failed for target service instances");
+ return (DCMD_ERR);
+ }
+
+ return (DCMD_OK);
+}
+
+/*ARGSUSED*/
+static int
+iscsi_tpg_walk_cb(uintptr_t addr, const void *list_walker_data,
+ void *idc_void)
+{
+ /* We don't particularly care about the list walker data */
+ iscsi_dcmd_ctrl_t *idc = idc_void;
+ int rc;
+
+ rc = iscsi_tpg_impl(addr, idc);
+
+ return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR);
+}
+
+/*ARGSUSED*/
+static int
+iscsi_tgt_walk_cb(uintptr_t addr, const void *list_walker_data,
+ void *idc_void)
+{
+ /* We don't particularly care about the list walker data */
+ iscsi_dcmd_ctrl_t *idc = idc_void;
+ int rc;
+
+ rc = iscsi_tgt_impl(addr, idc);
+
+ return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR);
+}
+
+/*ARGSUSED*/
+static int
+iscsi_tpgt_walk_cb(uintptr_t addr, const void *list_walker_data,
+ void *idc_void)
+{
+ /* We don't particularly care about the list walker data */
+ iscsi_dcmd_ctrl_t *idc = idc_void;
+ int rc;
+
+ rc = iscsi_tpgt_impl(addr, idc);
+
+ return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR);
+}
+
+/*ARGSUSED*/
+static int
+iscsi_portal_walk_cb(uintptr_t addr, const void *list_walker_data,
+ void *idc_void)
+{
+ /* We don't particularly care about the list walker data */
+ iscsi_dcmd_ctrl_t *idc = idc_void;
+ int rc;
+
+ rc = iscsi_portal_impl(addr, idc);
+
+ return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR);
+}
+
+/*ARGSUSED*/
+static int
+iscsi_sess_walk_cb(uintptr_t addr, const void *list_walker_data,
+ void *idc_void)
+{
+ /* We don't particularly care about the list walker data */
+ iscsi_dcmd_ctrl_t *idc = idc_void;
+ int rc;
+
+ rc = iscsi_sess_impl(addr, idc);
+
+ return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR);
+}
+
+/*ARGSUSED*/
+static int
+iscsi_sess_conn_walk_cb(uintptr_t addr, const void *list_walker_data,
+ void *idc_void)
+{
+ /* We don't particularly care about the list walker data */
+ iscsi_dcmd_ctrl_t *idc = idc_void;
+ iscsit_conn_t ict;
+ int rc;
+
+ /*
+ * This function is different from iscsi_conn_walk_cb because
+ * we get an iscsit_conn_t instead of an idm_conn_t
+ *
+ * Read iscsit_conn_t, use to get idm_conn_t pointer
+ */
+ if (mdb_vread(&ict, sizeof (iscsit_conn_t), addr) !=
+ sizeof (iscsit_conn_t)) {
+ return (DCMD_ERR);
+ }
+ rc = iscsi_conn_impl((uintptr_t)ict.ict_ic, idc);
+
+ return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR);
+}
+
+/*ARGSUSED*/
+static int
+iscsi_conn_walk_cb(uintptr_t addr, const void *list_walker_data,
+ void *idc_void)
+{
+ /* We don't particularly care about the list walker data */
+ iscsi_dcmd_ctrl_t *idc = idc_void;
+ int rc;
+
+ rc = iscsi_conn_impl(addr, idc);
+
+ return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR);
+}
+
+/*ARGSUSED*/
+static int
+iscsi_buffer_walk_cb(uintptr_t addr, const void *list_walker_data,
+ void *idc_void)
+{
+ /* We don't particularly care about the list walker data */
+ iscsi_dcmd_ctrl_t *idc = idc_void;
+ int rc;
+
+ rc = iscsi_buffer_impl(addr, idc);
+
+ return ((rc == DCMD_OK) ? WALK_NEXT : WALK_ERR);
+}
+
+static int
+iscsi_tgt_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc)
+{
+ iscsit_tgt_t tgt;
+ uintptr_t avl_addr, rc_addr, states_addr;
+ char tgt_name[MAX_ISCSI_NODENAMELEN];
+ int verbose, states, rc_audit;
+
+ /*
+ * Read iscsit_tgt_t
+ */
+ if (mdb_vread(&tgt, sizeof (iscsit_tgt_t), addr) !=
+ sizeof (iscsit_tgt_t)) {
+ return (DCMD_ERR);
+ }
+
+ /*
+ * Read target name if available
+ */
+ if ((tgt.target_name == NULL) ||
+ (mdb_readstr(tgt_name, sizeof (tgt_name),
+ (uintptr_t)tgt.target_name) == -1)) {
+ strcpy(tgt_name, "N/A");
+ }
+
+ /*
+ * Brief output
+ *
+ * iscsit_tgt_t pointer
+ * iscsit_tgt_t.target_stmf_state
+ * iscsit_tgt_t.target_sess_list.avl_numnodes (session count)
+ * iscsit_tgt_t.target_name;
+ */
+
+ verbose = idc->idc_verbose;
+ states = idc->u.child.idc_states;
+ rc_audit = idc->u.child.idc_rc_audit;
+
+ /* For now we will ignore the verbose flag */
+ if (idc->u.child.idc_tgt) {
+ /* Print target data */
+ if (idc->idc_header) {
+ mdb_printf("%<u>%-19s %-4s %-8s%</u>\n",
+ "iscsit_tgt_t", "Sess", "State");
+ }
+ mdb_printf("%-19p %-4d %-8d\n", addr,
+ tgt.target_sess_list.avl_numnodes,
+ tgt.target_state);
+ mdb_printf(" %s\n", tgt_name);
+ }
+
+ idc->idc_header = 0;
+ idc->idc_verbose = 0;
+
+ /*
+ * Print states if requested
+ */
+ if (idc->u.child.idc_tgt && states) {
+ states_addr = addr + offsetof(iscsit_tgt_t, target_state_audit);
+
+ (void) mdb_inc_indent(4);
+ mdb_printf("State History:\n");
+ if (iscsi_sm_audit_impl(states_addr) != DCMD_OK)
+ return (DCMD_ERR);
+ idc->u.child.idc_states = 0;
+ (void) mdb_dec_indent(4);
+ }
+
+ /*
+ * Print refcnt audit data if requested
+ */
+ if (idc->u.child.idc_tgt && rc_audit) {
+ (void) mdb_inc_indent(4);
+ mdb_printf("target_sess_refcnt:\n");
+ rc_addr = addr +
+ offsetof(iscsit_tgt_t, target_sess_refcnt);
+ if (iscsi_refcnt_impl(rc_addr) != DCMD_OK)
+ return (DCMD_ERR);
+
+ mdb_printf("target_refcnt:\n");
+ rc_addr = addr +
+ offsetof(iscsit_tgt_t, target_refcnt);
+
+ if (iscsi_refcnt_impl(rc_addr) != DCMD_OK)
+ return (DCMD_ERR);
+ idc->u.child.idc_rc_audit = 0;
+ (void) mdb_dec_indent(4);
+ }
+
+ /* Any child objects to walk? */
+ if (idc->u.child.idc_tpgt || idc->u.child.idc_sess ||
+ idc->u.child.idc_conn || idc->u.child.idc_task ||
+ idc->u.child.idc_buffer) {
+ /* Walk TPGT tree */
+ idc->idc_header = 1;
+ (void) mdb_inc_indent(4);
+ avl_addr = addr +
+ offsetof(iscsit_tgt_t, target_tpgt_list);
+ if (mdb_pwalk("avl", iscsi_tpgt_walk_cb, idc,
+ avl_addr) == -1) {
+ mdb_warn("target tpgt list walk failed");
+ (void) mdb_dec_indent(4);
+ return (DCMD_ERR);
+ }
+ (void) mdb_dec_indent(4);
+
+ /* Walk sess tree */
+ idc->idc_header = 1;
+ (void) mdb_inc_indent(4);
+ avl_addr = addr + offsetof(iscsit_tgt_t, target_sess_list);
+ if (mdb_pwalk("avl", iscsi_sess_walk_cb, idc,
+ avl_addr) == -1) {
+ mdb_warn("target sess list walk failed");
+ (void) mdb_dec_indent(4);
+ return (DCMD_ERR);
+ }
+ (void) mdb_dec_indent(4);
+
+ idc->idc_header = 0;
+ }
+
+ idc->idc_verbose = verbose;
+ idc->u.child.idc_states = states;
+ idc->u.child.idc_rc_audit = rc_audit;
+ return (DCMD_OK);
+}
+
+static int
+iscsi_tpgt_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc)
+{
+ iscsit_tpgt_t tpgt;
+ iscsit_tpg_t tpg;
+ uintptr_t avl_addr, tpg_addr;
+
+ /*
+ * Read iscsit_tpgt_t
+ */
+ if (mdb_vread(&tpgt, sizeof (iscsit_tpgt_t), addr) !=
+ sizeof (iscsit_tpgt_t)) {
+ return (DCMD_ERR);
+ }
+
+ tpg_addr = (uintptr_t)tpgt.tpgt_tpg;
+
+ /*
+ * Read iscsit_tpg_t
+ */
+ if (mdb_vread(&tpg, sizeof (iscsit_tpg_t), tpg_addr) !=
+ sizeof (iscsit_tpg_t)) {
+ return (DCMD_ERR);
+ }
+
+ /*
+ * Brief output
+ *
+ * iscsit_tpgt_t pointer
+ * iscsit_tpg_t pointer
+ * iscsit_tpg_t.tpg_name
+ * iscsit_tpgt_t.tpgt_tag;
+ */
+
+ /* For now we will ignore the verbose flag */
+ if (idc->u.child.idc_tpgt) {
+ /* Print target data */
+ if (idc->idc_header) {
+ mdb_printf("%<u>%-?s %-?s %-18s %-6s%</u>\n",
+ "iscsit_tpgt_t", "iscsit_tpg_t", "Name", "Tag");
+ }
+ mdb_printf("%?p %?p %-18s 0x%04x\n", addr, tpgt.tpgt_tpg,
+ tpg.tpg_name, tpgt.tpgt_tag);
+ }
+
+ /*
+ * Assume for now that anyone interested in TPGT wants to see the
+ * portals as well.
+ */
+ idc->idc_header = 1;
+ (void) mdb_inc_indent(4);
+ avl_addr = tpg_addr + offsetof(iscsit_tpg_t, tpg_portal_list);
+ if (mdb_pwalk("avl", iscsi_portal_walk_cb, idc, avl_addr) == -1) {
+ mdb_warn("portal list walk failed");
+ (void) mdb_dec_indent(4);
+ return (DCMD_ERR);
+ }
+ (void) mdb_dec_indent(4);
+ idc->idc_header = 0;
+
+ return (DCMD_OK);
+}
+
+static int
+iscsi_tpg_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc)
+{
+ iscsit_tpg_t tpg;
+ uintptr_t avl_addr;
+
+ /*
+ * Read iscsit_tpg_t
+ */
+ if (mdb_vread(&tpg, sizeof (iscsit_tpg_t), addr) !=
+ sizeof (iscsit_tpg_t)) {
+ return (DCMD_ERR);
+ }
+
+ /*
+ * Brief output
+ *
+ * iscsit_tpgt_t pointer
+ * iscsit_tpg_t pointer
+ * iscsit_tpg_t.tpg_name
+ * iscsit_tpgt_t.tpgt_tag;
+ */
+
+ /* For now we will ignore the verbose flag */
+
+ /* Print target data */
+ if (idc->idc_header) {
+ mdb_printf("%<u>%-?s %-18s%</u>\n",
+ "iscsit_tpg_t", "Name");
+ }
+ mdb_printf("%?p %-18s\n", addr, tpg.tpg_name);
+
+
+ /*
+ * Assume for now that anyone interested in TPG wants to see the
+ * portals as well.
+ */
+ idc->idc_header = 1;
+ (void) mdb_inc_indent(4);
+ avl_addr = addr + offsetof(iscsit_tpg_t, tpg_portal_list);
+ if (mdb_pwalk("avl", iscsi_portal_walk_cb, idc, avl_addr) == -1) {
+ mdb_warn("portal list walk failed");
+ (void) mdb_dec_indent(4);
+ return (DCMD_ERR);
+ }
+ (void) mdb_dec_indent(4);
+ idc->idc_header = 0;
+
+ return (DCMD_OK);
+}
+
+static int
+iscsi_portal_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc)
+{
+ iscsit_portal_t portal;
+ char portal_addr[PORTAL_STR_LEN];
+ if (idc->u.child.idc_portal) {
+ /*
+ * Read iscsit_portal_t
+ */
+ if (mdb_vread(&portal, sizeof (iscsit_portal_t), addr) !=
+ sizeof (iscsit_portal_t)) {
+ return (DCMD_ERR);
+ }
+
+ /* Print portal data */
+ if (idc->idc_header) {
+ mdb_printf("%<u>%-?s %-?s %-30s%</u>\n",
+ "iscsit_portal_t", "idm_svc_t", "IP:Port");
+ }
+ sa_to_str(&portal.portal_addr, portal_addr);
+ mdb_printf("%?p %?p %s\n", addr, portal.portal_svc,
+ portal_addr);
+ }
+
+ return (DCMD_OK);
+}
+
+static int
+iscsi_sess_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc)
+{
+ iscsit_sess_t ist;
+ uintptr_t list_addr, states_addr, rc_addr;
+ char ini_name[80];
+ char tgt_name[80];
+ int verbose, states, rc_audit;
+
+ /*
+ * Read iscsit_sess_t
+ */
+ if (mdb_vread(&ist, sizeof (iscsit_sess_t), addr) !=
+ sizeof (iscsit_sess_t)) {
+ return (DCMD_ERR);
+ }
+
+ /*
+ * Brief output
+ *
+ * iscsit_sess_t pointer
+ * iscsit_sess_t.ist_state/iscsit_sess_t.ist_ffp_conn_count
+ * iscsit_sess_t.ist_tsih
+ * iscsit_sess_t.ist_initiator_name
+ */
+
+ verbose = idc->idc_verbose;
+ states = idc->u.child.idc_states;
+ rc_audit = idc->u.child.idc_rc_audit;
+
+ if (idc->u.child.idc_sess) {
+ if (verbose) {
+ /*
+ * Read initiator name if available
+ */
+ if ((ist.ist_initiator_name == NULL) ||
+ (mdb_readstr(ini_name, sizeof (ini_name),
+ (uintptr_t)ist.ist_initiator_name) == -1)) {
+ strcpy(ini_name, "N/A");
+ }
+
+ /*
+ * Read target name if available
+ */
+ if ((ist.ist_target_name == NULL) ||
+ (mdb_readstr(tgt_name, sizeof (tgt_name),
+ (uintptr_t)ist.ist_target_name) == -1)) {
+ strcpy(tgt_name, "N/A");
+ }
+
+ mdb_printf("Session %p\n", addr);
+ mdb_printf("%16s: %d\n", "State",
+ ist.ist_state);
+ mdb_printf("%16s: %d\n", "Last State",
+ ist.ist_last_state);
+ mdb_printf("%16s: %d\n", "FFP Connections",
+ ist.ist_ffp_conn_count);
+ mdb_printf("%16s: %02x%02x%02x%02x%02x%02x\n", "ISID",
+ ist.ist_isid[0], ist.ist_isid[1], ist.ist_isid[2],
+ ist.ist_isid[3], ist.ist_isid[4], ist.ist_isid[5]);
+ mdb_printf("%16s: 0x%04x\n", "TSIH",
+ ist.ist_tsih);
+ mdb_printf("%16s: %s\n", "Initiator IQN",
+ ini_name);
+ mdb_printf("%16s: %s\n", "Target IQN",
+ tgt_name);
+ mdb_printf("%16s: %08x\n", "ExpCmdSN",
+ ist.ist_expcmdsn);
+ mdb_printf("%16s: %08x\n", "MaxCmdSN",
+ ist.ist_maxcmdsn);
+ } else {
+ /* Print session data */
+ if (idc->idc_header) {
+ mdb_printf("%<u>%-?s %10s %-12s %-6s%</u>\n",
+ "iscsit_sess_t", "State/Conn", "ISID",
+ "TSIH");
+ }
+ mdb_printf("%?p %4d/%-4d %02x%02x%02x%02x%02x%02x "
+ "0x%04x\n", addr,
+ ist.ist_state, ist.ist_ffp_conn_count,
+ ist.ist_isid[0], ist.ist_isid[1], ist.ist_isid[2],
+ ist.ist_isid[3], ist.ist_isid[4], ist.ist_isid[5],
+ ist.ist_tsih);
+ }
+ idc->idc_header = 0;
+ }
+
+ idc->idc_verbose = 0;
+
+ /*
+ * Print states if requested
+ */
+ if (states) {
+ states_addr = addr + offsetof(iscsit_sess_t, ist_state_audit);
+
+ (void) mdb_inc_indent(4);
+ mdb_printf("State History:\n");
+ if (iscsi_sm_audit_impl(states_addr) != DCMD_OK)
+ return (DCMD_ERR);
+
+ /* Don't print state history for child objects */
+ idc->u.child.idc_states = 0;
+ (void) mdb_dec_indent(4);
+ }
+
+ /*
+ * Print refcnt audit data if requested
+ */
+ if (rc_audit) {
+ (void) mdb_inc_indent(4);
+ mdb_printf("Reference History:\n");
+ rc_addr = addr +
+ offsetof(iscsit_sess_t, ist_refcnt);
+ if (iscsi_refcnt_impl(rc_addr) != DCMD_OK)
+ return (DCMD_ERR);
+
+ /* Don't print audit data for child objects */
+ idc->u.child.idc_rc_audit = 0;
+ (void) mdb_dec_indent(4);
+ }
+
+ /* Any child objects to walk? */
+ if (idc->u.child.idc_conn || idc->u.child.idc_task ||
+ idc->u.child.idc_buffer) {
+ /* Walk conn list */
+ idc->idc_header = 1;
+ (void) mdb_inc_indent(4);
+ list_addr = addr + offsetof(iscsit_sess_t, ist_conn_list);
+ if (mdb_pwalk("list", iscsi_sess_conn_walk_cb, idc,
+ list_addr) == -1) {
+ mdb_warn("session conn list walk failed");
+ (void) mdb_dec_indent(4);
+ return (DCMD_ERR);
+ }
+ (void) mdb_dec_indent(4);
+ idc->idc_header = 0;
+ }
+
+ idc->idc_verbose = verbose;
+ idc->u.child.idc_states = states;
+ idc->u.child.idc_rc_audit = rc_audit;
+
+ return (DCMD_OK);
+}
+
+static int
+iscsi_conn_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc)
+{
+ uintptr_t idm_global_addr, states_addr, rc_addr;
+ uintptr_t task_addr, task_ptr;
+ GElf_Sym sym;
+ idm_task_t idt;
+ idm_conn_t ic;
+ char *conn_type;
+ int task_idx;
+ char laddr[PORTAL_STR_LEN];
+ char raddr[PORTAL_STR_LEN];
+ int verbose, states, rc_audit;
+
+ /*
+ * Get pointer to task table
+ */
+
+ if (mdb_lookup_by_name("idm", &sym) == -1) {
+ mdb_warn("failed to find symbol 'idm'");
+ return (DCMD_ERR);
+ }
+
+ idm_global_addr = (uintptr_t)sym.st_value;
+
+ if (mdb_vread(&task_ptr, sizeof (uintptr_t),
+ idm_global_addr + offsetof(idm_global_t, idm_taskid_table)) !=
+ sizeof (uintptr_t)) {
+ mdb_warn("Failed to read address of task table");
+ return (DCMD_ERR);
+ }
+
+ /*
+ * Read idm_conn_t
+ */
+ if (mdb_vread(&ic, sizeof (idm_conn_t), addr) != sizeof (idm_conn_t)) {
+ return (DCMD_ERR);
+ }
+ conn_type = (ic.ic_conn_type == CONN_TYPE_INI) ? "Ini" :
+ (ic.ic_conn_type == CONN_TYPE_TGT) ? "Tgt" : "Unk";
+
+ /*
+ * Brief output
+ *
+ * idm_conn_t pointer
+ * idm_conn_t.ic_conn_type
+ * idm_conn_t.ic_statet+idm_conn_t.ic_ffp
+ */
+
+ verbose = idc->idc_verbose;
+ states = idc->u.child.idc_states;
+ rc_audit = idc->u.child.idc_rc_audit;
+
+ if (idc->u.child.idc_conn) {
+ if (idc->idc_verbose) {
+ mdb_printf("IDM Conn %p\n", addr);
+ if (ic.ic_conn_type == CONN_TYPE_TGT) {
+ iscsi_print_iscsit_conn_data(&ic);
+ } else {
+ iscsi_print_idm_conn_data(&ic);
+ }
+ } else {
+ /* Print connection data */
+ if (idc->idc_header) {
+ mdb_printf("%<u>%-?s %-6s %-10s %12s%</u>\n",
+ "idm_conn_t", "Type", "Transport",
+ "State/FFP");
+ }
+ mdb_printf("%?p %-6s %-10s %6d/%-6d\n", addr, conn_type,
+ (ic.ic_transport_type ==
+ IDM_TRANSPORT_TYPE_ISER) ? "ISER_IB" :
+ (ic.ic_transport_type ==
+ IDM_TRANSPORT_TYPE_SOCKETS) ? "SOCKETS" :
+ "N/A",
+ ic.ic_state, ic.ic_ffp);
+ if (idc->u.child.idc_print_ip) {
+ sa_to_str(&ic.ic_laddr, laddr);
+ sa_to_str(&ic.ic_raddr, raddr);
+ mdb_printf(" L%s R%s\n",
+ laddr, raddr);
+ }
+ }
+ }
+ idc->idc_header = 0;
+
+ idc->idc_verbose = 0;
+
+ /*
+ * Print states if requested
+ */
+ if (states) {
+ states_addr = addr + offsetof(idm_conn_t, ic_state_audit);
+
+ (void) mdb_inc_indent(4);
+ mdb_printf("State History:\n");
+ if (iscsi_sm_audit_impl(states_addr) != DCMD_OK)
+ return (DCMD_ERR);
+
+ /* Don't print state history for child objects */
+ idc->u.child.idc_states = 0;
+ (void) mdb_dec_indent(4);
+ }
+
+ /*
+ * Print refcnt audit data if requested
+ */
+ if (rc_audit) {
+ (void) mdb_inc_indent(4);
+ mdb_printf("Reference History:\n");
+ rc_addr = addr + offsetof(idm_conn_t, ic_refcnt);
+ if (iscsi_refcnt_impl(rc_addr) != DCMD_OK)
+ return (DCMD_ERR);
+
+ /* Don't print audit data for child objects */
+ idc->u.child.idc_rc_audit = 0;
+ (void) mdb_dec_indent(4);
+ }
+
+ task_idx = 0;
+
+ /* Any child objects to walk? */
+ if (idc->u.child.idc_task || idc->u.child.idc_buffer) {
+ idc->idc_header = 1;
+ while (task_idx < IDM_TASKIDS_MAX) {
+
+ /*
+ * Read the next idm_task_t
+ */
+
+ if (mdb_vread(&task_addr, sizeof (uintptr_t),
+ task_ptr) != sizeof (uintptr_t)) {
+ mdb_warn("Failed to read task pointer");
+ return (DCMD_ERR);
+ }
+
+ if (task_addr == NULL) {
+ task_ptr += sizeof (uintptr_t);
+ task_idx++;
+ continue;
+ }
+
+ if (mdb_vread(&idt, sizeof (idm_task_t), task_addr)
+ != sizeof (idm_task_t)) {
+ mdb_warn("Failed to read task pointer");
+ return (DCMD_ERR);
+ }
+
+ if (((uintptr_t)idt.idt_ic == addr) &&
+ (idt.idt_state != TASK_IDLE)) {
+ (void) mdb_inc_indent(4);
+ if (iscsi_i_task_impl(&idt, task_addr, idc)
+ == -1) {
+ mdb_warn("Failed to walk connection "
+ "task tree");
+ (void) mdb_dec_indent(4);
+ return (DCMD_ERR);
+ }
+ (void) mdb_dec_indent(4);
+ }
+
+ task_ptr += sizeof (uintptr_t);
+ task_idx++;
+ }
+ idc->idc_header = 0;
+ }
+
+ idc->idc_verbose = verbose;
+ idc->u.child.idc_states = states;
+ idc->u.child.idc_rc_audit = rc_audit;
+
+ return (DCMD_OK);
+}
+
+static void
+iscsi_print_iscsit_conn_data(idm_conn_t *ic)
+{
+ iscsit_conn_t ict;
+ char *csg;
+ char *nsg;
+
+ iscsi_print_idm_conn_data(ic);
+
+ if (mdb_vread(&ict, sizeof (iscsit_conn_t),
+ (uintptr_t)ic->ic_handle) != sizeof (iscsit_conn_t)) {
+ mdb_printf("**Failed to read conn private data\n");
+ return;
+ }
+
+ if (ict.ict_login_sm.icl_login_state != ILS_LOGIN_DONE) {
+ switch (ict.ict_login_sm.icl_login_csg) {
+ case ISCSI_SECURITY_NEGOTIATION_STAGE:
+ csg = "Security";
+ break;
+ case ISCSI_OP_PARMS_NEGOTIATION_STAGE:
+ csg = "Operational";
+ break;
+ case ISCSI_FULL_FEATURE_PHASE:
+ csg = "FFP";
+ break;
+ default:
+ csg = "Unknown";
+ }
+ switch (ict.ict_login_sm.icl_login_nsg) {
+ case ISCSI_SECURITY_NEGOTIATION_STAGE:
+ nsg = "Security";
+ break;
+ case ISCSI_OP_PARMS_NEGOTIATION_STAGE:
+ nsg = "Operational";
+ break;
+ case ISCSI_FULL_FEATURE_PHASE:
+ nsg = "FFP";
+ break;
+ default:
+ nsg = "Unknown";
+ }
+ mdb_printf("%20s: %d\n", "Login State",
+ ict.ict_login_sm.icl_login_state);
+ mdb_printf("%20s: %d\n", "Login Last State",
+ ict.ict_login_sm.icl_login_last_state);
+ mdb_printf("%20s: %s\n", "CSG", csg);
+ mdb_printf("%20s: %s\n", "NSG", nsg);
+ mdb_printf("%20s: %d\n", "Transit",
+ ict.ict_login_sm.icl_login_transit >> 7);
+ mdb_printf("%20s: %p\n", "Request nvlist",
+ ict.ict_login_sm.icl_request_nvlist);
+ mdb_printf("%20s: %p\n", "Response nvlist",
+ ict.ict_login_sm.icl_response_nvlist);
+ mdb_printf("%20s: %p\n", "Negotiated nvlist",
+ ict.ict_login_sm.icl_negotiated_values);
+ if (ict.ict_login_sm.icl_login_state == ILS_LOGIN_ERROR) {
+ mdb_printf("%20s: 0x%02x\n", "Error Class",
+ ict.ict_login_sm.icl_login_resp_err_class);
+ mdb_printf("%20s: 0x%02x\n", "Error Detail",
+ ict.ict_login_sm.icl_login_resp_err_detail);
+ }
+ }
+ mdb_printf("%20s: 0x%04x\n", "CID", ict.ict_cid);
+ mdb_printf("%20s: 0x%08x\n", "StatSN", ict.ict_statsn);
+}
+
+static void
+iscsi_print_idm_conn_data(idm_conn_t *ic)
+{
+ char laddr[PORTAL_STR_LEN];
+ char raddr[PORTAL_STR_LEN];
+
+ sa_to_str(&ic->ic_laddr, laddr);
+ sa_to_str(&ic->ic_raddr, raddr);
+
+ mdb_printf("%20s: %s\n", "Conn Type",
+ ((ic->ic_conn_type == CONN_TYPE_TGT) ? "Target" :
+ ((ic->ic_conn_type == CONN_TYPE_INI) ? "Initiator" :
+ "Unknown")));
+ if (ic->ic_conn_type == CONN_TYPE_TGT) {
+ mdb_printf("%20s: %p\n", "Svc. Binding",
+ ic->ic_svc_binding);
+ }
+ mdb_printf("%20s: %s\n", "Transport",
+ (ic->ic_transport_type == IDM_TRANSPORT_TYPE_ISER) ? "ISER_IB" :
+ (ic->ic_transport_type == IDM_TRANSPORT_TYPE_SOCKETS) ? "SOCKETS" :
+ "N/A");
+
+ mdb_printf("%20s: %s\n", "Local IP", laddr);
+ mdb_printf("%20s: %s\n", "Remote IP", raddr);
+ mdb_printf("%20s: %d\n", "State",
+ ic->ic_state);
+ mdb_printf("%20s: %d\n", "Last State",
+ ic->ic_last_state);
+ mdb_printf("%20s: %d %s\n", "Refcount",
+ ic->ic_refcnt.ir_refcnt,
+ (ic->ic_refcnt.ir_waiting == REF_NOWAIT) ? "" :
+ ((ic->ic_refcnt.ir_waiting == REF_WAIT_SYNC) ? "REF_WAIT_SYNC" :
+ ((ic->ic_refcnt.ir_waiting == REF_WAIT_ASYNC) ? "REF_WAIT_ASYNC" :
+ "UNKNOWN")));
+}
+
+static int
+iscsi_i_task_impl(idm_task_t *idt, uintptr_t addr, iscsi_dcmd_ctrl_t *idc)
+{
+ uintptr_t list_addr, rc_addr;
+ idm_conn_type_t conn_type;
+ int verbose, states, rc_audit;
+
+ conn_type = idm_conn_type((uintptr_t)idt->idt_ic);
+
+ verbose = idc->idc_verbose;
+ states = idc->u.child.idc_states;
+ rc_audit = idc->u.child.idc_rc_audit;
+
+ if (idc->u.child.idc_task) {
+ if (verbose) {
+ mdb_printf("Task %p\n", addr);
+ (void) mdb_inc_indent(2);
+ if (conn_type == CONN_TYPE_TGT) {
+ iscsi_print_iscsit_task_data(idt);
+ }
+ (void) mdb_dec_indent(2);
+ } else {
+ /* Print task data */
+ if (idc->idc_header) {
+ mdb_printf(
+ "%<u>%-?s %-?s %-8s %-8s %-8s%</u>\n",
+ "Tasks/Active:", "Private",
+ "Data SN", "Exp SN",
+ (conn_type == CONN_TYPE_TGT ? "TTT" :
+ (conn_type == CONN_TYPE_INI ? "ITT" :
+ "TT")), "Handle");
+ }
+ mdb_printf("%?p %?p %08x %08x %08x %08x\n", addr,
+ idt->idt_private, idt->idt_exp_datasn,
+ idt->idt_exp_rttsn, idt->idt_tt,
+ idt->idt_client_handle);
+ }
+ }
+ idc->idc_header = 0;
+ idc->idc_verbose = 0;
+
+ /*
+ * Print states if requested
+ */
+#if 0
+ if (states) {
+ states_addr = addr + offsetof(idm_task_t, idt_state_audit);
+
+ (void) mdb_inc_indent(4);
+ mdb_printf("State History:\n");
+ if (iscsi_sm_audit_impl(states_addr) != DCMD_OK)
+ return (DCMD_ERR);
+
+ /* Don't print state history for child objects */
+ idc->u.child.idc_states = 0;
+ (void) mdb_dec_indent(4);
+ }
+#endif
+
+ /*
+ * Print refcnt audit data if requested
+ */
+ if (rc_audit) {
+ (void) mdb_inc_indent(4);
+ mdb_printf("Reference History:\n");
+ rc_addr = addr +
+ offsetof(idm_task_t, idt_refcnt);
+ if (iscsi_refcnt_impl(rc_addr) != DCMD_OK)
+ return (DCMD_ERR);
+
+ /* Don't print audit data for child objects */
+ idc->u.child.idc_rc_audit = 0;
+ (void) mdb_dec_indent(4);
+ }
+
+
+ /* Buffers are leaf objects */
+ if (idc->u.child.idc_buffer) {
+ /* Walk in buffer list */
+ (void) mdb_inc_indent(2);
+ mdb_printf("In buffers:\n");
+ idc->idc_header = 1;
+ (void) mdb_inc_indent(2);
+ list_addr = addr + offsetof(idm_task_t, idt_inbufv);
+ if (mdb_pwalk("list", iscsi_buffer_walk_cb, idc, list_addr) ==
+ -1) {
+ mdb_warn("list walk failed for task in buffers");
+ (void) mdb_dec_indent(4);
+ return (DCMD_ERR);
+ }
+ (void) mdb_dec_indent(2);
+ /* Walk out buffer list */
+ mdb_printf("Out buffers:\n");
+ idc->idc_header = 1;
+ (void) mdb_inc_indent(2);
+ list_addr = addr + offsetof(idm_task_t, idt_outbufv);
+ if (mdb_pwalk("list", iscsi_buffer_walk_cb, idc, list_addr) ==
+ -1) {
+ mdb_warn("list walk failed for task out buffers\n");
+ (void) mdb_dec_indent(2);
+ return (DCMD_ERR);
+ }
+ (void) mdb_dec_indent(4);
+ }
+
+ idc->idc_verbose = verbose;
+ idc->u.child.idc_states = states;
+ idc->u.child.idc_rc_audit = rc_audit;
+
+ return (DCMD_OK);
+}
+
+static int
+iscsi_task_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc)
+{
+ idm_task_t idt;
+
+ /*
+ * Read idm_conn_t
+ */
+ if (mdb_vread(&idt, sizeof (idm_task_t), addr) != sizeof (idm_task_t)) {
+ return (DCMD_ERR);
+ }
+
+ return (iscsi_i_task_impl(&idt, addr, idc));
+}
+
+#define ISCSI_CDB_INDENT 16
+
+static void
+iscsi_print_iscsit_task_data(idm_task_t *idt)
+{
+ iscsit_task_t itask;
+ boolean_t good_scsi_task = B_TRUE;
+ scsi_task_t scsi_task;
+
+ if (mdb_vread(&itask, sizeof (iscsit_task_t),
+ (uintptr_t)idt->idt_private) != sizeof (iscsit_task_t)) {
+ mdb_printf("**Failed to read idt_private data\n");
+ return;
+ }
+
+ if (mdb_vread(&scsi_task, sizeof (scsi_task_t),
+ (uintptr_t)itask.it_stmf_task) != sizeof (scsi_task_t)) {
+ good_scsi_task = B_FALSE;
+ }
+
+ mdb_printf("%20s: %p/%p/%p%s\n",
+ "iscsit/STMF/LU", idt->idt_private,
+ itask.it_stmf_task, good_scsi_task ? scsi_task.task_lu_private : 0,
+ good_scsi_task ? "" : "**");
+ if (good_scsi_task) {
+ mdb_printf("%20s: %08x/%08x\n", "ITT/TTT",
+ itask.it_itt, itask.it_ttt);
+ mdb_printf("%20s: %08x\n", "CmdSN",
+ itask.it_cmdsn);
+ mdb_printf("%20s: %02x %02x %02x %02x %02x %02x %02x %02x\n",
+ "LU number",
+ scsi_task.task_lun_no[0], scsi_task.task_lun_no[1],
+ scsi_task.task_lun_no[2], scsi_task.task_lun_no[3],
+ scsi_task.task_lun_no[4], scsi_task.task_lun_no[5],
+ scsi_task.task_lun_no[6], scsi_task.task_lun_no[7]);
+ mdb_printf(" CDB (%d bytes):\n",
+ scsi_task.task_cdb_length);
+ (void) mdb_inc_indent(ISCSI_CDB_INDENT);
+ if (mdb_dumpptr((uintptr_t)scsi_task.task_cdb,
+ scsi_task.task_cdb_length,
+ MDB_DUMP_RELATIVE | MDB_DUMP_TRIM |
+ MDB_DUMP_GROUP(1),
+ (mdb_dumpptr_cb_t)mdb_vread, NULL)) {
+ mdb_printf("** Invalid CDB addr (%p)\n",
+ scsi_task.task_cdb);
+ }
+ (void) mdb_dec_indent(ISCSI_CDB_INDENT);
+ mdb_printf("%20s: %d/%d\n", "STMF cur/max bufs",
+ scsi_task.task_cur_nbufs,
+ scsi_task.task_max_nbufs);
+ mdb_printf("%20s: 0x%08x/0x%08x/0x%08x\n", "Bytes Exp/Cmd/Done",
+ scsi_task.task_expected_xfer_length,
+ scsi_task.task_cmd_xfer_length,
+ scsi_task.task_nbytes_transferred);
+ mdb_printf("%20s: 0x%x/0x%x\n", "TX-ini start/done",
+ idt->idt_tx_to_ini_start,
+ idt->idt_tx_to_ini_done);
+ mdb_printf("%20s: 0x%x/0x%x\n", "RX-ini start/done",
+ idt->idt_rx_from_ini_start,
+ idt->idt_rx_from_ini_done);
+ }
+}
+
+static int
+iscsi_buffer_impl(uintptr_t addr, iscsi_dcmd_ctrl_t *idc)
+{
+ idm_buf_t idb;
+
+ /*
+ * Read idm_buf_t
+ */
+ if (mdb_vread(&idb, sizeof (idm_buf_t), addr) != sizeof (idm_buf_t)) {
+ return (DCMD_ERR);
+ }
+
+
+ if (idc->idc_header) {
+ mdb_printf("%<u>%-?s %?s/%-8s %8s %8s %8s%</u>\n",
+ "idm_buf_t", "Mem Rgn", "Length",
+ "Rel Off", "Xfer Len", "Exp. Off");
+ }
+ idc->idc_header = 0;
+
+ /* Print buffer data */
+ mdb_printf("%?p %?p/%08x %8x %8x %08x\n", addr,
+ idb.idb_buf, idb.idb_buflen,
+ idb.idb_bufoffset, idb.idb_xfer_len,
+ idb.idb_exp_offset);
+
+
+ /* Buffers are leaf objects */
+
+ return (DCMD_OK);
+}
+
+static int
+iscsi_refcnt_impl(uintptr_t addr)
+{
+ idm_refcnt_t refcnt;
+ refcnt_audit_buf_t *anb;
+ int ctr;
+
+ /*
+ * Print refcnt info
+ */
+ if (mdb_vread(&refcnt, sizeof (idm_refcnt_t), addr) !=
+ sizeof (idm_refcnt_t)) {
+ return (DCMD_ERR);
+ }
+
+ anb = &refcnt.ir_audit_buf;
+
+ ctr = anb->anb_max_index + 1;
+ anb->anb_index--;
+ anb->anb_index &= anb->anb_max_index;
+
+ while (ctr) {
+ refcnt_audit_record_t *anr;
+
+ anr = anb->anb_records + anb->anb_index;
+
+ if (anr->anr_depth) {
+ char c[MDB_SYM_NAMLEN];
+ GElf_Sym sym;
+ int i;
+
+ mdb_printf("\nRefCnt: %u\t", anr->anr_refcnt);
+
+ for (i = 0; i < anr->anr_depth; i++) {
+ if (mdb_lookup_by_addr(anr->anr_stack[i],
+ MDB_SYM_FUZZY, c, sizeof (c),
+ &sym) == -1) {
+ continue;
+ }
+ mdb_printf("%s+0x%1x", c,
+ anr->anr_stack[i] -
+ (uintptr_t)sym.st_value);
+ ++i;
+ break;
+ }
+
+ while (i < anr->anr_depth) {
+ if (mdb_lookup_by_addr(anr->anr_stack[i],
+ MDB_SYM_FUZZY, c, sizeof (c),
+ &sym) == -1) {
+ ++i;
+ continue;
+ }
+ mdb_printf("\n\t\t%s+0x%1x", c,
+ anr->anr_stack[i] -
+ (uintptr_t)sym.st_value);
+ ++i;
+ }
+ mdb_printf("\n");
+ }
+ anb->anb_index--;
+ anb->anb_index &= anb->anb_max_index;
+ ctr--;
+ }
+
+ return (DCMD_OK);
+}
+
+static int
+iscsi_sm_audit_impl(uintptr_t addr)
+{
+ sm_audit_buf_t audit_buf;
+ int ctr;
+ const char *event_name;
+ const char *state_name;
+ const char *new_state_name;
+ char ts_string[40];
+ /*
+ * Print refcnt info
+ */
+ if (mdb_vread(&audit_buf, sizeof (sm_audit_buf_t), addr) !=
+ sizeof (sm_audit_buf_t)) {
+ return (DCMD_ERR);
+ }
+
+ ctr = audit_buf.sab_max_index + 1;
+ audit_buf.sab_index++;
+ audit_buf.sab_index &= audit_buf.sab_max_index;
+
+ while (ctr) {
+ sm_audit_record_t *sar;
+
+ sar = audit_buf.sab_records + audit_buf.sab_index;
+
+ iscsi_format_timestamp(ts_string, 40, &sar->sar_timestamp);
+
+ switch (sar->sar_type) {
+ case SAR_STATE_EVENT:
+ switch (sar->sar_sm_type) {
+ case SAS_IDM_CONN:
+ state_name =
+ iscsi_idm_conn_state(sar->sar_state);
+ event_name =
+ iscsi_idm_conn_event(sar->sar_event);
+ break;
+ case SAS_ISCSIT_TGT:
+ state_name =
+ iscsi_iscsit_tgt_state(sar->sar_state);
+ event_name =
+ iscsi_iscsit_tgt_event(sar->sar_event);
+ break;
+ case SAS_ISCSIT_SESS:
+ state_name =
+ iscsi_iscsit_sess_state(sar->sar_state);
+ event_name =
+ iscsi_iscsit_sess_event(sar->sar_event);
+ break;
+ case SAS_ISCSIT_LOGIN:
+ state_name =
+ iscsi_iscsit_login_state(sar->sar_state);
+ event_name =
+ iscsi_iscsit_login_event(sar->sar_event);
+ break;
+ default:
+ state_name = event_name = "N/A";
+ break;
+ }
+ mdb_printf("%s|%s (%d)\n\t%9s %s (%d) %p\n",
+ ts_string, state_name, sar->sar_state,
+ "Event", event_name,
+ sar->sar_event, sar->sar_event_info);
+
+ break;
+ case SAR_STATE_CHANGE:
+ switch (sar->sar_sm_type) {
+ case SAS_IDM_CONN:
+ state_name =
+ iscsi_idm_conn_state(sar->sar_state);
+ new_state_name =
+ iscsi_idm_conn_state(sar->sar_new_state);
+ break;
+ case SAS_IDM_TASK:
+ state_name =
+ iscsi_idm_task_state(sar->sar_state);
+ new_state_name =
+ iscsi_idm_task_state(sar->sar_new_state);
+ break;
+ case SAS_ISCSIT_TGT:
+ state_name =
+ iscsi_iscsit_tgt_state(sar->sar_state);
+ new_state_name =
+ iscsi_iscsit_tgt_state(sar->sar_new_state);
+ break;
+ case SAS_ISCSIT_SESS:
+ state_name =
+ iscsi_iscsit_sess_state(sar->sar_state);
+ new_state_name =
+ iscsi_iscsit_sess_state(sar->sar_new_state);
+ break;
+ case SAS_ISCSIT_LOGIN:
+ state_name =
+ iscsi_iscsit_login_state(sar->sar_state);
+ new_state_name =
+ iscsi_iscsit_login_state(
+ sar->sar_new_state);
+ break;
+ default:
+ break;
+ }
+ mdb_printf("%s|%s (%d)\n\t%9s %s (%d)\n",
+ ts_string, state_name, sar->sar_state,
+ "New State", new_state_name, sar->sar_new_state);
+ default:
+ state_name = new_state_name = "N/A";
+ break;
+ }
+
+ audit_buf.sab_index++;
+ audit_buf.sab_index &= audit_buf.sab_max_index;
+ ctr--;
+ }
+
+ return (DCMD_OK);
+}
+
+static const char *
+iscsi_idm_conn_event(int event)
+{
+ const char *name = "N/A";
+
+ event = (event > CE_MAX_EVENT) ? CE_MAX_EVENT : event;
+ name = idm_ce_name[event];
+
+ return (name);
+}
+
+static const char *
+iscsi_iscsit_tgt_event(int event)
+{
+ const char *name = "N/A";
+
+ event = (event > TE_MAX_EVENT) ? TE_MAX_EVENT : event;
+ name = iscsit_te_name[event];
+
+ return (name);
+}
+
+static const char *
+iscsi_iscsit_sess_event(int event)
+{
+ const char *name = "N/A";
+
+ event = (event > SE_MAX_EVENT) ? SE_MAX_EVENT : event;
+ name = iscsit_se_name[event];
+
+ return (name);
+}
+
+static const char *
+iscsi_iscsit_login_event(int event)
+{
+ const char *name = "N/A";
+
+ event = (event > ILE_MAX_EVENT) ? ILE_MAX_EVENT : event;
+ name = iscsit_ile_name[event];
+
+ return (name);
+}
+
+static const char *
+iscsi_idm_conn_state(int state)
+{
+ const char *name = "N/A";
+
+ state = (state > CS_MAX_STATE) ? CS_MAX_STATE : state;
+ name = idm_cs_name[state];
+
+ return (name);
+}
+
+/*ARGSUSED*/
+static const char *
+iscsi_idm_task_state(int state)
+{
+ const char *name = "N/A";
+ return (name);
+}
+
+static const char *
+iscsi_iscsit_tgt_state(int state)
+{
+ const char *name = "N/A";
+
+ state = (state > TS_MAX_STATE) ? TS_MAX_STATE : state;
+ name = iscsit_ts_name[state];
+
+ return (name);
+}
+
+static const char *
+iscsi_iscsit_sess_state(int state)
+{
+ const char *name = "N/A";
+
+ state = (state > SS_MAX_STATE) ? SS_MAX_STATE : state;
+ name = iscsit_ss_name[state];
+
+ return (name);
+}
+
+static const char *
+iscsi_iscsit_login_state(int state)
+{
+ const char *name = "N/A";
+
+ state = (state > ILS_MAX_STATE) ? ILS_MAX_STATE : state;
+ name = iscsit_ils_name[state];
+
+ return (name);
+}
+
+
+
+/*
+ * Retrieve connection type given a kernel address
+ */
+static idm_conn_type_t
+idm_conn_type(uintptr_t addr)
+{
+ idm_conn_type_t result = 0; /* Unknown */
+ uintptr_t idm_conn_type_addr;
+
+ idm_conn_type_addr = addr + offsetof(idm_conn_t, ic_conn_type);
+ (void) mdb_vread(&result, sizeof (result), idm_conn_type_addr);
+
+ return (result);
+}
+
+/*
+ * Convert a sockaddr to the string representation, suitable for
+ * storing in an nvlist or printing out in a list.
+ */
+static int
+sa_to_str(struct sockaddr_storage *sa, char *buf)
+{
+ char pbuf[7];
+ const char *bufp;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ uint16_t port;
+
+ if (!sa || !buf) {
+ return (EINVAL);
+ }
+
+ buf[0] = '\0';
+
+ if (sa->ss_family == AF_INET) {
+ sin = (struct sockaddr_in *)sa;
+ bufp = inet_ntop(AF_INET,
+ (const void *)&(sin->sin_addr.s_addr),
+ buf, PORTAL_STR_LEN);
+ if (bufp == NULL) {
+ return (-1);
+ }
+ mdb_nhconvert(&port, &sin->sin_port, sizeof (uint16_t));
+ } else if (sa->ss_family == AF_INET6) {
+ strlcat(buf, "[", sizeof (buf));
+ sin6 = (struct sockaddr_in6 *)sa;
+ bufp = inet_ntop(AF_INET6,
+ (const void *)&sin6->sin6_addr.s6_addr,
+ &buf[1], PORTAL_STR_LEN - 1);
+ if (bufp == NULL) {
+ return (-1);
+ }
+ strlcat(buf, "]", PORTAL_STR_LEN);
+ mdb_nhconvert(&port, &sin->sin_port, sizeof (uint16_t));
+ } else {
+ return (EINVAL);
+ }
+
+
+ mdb_snprintf(pbuf, sizeof (pbuf), ":%u", port);
+ strlcat(buf, pbuf, PORTAL_STR_LEN);
+
+ return (0);
+}
+
+
+static void
+iscsi_format_timestamp(char *ts_str, int strlen, timespec_t *ts)
+{
+ mdb_snprintf(ts_str, strlen, "%Y:%03d:%03d:%03d", ts->tv_sec,
+ (ts->tv_nsec / 1000000) % 1000, (ts->tv_nsec / 1000) % 1000,
+ ts->tv_nsec % 1000);
+}
+
+/*
+ * Help information for the iscsi_isns dcmd
+ */
+static void
+iscsi_isns_help(void)
+{
+ mdb_printf("iscsi_isns:\n");
+ mdb_inc_indent(4);
+ mdb_printf("-e: Print ESI information\n");
+ mdb_printf("-p: Print portal information\n");
+ mdb_printf("-s: Print iSNS server information\n");
+ mdb_printf("-t: Print target information\n");
+ mdb_printf("-v: Add verbosity to the other options' output\n");
+ mdb_dec_indent(4);
+}
+
+/* ARGSUSED */
+static int
+iscsi_isns_esi_cb(uintptr_t addr, const void *walker_data, void *data)
+{
+ iscsi_dcmd_ctrl_t *idc = (iscsi_dcmd_ctrl_t *)data;
+ isns_esi_tinfo_t tinfo;
+
+ if (mdb_vread(&tinfo, sizeof (isns_esi_tinfo_t), addr) !=
+ sizeof (isns_esi_tinfo_t)) {
+ return (WALK_ERR);
+ }
+
+ mdb_printf("ESI portal : 0x%p\n", tinfo.esi_portal);
+ if (idc->idc_verbose) {
+ mdb_inc_indent(4);
+ iscsi_isns_portal_cb((uintptr_t)tinfo.esi_portal, NULL, data);
+ mdb_dec_indent(4);
+ }
+ mdb_printf("ESI thread/thr did : 0x%p / %d\n", tinfo.esi_thread,
+ tinfo.esi_thread_did);
+ mdb_printf("ESI sonode : 0x%p\n", tinfo.esi_so);
+ mdb_printf("ESI port : %d\n", tinfo.esi_port);
+ mdb_printf("ESI thread running : %s\n",
+ (tinfo.esi_thread_running) ? "Yes" : "No");
+ if (!tinfo.esi_thread_running) {
+ mdb_printf("ESI thread failed : %s\n",
+ (tinfo.esi_thread_failed) ? "Yes" : "No");
+ }
+ mdb_printf("ESI registered : %s\n\n",
+ (tinfo.esi_registered) ? "Yes" : "No");
+
+ return (WALK_NEXT);
+}
+
+static int
+iscsi_isns_esi(iscsi_dcmd_ctrl_t *idc)
+{
+ GElf_Sym sym;
+ uintptr_t esi_list;
+
+ if (mdb_lookup_by_name("esi_list", &sym) == -1) {
+ mdb_warn("failed to find symbol 'esi_list'");
+ return (DCMD_ERR);
+ }
+
+ esi_list = (uintptr_t)sym.st_value;
+ idc->idc_header = 1;
+
+ if (mdb_pwalk("list", iscsi_isns_esi_cb, idc, esi_list) == -1) {
+ mdb_warn("avl walk failed for esi_list");
+ return (DCMD_ERR);
+ }
+
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+iscsi_isns_portal_cb(uintptr_t addr, const void *walker_data, void *data)
+{
+ iscsi_dcmd_ctrl_t *idc = (iscsi_dcmd_ctrl_t *)data;
+ isns_portal_list_t portal;
+ char portal_addr[PORTAL_STR_LEN];
+ struct sockaddr_storage *ss;
+
+ if (mdb_vread(&portal, sizeof (isns_portal_list_t), addr) !=
+ sizeof (isns_portal_list_t)) {
+ return (WALK_ERR);
+ }
+
+ ss = &portal.portal_addr;
+ sa_to_str(ss, portal_addr);
+ mdb_printf("Portal IP address ");
+
+ if (ss->ss_family == AF_INET) {
+ mdb_printf("(v4): %s", portal_addr);
+ } else {
+ mdb_printf("(v6): %s", portal_addr);
+ }
+
+ if (portal.portal_iscsit == NULL) {
+ mdb_printf(" (Default portal)\n");
+ } else {
+ mdb_printf("\n");
+ }
+
+ if ((portal.portal_iscsit != NULL) && (idc->idc_verbose)) {
+ iscsi_portal_impl((uintptr_t)portal.portal_iscsit, idc);
+ }
+
+ mdb_printf("Portal ESI info: 0x%p\n\n", portal.portal_esi);
+
+ return (WALK_NEXT);
+}
+
+static int
+iscsi_isns_portals(iscsi_dcmd_ctrl_t *idc)
+{
+ GElf_Sym sym;
+ uintptr_t portal_list;
+
+ if (mdb_lookup_by_name("portal_list", &sym) == -1) {
+ mdb_warn("failed to find symbol 'portal_list'");
+ return (DCMD_ERR);
+ }
+
+ portal_list = (uintptr_t)sym.st_value;
+ idc->idc_header = 1;
+
+ if (mdb_pwalk("list", iscsi_isns_portal_cb, idc, portal_list) == -1) {
+ mdb_warn("avl walk failed for portal_list");
+ return (DCMD_ERR);
+ }
+
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+iscsi_isns_targets_cb(uintptr_t addr, const void *walker_data, void *data)
+{
+ iscsi_dcmd_ctrl_t *idc = (iscsi_dcmd_ctrl_t *)data;
+ isns_target_t itarget;
+ int rc = 0;
+
+ if (mdb_vread(&itarget, sizeof (isns_target_t), addr) !=
+ sizeof (isns_target_t)) {
+ return (WALK_ERR);
+ }
+
+ idc->idc_header = 1;
+
+ mdb_printf("Target: %p\n", itarget.target);
+ mdb_inc_indent(4);
+ mdb_printf("Registered: %s\n",
+ (itarget.target_registered) ? "Yes" : "No");
+
+ rc = iscsi_tgt_impl((uintptr_t)itarget.target, idc);
+
+ mdb_dec_indent(4);
+
+ if (rc == DCMD_OK) {
+ return (WALK_NEXT);
+ }
+
+ return (WALK_ERR);
+}
+
+static int
+iscsi_isns_targets(iscsi_dcmd_ctrl_t *idc)
+{
+ GElf_Sym sym;
+ uintptr_t isns_target_list;
+
+ if (mdb_lookup_by_name("isns_target_list", &sym) == -1) {
+ mdb_warn("failed to find symbol 'isns_target_list'");
+ return (DCMD_ERR);
+ }
+
+ isns_target_list = (uintptr_t)sym.st_value;
+ idc->idc_header = 1;
+ idc->u.child.idc_tgt = 1;
+
+ if (mdb_pwalk("avl", iscsi_isns_targets_cb, idc,
+ isns_target_list) == -1) {
+ mdb_warn("avl walk failed for isns_target_list");
+ return (DCMD_ERR);
+ }
+
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+iscsi_isns_servers_cb(uintptr_t addr, const void *walker_data, void *data)
+{
+ GElf_Sym sym;
+ iscsit_isns_svr_t server;
+ char server_addr[PORTAL_STR_LEN];
+ struct sockaddr_storage *ss;
+ clock_t lbolt;
+
+ if (mdb_vread(&server, sizeof (iscsit_isns_svr_t), addr) !=
+ sizeof (iscsit_isns_svr_t)) {
+ return (WALK_ERR);
+ }
+
+ if (mdb_lookup_by_name("lbolt", &sym) == -1) {
+ mdb_warn("failed to find symbol 'lbolt'");
+ return (DCMD_ERR);
+ }
+
+ if (mdb_vread(&lbolt, sizeof (clock_t), sym.st_value) !=
+ sizeof (clock_t)) {
+ return (WALK_ERR);
+ }
+
+ mdb_printf("iSNS server %p:\n", addr);
+ mdb_inc_indent(4);
+ ss = &server.svr_sa;
+ sa_to_str(ss, server_addr);
+
+ mdb_printf("IP address ");
+ if (ss->ss_family == AF_INET) {
+ mdb_printf("(v4): %s\n", server_addr);
+ } else {
+ mdb_printf("(v6): %s\n", server_addr);
+ }
+
+ mdb_printf("Last ESI message : %d seconds ago\n",
+ ((lbolt - server.svr_last_msg) / 100));
+ mdb_printf("Client registered: %s\n",
+ (server.svr_registered) ? "Yes" : "No");
+ mdb_dec_indent(4);
+
+ return (WALK_ERR);
+}
+
+static int
+iscsi_isns_servers(iscsi_dcmd_ctrl_t *idc)
+{
+ uintptr_t iscsit_global_addr;
+ uintptr_t list_addr;
+ GElf_Sym sym;
+
+ if (mdb_lookup_by_name("iscsit_global", &sym) == -1) {
+ mdb_warn("failed to find symbol 'iscsit_global'");
+ return (DCMD_ERR);
+ }
+
+ iscsit_global_addr = (uintptr_t)sym.st_value;
+ idc->idc_header = 1;
+ list_addr = iscsit_global_addr +
+ offsetof(iscsit_global_t, global_isns_cfg.isns_svrs);
+
+ if (mdb_pwalk("list", iscsi_isns_servers_cb, idc, list_addr) == -1) {
+ mdb_warn("list walk failed for iSNS servers");
+ return (DCMD_ERR);
+ }
+
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+iscsi_isns(uintptr_t addr, uint_t flags, int argc, const mdb_arg_t *argv)
+{
+ iscsi_dcmd_ctrl_t idc;
+ int portals = 0, esi = 0, targets = 0, verbose = 0, servers = 0;
+
+ if (flags & DCMD_ADDRSPEC) {
+ mdb_warn("iscsi_isns is only a global dcmd.");
+ return (DCMD_ERR);
+ }
+
+ bzero(&idc, sizeof (idc));
+ if (mdb_getopts(argc, argv,
+ 'e', MDB_OPT_SETBITS, TRUE, &esi,
+ 'p', MDB_OPT_SETBITS, TRUE, &portals,
+ 's', MDB_OPT_SETBITS, TRUE, &servers,
+ 't', MDB_OPT_SETBITS, TRUE, &targets,
+ 'v', MDB_OPT_SETBITS, TRUE, &verbose,
+ NULL) != argc)
+ return (DCMD_USAGE);
+
+ if ((esi + portals + targets + servers) > 1) {
+ mdb_printf("Only one of e, p, s, and t must be provided");
+ return (DCMD_ERR);
+ }
+
+ if ((esi | portals | targets | servers) == 0) {
+ mdb_printf("Exactly one of e, p, s, or t must be provided");
+ return (DCMD_ERR);
+ }
+
+ idc.idc_verbose = verbose;
+
+ if (esi) {
+ return (iscsi_isns_esi(&idc));
+ }
+
+ if (portals) {
+ return (iscsi_isns_portals(&idc));
+ }
+
+ if (servers) {
+ return (iscsi_isns_servers(&idc));
+ }
+
+ return (iscsi_isns_targets(&idc));
+}
+
+/*
+ * inet_ntop -- Convert an IPv4 or IPv6 address in binary form into
+ * printable form, and return a pointer to that string. Caller should
+ * provide a buffer of correct length to store string into.
+ * Note: this routine is kernel version of inet_ntop. It has similar
+ * format as inet_ntop() defined in rfc2553. But it does not do
+ * error handling operations exactly as rfc2553 defines. This function
+ * is used by kernel inet directory routines only for debugging.
+ * This inet_ntop() function, does not return NULL if third argument
+ * is NULL. The reason is simple that we don't want kernel to panic
+ * as the output of this function is directly fed to ip<n>dbg macro.
+ * Instead it uses a local buffer for destination address for
+ * those calls which purposely pass NULL ptr for the destination
+ * buffer. This function is thread-safe when the caller passes a non-
+ * null buffer with the third argument.
+ */
+/* ARGSUSED */
+
+#define OK_16PTR(p) (!((uintptr_t)(p) & 0x1))
+#if defined(__x86)
+#define OK_32PTR(p) OK_16PTR(p)
+#else
+#define OK_32PTR(p) (!((uintptr_t)(p) & 0x3))
+#endif
+
+char *
+inet_ntop(int af, const void *addr, char *buf, int addrlen)
+{
+ static char local_buf[PORTAL_STR_LEN];
+ static char *err_buf1 = "<badaddr>";
+ static char *err_buf2 = "<badfamily>";
+ in6_addr_t *v6addr;
+ uchar_t *v4addr;
+ char *caddr;
+
+ /*
+ * We don't allow thread unsafe inet_ntop calls, they
+ * must pass a non-null buffer pointer. For DEBUG mode
+ * we use the ASSERT() and for non-debug kernel it will
+ * silently allow it for now. Someday we should remove
+ * the static buffer from this function.
+ */
+
+ ASSERT(buf != NULL);
+ if (buf == NULL)
+ buf = local_buf;
+ buf[0] = '\0';
+
+ /* Let user know politely not to send NULL or unaligned addr */
+ if (addr == NULL || !(OK_32PTR(addr))) {
+ return (err_buf1);
+ }
+
+
+#define UC(b) (((int)b) & 0xff)
+ switch (af) {
+ case AF_INET:
+ ASSERT(addrlen >= INET_ADDRSTRLEN);
+ v4addr = (uchar_t *)addr;
+ (void) mdb_snprintf(buf, INET6_ADDRSTRLEN,
+ "%03d.%03d.%03d.%03d",
+ UC(v4addr[0]), UC(v4addr[1]), UC(v4addr[2]), UC(v4addr[3]));
+ return (buf);
+
+ case AF_INET6:
+ ASSERT(addrlen >= INET6_ADDRSTRLEN);
+ v6addr = (in6_addr_t *)addr;
+ if (IN6_IS_ADDR_V4MAPPED(v6addr)) {
+ caddr = (char *)addr;
+ (void) mdb_snprintf(buf, INET6_ADDRSTRLEN,
+ "::ffff:%d.%d.%d.%d",
+ UC(caddr[12]), UC(caddr[13]),
+ UC(caddr[14]), UC(caddr[15]));
+ } else if (IN6_IS_ADDR_V4COMPAT(v6addr)) {
+ caddr = (char *)addr;
+ (void) mdb_snprintf(buf, INET6_ADDRSTRLEN,
+ "::%d.%d.%d.%d",
+ UC(caddr[12]), UC(caddr[13]), UC(caddr[14]),
+ UC(caddr[15]));
+ } else if (IN6_IS_ADDR_UNSPECIFIED(v6addr)) {
+ (void) mdb_snprintf(buf, INET6_ADDRSTRLEN, "::");
+ } else {
+ convert2ascii(buf, v6addr);
+ }
+ return (buf);
+
+ default:
+ return (err_buf2);
+ }
+#undef UC
+}
+
+/*
+ *
+ * v6 formats supported
+ * General format xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx
+ * The short hand notation :: is used for COMPAT addr
+ * Other forms : fe80::xxxx:xxxx:xxxx:xxxx
+ */
+static void
+convert2ascii(char *buf, const in6_addr_t *addr)
+{
+ int hexdigits;
+ int head_zero = 0;
+ int tail_zero = 0;
+ /* tempbuf must be big enough to hold ffff:\0 */
+ char tempbuf[6];
+ char *ptr;
+ uint16_t out_addr_component;
+ uint16_t *addr_component;
+ size_t len;
+ boolean_t first = B_FALSE;
+ boolean_t med_zero = B_FALSE;
+ boolean_t end_zero = B_FALSE;
+
+ addr_component = (uint16_t *)addr;
+ ptr = buf;
+
+ /* First count if trailing zeroes higher in number */
+ for (hexdigits = 0; hexdigits < 8; hexdigits++) {
+ if (*addr_component == 0) {
+ if (hexdigits < 4)
+ head_zero++;
+ else
+ tail_zero++;
+ }
+ addr_component++;
+ }
+ addr_component = (uint16_t *)addr;
+ if (tail_zero > head_zero && (head_zero + tail_zero) != 7)
+ end_zero = B_TRUE;
+
+ for (hexdigits = 0; hexdigits < 8; hexdigits++) {
+
+ /* if entry is a 0 */
+
+ if (*addr_component == 0) {
+ if (!first && *(addr_component + 1) == 0) {
+ if (end_zero && (hexdigits < 4)) {
+ *ptr++ = '0';
+ *ptr++ = ':';
+ } else {
+ /*
+ * address starts with 0s ..
+ * stick in leading ':' of pair
+ */
+ if (hexdigits == 0)
+ *ptr++ = ':';
+ /* add another */
+ *ptr++ = ':';
+ first = B_TRUE;
+ med_zero = B_TRUE;
+ }
+ } else if (first && med_zero) {
+ if (hexdigits == 7)
+ *ptr++ = ':';
+ addr_component++;
+ continue;
+ } else {
+ *ptr++ = '0';
+ *ptr++ = ':';
+ }
+ addr_component++;
+ continue;
+ }
+ if (med_zero)
+ med_zero = B_FALSE;
+
+ tempbuf[0] = '\0';
+ mdb_nhconvert(&out_addr_component, addr_component,
+ sizeof (uint16_t));
+ (void) mdb_snprintf(tempbuf, 6, "%x:", out_addr_component);
+ len = strlen(tempbuf);
+ bcopy(tempbuf, ptr, len);
+ ptr = ptr + len;
+ addr_component++;
+ }
+ *--ptr = '\0';
+}
+
+
+/*
+ * MDB module linkage information:
+ *
+ * We declare a list of structures describing our dcmds, a list of structures
+ * describing our walkers and a function named _mdb_init to return a pointer
+ * to our module information.
+ */
+static const mdb_dcmd_t dcmds[] = {
+ { "iscsi_tgt", "[-agsctbSRv]",
+ "iSCSI target information", iscsi_tgt },
+ { "iscsi_tpg", "[-v]",
+ "iSCSI target portal group information", iscsi_tpg },
+ { "iscsi_sess", "[-abtvcSRIT]",
+ "iSCSI session information", iscsi_sess },
+ { "iscsi_conn", "[-abtvSRIT]",
+ "iSCSI connection information", iscsi_conn },
+ { "iscsi_task", "[-bSRv]",
+ "iSCSI task information", iscsi_task },
+ { "iscsi_refcnt", "",
+ "Print audit informtion for idm_refcnt_t", iscsi_refcnt },
+ { "iscsi_states", "",
+ "Dump events and state transitions recorded in an\t"
+ "\t\tidm_sm_audit_t structure", iscsi_states },
+ { "iscsi_isns", "[-epstv]",
+ "Print iscsit iSNS information", iscsi_isns, iscsi_isns_help },
+ { NULL }
+};
+
+/*
+ * No walkers for now. Initiator might need some since it doesn't use list_t
+ */
+
+static const mdb_modinfo_t modinfo = {
+ MDB_API_VERSION, dcmds, NULL
+};
+
+const mdb_modinfo_t *
+_mdb_init(void)
+{
+ return (&modinfo);
+}
diff --git a/usr/src/cmd/mdb/intel/amd64/idm/Makefile b/usr/src/cmd/mdb/intel/amd64/idm/Makefile
new file mode 100644
index 0000000000..e1fc48fdfd
--- /dev/null
+++ b/usr/src/cmd/mdb/intel/amd64/idm/Makefile
@@ -0,0 +1,38 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+MODULE = idm.so
+MDBTGT = kvm
+
+MODSRCS = idm.c
+
+ISCSITBASE = ../../../../../uts/common/io/comstar/port/iscsit
+
+include ../../../../Makefile.cmd
+include ../../../../Makefile.cmd.64
+include ../../Makefile.amd64
+include ../../../Makefile.module
+
+CPPFLAGS += -I$(ISCSITBASE)
diff --git a/usr/src/cmd/mdb/intel/ia32/idm/Makefile b/usr/src/cmd/mdb/intel/ia32/idm/Makefile
new file mode 100644
index 0000000000..45777b1d0b
--- /dev/null
+++ b/usr/src/cmd/mdb/intel/ia32/idm/Makefile
@@ -0,0 +1,37 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+MODULE = idm.so
+MDBTGT = kvm
+
+MODSRCS = idm.c
+
+ISCSITBASE = ../../../../../uts/common/io/comstar/port/iscsit
+
+include ../../../../Makefile.cmd
+include ../../Makefile.ia32
+include ../../../Makefile.module
+
+CPPFLAGS += -I$(ISCSITBASE)
diff --git a/usr/src/cmd/mdb/sparc/v9/idm/Makefile b/usr/src/cmd/mdb/sparc/v9/idm/Makefile
new file mode 100644
index 0000000000..cab142407e
--- /dev/null
+++ b/usr/src/cmd/mdb/sparc/v9/idm/Makefile
@@ -0,0 +1,38 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+MODULE = idm.so
+MDBTGT = kvm
+
+MODSRCS = idm.c
+
+ISCSITBASE = ../../../../../uts/common/io/comstar/port/iscsit
+
+include ../../../../Makefile.cmd
+include ../../../../Makefile.cmd.64
+include ../../Makefile.sparcv9
+include ../../../Makefile.module
+
+CPPFLAGS += -I$(ISCSITBASE)
diff --git a/usr/src/common/iscsi/base64.c b/usr/src/common/iscsi/base64.c
new file mode 100644
index 0000000000..f4f336480e
--- /dev/null
+++ b/usr/src/common/iscsi/base64.c
@@ -0,0 +1,255 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/types.h>
+#ifdef _KERNEL
+#include <sys/sunddi.h>
+#include <sys/errno.h>
+#else
+#include <string.h>
+#include <errno.h>
+#endif /* _KERNEL */
+
+/*
+ * base64 decoding table (from uudecode.c)
+ */
+/* BEGIN CSTYLED */
+ static char base64_decode_tab[] = {
+ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377',
+ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377',
+ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377',
+ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377',
+ '\377', '\377', '\377', '\377', '\377', '\377', '\377', '\377',
+ '\377', '\377', '\377', 62, '\377', '\377', '\377', 63,
+ 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, '\377', '\377', '\377', '\377', '\377', '\377',
+ '\377', 0, 1, 2, 3, 4, 5, 6,
+ 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22,
+ 23, 24, 25, '\377', '\377', '\377', '\377', '\377',
+ '\377', 26, 27, 28, 29, 30, 31, 32,
+ 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48,
+ 49, 50, 51, '\377', '\377', '\377', '\377', '\377'
+};
+/* END CSTYLED */
+
+/* true if the character is in the base64 encoding table */
+#define validbase64(c) (('A' <= (c) && (c) <= 'Z') || \
+ ('a' <= (c) && (c) <= 'z') || \
+ ('0' <= (c) && (c) <= '9') || \
+ (c) == '+' || (c) == '/')
+
+static int
+outdec64(unsigned char *out, unsigned char *chr, int num)
+{
+
+ unsigned char char1, char2, char3, char4;
+ unsigned char *outptr = out;
+ int rc = 0;
+
+ switch (num) {
+ case 0:
+ case 1: /* these are impossible */
+ default:
+ break;
+ case 2: /* 2 base64 bytes == 1 decoded byte */
+ char1 = base64_decode_tab[chr[0]] & 0xFF;
+ char2 = base64_decode_tab[chr[1]] & 0xFF;
+ *(outptr++) = ((char1 << 2) & 0xFC) |
+ ((char2 >> 4) & 0x03);
+ rc = 1;
+ break;
+ case 3: /* 3 base64 bytes == 2 decoded bytes */
+ char1 = base64_decode_tab[chr[0]] & 0xFF;
+ char2 = base64_decode_tab[chr[1]] & 0xFF;
+ char3 = base64_decode_tab[chr[2]] & 0xFF;
+ *(outptr++) = ((char1 << 2) & 0xFC) |
+ ((char2 >> 4) & 0x03);
+ *(outptr++) = ((char2 << 4) & 0xF0) |
+ ((char3 >> 2) & 0x0F);
+ rc = 2;
+ break;
+ case 4: /* 4 base64 bytes == 3 decoded bytes */
+ char1 = base64_decode_tab[chr[0]] & 0xFF;
+ char2 = base64_decode_tab[chr[1]] & 0xFF;
+ char3 = base64_decode_tab[chr[2]] & 0xFF;
+ char4 = base64_decode_tab[chr[3]] & 0xFF;
+ *(outptr++) = ((char1 << 2) & 0xFC) |
+ ((char2 >> 4) & 0x03);
+ *(outptr++) = ((char2 << 4) & 0xF0) |
+ ((char3 >> 2) & 0x0F);
+ *(outptr++) = ((char3 << 6) & 0xC0) |
+ (char4 & 0x3F);
+ rc = 3;
+ break;
+ }
+ return (rc);
+}
+
+#define BUFSIZE 12
+
+int
+iscsi_base64_str_to_binary(char *hstr, int hstr_len,
+ uint8_t *binary, int binary_buf_len, int *out_len)
+{
+ char *iptr;
+ uint8_t tmp_out[BUFSIZE];
+ int octets, endseen, numbase64chars;
+ unsigned char chr[4], curchr;
+
+ /*
+ * base64 decode algorith, adapted from uudecode.c
+ *
+ * A valid base64 string is a multiple of 4 bytes in length
+ */
+ if ((hstr_len % 4) != 0)
+ return (EINVAL);
+
+ endseen = numbase64chars = 0;
+ *out_len = 0;
+ iptr = hstr;
+
+ while (((curchr = *(iptr++)) != NULL) &&
+ (((uintptr_t)iptr - (uintptr_t)hstr) <= hstr_len)) {
+ /* decode chars */
+ if (curchr == '=') /* if end */
+ endseen++;
+
+ if (validbase64(curchr))
+ chr[numbase64chars++] = curchr;
+ /*
+ * if we've gathered 4 base64 octets
+ * we need to decode and output them
+ */
+ if (numbase64chars == 4) {
+ octets = outdec64(tmp_out, chr, 4);
+ numbase64chars = 0;
+
+ if (*out_len + octets > binary_buf_len)
+ return (E2BIG);
+
+ (void) memcpy(binary + *out_len, tmp_out, octets);
+ *out_len += octets;
+ }
+
+ /*
+ * handle any remaining base64 octets at end
+ */
+ if (endseen && numbase64chars > 0) {
+ octets = outdec64(tmp_out, chr, numbase64chars);
+ numbase64chars = 0;
+ if (*out_len + octets > binary_buf_len)
+ return (E2BIG);
+
+ (void) memcpy(binary + *out_len, tmp_out, octets);
+ *out_len += octets;
+ }
+ }
+
+ return (0);
+}
+
+
+static char base64_encode_tab[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz0123456789+/";
+
+#define ENC(c) base64_encode_tab[(c) & 0x3f]
+
+#define BASE64_BUF_HAS_ROOM(bytes_needed) \
+ ((optr + (bytes_needed)) <= base64_str_buf + base64_buf_len)
+
+int
+iscsi_binary_to_base64_str(uint8_t *in_buf, int in_buf_len,
+ char *base64_str_buf, int base64_buf_len)
+{
+ uint8_t *iptr;
+ char *optr;
+ int in_bytes_remaining;
+
+ /* base64 encode algorith, adapted from uuencode.c */
+ iptr = in_buf;
+ optr = base64_str_buf;
+
+ /*
+ * read must be a multiple of 3 bytes for
+ * this algorithm to work, and also must
+ * be small enough that read_size * (4/3)
+ * will always be 76 bytes or less, since
+ * base64 lines can be no longer than that
+ */
+ while (iptr + 3 <= in_buf + in_buf_len) {
+ if (!BASE64_BUF_HAS_ROOM(4))
+ return (E2BIG);
+
+ *(optr++) = ENC(*iptr >> 2);
+ *(optr++) = ENC((*iptr << 4) & 060 |
+ (*(iptr + 1) >> 4) & 017);
+ *(optr++) = ENC((*(iptr + 1) << 2)
+ & 074 | (*(iptr + 2) >> 6) & 03);
+ *(optr++) = ENC(*(iptr + 2) & 077);
+
+ iptr += 3;
+ }
+
+ /* need output padding ? */
+ in_bytes_remaining = ((uintptr_t)in_buf + in_buf_len) - (uintptr_t)iptr;
+ /* ASSERT(in_bytes_remaining < 3); */
+ switch (in_bytes_remaining) {
+ case 0:
+ /* no-op - 24 bits of data encoded */
+ if (!BASE64_BUF_HAS_ROOM(1))
+ return (E2BIG);
+ *(optr++) = '\0';
+ break;
+ case 1:
+ /* 8 bits encoded - pad with 2 '=' */
+ if (!BASE64_BUF_HAS_ROOM(5))
+ return (E2BIG);
+ *(optr++) = ENC((*iptr & 0xFC) >> 2);
+ *(optr++) = ENC((*iptr & 0x03) << 4);
+ *(optr++) = '=';
+ *(optr++) = '=';
+ *(optr++) = '\0';
+ break;
+ case 2:
+ /* 16 bits encoded - pad with 1 '=' */
+ if (!BASE64_BUF_HAS_ROOM(5))
+ return (E2BIG);
+ *(optr++) = ENC((*iptr & 0xFC) >> 2);
+ *(optr++) = ENC(((*iptr & 0x03) << 4) |
+ ((*(iptr + 1) & 0xF0) >> 4));
+ *(optr++) = ENC((*(iptr + 1) & 0x0F) << 2);
+ *(optr++) = '=';
+ *(optr++) = '\0';
+ break;
+ default:
+ /* impossible */
+ break;
+ }
+
+ return (0);
+}
diff --git a/usr/src/common/iscsit/iscsit_common.c b/usr/src/common/iscsit/iscsit_common.c
new file mode 100644
index 0000000000..642272fb64
--- /dev/null
+++ b/usr/src/common/iscsit/iscsit_common.c
@@ -0,0 +1,1590 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/time.h>
+
+#if defined(_KERNEL)
+#include <sys/ddi.h>
+#include <sys/types.h>
+#include <sys/sunddi.h>
+#include <sys/socket.h>
+#include <inet/tcp.h>
+#else
+#include <stdio.h>
+#include <strings.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+
+#include <sys/iscsit/iscsit_common.h>
+#include <sys/iscsi_protocol.h>
+#include <sys/iscsit/isns_protocol.h>
+
+void *
+iscsit_zalloc(size_t size)
+{
+#if defined(_KERNEL)
+ return (kmem_zalloc(size, KM_SLEEP));
+#else
+ return (calloc(1, size));
+#endif
+}
+
+void
+iscsit_free(void *buf, size_t size) /* ARGSUSED */
+{
+#if defined(_KERNEL)
+ kmem_free(buf, size);
+#else
+ free(buf);
+#endif
+}
+
+/*
+ * default_port should be the port to be used, if not specified
+ * as part of the supplied string 'arg'.
+ */
+
+#define NI_MAXHOST 1025
+#define NI_MAXSERV 32
+
+
+struct sockaddr_storage *
+it_common_convert_sa(char *arg, struct sockaddr_storage *buf,
+ uint32_t default_port)
+{
+ /* Why does addrbuf need to be this big!??! XXX */
+ char addrbuf[NI_MAXHOST + NI_MAXSERV + 1];
+ char *addr_str;
+ char *port_str;
+#ifndef _KERNEL
+ char *errchr;
+#endif
+ long tmp_port = 0;
+ sa_family_t af;
+
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ struct sockaddr_storage *sa = buf;
+
+ if (!arg || !buf) {
+ return (NULL);
+ }
+
+ bzero(buf, sizeof (struct sockaddr_storage));
+
+ /* don't modify the passed-in string */
+ (void) strlcpy(addrbuf, arg, sizeof (addrbuf));
+
+ addr_str = addrbuf;
+
+ if (*addr_str == '[') {
+ /*
+ * An IPv6 address must be inside square brackets
+ */
+ port_str = strchr(addr_str, ']');
+ if (!port_str) {
+ /* No closing bracket */
+ return (NULL);
+ }
+
+ /* strip off the square brackets so we can convert */
+ addr_str++;
+ *port_str = '\0';
+ port_str++;
+
+ if (*port_str == ':') {
+ /* TCP port to follow */
+ port_str++;
+ } else if (*port_str == '\0') {
+ /* No port specified */
+ port_str = NULL;
+ } else {
+ /* malformed */
+ return (NULL);
+ }
+ af = AF_INET6;
+ } else {
+ port_str = strchr(addr_str, ':');
+ if (port_str) {
+ *port_str = '\0';
+ port_str++;
+ }
+ af = AF_INET;
+ }
+
+ if (port_str) {
+#if defined(_KERNEL)
+ if (ddi_strtol(port_str, NULL, 10, &tmp_port) != 0) {
+ return (NULL);
+ }
+#else
+ tmp_port = strtol(port_str, &errchr, 10);
+#endif
+ if (tmp_port < 0 || tmp_port > 65535) {
+ return (NULL);
+ }
+ } else {
+ tmp_port = default_port;
+ }
+
+ sa->ss_family = af;
+
+ sin = (struct sockaddr_in *)sa;
+ if (af == AF_INET) {
+ if (inet_pton(af, addr_str,
+ (void *)&(sin->sin_addr.s_addr)) != 1) {
+ return (NULL);
+ }
+ /*
+ * intet_pton does not seem to convert to network
+ * order in kernel. This is a workaround until the
+ * inet_pton works or we have our own inet_pton function.
+ */
+#ifdef _KERNEL
+ sin->sin_addr.s_addr = ntohl((uint32_t)sin->sin_addr.s_addr);
+#endif
+ sin->sin_port = htons(tmp_port);
+ } else {
+ sin6 = (struct sockaddr_in6 *)sa;
+ if (inet_pton(af, addr_str,
+ (void *)&(sin6->sin6_addr.s6_addr)) != 1) {
+ return (NULL);
+ }
+ sin6->sin6_port = htons(tmp_port);
+ }
+
+ /* successful */
+ return (sa);
+}
+
+
+/* Functions to convert iSCSI target structures to/from nvlists. */
+
+#ifndef _KERNEL
+int
+it_config_to_nv(it_config_t *cfg, nvlist_t **nvl)
+{
+ int ret;
+ nvlist_t *nv;
+ nvlist_t *lnv = NULL;
+
+ if (!nvl) {
+ return (EINVAL);
+ }
+
+ *nvl = NULL;
+
+ ret = nvlist_alloc(&nv, NV_UNIQUE_NAME_TYPE, 0);
+ if (ret != 0) {
+ return (ret);
+ }
+
+ /* if there's no config, store an empty list */
+ if (!cfg) {
+ *nvl = nv;
+ return (0);
+ }
+
+ ret = nvlist_add_uint32(nv, "cfgVersion", cfg->config_version);
+ if (ret == 0) {
+ ret = it_tgtlist_to_nv(cfg->config_tgt_list, &lnv);
+ }
+
+ if ((ret == 0) && (lnv != NULL)) {
+ ret = nvlist_add_nvlist(nv, "targetList", lnv);
+ nvlist_free(lnv);
+ lnv = NULL;
+ }
+
+ if (ret == 0) {
+ ret = it_tpglist_to_nv(cfg->config_tpg_list, &lnv);
+ }
+
+ if ((ret == 0) && (lnv != NULL)) {
+ ret = nvlist_add_nvlist(nv, "tpgList", lnv);
+ nvlist_free(lnv);
+ lnv = NULL;
+ }
+
+ if (ret == 0) {
+ ret = it_inilist_to_nv(cfg->config_ini_list, &lnv);
+ }
+
+ if ((ret == 0) && (lnv != NULL)) {
+ ret = nvlist_add_nvlist(nv, "iniList", lnv);
+ nvlist_free(lnv);
+ lnv = NULL;
+ }
+
+ if (ret == 0) {
+ ret = nvlist_add_nvlist(nv, "globalProperties",
+ cfg->config_global_properties);
+ }
+
+ if (ret == 0) {
+ *nvl = nv;
+ } else {
+ nvlist_free(nv);
+ }
+
+ return (ret);
+}
+#endif /* !_KERNEL */
+
+/*
+ * nvlist version of config is 3 list-of-list, + 1 proplist. arrays
+ * are interesting, but lists-of-lists are more useful when doing
+ * individual lookups when we later add support for it. Also, no
+ * need to store name in individual struct representation.
+ */
+int
+it_nv_to_config(nvlist_t *nvl, it_config_t **cfg)
+{
+ int ret;
+ uint32_t intval;
+ nvlist_t *listval;
+ it_config_t *tmpcfg;
+
+ if (!cfg) {
+ return (EINVAL);
+ }
+
+ /* initialize output */
+ *cfg = NULL;
+
+ tmpcfg = iscsit_zalloc(sizeof (it_config_t));
+ if (tmpcfg == NULL) {
+ return (ENOMEM);
+ }
+
+ if (!nvl) {
+ /* nothing to decode, but return the empty cfg struct */
+ ret = nvlist_alloc(&tmpcfg->config_global_properties,
+ NV_UNIQUE_NAME, 0);
+ if (ret != 0) {
+ iscsit_free(tmpcfg, sizeof (it_config_t));
+ return (ret);
+ }
+ *cfg = tmpcfg;
+ return (0);
+ }
+
+ ret = nvlist_lookup_uint32(nvl, "cfgVersion", &intval);
+ if (ret != 0) {
+ iscsit_free(tmpcfg, sizeof (it_config_t));
+ return (ret);
+ }
+
+ tmpcfg->config_version = intval;
+
+ ret = nvlist_lookup_nvlist(nvl, "targetList", &listval);
+ if (ret == 0) {
+ /* decode list of it_tgt_t */
+ ret = it_nv_to_tgtlist(listval, &(tmpcfg->config_tgt_count),
+ &(tmpcfg->config_tgt_list));
+ }
+
+ ret = nvlist_lookup_nvlist(nvl, "tpgList", &listval);
+ if (ret == 0) {
+ /* decode list of it_tpg_t */
+ ret = it_nv_to_tpglist(listval, &(tmpcfg->config_tpg_count),
+ &(tmpcfg->config_tpg_list));
+ }
+
+ ret = nvlist_lookup_nvlist(nvl, "iniList", &listval);
+ if (ret == 0) {
+ /* decode list of initiators */
+ ret = it_nv_to_inilist(listval, &(tmpcfg->config_ini_count),
+ &(tmpcfg->config_ini_list));
+ }
+
+ ret = nvlist_lookup_nvlist(nvl, "globalProperties", &listval);
+ if (ret == 0) {
+ /*
+ * don't depend on the original nvlist staying in-scope,
+ * duplicate the nvlist
+ */
+ ret = nvlist_dup(listval, &(tmpcfg->config_global_properties),
+ 0);
+ } else if (ret == ENOENT) {
+ /*
+ * No global properties defined, make an empty list
+ */
+ ret = nvlist_alloc(&tmpcfg->config_global_properties,
+ NV_UNIQUE_NAME, 0);
+ }
+
+ if (ret == 0) {
+ char **isnsArray = NULL;
+ uint32_t numisns = 0;
+
+ /*
+ * decode the list of iSNS server information to make
+ * references from the kernel simpler.
+ */
+ if (tmpcfg->config_global_properties) {
+ ret = nvlist_lookup_string_array(
+ tmpcfg->config_global_properties,
+ PROP_ISNS_SERVER,
+ &isnsArray, &numisns);
+ if (ret == 0) {
+ ret = it_array_to_portallist(isnsArray,
+ numisns, ISNS_DEFAULT_SERVER_PORT,
+ &tmpcfg->config_isns_svr_list,
+ &tmpcfg->config_isns_svr_count);
+ } else if (ret == ENOENT) {
+ /* It's OK if we don't have any iSNS servers */
+ ret = 0;
+ }
+ }
+ }
+
+ if (ret == 0) {
+ *cfg = tmpcfg;
+ } else {
+ it_config_free_cmn(tmpcfg);
+ }
+
+ return (ret);
+}
+
+it_tgt_t *
+it_tgt_lookup(it_config_t *cfg, char *tgt_name)
+{
+ it_tgt_t *cfg_tgt = NULL;
+
+ for (cfg_tgt = cfg->config_tgt_list;
+ cfg_tgt != NULL;
+ cfg_tgt = cfg_tgt->tgt_next) {
+ if (strncmp(cfg_tgt->tgt_name, tgt_name,
+ MAX_ISCSI_NODENAMELEN) == 0) {
+ return (cfg_tgt);
+ }
+ }
+
+ return (NULL);
+}
+
+int
+it_nv_to_tgtlist(nvlist_t *nvl, uint32_t *count, it_tgt_t **tgtlist)
+{
+ int ret = 0;
+ it_tgt_t *tgt;
+ it_tgt_t *prev = NULL;
+ nvpair_t *nvp = NULL;
+ nvlist_t *nvt;
+ char *name;
+
+ if (!tgtlist || !count) {
+ return (EINVAL);
+ }
+
+ *tgtlist = NULL;
+ *count = 0;
+
+ if (!nvl) {
+ /* nothing to do */
+ return (0);
+ }
+
+ while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
+ name = nvpair_name(nvp);
+
+ ret = nvpair_value_nvlist(nvp, &nvt);
+ if (ret != 0) {
+ /* invalid entry? */
+ continue;
+ }
+
+ ret = it_nv_to_tgt(nvt, name, &tgt);
+ if (ret != 0) {
+ break;
+ }
+
+ (*count)++;
+
+ if (*tgtlist == NULL) {
+ *tgtlist = tgt;
+ } else {
+ prev->tgt_next = tgt;
+ }
+ prev = tgt;
+ }
+
+ if (ret != 0) {
+ it_tgt_free_cmn(*tgtlist);
+ *tgtlist = NULL;
+ }
+
+ return (ret);
+}
+
+int
+it_tgtlist_to_nv(it_tgt_t *tgtlist, nvlist_t **nvl)
+{
+ int ret;
+ it_tgt_t *tgtp = tgtlist;
+ nvlist_t *pnv = NULL;
+ nvlist_t *tnv;
+
+ if (!nvl) {
+ return (EINVAL);
+ }
+
+ if (!tgtlist) {
+ /* nothing to do */
+ return (0);
+ }
+
+ /* create the target list if required */
+ if (*nvl == NULL) {
+ ret = nvlist_alloc(&pnv, NV_UNIQUE_NAME, 0);
+ if (ret != 0) {
+ return (ret);
+ }
+ *nvl = pnv;
+ }
+
+ while (tgtp) {
+ ret = it_tgt_to_nv(tgtp, &tnv);
+
+ if (ret != 0) {
+ break;
+ }
+
+ ret = nvlist_add_nvlist(*nvl, tgtp->tgt_name, tnv);
+
+ if (ret != 0) {
+ break;
+ }
+
+ nvlist_free(tnv);
+
+ tgtp = tgtp->tgt_next;
+ }
+
+ if (ret != 0) {
+ if (pnv) {
+ nvlist_free(pnv);
+ *nvl = NULL;
+ }
+ }
+
+ return (ret);
+}
+
+int
+it_tgt_to_nv(it_tgt_t *tgt, nvlist_t **nvl)
+{
+ int ret;
+ nvlist_t *tnv = NULL;
+
+ if (!nvl) {
+ return (EINVAL);
+ }
+
+ if (!tgt) {
+ /* nothing to do */
+ return (0);
+ }
+
+ ret = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0);
+ if (ret != 0) {
+ return (ret);
+ }
+
+ if (tgt->tgt_properties) {
+ ret = nvlist_add_nvlist(*nvl, "properties",
+ tgt->tgt_properties);
+ }
+
+ if (ret == 0) {
+ ret = nvlist_add_uint64(*nvl, "generation",
+ tgt->tgt_generation);
+ }
+
+ if (ret == 0) {
+ ret = it_tpgtlist_to_nv(tgt->tgt_tpgt_list, &tnv);
+ }
+
+ if ((ret == 0) && tnv) {
+ ret = nvlist_add_nvlist(*nvl, "tpgtList", tnv);
+ nvlist_free(tnv);
+ }
+
+ if (ret != 0) {
+ nvlist_free(*nvl);
+ *nvl = NULL;
+ }
+
+ return (ret);
+}
+
+int
+it_nv_to_tgt(nvlist_t *nvl, char *name, it_tgt_t **tgt)
+{
+ int ret;
+ it_tgt_t *ttgt;
+ nvlist_t *listval;
+ uint32_t intval;
+
+ if (!nvl || !tgt || !name) {
+ return (EINVAL);
+ }
+
+ *tgt = NULL;
+
+ ttgt = iscsit_zalloc(sizeof (it_tgt_t));
+ if (!ttgt) {
+ return (ENOMEM);
+ }
+
+ (void) strlcpy(ttgt->tgt_name, name, sizeof (ttgt->tgt_name));
+
+ ret = nvlist_lookup_nvlist(nvl, "properties", &listval);
+ if (ret == 0) {
+ /* duplicate list so it does not go out of context */
+ ret = nvlist_dup(listval, &(ttgt->tgt_properties), 0);
+ } else if (ret == ENOENT) {
+ ret = 0;
+ }
+
+ if (ret == 0) {
+ ret = nvlist_lookup_uint64(nvl, "generation",
+ &(ttgt->tgt_generation));
+ } else if (ret == ENOENT) {
+ ret = 0;
+ }
+
+ if (ret == 0) {
+ ret = nvlist_lookup_nvlist(nvl, "tpgtList", &listval);
+ }
+
+ if (ret == 0) {
+ ret = it_nv_to_tpgtlist(listval, &intval,
+ &(ttgt->tgt_tpgt_list));
+ ttgt->tgt_tpgt_count = intval;
+ } else if (ret == ENOENT) {
+ ret = 0;
+ }
+
+ if (ret == 0) {
+ *tgt = ttgt;
+ } else {
+ it_tgt_free_cmn(ttgt);
+ }
+
+ return (ret);
+}
+
+int
+it_tpgt_to_nv(it_tpgt_t *tpgt, nvlist_t **nvl)
+{
+ int ret;
+
+ if (!nvl) {
+ return (EINVAL);
+ }
+
+ if (!tpgt) {
+ /* nothing to do */
+ return (0);
+ }
+
+ ret = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0);
+ if (ret != 0) {
+ return (ret);
+ }
+
+ ret = nvlist_add_uint16(*nvl, "tag", tpgt->tpgt_tag);
+ if (ret == 0) {
+ ret = nvlist_add_uint64(*nvl, "generation",
+ tpgt->tpgt_generation);
+ }
+
+ if (ret != 0) {
+ nvlist_free(*nvl);
+ *nvl = NULL;
+ }
+
+ return (ret);
+}
+
+int
+it_nv_to_tpgt(nvlist_t *nvl, char *name, it_tpgt_t **tpgt)
+{
+ int ret;
+ it_tpgt_t *ptr;
+
+ if (!tpgt || !name) {
+ return (EINVAL);
+ }
+
+ *tpgt = NULL;
+
+ if (!nvl) {
+ return (0);
+ }
+
+ ptr = iscsit_zalloc(sizeof (it_tpgt_t));
+ if (!ptr) {
+ return (ENOMEM);
+ }
+
+ (void) strlcpy(ptr->tpgt_tpg_name, name, sizeof (ptr->tpgt_tpg_name));
+
+ ret = nvlist_lookup_uint16(nvl, "tag", &(ptr->tpgt_tag));
+ if (ret == 0) {
+ ret = nvlist_lookup_uint64(nvl, "generation",
+ &(ptr->tpgt_generation));
+ }
+
+ if (ret == 0) {
+ *tpgt = ptr;
+ } else {
+ iscsit_free(ptr, sizeof (it_tpgt_t));
+ }
+
+ return (ret);
+}
+
+int
+it_tpgtlist_to_nv(it_tpgt_t *tpgtlist, nvlist_t **nvl)
+{
+ int ret;
+ nvlist_t *pnv = NULL;
+ nvlist_t *tnv;
+ it_tpgt_t *ptr = tpgtlist;
+
+ if (!nvl) {
+ return (EINVAL);
+ }
+
+ if (!tpgtlist) {
+ /* nothing to do */
+ return (0);
+ }
+
+ /* create the target list if required */
+ if (*nvl == NULL) {
+ ret = nvlist_alloc(&pnv, NV_UNIQUE_NAME, 0);
+ if (ret != 0) {
+ return (ret);
+ }
+ *nvl = pnv;
+ }
+
+ while (ptr) {
+ ret = it_tpgt_to_nv(ptr, &tnv);
+
+ if (ret != 0) {
+ break;
+ }
+
+ ret = nvlist_add_nvlist(*nvl, ptr->tpgt_tpg_name, tnv);
+
+ if (ret != 0) {
+ break;
+ }
+
+ nvlist_free(tnv);
+
+ ptr = ptr->tpgt_next;
+ }
+
+ if (ret != 0) {
+ if (pnv) {
+ nvlist_free(pnv);
+ *nvl = NULL;
+ }
+ }
+
+ return (ret);
+}
+
+int
+it_nv_to_tpgtlist(nvlist_t *nvl, uint32_t *count, it_tpgt_t **tpgtlist)
+{
+ int ret = 0;
+ it_tpgt_t *tpgt;
+ it_tpgt_t *prev = NULL;
+ nvpair_t *nvp = NULL;
+ nvlist_t *nvt;
+ char *name;
+
+ if (!tpgtlist || !count) {
+ return (EINVAL);
+ }
+
+ *tpgtlist = NULL;
+ *count = 0;
+
+ if (!nvl) {
+ /* nothing to do */
+ return (0);
+ }
+
+ while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
+ name = nvpair_name(nvp);
+
+ ret = nvpair_value_nvlist(nvp, &nvt);
+ if (ret != 0) {
+ /* invalid entry? */
+ continue;
+ }
+
+ ret = it_nv_to_tpgt(nvt, name, &tpgt);
+ if (ret != 0) {
+ break;
+ }
+
+ (*count)++;
+
+ if (*tpgtlist == NULL) {
+ *tpgtlist = tpgt;
+ } else {
+ prev->tpgt_next = tpgt;
+ }
+
+ prev = tpgt;
+ }
+
+ if (ret != 0) {
+ it_tpgt_free_cmn(*tpgtlist);
+ *tpgtlist = NULL;
+ }
+
+ return (ret);
+}
+
+#ifndef _KERNEL
+int
+it_tpg_to_nv(it_tpg_t *tpg, nvlist_t **nvl)
+{
+ int ret;
+ char **portalArray = NULL;
+ int i;
+ it_portal_t *ptr;
+
+ if (!nvl) {
+ return (EINVAL);
+ }
+
+ if (!tpg) {
+ /* nothing to do */
+ return (0);
+ }
+
+ ret = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0);
+ if (ret != 0) {
+ return (ret);
+ }
+
+ ret = nvlist_add_uint64(*nvl, "generation", tpg->tpg_generation);
+
+ if ((ret == 0) && tpg->tpg_portal_list) {
+ /* add the portals */
+ portalArray = iscsit_zalloc(tpg->tpg_portal_count *
+ sizeof (it_portal_t));
+ if (portalArray == NULL) {
+ nvlist_free(*nvl);
+ *nvl = NULL;
+ return (ENOMEM);
+ }
+
+ i = 0;
+ ptr = tpg->tpg_portal_list;
+
+ while (ptr && (i < tpg->tpg_portal_count)) {
+ ret = sockaddr_to_str(&(ptr->portal_addr),
+ &(portalArray[i]));
+ if (ret != 0) {
+ break;
+ }
+ ptr = ptr->next;
+ i++;
+ }
+ }
+
+ if ((ret == 0) && portalArray) {
+ ret = nvlist_add_string_array(*nvl, "portalList",
+ portalArray, i);
+ }
+
+
+ if (portalArray) {
+ while (i > 0) {
+ if (portalArray[i]) {
+ iscsit_free(portalArray[i],
+ strlen(portalArray[i] + 1));
+ }
+ i--;
+ }
+ iscsit_free(portalArray,
+ tpg->tpg_portal_count * sizeof (it_portal_t));
+ }
+
+ if (ret != 0) {
+ nvlist_free(*nvl);
+ *nvl = NULL;
+ }
+
+ return (ret);
+}
+#endif /* !_KERNEL */
+
+int
+it_nv_to_tpg(nvlist_t *nvl, char *name, it_tpg_t **tpg)
+{
+ int ret;
+ it_tpg_t *ptpg;
+ char **portalArray = NULL;
+ uint32_t count = 0;
+
+ if (!name || !tpg) {
+ return (EINVAL);
+ }
+
+ *tpg = NULL;
+
+ ptpg = iscsit_zalloc(sizeof (it_tpg_t));
+ if (ptpg == NULL) {
+ return (ENOMEM);
+ }
+
+ (void) strlcpy(ptpg->tpg_name, name, sizeof (ptpg->tpg_name));
+
+ ret = nvlist_lookup_uint64(nvl, "generation",
+ &(ptpg->tpg_generation));
+
+ if (ret == 0) {
+ ret = nvlist_lookup_string_array(nvl, "portalList",
+ &portalArray, &count);
+ }
+
+ if (ret == 0) {
+ /* set the portals */
+ ret = it_array_to_portallist(portalArray, count,
+ ISCSI_LISTEN_PORT, &ptpg->tpg_portal_list,
+ &ptpg->tpg_portal_count);
+ } else if (ret == ENOENT) {
+ ret = 0;
+ }
+
+ if (ret == 0) {
+ *tpg = ptpg;
+ } else {
+ it_tpg_free_cmn(ptpg);
+ }
+
+ return (ret);
+}
+
+
+
+
+#ifndef _KERNEL
+int
+it_tpglist_to_nv(it_tpg_t *tpglist, nvlist_t **nvl)
+{
+ int ret;
+ nvlist_t *pnv = NULL;
+ nvlist_t *tnv;
+ it_tpg_t *ptr = tpglist;
+
+ if (!nvl) {
+ return (EINVAL);
+ }
+
+ if (!tpglist) {
+ /* nothing to do */
+ return (0);
+ }
+
+ /* create the target portal group list if required */
+ if (*nvl == NULL) {
+ ret = nvlist_alloc(&pnv, NV_UNIQUE_NAME, 0);
+ if (ret != 0) {
+ return (ret);
+ }
+ *nvl = pnv;
+ }
+
+ while (ptr) {
+ ret = it_tpg_to_nv(ptr, &tnv);
+
+ if (ret != 0) {
+ break;
+ }
+
+ ret = nvlist_add_nvlist(*nvl, ptr->tpg_name, tnv);
+
+ if (ret != 0) {
+ break;
+ }
+
+ nvlist_free(tnv);
+
+ ptr = ptr->tpg_next;
+ }
+
+ if (ret != 0) {
+ if (pnv) {
+ nvlist_free(pnv);
+ *nvl = NULL;
+ }
+ }
+
+ return (ret);
+}
+#endif /* !_KERNEL */
+
+it_tpg_t *
+it_tpg_lookup(it_config_t *cfg, char *tpg_name)
+{
+ it_tpg_t *cfg_tpg = NULL;
+
+ for (cfg_tpg = cfg->config_tpg_list;
+ cfg_tpg != NULL;
+ cfg_tpg = cfg_tpg->tpg_next) {
+ if (strncmp(&cfg_tpg->tpg_name[0], tpg_name,
+ MAX_TPG_NAMELEN) == 0) {
+ return (cfg_tpg);
+ }
+ }
+
+ return (NULL);
+}
+
+int
+it_sa_compare(struct sockaddr_storage *sa1, struct sockaddr_storage *sa2)
+{
+ struct sockaddr_in *sin1, *sin2;
+ struct sockaddr_in6 *sin6_1, *sin6_2;
+
+ /*
+ * XXX - should we check here for IPv4 addrs mapped to v6?
+ * see also iscsit_is_v4_mapped in iscsit_login.c
+ */
+
+ if (sa1->ss_family != sa2->ss_family) {
+ return (1);
+ }
+
+ /*
+ * sockaddr_in has padding which may not be initialized.
+ * be more specific in the comparison, and don't trust the
+ * caller has fully initialized the structure.
+ */
+ if (sa1->ss_family == AF_INET) {
+ sin1 = (struct sockaddr_in *)sa1;
+ sin2 = (struct sockaddr_in *)sa2;
+ if ((bcmp(&sin1->sin_addr, &sin2->sin_addr,
+ sizeof (struct in_addr)) == 0) &&
+ (sin1->sin_port == sin2->sin_port)) {
+ return (0);
+ }
+ } else if (sa1->ss_family == AF_INET6) {
+ sin6_1 = (struct sockaddr_in6 *)sa1;
+ sin6_2 = (struct sockaddr_in6 *)sa2;
+ if (bcmp(sin6_1, sin6_2, sizeof (struct sockaddr_in6)) == 0) {
+ return (0);
+ }
+ }
+
+ return (1);
+}
+
+it_portal_t *
+it_portal_lookup(it_tpg_t *tpg, struct sockaddr_storage *sa)
+{
+ it_portal_t *cfg_portal;
+
+ for (cfg_portal = tpg->tpg_portal_list;
+ cfg_portal != NULL;
+ cfg_portal = cfg_portal->next) {
+ if (it_sa_compare(sa, &cfg_portal->portal_addr) == 0)
+ return (cfg_portal);
+ }
+
+ return (NULL);
+}
+
+it_portal_t *
+it_sns_svr_lookup(it_config_t *cfg, struct sockaddr_storage *sa)
+{
+ it_portal_t *cfg_portal;
+
+ for (cfg_portal = cfg->config_isns_svr_list;
+ cfg_portal != NULL;
+ cfg_portal = cfg_portal->next) {
+ if (it_sa_compare(sa, &cfg_portal->portal_addr) == 0)
+ return (cfg_portal);
+ }
+
+ return (NULL);
+}
+
+int
+it_nv_to_tpglist(nvlist_t *nvl, uint32_t *count, it_tpg_t **tpglist)
+{
+ int ret = 0;
+ it_tpg_t *tpg;
+ it_tpg_t *prev = NULL;
+ nvpair_t *nvp = NULL;
+ nvlist_t *nvt;
+ char *name;
+
+ if (!tpglist || !count) {
+ return (EINVAL);
+ }
+
+ *tpglist = NULL;
+ *count = 0;
+
+ if (!nvl) {
+ /* nothing to do */
+ return (0);
+ }
+
+ while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
+ name = nvpair_name(nvp);
+
+ ret = nvpair_value_nvlist(nvp, &nvt);
+ if (ret != 0) {
+ /* invalid entry? */
+ continue;
+ }
+
+ ret = it_nv_to_tpg(nvt, name, &tpg);
+ if (ret != 0) {
+ break;
+ }
+
+ (*count)++;
+
+ if (*tpglist == NULL) {
+ *tpglist = tpg;
+ } else {
+ prev->tpg_next = tpg;
+ }
+ prev = tpg;
+ }
+
+ if (ret != 0) {
+ it_tpg_free_cmn(*tpglist);
+ *tpglist = NULL;
+ }
+
+ return (ret);
+}
+
+int
+it_ini_to_nv(it_ini_t *ini, nvlist_t **nvl)
+{
+ int ret;
+
+ if (!nvl) {
+ return (EINVAL);
+ }
+
+ if (!ini) {
+ return (0);
+ }
+
+ ret = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0);
+ if (ret != 0) {
+ return (ret);
+ }
+
+ if (ini->ini_properties) {
+ ret = nvlist_add_nvlist(*nvl, "properties",
+ ini->ini_properties);
+ }
+
+ if (ret == 0) {
+ ret = nvlist_add_uint64(*nvl, "generation",
+ ini->ini_generation);
+ } else if (ret == ENOENT) {
+ ret = 0;
+ }
+
+ if (ret != 0) {
+ nvlist_free(*nvl);
+ *nvl = NULL;
+ }
+
+ return (ret);
+}
+
+int
+it_nv_to_ini(nvlist_t *nvl, char *name, it_ini_t **ini)
+{
+ int ret;
+ it_ini_t *inip;
+ nvlist_t *listval;
+
+ if (!name || !ini) {
+ return (EINVAL);
+ }
+
+ *ini = NULL;
+
+ if (!nvl) {
+ return (0);
+ }
+
+ inip = iscsit_zalloc(sizeof (it_ini_t));
+ if (!inip) {
+ return (ENOMEM);
+ }
+
+ (void) strlcpy(inip->ini_name, name, sizeof (inip->ini_name));
+
+ ret = nvlist_lookup_nvlist(nvl, "properties", &listval);
+ if (ret == 0) {
+ ret = nvlist_dup(listval, &(inip->ini_properties), 0);
+ } else if (ret == ENOENT) {
+ ret = 0;
+ }
+
+ if (ret == 0) {
+ ret = nvlist_lookup_uint64(nvl, "generation",
+ &(inip->ini_generation));
+ }
+
+ if (ret == 0) {
+ *ini = inip;
+ } else {
+ it_ini_free_cmn(inip);
+ }
+
+ return (ret);
+}
+
+int
+it_inilist_to_nv(it_ini_t *inilist, nvlist_t **nvl)
+{
+ int ret;
+ nvlist_t *pnv = NULL;
+ nvlist_t *tnv;
+ it_ini_t *ptr = inilist;
+
+ if (!nvl) {
+ return (EINVAL);
+ }
+
+ if (!inilist) {
+ return (0);
+ }
+
+ /* create the target list if required */
+ if (*nvl == NULL) {
+ ret = nvlist_alloc(&pnv, NV_UNIQUE_NAME, 0);
+ if (ret != 0) {
+ return (ret);
+ }
+ *nvl = pnv;
+ }
+
+ while (ptr) {
+ ret = it_ini_to_nv(ptr, &tnv);
+
+ if (ret != 0) {
+ break;
+ }
+
+ ret = nvlist_add_nvlist(*nvl, ptr->ini_name, tnv);
+
+ if (ret != 0) {
+ break;
+ }
+
+ nvlist_free(tnv);
+
+ ptr = ptr->ini_next;
+ }
+
+ if (ret != 0) {
+ if (pnv) {
+ nvlist_free(pnv);
+ *nvl = NULL;
+ }
+ }
+
+ return (ret);
+}
+
+int
+it_nv_to_inilist(nvlist_t *nvl, uint32_t *count, it_ini_t **inilist)
+{
+ int ret = 0;
+ it_ini_t *inip;
+ it_ini_t *prev = NULL;
+ nvpair_t *nvp = NULL;
+ nvlist_t *nvt;
+ char *name;
+
+ if (!inilist || !count) {
+ return (EINVAL);
+ }
+
+ *inilist = NULL;
+ *count = 0;
+
+ if (!nvl) {
+ /* nothing to do */
+ return (0);
+ }
+
+ while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
+ name = nvpair_name(nvp);
+
+ ret = nvpair_value_nvlist(nvp, &nvt);
+ if (ret != 0) {
+ /* invalid entry? */
+ continue;
+ }
+
+ ret = it_nv_to_ini(nvt, name, &inip);
+ if (ret != 0) {
+ break;
+ }
+
+ (*count)++;
+
+ if (*inilist == NULL) {
+ *inilist = inip;
+ } else {
+ prev->ini_next = inip;
+ }
+ prev = inip;
+ }
+
+ if (ret != 0) {
+ it_ini_free_cmn(*inilist);
+ *inilist = NULL;
+ }
+
+ return (ret);
+}
+
+/*
+ * Convert a sockaddr to the string representation, suitable for
+ * storing in an nvlist or printing out in a list.
+ */
+#ifndef _KERNEL
+int
+sockaddr_to_str(struct sockaddr_storage *sa, char **addr)
+{
+ int ret;
+ char buf[INET6_ADDRSTRLEN + 7]; /* addr : port */
+ char pbuf[7];
+ const char *bufp;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ uint16_t port;
+
+ if (!sa || !addr) {
+ return (EINVAL);
+ }
+
+ buf[0] = '\0';
+
+ if (sa->ss_family == AF_INET) {
+ sin = (struct sockaddr_in *)sa;
+ bufp = inet_ntop(AF_INET,
+ (const void *)&(sin->sin_addr.s_addr),
+ buf, sizeof (buf));
+ if (bufp == NULL) {
+ ret = errno;
+ return (ret);
+ }
+ port = ntohs(sin->sin_port);
+ } else if (sa->ss_family == AF_INET6) {
+ (void) strlcat(buf, "[", sizeof (buf));
+ sin6 = (struct sockaddr_in6 *)sa;
+ bufp = inet_ntop(AF_INET6,
+ (const void *)&sin6->sin6_addr.s6_addr,
+ &buf[1], (sizeof (buf) - 1));
+ if (bufp == NULL) {
+ ret = errno;
+ return (ret);
+ }
+ (void) strlcat(buf, "]", sizeof (buf));
+ port = ntohs(sin6->sin6_port);
+ } else {
+ return (EINVAL);
+ }
+
+
+ (void) snprintf(pbuf, sizeof (pbuf), ":%u", port);
+ (void) strlcat(buf, pbuf, sizeof (buf));
+
+ *addr = strdup(buf);
+ if (*addr == NULL) {
+ return (ENOMEM);
+ }
+
+ return (0);
+}
+#endif /* !_KERNEL */
+
+int
+it_array_to_portallist(char **arr, uint32_t count, uint32_t default_port,
+ it_portal_t **portallist, uint32_t *list_count)
+{
+ int ret = 0;
+ int i;
+ it_portal_t *portal;
+ it_portal_t *prev = NULL;
+ it_portal_t *tmp;
+
+ if (!arr || !portallist || !list_count) {
+ return (EINVAL);
+ }
+
+ *list_count = 0;
+ *portallist = NULL;
+
+ for (i = 0; i < count; i++) {
+ if (!arr[i]) {
+ /* should never happen */
+ continue;
+ }
+ portal = iscsit_zalloc(sizeof (it_portal_t));
+ if (!portal) {
+ ret = ENOMEM;
+ break;
+ }
+ if (it_common_convert_sa(arr[i],
+ &(portal->portal_addr), default_port) == NULL) {
+ iscsit_free(portal, sizeof (it_portal_t));
+ ret = EINVAL;
+ break;
+ }
+
+ /* make sure no duplicates */
+ tmp = *portallist;
+ while (tmp) {
+ if (it_sa_compare(&(tmp->portal_addr),
+ &(portal->portal_addr)) == 0) {
+ iscsit_free(portal, sizeof (it_portal_t));
+ portal = NULL;
+ break;
+ }
+ tmp = tmp->next;
+ }
+
+ if (!portal) {
+ continue;
+ }
+
+ /*
+ * The first time through the loop, *portallist == NULL
+ * because we assigned it to NULL above. Subsequently
+ * prev will have been set. Therefor it's OK to put
+ * lint override before prev->next assignment.
+ */
+ if (*portallist == NULL) {
+ *portallist = portal;
+ } else {
+ prev->next = portal;
+ }
+
+ prev = portal;
+ (*list_count)++;
+ }
+
+ return (ret);
+}
+
+/*
+ * Function: it_config_free_cmn()
+ *
+ * Free any resources associated with the it_config_t structure.
+ *
+ * Parameters:
+ * cfg A C representation of the current iSCSI configuration
+ */
+void
+it_config_free_cmn(it_config_t *cfg)
+{
+ if (!cfg) {
+ return;
+ }
+
+ if (cfg->config_tgt_list) {
+ it_tgt_free_cmn(cfg->config_tgt_list);
+ }
+
+ if (cfg->config_tpg_list) {
+ it_tpg_free_cmn(cfg->config_tpg_list);
+ }
+
+ if (cfg->config_ini_list) {
+ it_ini_free_cmn(cfg->config_ini_list);
+ }
+
+ if (cfg->config_global_properties) {
+ nvlist_free(cfg->config_global_properties);
+ }
+
+ if (cfg->config_isns_svr_list) {
+ it_portal_t *pp = cfg->config_isns_svr_list;
+ it_portal_t *pp_next;
+
+ while (pp) {
+ pp_next = pp->next;
+ iscsit_free(pp, sizeof (it_portal_t));
+ pp = pp_next;
+ }
+ }
+
+ iscsit_free(cfg, sizeof (it_config_t));
+}
+
+/*
+ * Function: it_tgt_free_cmn()
+ *
+ * Frees an it_tgt_t structure. If tgt_next is not NULL, frees
+ * all structures in the list.
+ */
+void
+it_tgt_free_cmn(it_tgt_t *tgt)
+{
+ it_tgt_t *tgtp = tgt;
+ it_tgt_t *next;
+
+ if (!tgt) {
+ return;
+ }
+
+ while (tgtp) {
+ next = tgtp->tgt_next;
+
+ if (tgtp->tgt_tpgt_list) {
+ it_tpgt_free_cmn(tgtp->tgt_tpgt_list);
+ }
+
+ if (tgtp->tgt_properties) {
+ nvlist_free(tgtp->tgt_properties);
+ }
+
+ iscsit_free(tgtp, sizeof (it_tgt_t));
+
+ tgtp = next;
+ }
+}
+
+/*
+ * Function: it_tpgt_free_cmn()
+ *
+ * Deallocates resources of an it_tpgt_t structure. If tpgt->next
+ * is not NULL, frees all members of the list.
+ */
+void
+it_tpgt_free_cmn(it_tpgt_t *tpgt)
+{
+ it_tpgt_t *tpgtp = tpgt;
+ it_tpgt_t *next;
+
+ if (!tpgt) {
+ return;
+ }
+
+ while (tpgtp) {
+ next = tpgtp->tpgt_next;
+
+ iscsit_free(tpgtp, sizeof (it_tpgt_t));
+
+ tpgtp = next;
+ }
+}
+
+/*
+ * Function: it_tpg_free_cmn()
+ *
+ * Deallocates resources associated with an it_tpg_t structure.
+ * If tpg->next is not NULL, frees all members of the list.
+ */
+void
+it_tpg_free_cmn(it_tpg_t *tpg)
+{
+ it_tpg_t *tpgp = tpg;
+ it_tpg_t *next;
+ it_portal_t *portalp;
+ it_portal_t *pnext;
+
+ while (tpgp) {
+ next = tpgp->tpg_next;
+
+ portalp = tpgp->tpg_portal_list;
+
+ while (portalp) {
+ pnext = portalp->next;
+ iscsit_free(portalp, sizeof (it_portal_t));
+ portalp = pnext;
+ }
+
+ iscsit_free(tpgp, sizeof (it_tpg_t));
+
+ tpgp = next;
+ }
+}
+
+/*
+ * Function: it_ini_free_cmn()
+ *
+ * Deallocates resources of an it_ini_t structure. If ini->next is
+ * not NULL, frees all members of the list.
+ */
+void
+it_ini_free_cmn(it_ini_t *ini)
+{
+ it_ini_t *inip = ini;
+ it_ini_t *next;
+
+ if (!ini) {
+ return;
+ }
+
+ while (inip) {
+ next = inip->ini_next;
+
+ if (inip->ini_properties) {
+ nvlist_free(inip->ini_properties);
+ }
+
+ iscsit_free(inip, sizeof (it_ini_t));
+
+ inip = next;
+ }
+}
diff --git a/usr/src/lib/Makefile b/usr/src/lib/Makefile
index 5b4d3f0d57..94e5f1d7f3 100644
--- a/usr/src/lib/Makefile
+++ b/usr/src/lib/Makefile
@@ -113,6 +113,7 @@ SUBDIRS += \
libinetcfg \
libinetutil \
libipmp \
+ libiscsit \
libiscsitgt \
libkmf \
libkstat \
@@ -388,6 +389,7 @@ HDRSUBDIRS= \
libipmi \
libipmp \
libipp \
+ libiscsit \
libiscsitgt \
libkstat \
libkvm \
@@ -545,6 +547,7 @@ libfstyp: libnvpair
libelfsign: libcryptoutil libkmf
libidmap: libnsl
libinetcfg: libnsl libsocket libdlpi
+libiscsit: libc libnvpair libstmf libuuid libnsl
libkmf: libcryptoutil pkcs11 openssl
libnsl: libmd5 libscf
libmapid: libresolv
diff --git a/usr/src/lib/libiscsit/Makefile b/usr/src/lib/libiscsit/Makefile
new file mode 100644
index 0000000000..e4dc297543
--- /dev/null
+++ b/usr/src/lib/libiscsit/Makefile
@@ -0,0 +1,56 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+include ../Makefile.lib
+
+SUBDIRS = $(MACH)
+$(BUILD64)SUBDIRS += $(MACH64)
+
+all := TARGET= all
+clean := TARGET= clean
+clobber := TARGET= clobber
+install := TARGET= install
+lint := TARGET= lint
+
+
+HDRS= libiscsit.h
+HDRDIR= common
+ROOTHDRS= $(ROOTHDRDIR)/libiscsit.h
+
+.KEEP_STATE:
+
+all clean clobber install lint: $(SUBDIRS)
+
+$(ROOTHDRDIR)/%: common/%
+ $(INS.file)
+install_h: $(ROOTHDRS)
+
+check: $(CHECKHDRS)
+
+$(SUBDIRS): FRC
+ @cd $@; pwd; $(MAKE) $(TARGET)
+
+FRC:
+
diff --git a/usr/src/lib/libiscsit/Makefile.com b/usr/src/lib/libiscsit/Makefile.com
new file mode 100644
index 0000000000..7e0a23201c
--- /dev/null
+++ b/usr/src/lib/libiscsit/Makefile.com
@@ -0,0 +1,66 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+LIBRARY= libiscsit.a
+VERS= .1
+
+ISCSIT_OBJS_SHARED= iscsit_common.o
+ISCSI_OBJS_SHARED= base64.o
+OBJS_COMMON= libiscsit.o
+OBJECTS= $(OBJS_COMMON) $(ISCSIT_OBJS_SHARED) $(ISCSI_OBJS_SHARED)
+
+include ../../Makefile.lib
+
+LIBS= $(DYNLIB) $(LINTLIB)
+
+SRCDIR = ../common
+
+INCS += -I$(SRCDIR)
+
+C99MODE= -xc99=%all
+C99LMODE= -Xc99=%all
+LDLIBS += -lc -lnvpair -lstmf -luuid -lnsl
+CPPFLAGS += $(INCS) -D_REENTRANT
+
+SRCS= $(OBJS_COMMON:%.o=$(SRCDIR)/%.c) \
+ $(ISCSIT_OBJS_SHARED:%.o=$(SRC)/common/iscsit/%.c) \
+ $(ISCSI_OBJS_SHARED:%.o=$(SRC)/common/iscsi/%.c)
+$(LINTLIB) := SRCS= $(SRCDIR)/$(LINTSRC)
+
+.KEEP_STATE:
+
+all: $(LIBS)
+
+lint: lintcheck
+
+pics/%.o: ../../../common/iscsit/%.c
+ $(COMPILE.c) -o $@ $<
+ $(POST_PROCESS_O)
+
+pics/%.o: ../../../common/iscsi/%.c
+ $(COMPILE.c) -o $@ $<
+ $(POST_PROCESS_O)
+
+include ../../Makefile.targ
diff --git a/usr/src/lib/libiscsit/amd64/Makefile b/usr/src/lib/libiscsit/amd64/Makefile
new file mode 100644
index 0000000000..821cbc8a82
--- /dev/null
+++ b/usr/src/lib/libiscsit/amd64/Makefile
@@ -0,0 +1,32 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+
+include ../Makefile.com
+include ../../Makefile.lib.64
+
+all: $(LIBS)
+
+install: all $(ROOTLIBS64) $(ROOTLINKS64) $(ROOTLINT)
diff --git a/usr/src/lib/libiscsit/common/libiscsit.c b/usr/src/lib/libiscsit/common/libiscsit.c
new file mode 100644
index 0000000000..a9b2edeae4
--- /dev/null
+++ b/usr/src/lib/libiscsit/common/libiscsit.c
@@ -0,0 +1,1889 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <uuid/uuid.h>
+#include <errno.h>
+#include <unistd.h>
+#include <strings.h>
+#include <libintl.h>
+
+#include <libstmf.h>
+#include <libiscsit.h>
+#include <sys/iscsit/iscsit_common.h>
+#include <sys/iscsi_protocol.h>
+#include <sys/iscsit/isns_protocol.h>
+
+/* From iscsitgtd */
+#define TARGET_NAME_VERS 2
+
+/* this should be defined someplace central... */
+#define ISCSI_NAME_LEN_MAX 223
+
+/* max length of a base64 encoded secret */
+#define MAX_BASE64_LEN 341
+
+/* Default RADIUS server port */
+#define DEFAULT_RADIUS_PORT 1812
+
+/*
+ * The kernel reserves target portal group tag value 1 as the default.
+ */
+#define ISCSIT_DEFAULT_TPGT 1
+#define MAXTAG 0xffff
+
+/* helper for property list validation */
+#define PROPERR(lst, key, value) { \
+ if (lst) { \
+ (void) nvlist_add_string(lst, key, value); \
+ } \
+}
+
+/* helper function declarations */
+static int
+it_iqn_generate(char *iqn_buf, int iqn_buf_len, char *opt_iqn_suffix);
+
+static int
+it_val_pass(char *name, char *val, nvlist_t *e);
+
+/* consider making validate funcs public */
+static int
+it_validate_configprops(nvlist_t *nvl, nvlist_t *errs);
+
+static int
+it_validate_tgtprops(nvlist_t *nvl, nvlist_t *errs);
+
+static int
+it_validate_iniprops(nvlist_t *nvl, nvlist_t *errs);
+
+/*
+ * Function: it_config_load()
+ *
+ * Allocate and create an it_config_t structure representing the
+ * current iSCSI configuration. This structure is compiled using
+ * the 'provider' data returned by stmfGetProviderData(). If there
+ * is no provider data associated with iscsit, the it_config_t
+ * structure will be set to a default configuration.
+ *
+ * Parameters:
+ * cfg A C representation of the current iSCSI configuration
+ *
+ * Return Values:
+ * 0 Success
+ * ENOMEM Could not allocate resources
+ * EINVAL Invalid parameter
+ */
+int
+it_config_load(it_config_t **cfg)
+{
+ int ret = 0;
+ nvlist_t *cfg_nv = NULL;
+ it_config_t *newcfg = NULL;
+ uint64_t stmf_token = 0;
+
+ if (!cfg) {
+ return (EINVAL);
+ }
+
+ *cfg = NULL;
+
+ ret = stmfGetProviderDataProt(ISCSIT_MODNAME, &cfg_nv,
+ STMF_PORT_PROVIDER_TYPE, &stmf_token);
+
+ if ((ret == STMF_STATUS_SUCCESS) ||
+ (ret == STMF_ERROR_NOT_FOUND)) {
+ /*
+ * If not initialized yet, return empty it_config_t
+ * Else, convert nvlist to struct
+ */
+ ret = it_nv_to_config(cfg_nv, &newcfg);
+ }
+
+ if (ret == 0) {
+ newcfg->stmf_token = stmf_token;
+ *cfg = newcfg;
+ }
+
+ return (ret);
+}
+
+/*
+ * Function: it_config_commit()
+ *
+ * Informs the iscsit service that the configuration has changed and
+ * commits the new configuration to persistent store by calling
+ * stmfSetProviderData. This function can be called multiple times
+ * during a configuration sequence if necessary.
+ *
+ * Parameters:
+ * cfg A C representation of the current iSCSI configuration
+ *
+ * Return Values:
+ * 0 Success
+ * ENOMEM Could not allocate resources
+ * EINVAL Invalid it_config_t structure
+ * TBD ioctl() failed
+ * TBD could not save config to STMF
+ */
+int
+it_config_commit(it_config_t *cfg)
+{
+ int ret;
+ nvlist_t *cfgnv = NULL;
+ char *packednv = NULL;
+ int iscsit_fd = -1;
+ size_t pnv_size;
+ iscsit_ioc_set_config_t iop;
+ it_tgt_t *tgtp;
+
+ if (!cfg) {
+ return (EINVAL);
+ }
+
+ iscsit_fd = open(ISCSIT_NODE, O_RDWR|O_EXCL);
+ if (iscsit_fd == -1) {
+ ret = errno;
+ return (ret);
+ }
+
+ ret = it_config_to_nv(cfg, &cfgnv);
+ if (ret == 0) {
+ ret = nvlist_size(cfgnv, &pnv_size, NV_ENCODE_NATIVE);
+ }
+
+ if (ret == 0) {
+ packednv = malloc(pnv_size);
+ if (!packednv) {
+ ret = ENOMEM;
+ } else {
+ ret = nvlist_pack(cfgnv, &packednv, &pnv_size,
+ NV_ENCODE_NATIVE, 0);
+ }
+ }
+
+ /*
+ * Send the changes to the kernel first, for now. Kernel
+ * will be the final sanity check before config is saved
+ * persistently.
+ *
+ * XXX - this leaves open the simultaneous-change hole
+ * that STMF was trying to solve, but is a better sanity
+ * check. Final decision on save order/config generation
+ * number TBD.
+ */
+ if (ret == 0) {
+ iop.set_cfg_vers = ISCSIT_API_VERS0;
+ iop.set_cfg_pnvlist = packednv;
+ iop.set_cfg_pnvlist_len = pnv_size;
+ if ((ioctl(iscsit_fd, ISCSIT_IOC_SET_CONFIG, &iop)) != 0) {
+ ret = errno;
+ }
+ }
+
+ /*
+ * Before saving the config persistently, remove any
+ * PROP_OLD_TARGET_NAME entries. This is only interesting to
+ * the active service.
+ */
+ if (ret == 0) {
+ tgtp = cfg->config_tgt_list;
+ for (; tgtp != NULL; tgtp = tgtp->tgt_next) {
+ if (!tgtp->tgt_properties) {
+ continue;
+ }
+ if (nvlist_exists(tgtp->tgt_properties,
+ PROP_OLD_TARGET_NAME)) {
+ (void) nvlist_remove_all(tgtp->tgt_properties,
+ PROP_OLD_TARGET_NAME);
+ }
+ }
+ }
+
+ /*
+ * stmfGetProviderDataProt() checks to ensure
+ * that the config data hasn't changed since we fetched it.
+ *
+ * The kernel now has a version we need to save persistently.
+ * CLI will 'do the right thing' and warn the user if it
+ * gets STMF_ERROR_PROV_DATA_STALE. We'll try once to revert
+ * the kernel to the persistently saved data, but ultimately,
+ * it's up to the administrator to validate things are as they
+ * want them to be.
+ */
+ if (ret == 0) {
+ ret = stmfSetProviderDataProt(ISCSIT_MODNAME, cfgnv,
+ STMF_PORT_PROVIDER_TYPE, &(cfg->stmf_token));
+
+ if (ret == STMF_STATUS_SUCCESS) {
+ ret = 0;
+ } else if (ret == STMF_ERROR_NOMEM) {
+ ret = ENOMEM;
+ } else if (ret == STMF_ERROR_PROV_DATA_STALE) {
+ int st;
+ it_config_t *rcfg = NULL;
+
+ st = it_config_load(&rcfg);
+ if (st == 0) {
+ (void) it_config_commit(rcfg);
+ it_config_free(rcfg);
+ }
+ }
+ }
+
+ (void) close(iscsit_fd);
+
+ if (packednv) {
+ free(packednv);
+ }
+
+ if (cfgnv) {
+ nvlist_free(cfgnv);
+ }
+
+ return (ret);
+}
+
+/*
+ * Function: it_config_setprop()
+ *
+ * Validate the provided property list and set the global properties
+ * for iSCSI Target. If errlist is not NULL, returns detailed
+ * errors for each property that failed. The format for errorlist
+ * is key = property, value = error string.
+ *
+ * Parameters:
+ *
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * proplist nvlist_t containing properties for this target.
+ * errlist (optional) nvlist_t of errors encountered when
+ * validating the properties.
+ *
+ * Return Values:
+ * 0 Success
+ * EINVAL Invalid property
+ *
+ */
+int
+it_config_setprop(it_config_t *cfg, nvlist_t *proplist, nvlist_t **errlist)
+{
+ int ret;
+ it_portal_t *isns = NULL;
+ it_portal_t *pnext = NULL;
+ it_portal_t *newisnslist = NULL;
+ char **arr;
+ uint32_t count;
+ uint32_t newcount;
+ nvlist_t *cprops = NULL;
+ char *val = NULL;
+
+ if (!cfg || !proplist) {
+ return (EINVAL);
+ }
+
+ if (errlist) {
+ (void) nvlist_alloc(errlist, 0, 0);
+ }
+
+ /*
+ * copy the existing properties, merge, then validate
+ * the merged properties before committing them.
+ */
+ if (cfg->config_global_properties) {
+ ret = nvlist_dup(cfg->config_global_properties, &cprops, 0);
+ } else {
+ ret = nvlist_alloc(&cprops, NV_UNIQUE_NAME, 0);
+ }
+
+ /* base64 encode the radius secret, if it's changed */
+ val = NULL;
+ (void) nvlist_lookup_string(proplist, PROP_RADIUS_SECRET, &val);
+ if (val) {
+ char bsecret[MAX_BASE64_LEN];
+
+ ret = it_val_pass(PROP_RADIUS_SECRET, val, *errlist);
+
+ if (ret == 0) {
+ (void) memset(bsecret, 0, MAX_BASE64_LEN);
+
+ ret = iscsi_binary_to_base64_str((uint8_t *)val,
+ strlen(val), bsecret, MAX_BASE64_LEN);
+
+ if (ret == 0) {
+ /* replace the value in the nvlist */
+ ret = nvlist_add_string(proplist,
+ PROP_RADIUS_SECRET, bsecret);
+ }
+ }
+ }
+
+ if (ret == 0) {
+ ret = nvlist_merge(cprops, proplist, 0);
+ }
+
+ /* see if we need to remove the radius server setting */
+ val = NULL;
+ (void) nvlist_lookup_string(cprops, PROP_RADIUS_SERVER, &val);
+ if (val && (strcasecmp(val, "none") == 0)) {
+ (void) nvlist_remove_all(cprops, PROP_RADIUS_SERVER);
+ }
+
+ /* and/or remove the alias */
+ val = NULL;
+ (void) nvlist_lookup_string(cprops, PROP_ALIAS, &val);
+ if (val && (strcasecmp(val, "none") == 0)) {
+ (void) nvlist_remove_all(cprops, PROP_ALIAS);
+ }
+
+ if (ret == 0) {
+ ret = it_validate_configprops(cprops, *errlist);
+ }
+
+ if (ret != 0) {
+ if (cprops) {
+ nvlist_free(cprops);
+ }
+ return (ret);
+ }
+
+ /*
+ * Update iSNS server list, if exists in provided property list.
+ */
+ ret = nvlist_lookup_string_array(proplist, PROP_ISNS_SERVER,
+ &arr, &count);
+
+ if (ret == 0) {
+ /* special case: if "none", remove all defined */
+ if (strcasecmp(arr[0], "none") != 0) {
+ ret = it_array_to_portallist(arr, count,
+ ISNS_DEFAULT_SERVER_PORT, &newisnslist, &newcount);
+ } else {
+ newisnslist = NULL;
+ newcount = 0;
+ (void) nvlist_remove_all(cprops, PROP_ISNS_SERVER);
+ }
+
+ if (ret == 0) {
+ isns = cfg->config_isns_svr_list;
+ while (isns) {
+ pnext = isns->next;
+ free(isns);
+ isns = pnext;
+ }
+
+ cfg->config_isns_svr_list = newisnslist;
+ cfg->config_isns_svr_count = newcount;
+
+ /*
+ * Replace the array in the nvlist to ensure
+ * duplicates are properly removed & port numbers
+ * are added.
+ */
+ if (newcount > 0) {
+ int i = 0;
+ char **newarray;
+
+ newarray = malloc(sizeof (char *) * newcount);
+ if (newarray == NULL) {
+ ret = ENOMEM;
+ } else {
+ for (isns = newisnslist; isns != NULL;
+ isns = isns->next) {
+ (void) sockaddr_to_str(
+ &(isns->portal_addr),
+ &(newarray[i++]));
+ }
+ (void) nvlist_add_string_array(cprops,
+ PROP_ISNS_SERVER, newarray,
+ newcount);
+
+ for (i = 0; i < newcount; i++) {
+ if (newarray[i]) {
+ free(newarray[i]);
+ }
+ }
+ free(newarray);
+ }
+ }
+ }
+ } else if (ret == ENOENT) {
+ /* not an error */
+ ret = 0;
+ }
+
+ if (ret == 0) {
+ /* replace the global properties list */
+ nvlist_free(cfg->config_global_properties);
+ cfg->config_global_properties = cprops;
+ } else {
+ if (cprops) {
+ nvlist_free(cprops);
+ }
+ }
+
+ return (ret);
+}
+
+/*
+ * Function: it_config_free()
+ *
+ * Free any resources associated with the it_config_t structure.
+ *
+ * Parameters:
+ * cfg A C representation of the current iSCSI configuration
+ */
+void
+it_config_free(it_config_t *cfg)
+{
+ it_config_free_cmn(cfg);
+}
+
+/*
+ * Function: it_tgt_create()
+ *
+ * Allocate and create an it_tgt_t structure representing a new iSCSI
+ * target node. If tgt_name is NULL, then a unique target node name will
+ * be generated automatically. Otherwise, the value of tgt_name will be
+ * used as the target node name. The new it_tgt_t structure is added to
+ * the target list (cfg_tgt_list) in the configuration structure, and the
+ * new target will not be instantiated until the modified configuration
+ * is committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * tgt Pointer to an iSCSI target structure
+ * tgt_name The target node name for the target to be created.
+ * The name must be in either IQN or EUI format. If
+ * this value is NULL, a node name will be generated
+ * automatically in IQN format.
+ *
+ * Return Values:
+ * 0 Success
+ * ENOMEM Could not allocated resources
+ * EINVAL Invalid parameter
+ * EFAULT Invalid iSCSI name specified
+ */
+int
+it_tgt_create(it_config_t *cfg, it_tgt_t **tgt, char *tgt_name)
+{
+ int ret = 0;
+ it_tgt_t *ptr;
+ it_tgt_t *cfgtgt;
+ char *namep = tgt_name;
+ char buf[ISCSI_NAME_LEN_MAX + 1];
+
+ if (!cfg || !tgt) {
+ return (EINVAL);
+ }
+
+ if (!namep) {
+ /* generate a name */
+
+ ret = it_iqn_generate(buf, sizeof (buf), NULL);
+ if (ret != 0) {
+ return (ret);
+ }
+ namep = buf;
+ } else {
+ /* validate the passed-in name */
+ if (!validate_iscsi_name(namep)) {
+ return (EFAULT);
+ }
+ }
+
+ /* make sure this name isn't already on the list */
+ cfgtgt = cfg->config_tgt_list;
+ while (cfgtgt != NULL) {
+ if (strcmp(namep, cfgtgt->tgt_name) == 0) {
+ return (EEXIST);
+ }
+ cfgtgt = cfgtgt->tgt_next;
+ }
+
+ ptr = calloc(1, sizeof (it_tgt_t));
+ if (ptr == NULL) {
+ return (ENOMEM);
+ }
+
+ (void) strlcpy(ptr->tgt_name, namep, sizeof (ptr->tgt_name));
+ ptr->tgt_generation = 1;
+ ptr->tgt_next = cfg->config_tgt_list;
+ cfg->config_tgt_list = ptr;
+ cfg->config_tgt_count++;
+
+ *tgt = ptr;
+
+ return (0);
+}
+
+/*
+ * Function: it_tgt_setprop()
+ *
+ * Validate the provided property list and set the properties for
+ * the specified target. If errlist is not NULL, returns detailed
+ * errors for each property that failed. The format for errorlist
+ * is key = property, value = error string.
+ *
+ * Parameters:
+ *
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * tgt Pointer to an iSCSI target structure
+ * proplist nvlist_t containing properties for this target.
+ * errlist (optional) nvlist_t of errors encountered when
+ * validating the properties.
+ *
+ * Return Values:
+ * 0 Success
+ * EINVAL Invalid property
+ *
+ */
+int
+it_tgt_setprop(it_config_t *cfg, it_tgt_t *tgt, nvlist_t *proplist,
+ nvlist_t **errlist)
+{
+ int ret;
+ nvlist_t *tprops = NULL;
+ char *val = NULL;
+
+ if (!cfg || !tgt || !proplist) {
+ return (EINVAL);
+ }
+
+ if (errlist) {
+ (void) nvlist_alloc(errlist, 0, 0);
+ }
+
+ /*
+ * copy the existing properties, merge, then validate
+ * the merged properties before committing them.
+ */
+ if (tgt->tgt_properties) {
+ ret = nvlist_dup(tgt->tgt_properties, &tprops, 0);
+ } else {
+ ret = nvlist_alloc(&tprops, NV_UNIQUE_NAME, 0);
+ }
+
+ if (ret == 0) {
+ ret = nvlist_merge(tprops, proplist, 0);
+ }
+
+ /* unset chap username or alias if requested */
+ val = NULL;
+ (void) nvlist_lookup_string(proplist, PROP_TARGET_CHAP_USER, &val);
+ if (val && (strcasecmp(val, "none") == 0)) {
+ (void) nvlist_remove_all(tprops, PROP_TARGET_CHAP_USER);
+ }
+
+ val = NULL;
+ (void) nvlist_lookup_string(proplist, PROP_ALIAS, &val);
+ if (val && (strcasecmp(val, "none") == 0)) {
+ (void) nvlist_remove_all(tprops, PROP_ALIAS);
+ }
+
+ /* base64 encode the CHAP secret, if it's changed */
+ val = NULL;
+ (void) nvlist_lookup_string(proplist, PROP_TARGET_CHAP_SECRET, &val);
+ if (val) {
+ char bsecret[MAX_BASE64_LEN];
+
+ ret = it_val_pass(PROP_TARGET_CHAP_SECRET, val, *errlist);
+
+ if (ret == 0) {
+ (void) memset(bsecret, 0, MAX_BASE64_LEN);
+
+ ret = iscsi_binary_to_base64_str((uint8_t *)val,
+ strlen(val), bsecret, MAX_BASE64_LEN);
+
+ if (ret == 0) {
+ /* replace the value in the nvlist */
+ ret = nvlist_add_string(tprops,
+ PROP_TARGET_CHAP_SECRET, bsecret);
+ }
+ }
+ }
+
+ if (ret == 0) {
+ ret = it_validate_tgtprops(tprops, *errlist);
+ }
+
+ if (ret != 0) {
+ if (tprops) {
+ nvlist_free(tprops);
+ }
+ return (ret);
+ }
+
+ if (tgt->tgt_properties) {
+ nvlist_free(tgt->tgt_properties);
+ }
+ tgt->tgt_properties = tprops;
+
+ return (0);
+}
+
+
+/*
+ * Function: it_tgt_delete()
+ *
+ * Delete target represented by 'tgt', where 'tgt' is an existing
+ * it_tgt_structure within the configuration 'cfg'. The target removal
+ * will not take effect until the modified configuration is committed
+ * by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * tgt Pointer to an iSCSI target structure
+ *
+ * force Set the target to offline before removing it from
+ * the config. If not specified, the operation will
+ * fail if the target is determined to be online.
+ * Return Values:
+ * 0 Success
+ * EBUSY Target is online
+ */
+int
+it_tgt_delete(it_config_t *cfg, it_tgt_t *tgt, boolean_t force)
+{
+ int ret;
+ it_tgt_t *ptgt;
+ it_tgt_t *prev = NULL;
+ stmfDevid devid;
+ stmfTargetProperties props;
+
+ if (!cfg || !tgt) {
+ return (0);
+ }
+
+ ptgt = cfg->config_tgt_list;
+ while (ptgt != NULL) {
+ if (strcmp(tgt->tgt_name, ptgt->tgt_name) == 0) {
+ break;
+ }
+ prev = ptgt;
+ ptgt = ptgt->tgt_next;
+ }
+
+ if (!ptgt) {
+ return (0);
+ }
+
+ /*
+ * check to see if this target is offline. If it is not,
+ * and the 'force' flag is TRUE, tell STMF to offline it
+ * before removing from the configuration.
+ */
+ ret = stmfDevidFromIscsiName(ptgt->tgt_name, &devid);
+ if (ret != STMF_STATUS_SUCCESS) {
+ /* can't happen? */
+ return (EINVAL);
+ }
+
+ ret = stmfGetTargetProperties(&devid, &props);
+ if (ret == STMF_STATUS_SUCCESS) {
+ /*
+ * only other return is STMF_ERROR_NOT_FOUND, which
+ * means we don't have to offline it.
+ */
+ if (props.status == STMF_TARGET_PORT_ONLINE) {
+ if (!force) {
+ return (EBUSY);
+ }
+ ret = stmfOfflineTarget(&devid);
+ if (ret != 0) {
+ return (EBUSY);
+ }
+ }
+ }
+
+ if (prev) {
+ prev->tgt_next = ptgt->tgt_next;
+ } else {
+ /* first one on the list */
+ cfg->config_tgt_list = ptgt->tgt_next;
+ }
+
+ ptgt->tgt_next = NULL; /* Only free this target */
+
+ cfg->config_tgt_count--;
+ it_tgt_free(ptgt);
+
+ return (0);
+}
+
+/*
+ * Function: it_tgt_free()
+ *
+ * Frees an it_tgt_t structure. If tgt_next is not NULL, frees
+ * all structures in the list.
+ */
+void
+it_tgt_free(it_tgt_t *tgt)
+{
+ it_tgt_free_cmn(tgt);
+}
+
+/*
+ * Function: it_tpgt_create()
+ *
+ * Allocate and create an it_tpgt_t structure representing a new iSCSI
+ * target portal group tag. The new it_tpgt_t structure is added to the
+ * target tpgt list (tgt_tpgt_list) in the it_tgt_t structure. The new
+ * target portal group tag will not be instantiated until the modified
+ * configuration is committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * tgt Pointer to the iSCSI target structure associated
+ * with the target portal group tag
+ * tpgt Pointer to a target portal group tag structure
+ * tpg_name The name of the TPG to be associated with this TPGT
+ * tpgt_tag 16-bit numerical identifier for this TPGT. If
+ * tpgt_tag is '0', this function will choose the
+ * tag number. If tpgt_tag is >0, and the requested
+ * tag is determined to be in use, another value
+ * will be chosen.
+ *
+ * Return Values:
+ * 0 Success
+ * ENOMEM Could not allocate resources
+ * EINVAL Invalid parameter
+ * EEXIST Specified tag name is already used.
+ * E2BIG No available tag numbers
+ */
+int
+it_tpgt_create(it_config_t *cfg, it_tgt_t *tgt, it_tpgt_t **tpgt,
+ char *tpg_name, uint16_t tpgt_tag)
+{
+ it_tpgt_t *ptr = NULL;
+ it_tpgt_t *cfgt;
+ char tagid_used[MAXTAG + 1];
+ uint16_t tagid = ISCSIT_DEFAULT_TPGT;
+
+ if (!cfg || !tgt || !tpgt || !tpg_name) {
+ return (EINVAL);
+ }
+
+ (void) memset(&(tagid_used[0]), 0, sizeof (tagid_used));
+
+ /*
+ * Make sure this name and/or tag isn't already on the list
+ * At the same time, capture all tag ids in use for this target
+ *
+ * About tag numbering -- since tag numbers are used by
+ * the iSCSI protocol, we should be careful about reusing
+ * them too quickly. Start with a value greater than the
+ * highest one currently defined. If current == MAXTAG,
+ * just find an unused tag.
+ */
+ cfgt = tgt->tgt_tpgt_list;
+ while (cfgt != NULL) {
+ tagid_used[cfgt->tpgt_tag] = 1;
+
+ if (strcmp(tpg_name, cfgt->tpgt_tpg_name) == 0) {
+ return (EEXIST);
+ }
+
+ if (cfgt->tpgt_tag > tagid) {
+ tagid = cfgt->tpgt_tag;
+ }
+
+ cfgt = cfgt->tpgt_next;
+ }
+
+ if ((tpgt_tag > ISCSIT_DEFAULT_TPGT) && (tpgt_tag < MAXTAG) &&
+ (tagid_used[tpgt_tag] == 0)) {
+ /* ok to use requested */
+ tagid = tpgt_tag;
+ } else if (tagid == MAXTAG) {
+ /*
+ * The highest value is used, find an available id.
+ */
+ tagid = ISCSIT_DEFAULT_TPGT + 1;
+ for (; tagid < MAXTAG; tagid++) {
+ if (tagid_used[tagid] == 0) {
+ break;
+ }
+ }
+ if (tagid >= MAXTAG) {
+ return (E2BIG);
+ }
+ } else {
+ /* next available ID */
+ tagid++;
+ }
+
+ ptr = calloc(1, sizeof (it_tpgt_t));
+ if (!ptr) {
+ return (ENOMEM);
+ }
+
+ (void) strlcpy(ptr->tpgt_tpg_name, tpg_name,
+ sizeof (ptr->tpgt_tpg_name));
+ ptr->tpgt_generation = 1;
+ ptr->tpgt_tag = tagid;
+
+ ptr->tpgt_next = tgt->tgt_tpgt_list;
+ tgt->tgt_tpgt_list = ptr;
+ tgt->tgt_tpgt_count++;
+ tgt->tgt_generation++;
+
+ *tpgt = ptr;
+
+ return (0);
+}
+
+/*
+ * Function: it_tpgt_delete()
+ *
+ * Delete the target portal group tag represented by 'tpgt', where
+ * 'tpgt' is an existing is_tpgt_t structure within the target 'tgt'.
+ * The target portal group tag removal will not take effect until the
+ * modified configuration is committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * tgt Pointer to the iSCSI target structure associated
+ * with the target portal group tag
+ * tpgt Pointer to a target portal group tag structure
+ */
+void
+it_tpgt_delete(it_config_t *cfg, it_tgt_t *tgt, it_tpgt_t *tpgt)
+{
+ it_tpgt_t *ptr;
+ it_tpgt_t *prev = NULL;
+
+ if (!cfg || !tgt || !tpgt) {
+ return;
+ }
+
+ ptr = tgt->tgt_tpgt_list;
+ while (ptr) {
+ if (ptr->tpgt_tag == tpgt->tpgt_tag) {
+ break;
+ }
+ prev = ptr;
+ ptr = ptr->tpgt_next;
+ }
+
+ if (!ptr) {
+ return;
+ }
+
+ if (prev) {
+ prev->tpgt_next = ptr->tpgt_next;
+ } else {
+ tgt->tgt_tpgt_list = ptr->tpgt_next;
+ }
+ ptr->tpgt_next = NULL;
+
+ tgt->tgt_tpgt_count--;
+ tgt->tgt_generation++;
+
+ it_tpgt_free(ptr);
+}
+
+/*
+ * Function: it_tpgt_free()
+ *
+ * Deallocates resources of an it_tpgt_t structure. If tpgt->next
+ * is not NULL, frees all members of the list.
+ */
+void
+it_tpgt_free(it_tpgt_t *tpgt)
+{
+ it_tpgt_free_cmn(tpgt);
+}
+
+/*
+ * Function: it_tpg_create()
+ *
+ * Allocate and create an it_tpg_t structure representing a new iSCSI
+ * target portal group. The new it_tpg_t structure is added to the global
+ * tpg list (cfg_tgt_list) in the it_config_t structure. The new target
+ * portal group will not be instantiated until the modified configuration
+ * is committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * tpg Pointer to the it_tpg_t structure representing
+ * the target portal group
+ * tpg_name Identifier for the target portal group
+ * portal_ip_port A string containing an appropriatedly formatted
+ * IP address:port. Both IPv4 and IPv6 addresses are
+ * permitted. This value becomes the first portal in
+ * the TPG -- applications can add additional values
+ * using it_portal_create() before committing the TPG.
+ * Return Values:
+ * 0 Success
+ * ENOMEM Cannot allocate resources
+ * EINVAL Invalid parameter
+ * EEXIST Requested portal in use by another target portal
+ * group
+ */
+int
+it_tpg_create(it_config_t *cfg, it_tpg_t **tpg, char *tpg_name,
+ char *portal_ip_port)
+{
+ int ret;
+ it_tpg_t *ptr;
+ it_portal_t *portal = NULL;
+
+ if (!cfg || !tpg || !tpg_name || !portal_ip_port) {
+ return (EINVAL);
+ }
+
+ *tpg = NULL;
+
+ ptr = cfg->config_tpg_list;
+ while (ptr) {
+ if (strcmp(tpg_name, ptr->tpg_name) == 0) {
+ break;
+ }
+ ptr = ptr->tpg_next;
+ }
+
+ if (ptr) {
+ return (EEXIST);
+ }
+
+ ptr = calloc(1, sizeof (it_tpg_t));
+ if (!ptr) {
+ return (ENOMEM);
+ }
+
+ ptr->tpg_generation = 1;
+ (void) strlcpy(ptr->tpg_name, tpg_name, sizeof (ptr->tpg_name));
+
+ /* create the portal */
+ ret = it_portal_create(cfg, ptr, &portal, portal_ip_port);
+ if (ret != 0) {
+ free(ptr);
+ return (ret);
+ }
+
+ ptr->tpg_next = cfg->config_tpg_list;
+ cfg->config_tpg_list = ptr;
+ cfg->config_tpg_count++;
+
+ *tpg = ptr;
+
+ return (0);
+}
+
+/*
+ * Function: it_tpg_delete()
+ *
+ * Delete target portal group represented by 'tpg', where 'tpg' is an
+ * existing it_tpg_t structure within the global configuration 'cfg'.
+ * The target portal group removal will not take effect until the
+ * modified configuration is committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * tpg Pointer to the it_tpg_t structure representing
+ * the target portal group
+ * force Remove this target portal group even if it's
+ * associated with one or more targets.
+ *
+ * Return Values:
+ * 0 Success
+ * EINVAL Invalid parameter
+ * EBUSY Portal group associated with one or more targets.
+ */
+int
+it_tpg_delete(it_config_t *cfg, it_tpg_t *tpg, boolean_t force)
+{
+ it_tpg_t *ptr;
+ it_tpg_t *prev = NULL;
+ it_tgt_t *tgt;
+ it_tpgt_t *tpgt;
+ it_tpgt_t *ntpgt;
+
+ if (!cfg || !tpg) {
+ return (EINVAL);
+ }
+
+ ptr = cfg->config_tpg_list;
+ while (ptr) {
+ if (strcmp(ptr->tpg_name, tpg->tpg_name) == 0) {
+ break;
+ }
+ prev = ptr;
+ ptr = ptr->tpg_next;
+ }
+
+ if (!ptr) {
+ return (0);
+ }
+
+ /*
+ * See if any targets are using this portal group.
+ * If there are, and the force flag is not set, fail.
+ */
+ tgt = cfg->config_tgt_list;
+ while (tgt) {
+ tpgt = tgt->tgt_tpgt_list;
+ while (tpgt) {
+ ntpgt = tpgt->tpgt_next;
+
+ if (strcmp(tpgt->tpgt_tpg_name, tpg->tpg_name)
+ == 0) {
+ if (!force) {
+ return (EBUSY);
+ }
+ it_tpgt_delete(cfg, tgt, tpgt);
+ }
+
+ tpgt = ntpgt;
+ }
+ tgt = tgt->tgt_next;
+ }
+
+ /* Now that it's not in use anywhere, remove the TPG */
+ if (prev) {
+ prev->tpg_next = ptr->tpg_next;
+ } else {
+ cfg->config_tpg_list = ptr->tpg_next;
+ }
+ ptr->tpg_next = NULL;
+
+ cfg->config_tpg_count--;
+
+ it_tpg_free(ptr);
+
+ return (0);
+}
+
+/*
+ * Function: it_tpg_free()
+ *
+ * Deallocates resources associated with an it_tpg_t structure.
+ * If tpg->next is not NULL, frees all members of the list.
+ */
+void
+it_tpg_free(it_tpg_t *tpg)
+{
+ it_tpg_free_cmn(tpg);
+}
+
+/*
+ * Function: it_portal_create()
+ *
+ * Add an it_portal_t structure presenting a new portal to the specified
+ * target portal group. The change to the target portal group will not take
+ * effect until the modified configuration is committed by calling
+ * it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configration obtained from
+ * it_config_load()
+ * tpg Pointer to the it_tpg_t structure representing the
+ * target portal group
+ * portal Pointer to the it_portal_t structure representing
+ * the portal
+ * portal_ip_port A string containing an appropriately formatted
+ * IP address or IP address:port in either IPv4 or
+ * IPv6 format.
+ * Return Values:
+ * 0 Success
+ * ENOMEM Could not allocate resources
+ * EINVAL Invalid parameter
+ * EEXIST Portal already configured for another portal group
+ */
+int
+it_portal_create(it_config_t *cfg, it_tpg_t *tpg, it_portal_t **portal,
+ char *portal_ip_port)
+{
+ struct sockaddr_storage sa;
+ it_portal_t *ptr;
+ it_tpg_t *ctpg = NULL;
+
+ if (!cfg || !tpg || !portal || !portal_ip_port) {
+ return (EINVAL);
+ }
+
+ if ((it_common_convert_sa(portal_ip_port, &sa, ISCSI_LISTEN_PORT))
+ == NULL) {
+ return (EINVAL);
+ }
+
+ /* Check that this portal doesn't appear in any other tag */
+ ctpg = cfg->config_tpg_list;
+ while (ctpg) {
+ ptr = ctpg->tpg_portal_list;
+ for (; ptr != NULL; ptr = ptr->next) {
+ if (it_sa_compare(&(ptr->portal_addr), &sa) != 0) {
+ continue;
+ }
+
+ /*
+ * Existing in the same group is not an error,
+ * but don't add it again.
+ */
+ if (strcmp(ctpg->tpg_name, tpg->tpg_name) == 0) {
+ return (0);
+ } else {
+ /* Not allowed */
+ return (EEXIST);
+ }
+ }
+ ctpg = ctpg->tpg_next;
+ }
+
+ ptr = calloc(1, sizeof (it_portal_t));
+ if (!ptr) {
+ return (ENOMEM);
+ }
+
+ (void) memcpy(&(ptr->portal_addr), &sa,
+ sizeof (struct sockaddr_storage));
+ ptr->next = tpg->tpg_portal_list;
+ tpg->tpg_portal_list = ptr;
+ tpg->tpg_portal_count++;
+ tpg->tpg_generation++;
+
+ return (0);
+}
+
+/*
+ * Function: it_portal_delete()
+ *
+ * Remove the specified portal from the specified target portal group.
+ * The portal removal will not take effect until the modified configuration
+ * is committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configration obtained from
+ * it_config_load()
+ * tpg Pointer to the it_tpg_t structure representing the
+ * target portal group
+ * portal Pointer to the it_portal_t structure representing
+ * the portal
+ */
+void
+it_portal_delete(it_config_t *cfg, it_tpg_t *tpg, it_portal_t *portal)
+{
+ it_portal_t *ptr;
+ it_portal_t *prev;
+
+ if (!cfg || !tpg || !portal) {
+ return;
+ }
+
+ ptr = tpg->tpg_portal_list;
+ while (ptr) {
+ if (memcmp(&(ptr->portal_addr), &(portal->portal_addr),
+ sizeof (ptr->portal_addr)) == 0) {
+ break;
+ }
+ prev = ptr;
+ ptr = ptr->next;
+ }
+
+ if (!ptr) {
+ return;
+ }
+
+ if (prev) {
+ prev->next = ptr->next;
+ } else {
+ tpg->tpg_portal_list = ptr->next;
+ }
+ tpg->tpg_portal_count--;
+ tpg->tpg_generation++;
+
+ free(ptr);
+}
+
+/*
+ * Function: it_ini_create()
+ *
+ * Add an initiator context to the global configuration. The new
+ * initiator context will not be instantiated until the modified
+ * configuration is committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configration obtained from
+ * it_config_load()
+ * ini Pointer to the it_ini_t structure representing
+ * the initiator context.
+ * ini_node_name The iSCSI node name of the remote initiator.
+ *
+ * Return Values:
+ * 0 Success
+ * ENOMEM Could not allocate resources
+ * EINVAL Invalid parameter.
+ * EFAULT Invalid initiator name
+ */
+int
+it_ini_create(it_config_t *cfg, it_ini_t **ini, char *ini_node_name)
+{
+ it_ini_t *ptr;
+
+ if (!cfg || !ini || !ini_node_name) {
+ return (EINVAL);
+ }
+
+ /*
+ * Ensure this is a valid ini name
+ */
+ if (!validate_iscsi_name(ini_node_name)) {
+ return (EFAULT);
+ }
+
+ ptr = cfg->config_ini_list;
+ while (ptr) {
+ if (strcmp(ptr->ini_name, ini_node_name) == 0) {
+ break;
+ }
+ ptr = ptr->ini_next;
+ }
+
+ if (ptr) {
+ return (EEXIST);
+ }
+
+ ptr = calloc(1, sizeof (it_ini_t));
+ if (!ptr) {
+ return (ENOMEM);
+ }
+
+ (void) strlcpy(ptr->ini_name, ini_node_name, sizeof (ptr->ini_name));
+ ptr->ini_generation = 1;
+ /* nvlist for props? */
+
+ ptr->ini_next = cfg->config_ini_list;
+ cfg->config_ini_list = ptr;
+ cfg->config_ini_count++;
+
+ *ini = ptr;
+
+ return (0);
+}
+
+/*
+ * Function: it_ini_setprop()
+ *
+ * Validate the provided property list and set the initiator properties.
+ * If errlist is not NULL, returns detailed errors for each property
+ * that failed. The format for errorlist is key = property,
+ * value = error string.
+ *
+ * Parameters:
+ *
+ * ini The initiator being updated.
+ * proplist nvlist_t containing properties for this target.
+ * errlist (optional) nvlist_t of errors encountered when
+ * validating the properties.
+ *
+ * Return Values:
+ * 0 Success
+ * EINVAL Invalid property
+ *
+ */
+int
+it_ini_setprop(it_ini_t *ini, nvlist_t *proplist, nvlist_t **errlist)
+{
+ int ret;
+ nvlist_t *iprops = NULL;
+ char *val = NULL;
+
+ if (!ini || !proplist) {
+ return (EINVAL);
+ }
+
+ if (errlist) {
+ (void) nvlist_alloc(errlist, 0, 0);
+ }
+
+ /*
+ * copy the existing properties, merge, then validate
+ * the merged properties before committing them.
+ */
+ if (ini->ini_properties) {
+ ret = nvlist_dup(ini->ini_properties, &iprops, 0);
+ } else {
+ ret = nvlist_alloc(&iprops, NV_UNIQUE_NAME, 0);
+ }
+
+ if (ret == 0) {
+ ret = nvlist_merge(iprops, proplist, 0);
+ }
+
+ /* unset chap username if requested */
+ if ((nvlist_lookup_string(proplist, PROP_CHAP_USER, &val)) == 0) {
+ if (strcasecmp(val, "none") == 0) {
+ (void) nvlist_remove_all(iprops, PROP_CHAP_USER);
+ }
+ }
+
+ /* base64 encode the CHAP secret, if it's changed */
+ if ((nvlist_lookup_string(proplist, PROP_CHAP_SECRET, &val)) == 0) {
+ char bsecret[MAX_BASE64_LEN];
+
+ ret = it_val_pass(PROP_CHAP_SECRET, val, *errlist);
+ if (ret == 0) {
+ (void) memset(bsecret, 0, MAX_BASE64_LEN);
+
+ ret = iscsi_binary_to_base64_str((uint8_t *)val,
+ strlen(val), bsecret, MAX_BASE64_LEN);
+
+ if (ret == 0) {
+ /* replace the value in the nvlist */
+ ret = nvlist_add_string(iprops,
+ PROP_CHAP_SECRET, bsecret);
+ }
+ }
+ }
+
+ if (ret == 0) {
+ ret = it_validate_iniprops(iprops, *errlist);
+ }
+
+ if (ret != 0) {
+ if (iprops) {
+ nvlist_free(iprops);
+ }
+ return (ret);
+ }
+
+ if (ini->ini_properties) {
+ nvlist_free(ini->ini_properties);
+ }
+ ini->ini_properties = iprops;
+
+ return (0);
+}
+
+/*
+ * Function: it_ini_delete()
+ *
+ * Remove the specified initiator context from the global configuration.
+ * The removal will not take effect until the modified configuration is
+ * committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configration obtained from
+ * it_config_load()
+ * ini Pointer to the it_ini_t structure representing
+ * the initiator context.
+ */
+void
+it_ini_delete(it_config_t *cfg, it_ini_t *ini)
+{
+ it_ini_t *ptr;
+ it_ini_t *prev = NULL;
+
+ if (!cfg || !ini) {
+ return;
+ }
+
+ ptr = cfg->config_ini_list;
+ while (ptr) {
+ if (strcmp(ptr->ini_name, ini->ini_name) == 0) {
+ break;
+ }
+ prev = ptr;
+ ptr = ptr->ini_next;
+ }
+
+ if (!ptr) {
+ return;
+ }
+
+ if (prev) {
+ prev->ini_next = ptr->ini_next;
+ } else {
+ cfg->config_ini_list = ptr->ini_next;
+ }
+
+ ptr->ini_next = NULL; /* Only free this initiator */
+
+ cfg->config_ini_count--;
+
+ it_ini_free(ptr);
+}
+
+/*
+ * Function: it_ini_free()
+ *
+ * Deallocates resources of an it_ini_t structure. If ini->next is
+ * not NULL, frees all members of the list.
+ */
+void
+it_ini_free(it_ini_t *ini)
+{
+ it_ini_free_cmn(ini);
+}
+
+/*
+ * Goes through the target property list and validates
+ * each entry. If errs is non-NULL, will return explicit errors
+ * for each property that fails validation.
+ */
+static int
+it_validate_tgtprops(nvlist_t *nvl, nvlist_t *errs)
+{
+ int errcnt = 0;
+ nvpair_t *nvp = NULL;
+ data_type_t nvtype;
+ char *name;
+ char *val;
+ char *auth = NULL;
+
+ if (!nvl) {
+ return (0);
+ }
+
+ while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
+ name = nvpair_name(nvp);
+ nvtype = nvpair_type(nvp);
+
+ if (!name) {
+ continue;
+ }
+
+ val = NULL;
+ if (strcmp(name, PROP_TARGET_CHAP_USER) == 0) {
+ if (nvtype != DATA_TYPE_STRING) {
+ PROPERR(errs, name,
+ gettext("must be a string value"));
+ errcnt++;
+ continue;
+ }
+ } else if (strcmp(name, PROP_TARGET_CHAP_SECRET) == 0) {
+ /*
+ * must be between 12 and 255 chars in cleartext.
+ * will be base64 encoded when it's set.
+ */
+ if (nvtype == DATA_TYPE_STRING) {
+ (void) nvpair_value_string(nvp, &val);
+ }
+
+ if (!val) {
+ PROPERR(errs, name,
+ gettext("must be a string value"));
+ errcnt++;
+ continue;
+ }
+ } else if (strcmp(name, PROP_ALIAS) == 0) {
+ if (nvtype != DATA_TYPE_STRING) {
+ PROPERR(errs, name,
+ gettext("must be a string value"));
+ errcnt++;
+ continue;
+ }
+ } else if (strcmp(name, PROP_AUTH) == 0) {
+ if (nvtype == DATA_TYPE_STRING) {
+ val = NULL;
+ (void) nvpair_value_string(nvp, &val);
+ }
+
+ if (!val) {
+ PROPERR(errs, name,
+ gettext("must be a string value"));
+ errcnt++;
+ continue;
+ }
+ if ((strcmp(val, PA_AUTH_NONE) != 0) &&
+ (strcmp(val, PA_AUTH_CHAP) != 0) &&
+ (strcmp(val, PA_AUTH_RADIUS) != 0) &&
+ (strcmp(val, "default") != 0)) {
+ PROPERR(errs, val, gettext(
+ "must be none, chap, radius or default"));
+ errcnt++;
+ }
+ auth = val;
+ continue;
+ } else if (strcmp(name, PROP_OLD_TARGET_NAME) == 0) {
+ continue;
+ } else {
+ /* unrecognized property */
+ PROPERR(errs, name, gettext("unrecognized property"));
+ errcnt++;
+ }
+ }
+
+ if (errcnt) {
+ return (EINVAL);
+ }
+
+ /* if auth is being set to default, remove from this nvlist */
+ if (auth && (strcmp(auth, "default") == 0)) {
+ (void) nvlist_remove_all(nvl, PROP_AUTH);
+ }
+
+ return (0);
+}
+
+/*
+ * Goes through the config property list and validates
+ * each entry. If errs is non-NULL, will return explicit errors
+ * for each property that fails validation.
+ */
+static int
+it_validate_configprops(nvlist_t *nvl, nvlist_t *errs)
+{
+ int errcnt = 0;
+ nvpair_t *nvp = NULL;
+ data_type_t nvtype;
+ char *name;
+ char *val;
+ struct sockaddr_storage sa;
+ char *auth = NULL;
+
+ if (!nvl) {
+ return (0);
+ }
+
+ while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
+ name = nvpair_name(nvp);
+ nvtype = nvpair_type(nvp);
+
+ if (!name) {
+ continue;
+ }
+
+ val = NULL;
+
+ /* prefetch string value as we mostly need it */
+ if (nvtype == DATA_TYPE_STRING) {
+ (void) nvpair_value_string(nvp, &val);
+ }
+
+ if (strcmp(name, PROP_ALIAS) == 0) {
+ if (!val) {
+ PROPERR(errs, name,
+ gettext("must be a string value"));
+ errcnt++;
+ }
+ } else if (strcmp(name, PROP_AUTH) == 0) {
+ if (!val) {
+ PROPERR(errs, name,
+ gettext("must be a string value"));
+ errcnt++;
+ continue;
+ }
+
+ if ((strcmp(val, PA_AUTH_NONE) != 0) &&
+ (strcmp(val, PA_AUTH_CHAP) != 0) &&
+ (strcmp(val, PA_AUTH_RADIUS) != 0)) {
+ PROPERR(errs, PROP_AUTH,
+ gettext("must be none, chap or radius"));
+ errcnt++;
+ }
+
+ auth = val;
+
+ } else if (strcmp(name, PROP_ISNS_ENABLED) == 0) {
+ if (nvtype != DATA_TYPE_BOOLEAN_VALUE) {
+ PROPERR(errs, name,
+ gettext("must be a boolean value"));
+ errcnt++;
+ }
+ } else if (strcmp(name, PROP_ISNS_SERVER) == 0) {
+ char **arr = NULL;
+ uint32_t acount = 0;
+
+ (void) nvlist_lookup_string_array(nvl, name,
+ &arr, &acount);
+
+ while (acount > 0) {
+ if (strcasecmp(arr[acount - 1], "none") == 0) {
+ break;
+ }
+ if ((it_common_convert_sa(arr[acount - 1],
+ &sa, 0)) == NULL) {
+ PROPERR(errs, arr[acount - 1],
+ gettext("invalid address"));
+ errcnt++;
+ }
+ acount--;
+ }
+
+ } else if (strcmp(name, PROP_RADIUS_SECRET) == 0) {
+ if (!val) {
+ PROPERR(errs, name,
+ gettext("must be a string value"));
+ errcnt++;
+ continue;
+ }
+ } else if (strcmp(name, PROP_RADIUS_SERVER) == 0) {
+ struct sockaddr_storage sa;
+
+ if (!val) {
+ PROPERR(errs, name,
+ gettext("must be a string value"));
+ errcnt++;
+ continue;
+ }
+
+ if ((it_common_convert_sa(val, &sa,
+ DEFAULT_RADIUS_PORT)) == NULL) {
+ PROPERR(errs, name,
+ gettext("invalid address"));
+ errcnt++;
+ } else {
+ /*
+ * rewrite this property to ensure port
+ * number is added.
+ */
+ char *rad = NULL;
+
+ if (sockaddr_to_str(&sa, &rad) == 0) {
+ (void) nvlist_add_string(nvl,
+ name, rad);
+ }
+ }
+ } else {
+ /* unrecognized property */
+ PROPERR(errs, name, gettext("unrecognized property"));
+ errcnt++;
+ }
+ }
+
+ /*
+ * if auth = radius, ensure radius server & secret are set.
+ */
+ if (auth) {
+ if (strcmp(auth, PA_AUTH_RADIUS) == 0) {
+ /* need server & secret for radius */
+ if (!nvlist_exists(nvl, PROP_RADIUS_SERVER)) {
+ PROPERR(errs, PROP_RADIUS_SERVER,
+ gettext("missing required property"));
+ errcnt++;
+ }
+ if (!nvlist_exists(nvl, PROP_RADIUS_SECRET)) {
+ PROPERR(errs, PROP_RADIUS_SECRET,
+ gettext("missing required property"));
+ errcnt++;
+ }
+ }
+ }
+
+ if (errcnt) {
+ return (EINVAL);
+ }
+
+ return (0);
+}
+
+/*
+ * Goes through the ini property list and validates
+ * each entry. If errs is non-NULL, will return explicit errors
+ * for each property that fails validation.
+ */
+static int
+it_validate_iniprops(nvlist_t *nvl, nvlist_t *errs)
+{
+ int errcnt = 0;
+ nvpair_t *nvp = NULL;
+ data_type_t nvtype;
+ char *name;
+ char *val;
+
+ if (!nvl) {
+ return (0);
+ }
+
+ while ((nvp = nvlist_next_nvpair(nvl, nvp)) != NULL) {
+ name = nvpair_name(nvp);
+ nvtype = nvpair_type(nvp);
+
+ if (!name) {
+ continue;
+ }
+
+ if (strcmp(name, PROP_CHAP_USER) == 0) {
+ if (nvtype != DATA_TYPE_STRING) {
+ PROPERR(errs, name,
+ gettext("must be a string value"));
+ errcnt++;
+ continue;
+ }
+ } else if (strcmp(name, PROP_CHAP_SECRET) == 0) {
+ /*
+ * must be between 12 and 255 chars in cleartext.
+ * will be base64 encoded when it's set.
+ */
+ if (nvtype == DATA_TYPE_STRING) {
+ val = NULL;
+ (void) nvpair_value_string(nvp, &val);
+ }
+
+ if (!val) {
+ PROPERR(errs, name,
+ gettext("must be a string value"));
+ errcnt++;
+ continue;
+ }
+ } else {
+ /* unrecognized property */
+ PROPERR(errs, name, gettext("unrecognized property"));
+ errcnt++;
+ }
+ }
+
+ if (errcnt) {
+ return (EINVAL);
+ }
+
+ return (0);
+}
+
+static int
+it_iqn_generate(char *iqn_buf, int iqn_buf_len, char *opt_iqn_suffix)
+{
+ int ret;
+ uuid_t id;
+ char id_str[UUID_PRINTABLE_STRING_LENGTH];
+
+ uuid_generate_random(id);
+ uuid_unparse(id, id_str);
+
+ if (opt_iqn_suffix) {
+ ret = snprintf(iqn_buf, iqn_buf_len, "iqn.1986-03.com.sun:"
+ "%02d:%s.%s", TARGET_NAME_VERS, id_str, opt_iqn_suffix);
+ } else {
+ ret = snprintf(iqn_buf, iqn_buf_len, "iqn.1986-03.com.sun:"
+ "%02d:%s", TARGET_NAME_VERS, id_str);
+ }
+
+ if (ret > iqn_buf_len) {
+ return (1);
+ }
+
+ return (0);
+}
+
+static int
+it_val_pass(char *name, char *val, nvlist_t *e)
+{
+ size_t sz;
+
+ if (!name || !val) {
+ return (EINVAL);
+ }
+
+ /*
+ * must be at least 12 chars and less than 256 chars cleartext.
+ */
+ sz = strlen(val);
+
+ /*
+ * Since we will be automatically encoding secrets we don't really
+ * need the prefix anymore.
+ */
+ if (sz < 12) {
+ PROPERR(e, name, gettext("secret too short"));
+ } else if (sz > 255) {
+ PROPERR(e, name, gettext("secret too long"));
+ } else {
+ /* all is well */
+ return (0);
+ }
+
+ return (1);
+}
+
+/*
+ * Function: validate_iscsi_name()
+ *
+ * Ensures the passed-in string is a valid IQN or EUI iSCSI name
+ *
+ */
+boolean_t
+validate_iscsi_name(char *in_name)
+{
+ size_t in_len;
+ int i;
+ char month[3];
+
+ if (in_name == NULL) {
+ return (B_FALSE);
+ }
+
+ in_len = strlen(in_name);
+ if (in_len < 12) {
+ return (B_FALSE);
+ }
+
+ if (strncasecmp(in_name, "iqn.", 4) == 0) {
+ /*
+ * IQN names are iqn.yyyy-mm.<xxx>
+ */
+ if ((!isdigit(in_name[4])) ||
+ (!isdigit(in_name[5])) ||
+ (!isdigit(in_name[6])) ||
+ (!isdigit(in_name[7])) ||
+ (in_name[8] != '-') ||
+ (!isdigit(in_name[9])) ||
+ (!isdigit(in_name[10])) ||
+ (in_name[11] != '.')) {
+ return (B_FALSE);
+ }
+
+ (void) strncpy(month, &(in_name[9]), 2);
+ month[2] = '\0';
+
+ i = atoi(month);
+ if ((i < 0) || (i > 12)) {
+ return (B_FALSE);
+ }
+
+ /* Finally, validate the overall length, in wide chars */
+ in_len = mbstowcs(NULL, in_name, 0);
+ if (in_len > ISCSI_NAME_LEN_MAX) {
+ return (B_FALSE);
+ }
+ } else if (strncasecmp(in_name, "eui.", 4) == 0) {
+ /*
+ * EUI names are "eui." + 16 hex chars
+ */
+ if (in_len != 20) {
+ return (B_FALSE);
+ }
+
+ for (i = 4; i < in_len; i++) {
+ if (!isxdigit(in_name[i])) {
+ return (B_FALSE);
+ }
+ }
+ } else {
+ return (B_FALSE);
+ }
+
+ return (B_TRUE);
+}
diff --git a/usr/src/lib/libiscsit/common/libiscsit.h b/usr/src/lib/libiscsit/common/libiscsit.h
new file mode 100644
index 0000000000..598a46a09c
--- /dev/null
+++ b/usr/src/lib/libiscsit/common/libiscsit.h
@@ -0,0 +1,747 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _LIBISCSIT_H
+#define _LIBISCSIT_H
+
+#ifndef _KERNEL
+#include <libnvpair.h>
+#include <sys/socket.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ISCSIT_MODNAME "iscsit"
+#define ISCSIT_NODE "/devices/pseudo/iscsit@0:iscsit"
+
+#define MAX_TPGT 256
+#define CFG_TPGTLIST "tpgt-list"
+
+/*
+ * Object Hierarchy
+ *
+ * _______________________
+ * | |
+ * | iSCSI Target Config |
+ * | it_config_t |
+ * |_______________________|
+ * | |
+ * | |
+ * | | ________ ________ ________
+ * | | | | | | | |
+ * | | | Target |-->| Target |-- - - -->| Target |
+ * | | |________| |________| |________|
+ * | | |
+ * | | |
+ * | | |
+ * | | | ______ ______
+ * | | | | | | |
+ * | | +----->| TPGT |-- - - -->| TPGT |
+ * | | |______| |______|
+ * | | | |
+ * | +--+ | |
+ * | | _______ _______ | ______ |
+ * | | | | | |<--+ | |<--+
+ * | +->| TPG |-->| TPG |-- - - -->| TPG |
+ * | |_______| |_______| |______|
+ * |
+ * | ___________ ___________ ___________
+ * | | | | | | |
+ * +---->| Initiator |-->| Initiator |-- - - -->| Initiator |
+ * | Context | | Context | | Context |
+ * |___________| |___________| |___________|
+ *
+ *
+ * it_config_t includes a list of global properties
+ *
+ * Targets include a list of properties which override the global properties
+ * if set
+ *
+ * Initiators also include a list of properties but never inherit properties
+ * from the global config.
+ */
+
+/* Maximum size of a Target Portal Group name */
+#define MAX_TPG_NAMELEN 256 /* XXX */
+
+/* Maximum size of an iSCSI Target Node name */
+#define MAX_ISCSI_NODENAMELEN 256 /* XXX */
+
+/*
+ * A target portal group tag is a binding between a target and a target
+ * portal group along with a numerical value associated with that binding.
+ * The numerical identifier is used as the 'target portal group tag' defined
+ * in RFC3720.
+ *
+ * tpgt_tpg_name The name of the target portal group associated with
+ * this target portal group tag.
+ * tpgt_generation Generation number which is incremented each time the
+ * structure changes.
+ * tpgt_next Next target portal group tag in th list of target portal
+ * group tags. If tpgt_next is NUL, then this is the last
+ * target portal group in the list.
+ * tpgt_tag A numerical identifier that uniquely identifies a
+ * target portal group within the associated target node.
+ */
+typedef struct it_tpgt_s {
+ char tpgt_tpg_name[MAX_TPG_NAMELEN];
+ uint64_t tpgt_generation;
+ struct it_tpgt_s *tpgt_next;
+ uint16_t tpgt_tag;
+} it_tpgt_t;
+
+/*
+ * An iSCSI target node is represented by an it_tgt_structure. Each
+ * target node includes a list of associated target portal group tags
+ * and a list of properties.
+ *
+ * tgt_name The iSCSI target node name in either IQN or EUI
+ * format (see RFC3720).
+ * tgt_generation Generation number which is incremented each time
+ * the structure changes.
+ * tgt_next Next target in the list of targets. If tgt_next
+ * is NULL, then this is the last target in the list.
+ * tgt_tpgt_list A linked list representing the current target
+ * portal group tags associated with this target.
+ * tgt_tpgt_count The number of currently defined target portal
+ * group tags.
+ * tgt_properties An nvlist representation of the properties
+ * associated with this target. This list can be
+ * manipulated using libnvpair(3lib), and should be
+ * validated and stored using it_tgt_setprop().
+ *
+ * Target nvlist Properties:
+ *
+ * nvlist Key Type Valid Values
+ * --------------------------------------------------------------------
+ * targetchapuser string any string or "none" to remove
+ * targetchapsecret string string of at least 12 characters
+ * but not more than 255 characters.
+ * secret will be base64 encoded when
+ * stored.
+ * alias string any string or "none" to remove
+ * auth string "radius", "chap", or "none"
+ *
+ */
+typedef struct it_tgt_s {
+ char tgt_name[MAX_ISCSI_NODENAMELEN];
+ uint64_t tgt_generation;
+ struct it_tgt_s *tgt_next;
+ it_tpgt_t *tgt_tpgt_list;
+ uint32_t tgt_tpgt_count;
+ nvlist_t *tgt_properties;
+} it_tgt_t;
+
+/*
+ * A target portal is represented by an IP address and a listening
+ * TCP port.
+ *
+ * portal_addr sockaddr_storage structure representing the
+ * IPv4 or IPv6 address and TCP port associated
+ * with the portal.
+ * portal_next Next portal in the list of portals. If
+ * portal_next is NULL, this is the last portal
+ * in the list.
+ */
+typedef struct it_portal_s {
+ struct sockaddr_storage portal_addr;
+ struct it_portal_s *next;
+} it_portal_t;
+
+/*
+ * A portal is an IP address and TCP port and a portal group is a set
+ * of portals. Each defined portal belongs to exactly one portal group.
+ * Applications can associate a target portal group with a particular
+ * target using a target portal group name. Initiators can only connect
+ * to targets through the portals associated with the target's target
+ * portal group tags.
+ *
+ * tpg_name Identifier for the target portal group.
+ * tpg_generation Generation number which is incremented each
+ * time this structure changes.
+ * tpg_next Next target portal group in the list of target
+ * portal groups. If tpg_next is NULL, this is the
+ * last target portal group in the list.
+ * tpg_portal_count Number of it_portal_t structures in the list.
+ * tpg_portal_list Linked list of it_portal_t structures.
+ */
+typedef struct it_tpg_s {
+ char tpg_name[MAX_TPG_NAMELEN];
+ uint64_t tpg_generation;
+ struct it_tpg_s *tpg_next;
+ uint32_t tpg_portal_count;
+ it_portal_t *tpg_portal_list;
+} it_tpg_t;
+
+/*
+ * A context representing a remote iSCSI initiator node. The purpose
+ * of this structure is to maintain information specific to a remote
+ * initiator such as the CHAP username and CHAP secret.
+ *
+ * ini_name the iSCSI node name of the remote initiator.
+ * ini_generation Generation number which is incremented each
+ * time this structure changes.
+ * ini_next Next initiator in the list of initiators.
+ * If ini_next is NULL, this is the last initiator
+ * in the list.
+ * ini_properties Name/Value list containing the properties
+ * associated with the initiator context. This list
+ * can be manipulated using libnvpair(3lib), and should
+ * be validated and stored using it_ini_setprop().
+ *
+ * Initiator nvlist Properties:
+ *
+ * nvlist Key Type Valid Values
+ * --------------------------------------------------------------------
+ * chapuser string any string
+ * chapsecret string string of at least 12 characters
+ * but not more than 255 characters.
+ * secret will be base64 encoded when
+ * stored.
+ */
+typedef struct it_ini_s {
+ char ini_name[MAX_ISCSI_NODENAMELEN];
+ uint64_t ini_generation;
+ struct it_ini_s *ini_next;
+ nvlist_t *ini_properties;
+} it_ini_t;
+
+
+/*
+ * This structure represents a complete configuration for the iscsit
+ * port provider. In addition to the global configuration, it_config_t
+ * includes lists of child objects including targets, target portal
+ * groups and initiator contexts. Each object includes a "generation"
+ * value which is used by the iscsit kernel driver to identify changes
+ * from one configuration update to the next.
+ *
+ * stmf_token A uint64_t that contains the value returned from a
+ * successful call to stmfGetProviderDataProt(3STMF).
+ * This token is used to verify that the configuration
+ * data persistently stored in COMSTAR has not been
+ * modified since this version was loaded.
+ * config_version Version number for this configuration structure
+ * config_tgt_list Linked list of target contexts representing the
+ * currently defined targets. Applications can add
+ * targets to or remove targets from this list using
+ * the it_tgt_create and it_tgt_delete functions.
+ * config_tgt_count The number of currently defined targets.
+ * config_tpg_list Linked list of target portal group contexts.
+ * Applications can add or remove target portal groups
+ * to/from this list using the it_tpg_create and
+ * it_tpg_delete functions.
+ * config_tpg_count The number of currently defined target portal groups
+ * config_ini_list Linked list of initiator contexts. Applications
+ * can add initiator contexts or remove initiator
+ * contexts from this list using the it_ini_create
+ * and it_ini_delete functions.
+ * config_ini_count The number of currently defined initiator contexts.
+ * config_global_properties
+ * Name/Value list representing the current global
+ * property settings. This list can be manipulated
+ * using libnvpair(3lib), and should be validated
+ * and stored using it_config_setprop().
+ * config_isns_svr_list
+ * Linked list of currently defined iSNS servers.
+ * Applications can add or remove iSNS servers by
+ * using the it_config_setprop() function and changing
+ * the array of iSNS servers stored in the "isnsserver"
+ * property.
+ * config_isns_svr_count
+ * The number of currently defined iSNS servers.
+ *
+ * Global nvlist Properties:
+ *
+ * nvlist Key Type Valid Values
+ * --------------------------------------------------------------------
+ * alias string any string
+ * auth string "radius", "chap", or "none"
+ * isns boolean B_TRUE, B_FALSE
+ * isnsserver string array Array of portal specifications of
+ * the form IPaddress:port. Port
+ * is optional; if not specified, the
+ * default iSNS port number of 3205 will
+ * be used. IPv6 addresses should
+ * be enclosed in square brackets '[' ']'.
+ * If "none" is specified, all defined
+ * iSNS servers will be removed from the
+ * configuration.
+ * radiusserver string IPaddress:port specification as
+ * described for 'isnsserver'.
+ * radiussecret string string of at least 12 characters
+ * but not more than 255 characters.
+ * secret will be base64 encoded when
+ * stored.
+ */
+typedef struct it_config_s {
+ uint64_t stmf_token;
+ uint32_t config_version;
+ it_tgt_t *config_tgt_list;
+ uint32_t config_tgt_count;
+ it_tpg_t *config_tpg_list;
+ uint32_t config_tpg_count;
+ it_ini_t *config_ini_list;
+ uint32_t config_ini_count;
+ it_portal_t *config_isns_svr_list;
+ uint32_t config_isns_svr_count;
+ nvlist_t *config_global_properties;
+} it_config_t;
+
+/*
+ * Function: it_config_load()
+ *
+ * Allocate and create an it_config_t structure representing the
+ * current iSCSI configuration. This structure is compiled using
+ * the 'provider' data returned by stmfGetProviderData(). If there
+ * is no provider data associated with iscsit, the it_config_t
+ * structure will be set to a default configuration.
+ *
+ * Parameters:
+ * cfg A C representation of the current iSCSI configuration
+ *
+ * Return Values:
+ * 0 Success
+ * ENOMEM Could not allocate resources
+ * EINVAL Invalid parameter
+ */
+int
+it_config_load(it_config_t **cfg);
+
+/*
+ * Function: it_config_commit()
+ *
+ * Informs the iscsit service that the configuration has changed and
+ * commits the new configuration to persistent store by calling
+ * stmfSetProviderData. This function can be called multiple times
+ * during a configuration sequence if necessary.
+ *
+ * Parameters:
+ * cfg A C representation of the current iSCSI configuration
+ *
+ * Return Values:
+ * 0 Success
+ * ENOMEM Could not allocate resources
+ * EINVAL Invalid it_config_t structure
+ * STMF_ERROR_SERVICE_DATA_VERSION Configuration was updated by another
+ * client. See stmfSetProviderDataProt().
+ */
+int
+it_config_commit(it_config_t *cfg);
+
+/*
+ * Function: it_config_setprop()
+ *
+ * Validate the provided property list and set the global properties
+ * for iSCSI Target. If errlist is not NULL, returns detailed
+ * errors for each property that failed. The format for errorlist
+ * is key = property, value = error string.
+ *
+ * Parameters:
+ *
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * proplist nvlist_t containing properties for this target.
+ * errlist (optional) nvlist_t of errors encountered when
+ * validating the properties.
+ *
+ * Return Values:
+ * 0 Success
+ * ENOMEM Could not allocate resources
+ * EINVAL Invalid property
+ *
+ */
+int
+it_config_setprop(it_config_t *cfg, nvlist_t *proplist, nvlist_t **errlist);
+
+/*
+ * Function: it_config_free()
+ *
+ * Free any resources associated with the it_config_t structure.
+ *
+ * Parameters:
+ * cfg A C representation of the current iSCSI configuration
+ */
+void
+it_config_free(it_config_t *cfg);
+
+/*
+ * Function: it_tgt_create()
+ *
+ * Allocate and create an it_tgt_t structure representing a new iSCSI
+ * target node. If tgt_name is NULL, then a unique target node name will
+ * be generated automatically. Otherwise, the value of tgt_name will be
+ * used as the target node name. The new it_tgt_t structure is added to
+ * the target list (cfg_tgt_list) in the configuration structure, and the
+ * new target will not be instantiated until the modified configuration
+ * is committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * tgt Pointer to an iSCSI target structure
+ * tgt_name The target node name for the target to be created.
+ * The name must be in either IQN or EUI format. If
+ * this value is NULL, a node name will be generated
+ * automatically in IQN format.
+ *
+ * Return Values:
+ * 0 Success
+ * ENOMEM Could not allocate resources
+ * EINVAL Invalid parameter
+ * EEXIST The requested target node name is already configured
+ * EFAULT Invalid iSCSI target name
+ */
+int
+it_tgt_create(it_config_t *cfg, it_tgt_t **tgt, char *tgt_name);
+
+/*
+ * Function: it_tgt_setprop()
+ *
+ * Validate the provided property list and set the properties for
+ * the specified target. If errlist is not NULL, returns detailed
+ * errors for each property that failed. The format for errorlist
+ * is key = property, value = error string.
+ *
+ * Parameters:
+ *
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * tgt Pointer to an iSCSI target structure
+ * proplist nvlist_t containing properties for this target.
+ * errlist (optional) nvlist_t of errors encountered when
+ * validating the properties.
+ *
+ * Return Values:
+ * 0 Success
+ * ENOMEM Could not allocate resources
+ * EINVAL Invalid property
+ *
+ */
+int
+it_tgt_setprop(it_config_t *cfg, it_tgt_t *tgt, nvlist_t *proplist,
+ nvlist_t **errlist);
+
+
+/*
+ * Function: it_tgt_delete()
+ *
+ * Delete target represented by 'tgt', where 'tgt' is an existing
+ * it_tgt_t structure within the configuration 'cfg'. The target removal
+ * will not take effect until the modified configuration is committed
+ * by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * tgt Pointer to an iSCSI target structure
+ * force Set the target to offline before removing it from
+ * the config. If not specified, the operation will
+ * fail if the target is determined to be online.
+ *
+ * Return Values:
+ * 0 Success
+ * EBUSY Target is online
+ */
+int
+it_tgt_delete(it_config_t *cfg, it_tgt_t *tgt, boolean_t force);
+
+/*
+ * Function: it_tpgt_create()
+ *
+ * Allocate and create an it_tpgt_t structure representing a new iSCSI
+ * target portal group tag. The new it_tpgt_t structure is added to the
+ * target tpgt list (tgt_tpgt_list) in the it_tgt_t structure. The new
+ * target portal group tag will not be instantiated until the modified
+ * configuration is committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * tgt Pointer to the iSCSI target structure associated
+ * with the target portal group tag
+ * tpgt Pointer to a target portal group tag structure
+ * tpg_name The name of the TPG to be associated with this TPGT
+ * tpgt_tag 16-bit numerical identifier for this TPGT. Valid
+ * values are 2 through 65535. If tpgt_tag is '0',
+ * this function will assign an appropriate tag number.
+ * If tpgt_tag is != 0, and the requested number is
+ * unavailable, another value will be chosen.
+ *
+ * Return Values:
+ * 0 Success
+ * ENOMEM Could not allocate resources
+ * EINVAL Invalid parameter
+ * EEXIST Specified TPG is already associated with the target
+ * E2BIG All tag numbers already in use
+ */
+int
+it_tpgt_create(it_config_t *cfg, it_tgt_t *tgt, it_tpgt_t **tpgt,
+ char *tpg_name, uint16_t tpgt_tag);
+
+/*
+ * Function: it_tpgt_delete()
+ *
+ * Delete the target portal group tag represented by 'tpgt', where
+ * 'tpgt' is an existing is_tpgt_t structure within the target 'tgt'.
+ * The target portal group tag removal will not take effect until the
+ * modified configuation is committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * tgt Pointer to the iSCSI target structure associated
+ * with the target portal group tag
+ * tpgt Pointer to a target portal group tag structure
+ */
+void
+it_tpgt_delete(it_config_t *cfg, it_tgt_t *tgt, it_tpgt_t *tpgt);
+
+/*
+ * Function: it_tpg_create()
+ *
+ * Allocate and create an it_tpg_t structure representing a new iSCSI
+ * target portal group. The new it_tpg_t structure is added to the global
+ * tpg list (cfg_tgt_list) in the it_config_t structure. The new target
+ * portal group will not be instantiated until the modified configuration
+ * is committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * tpg Pointer to the it_tpg_t structure representing
+ * the target portal group
+ * tpg_name Identifier for the target portal group
+ * portal_ip_port A string containing an appropriatedly formatted
+ * IP address:port. Both IPv4 and IPv6 addresses are
+ * permitted. This value becomes the first portal in
+ * the TPG -- applications can add additional values
+ * using it_portal_create() before committing the TPG.
+ * Return Values:
+ * 0 Success
+ * ENOMEM Cannot allocate resources
+ * EINVAL Invalid parameter
+ * EEXIST Portal already configured for another portal group
+ * associated with this target.
+ */
+int
+it_tpg_create(it_config_t *cfg, it_tpg_t **tpg, char *tpg_name,
+ char *portal_ip_port);
+
+/*
+ * Function: it_tpg_delete()
+ *
+ * Delete target portal group represented by 'tpg', where 'tpg' is an
+ * existing it_tpg_t structure within the global configuration 'cfg'.
+ * The target portal group removal will not take effect until the
+ * modified configuration is committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configuration obtained from
+ * it_config_load()
+ * tpg Pointer to the it_tpg_t structure representing
+ * the target portal group
+ * force Remove this target portal group even if it's
+ * associated with one or more targets.
+ *
+ * Return Values:
+ * 0 Success
+ * EINVAL Invalid parameter
+ * EBUSY Portal group associated with one or more targets.
+ */
+int
+it_tpg_delete(it_config_t *cfg, it_tpg_t *tpg, boolean_t force);
+
+/*
+ * Function: it_portal_create()
+ *
+ * Add an it_portal_t structure representing a new portal to the specified
+ * target portal group. The change to the target portal group will not take
+ * effect until the modified configuration is committed by calling
+ * it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configration obtained from
+ * it_config_load()
+ * tpg Pointer to the it_tpg_t structure representing the
+ * target portal group or "none" to remove
+ * portal Pointer to the it_portal_t structure representing
+ * the portal
+ * portal_ip_port A string containing an appropriately formatted
+ * IP address or IP address:port in either IPv4 or
+ * IPv6 format.
+ * Return Values:
+ * 0 Success
+ * ENOMEM Could not allocate resources
+ * EINVAL Invalid parameter
+ * EEXIST Portal already configured for another portal group
+ */
+int
+it_portal_create(it_config_t *cfg, it_tpg_t *tpg, it_portal_t **portal,
+ char *portal_ip_port);
+
+/*
+ * Function: it_portal_delete()
+ *
+ * Remove the specified portal from the specified target portal group.
+ * The portal removal will not take effect until the modified configuration
+ * is committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configration obtained from
+ * it_config_load()
+ * tpg Pointer to the it_tpg_t structure representing the
+ * target portal group
+ * portal Pointer to the it_portal_t structure representing
+ * the portal
+ */
+void
+it_portal_delete(it_config_t *cfg, it_tpg_t *tpg, it_portal_t *portal);
+
+/*
+ * Function: it_ini_create()
+ *
+ * Add an initiator context to the global configuration. The new
+ * initiator context will not be instantiated until the modified
+ * configuration is committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configration obtained from
+ * it_config_load()
+ * ini Pointer to the it_ini_t structure representing
+ * the initiator context.
+ * ini_node_name The iSCSI node name of the remote initiator.
+ *
+ * Return Values:
+ * 0 Success
+ * ENOMEM Could not allocate resources
+ * EINVAL Invalid parameter.
+ * EEXIST Initiator already configured
+ * EFAULT Invalid initiator name
+ */
+int
+it_ini_create(it_config_t *cfg, it_ini_t **ini, char *ini_node_name);
+
+/*
+ * Function: it_ini_setprop()
+ *
+ * Validate the provided property list and set the initiator properties.
+ * If errlist is not NULL, returns detailed errors for each property
+ * that failed. The format for errorlist is
+ * key = property, value = error string.
+ *
+ * Parameters:
+ *
+ * ini The initiator being updated.
+ * proplist nvlist_t containing properties for this target.
+ * errlist (optional) nvlist_t of errors encountered when
+ * validating the properties.
+ *
+ * Return Values:
+ * 0 Success
+ * ENOMEM Could not allocate resources
+ * EINVAL Invalid property
+ *
+ */
+int
+it_ini_setprop(it_ini_t *ini, nvlist_t *proplist, nvlist_t **errlist);
+
+/*
+ * Function: it_ini_delete()
+ *
+ * Remove the specified initiator context from the global configuration.
+ * The removal will not take effect until the modified configuration is
+ * committed by calling it_config_commit().
+ *
+ * Parameters:
+ * cfg The current iSCSI configration obtained from
+ * it_config_load()
+ * ini Pointer to the it_ini_t structure representing
+ * the initiator context.
+ */
+void
+it_ini_delete(it_config_t *cfg, it_ini_t *ini);
+
+/*
+ * Function: it_config_free()
+ *
+ * Free any resources associated with the it_config_t structure.
+ *
+ * Parameters:
+ * cfg A C representation of the current iSCSI configuration
+ */
+void
+it_config_free(it_config_t *cfg);
+
+/*
+ * Function: it_tgt_free()
+ *
+ * Frees an it_tgt_t structure. If tgt_next is not NULL, frees
+ * all structures in the list.
+ */
+void
+it_tgt_free(it_tgt_t *tgt);
+
+/*
+ * Function: it_tpgt_free()
+ *
+ * Deallocates resources of an it_tpgt_t structure. If tpgt->next
+ * is not NULL, frees all members of the list.
+ */
+void
+it_tpgt_free(it_tpgt_t *tpgt);
+
+/*
+ * Function: it_tpg_free()
+ *
+ * Deallocates resources associated with an it_tpg_t structure.
+ * If tpg->next is not NULL, frees all members of the list.
+ */
+void
+it_tpg_free(it_tpg_t *tpg);
+
+/*
+ * Function: it_ini_free()
+ *
+ * Deallocates resources of an it_ini_t structure. If ini->next is
+ * not NULL, frees all members of the list.
+ */
+void
+it_ini_free(it_ini_t *ini);
+
+/*
+ * Function: validate_iscsi_name()
+ *
+ * Ensures the passed-in string is a valid IQN or EUI iSCSI name
+ */
+boolean_t
+validate_iscsi_name(char *in_name);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _LIBISCSIT_H */
diff --git a/usr/src/lib/libiscsit/common/llib-liscsit b/usr/src/lib/libiscsit/common/llib-liscsit
new file mode 100644
index 0000000000..8d9fc33604
--- /dev/null
+++ b/usr/src/lib/libiscsit/common/llib-liscsit
@@ -0,0 +1,30 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+/*LINTLIBRARY*/
+/*PROTOLIB1*/
+
+#include <libiscsit.h>
+#include <sys/iscsit/iscsit_common.h>
diff --git a/usr/src/lib/libiscsit/common/mapfile-vers b/usr/src/lib/libiscsit/common/mapfile-vers
new file mode 100644
index 0000000000..cceac0978d
--- /dev/null
+++ b/usr/src/lib/libiscsit/common/mapfile-vers
@@ -0,0 +1,79 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+SUNW_1.1 {
+ global:
+ it_config_load;
+ it_config_commit;
+ it_config_setprop;
+ it_config_free;
+ it_tgt_create;
+ it_tgt_setprop;
+ it_tgt_delete;
+ it_tgt_free;
+ it_tpg_create;
+ it_tpg_delete;
+ it_tpg_free;
+ it_ini_create;
+ it_ini_delete;
+ it_ini_setprop;
+ it_ini_free;
+ it_tpgt_create;
+ it_tpgt_delete;
+ it_tpgt_free;
+ it_portal_create;
+};
+
+SUNWprivate {
+ global:
+ it_config_to_nv;
+ it_nv_to_config;
+ it_nv_to_tgtlist;
+ it_tgtlist_to_nv;
+ it_tgt_to_nv;
+ it_nv_to_tgt;
+ it_tpgt_to_nv;
+ it_nv_to_tpgt;
+ it_tpgtlist_to_nv;
+ it_nv_to_tpgtlist;
+ it_tpg_to_nv;
+ it_nv_to_tpg;
+ it_tpglist_to_nv;
+ it_nv_to_tpglist;
+ it_ini_to_nv;
+ it_nv_to_ini;
+ it_inilist_to_nv;
+ it_nv_to_inilist;
+ it_common_convert_sa;
+ it_config_free_cmn;
+ it_tgt_free_cmn;
+ it_tpg_free_cmn;
+ it_ini_free_cmn;
+ it_tpgt_free_cmn;
+ sockaddr_to_str;
+ validate_iscsi_name;
+ local:
+ *;
+};
+
diff --git a/usr/src/lib/libiscsit/i386/Makefile b/usr/src/lib/libiscsit/i386/Makefile
new file mode 100644
index 0000000000..95a52ca13f
--- /dev/null
+++ b/usr/src/lib/libiscsit/i386/Makefile
@@ -0,0 +1,31 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+
+include ../Makefile.com
+
+all: $(LIBS)
+
+install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT)
diff --git a/usr/src/lib/libiscsit/sparc/Makefile b/usr/src/lib/libiscsit/sparc/Makefile
new file mode 100644
index 0000000000..ef68abede6
--- /dev/null
+++ b/usr/src/lib/libiscsit/sparc/Makefile
@@ -0,0 +1,29 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+include ../Makefile.com
+
+all: $(LIBS)
+
+install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT)
diff --git a/usr/src/lib/libiscsit/sparcv9/Makefile b/usr/src/lib/libiscsit/sparcv9/Makefile
new file mode 100644
index 0000000000..0dba37b85a
--- /dev/null
+++ b/usr/src/lib/libiscsit/sparcv9/Makefile
@@ -0,0 +1,32 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+
+include ../Makefile.com
+include ../../Makefile.lib.64
+
+all: $(LIBS)
+
+install: all $(ROOTLIBS64) $(ROOTLINKS64)
diff --git a/usr/src/pkgdefs/Makefile b/usr/src/pkgdefs/Makefile
index d12b18d8b8..d68ca984a1 100644
--- a/usr/src/pkgdefs/Makefile
+++ b/usr/src/pkgdefs/Makefile
@@ -281,9 +281,13 @@ COMMON_SUBDIRS= \
SUNWippcore \
SUNWipplr \
SUNWipplu \
+ SUNWiscsidmr \
+ SUNWiscsidmu \
SUNWiscsir \
SUNWiscsitgtr \
SUNWiscsitgtu \
+ SUNWiscsitr \
+ SUNWiscsitu \
SUNWiscsiu \
SUNWisns \
SUNWisnsadm \
diff --git a/usr/src/pkgdefs/SUNWiscsidmr/Makefile b/usr/src/pkgdefs/SUNWiscsidmr/Makefile
new file mode 100644
index 0000000000..4554f5a618
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsidmr/Makefile
@@ -0,0 +1,37 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+include ../Makefile.com
+
+DATAFILES += depend
+
+.KEEP_STATE:
+
+all: $(FILES) depend
+
+install: all pkg
+
+include ../Makefile.targ
+
diff --git a/usr/src/pkgdefs/SUNWiscsidmr/pkginfo.tmpl b/usr/src/pkgdefs/SUNWiscsidmr/pkginfo.tmpl
new file mode 100644
index 0000000000..5bfb88a1bf
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsidmr/pkginfo.tmpl
@@ -0,0 +1,49 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+#
+# This required package information file describes characteristics of the
+# package, such as package abbreviation, full package name, package version,
+# and package architecture.
+#
+PKG="SUNWiscsidmr"
+NAME="Sun iSCSI Data Mover (Root)"
+ARCH="ISA"
+CATEGORY="system"
+BASEDIR=/
+SUNW_PKGVERS="1.0"
+SUNW_PKGTYPE="root"
+CLASSES="none"
+DESC="Sun iSCSI Data Mover (Root)"
+SUNW_PRODNAME="SunOS"
+SUNW_PRODVERS="RELEASE/VERSION"
+VERSION="ONVERS,REV=0.0.0"
+VENDOR="Sun Microsystems, Inc."
+HOTLINE="Please contact your local service provider"
+EMAIL=""
+MAXINST="1000"
+SUNW_PKG_ALLZONES="true"
+SUNW_PKG_HOLLOW="true"
+SUNW_PKG_THISZONE="false"
diff --git a/usr/src/pkgdefs/SUNWiscsidmr/prototype_com b/usr/src/pkgdefs/SUNWiscsidmr/prototype_com
new file mode 100644
index 0000000000..8d166ddfbe
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsidmr/prototype_com
@@ -0,0 +1,46 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+
+# This required package information file contains a list of package contents.
+# The 'pkgmk' command uses this file to identify the contents of a package
+# and their location on the development machine when building the package.
+# Can be created via a text editor or through use of the 'pkgproto' command.
+
+#!search <pathname pathname ...> # where to find pkg objects
+#!include <filename> # include another 'prototype' file
+#!default <mode> <owner> <group> # default used if not specified on entry
+#!<param>=<value> # puts parameter in pkg environment
+
+#
+i copyright
+i pkginfo
+i depend
+#
+# SUNWiscsidmr files
+#
+d none kernel 0755 root sys
+d none kernel/misc 0755 root sys
+d none kernel/kmdb 0755 root sys
diff --git a/usr/src/pkgdefs/SUNWiscsidmr/prototype_i386 b/usr/src/pkgdefs/SUNWiscsidmr/prototype_i386
new file mode 100644
index 0000000000..2f00ec513f
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsidmr/prototype_i386
@@ -0,0 +1,54 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# This required package information file contains a list of package contents.
+# The 'pkgmk' command uses this file to identify the contents of a package
+# and their location on the development machine when building the package.
+# Can be created via a text editor or through use of the 'pkgproto' command.
+
+#!search <pathname pathname ...> # where to find pkg objects
+#!include <filename> # include another 'prototype' file
+#!default <mode> <owner> <group> # default used if not specified on entry
+#!<param>=<value> # puts parameter in pkg environment
+
+#
+# Include ISA independent files (prototype_com)
+#
+!include prototype_com
+#
+#
+# List files which are Intel specific here
+#
+# source locations relative to the prototype file
+#
+#
+# SUNWiscsidmr
+#
+f none kernel/misc/idm 0755 root sys
+d none kernel/misc/amd64 0755 root sys
+f none kernel/misc/amd64/idm 0755 root sys
+f none kernel/kmdb/idm 0555 root sys
+d none kernel/kmdb/amd64 0755 root sys
+f none kernel/kmdb/amd64/idm 0555 root sys
diff --git a/usr/src/pkgdefs/SUNWiscsidmr/prototype_sparc b/usr/src/pkgdefs/SUNWiscsidmr/prototype_sparc
new file mode 100644
index 0000000000..129c19c745
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsidmr/prototype_sparc
@@ -0,0 +1,51 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# This required package information file contains a list of package contents.
+# The 'pkgmk' command uses this file to identify the contents of a package
+# and their location on the development machine when building the package.
+# Can be created via a text editor or through use of the 'pkgproto' command.
+
+#!search <pathname pathname ...> # where to find pkg objects
+#!include <filename> # include another 'prototype' file
+#!default <mode> <owner> <group> # default used if not specified on entry
+#!<param>=<value> # puts parameter in pkg environment
+#
+# Include ISA independent files (prototype_com)
+#
+!include prototype_com
+#
+#
+# List files which are SPARC specific here
+#
+# source locations relative to the prototype file
+#
+#
+# SUNWiscsidmr
+#
+d none kernel/misc/sparcv9 0755 root sys
+f none kernel/misc/sparcv9/idm 0755 root sys
+d none kernel/kmdb/sparcv9 0755 root sys
+f none kernel/kmdb/sparcv9/idm 0555 root sys
diff --git a/usr/src/pkgdefs/SUNWiscsidmu/Makefile b/usr/src/pkgdefs/SUNWiscsidmu/Makefile
new file mode 100644
index 0000000000..a5e2160367
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsidmu/Makefile
@@ -0,0 +1,35 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+include ../Makefile.com
+
+.KEEP_STATE:
+
+all: $(FILES) depend
+
+install: all pkg
+
+include ../Makefile.targ
+
diff --git a/usr/src/pkgdefs/SUNWiscsidmu/depend b/usr/src/pkgdefs/SUNWiscsidmu/depend
new file mode 100644
index 0000000000..995405be04
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsidmu/depend
@@ -0,0 +1,50 @@
+#
+# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# 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
+#
+# This package information file defines software dependencies associated
+# with the pkg. You can define three types of pkg dependencies with this file:
+# P indicates a prerequisite for installation
+# I indicates an incompatible package
+# R indicates a reverse dependency
+# <pkg.abbr> see pkginfo(4), PKG parameter
+# <name> see pkginfo(4), NAME parameter
+# <version> see pkginfo(4), VERSION parameter
+# <arch> see pkginfo(4), ARCH parameter
+# <type> <pkg.abbr> <name>
+# (<arch>)<version>
+# (<arch>)<version>
+# ...
+# <type> <pkg.abbr> <name>
+# ...
+#
+
+P SUNWcar Core Architecture, (Root)
+P SUNWcakr Core Solaris Kernel Architecture (Root)
+P SUNWkvm Core Architecture, (Kvm)
+P SUNWcsr Core Solaris, (Root)
+P SUNWckr Core Solaris Kernel (Root)
+P SUNWcnetr Core Solaris Network Infrastructure (Root)
+P SUNWcsu Core Solaris, (Usr)
+P SUNWcsd Core Solaris Devices
+P SUNWcsl Core Solaris Libraries
+P SUNWiscsidmr Sun iSCSI Data Mover (Root)
diff --git a/usr/src/pkgdefs/SUNWiscsidmu/pkginfo.tmpl b/usr/src/pkgdefs/SUNWiscsidmu/pkginfo.tmpl
new file mode 100644
index 0000000000..02dc05bc0a
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsidmu/pkginfo.tmpl
@@ -0,0 +1,49 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+#
+# This required package information file describes characteristics of the
+# package, such as package abbreviation, full package name, package version,
+# and package architecture.
+#
+PKG="SUNWiscsidmu"
+NAME="Sun iSCSI Data Mover (Usr)"
+ARCH="ISA"
+CATEGORY="system"
+BASEDIR=/
+SUNW_PKGVERS="1.0"
+SUNW_PKGTYPE="usr"
+CLASSES="none"
+DESC="Sun iSCSI Data Mover (Usr)"
+SUNW_PRODNAME="SunOS"
+SUNW_PRODVERS="RELEASE/VERSION"
+VERSION="ONVERS,REV=0.0.0"
+VENDOR="Sun Microsystems, Inc."
+HOTLINE="Please contact your local service provider"
+EMAIL=""
+MAXINST="1000"
+SUNW_PKG_ALLZONES="true"
+SUNW_PKG_HOLLOW="true"
+SUNW_PKG_THISZONE="false"
diff --git a/usr/src/pkgdefs/SUNWiscsidmu/prototype_com b/usr/src/pkgdefs/SUNWiscsidmu/prototype_com
new file mode 100644
index 0000000000..8acc9ea7a1
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsidmu/prototype_com
@@ -0,0 +1,47 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+
+# This required package information file contains a list of package contents.
+# The 'pkgmk' command uses this file to identify the contents of a package
+# and their location on the development machine when building the package.
+# Can be created via a text editor or through use of the 'pkgproto' command.
+
+#!search <pathname pathname ...> # where to find pkg objects
+#!include <filename> # include another 'prototype' file
+#!default <mode> <owner> <group> # default used if not specified on entry
+#!<param>=<value> # puts parameter in pkg environment
+
+#
+i copyright
+i pkginfo
+i depend
+#
+# SUNWiscsidmu files
+#
+d none usr 0755 root sys
+d none usr/lib 0755 root bin
+d none usr/lib/mdb 0755 root sys
+d none usr/lib/mdb/kvm 0755 root sys
diff --git a/usr/src/pkgdefs/SUNWiscsidmu/prototype_i386 b/usr/src/pkgdefs/SUNWiscsidmu/prototype_i386
new file mode 100644
index 0000000000..0cc0e0db79
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsidmu/prototype_i386
@@ -0,0 +1,51 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# This required package information file contains a list of package contents.
+# The 'pkgmk' command uses this file to identify the contents of a package
+# and their location on the development machine when building the package.
+# Can be created via a text editor or through use of the 'pkgproto' command.
+
+#!search <pathname pathname ...> # where to find pkg objects
+#!include <filename> # include another 'prototype' file
+#!default <mode> <owner> <group> # default used if not specified on entry
+#!<param>=<value> # puts parameter in pkg environment
+
+#
+# Include ISA independent files (prototype_com)
+#
+!include prototype_com
+#
+#
+# List files which are Intel specific here
+#
+# source locations relative to the prototype file
+#
+#
+# SUNWiscsidmu
+#
+f none usr/lib/mdb/kvm/idm.so 0555 root sys
+d none usr/lib/mdb/kvm/amd64 0755 root sys
+f none usr/lib/mdb/kvm/amd64/idm.so 0555 root sys
diff --git a/usr/src/pkgdefs/SUNWiscsidmu/prototype_sparc b/usr/src/pkgdefs/SUNWiscsidmu/prototype_sparc
new file mode 100644
index 0000000000..cbaa50c095
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsidmu/prototype_sparc
@@ -0,0 +1,49 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# This required package information file contains a list of package contents.
+# The 'pkgmk' command uses this file to identify the contents of a package
+# and their location on the development machine when building the package.
+# Can be created via a text editor or through use of the 'pkgproto' command.
+
+#!search <pathname pathname ...> # where to find pkg objects
+#!include <filename> # include another 'prototype' file
+#!default <mode> <owner> <group> # default used if not specified on entry
+#!<param>=<value> # puts parameter in pkg environment
+#
+# Include ISA independent files (prototype_com)
+#
+!include prototype_com
+#
+#
+# List files which are SPARC specific here
+#
+# source locations relative to the prototype file
+#
+#
+# SUNWiscsidmu
+#
+d none usr/lib/mdb/kvm/sparcv9 0755 root sys
+f none usr/lib/mdb/kvm/sparcv9/idm.so 0555 root sys
diff --git a/usr/src/pkgdefs/SUNWiscsitr/Makefile b/usr/src/pkgdefs/SUNWiscsitr/Makefile
new file mode 100644
index 0000000000..64a4ce0f7a
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsitr/Makefile
@@ -0,0 +1,37 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+include ../Makefile.com
+
+DATAFILES += i.manifest r.manifest
+
+.KEEP_STATE:
+
+all: $(FILES) depend i.manifest r.manifest
+
+install: all pkg
+
+include ../Makefile.targ
+
diff --git a/usr/src/pkgdefs/SUNWiscsitr/depend b/usr/src/pkgdefs/SUNWiscsitr/depend
new file mode 100644
index 0000000000..719947f50e
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsitr/depend
@@ -0,0 +1,50 @@
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# This package information file defines software dependencies associated
+# with the pkg. You can define three types of pkg dependencies with this file:
+# P indicates a prerequisite for installation
+# I indicates an incompatible package
+# R indicates a reverse dependency
+# <pkg.abbr> see pkginfo(4), PKG parameter
+# <name> see pkginfo(4), NAME parameter
+# <version> see pkginfo(4), VERSION parameter
+# <arch> see pkginfo(4), ARCH parameter
+# <type> <pkg.abbr> <name>
+# (<arch>)<version>
+# (<arch>)<version>
+# ...
+# <type> <pkg.abbr> <name>
+# ...
+#
+
+P SUNWcar Core Architecture, (Root)
+P SUNWcakr Core Solaris Kernel Architecture (Root)
+P SUNWkvm Core Architecture, (Kvm)
+P SUNWcsr Core Solaris, (Root)
+P SUNWckr Core Solaris Kernel (Root)
+P SUNWcnetr Core Solaris Network Infrastructure (Root)
+P SUNWcsu Core Solaris, (Usr)
+P SUNWcsd Core Solaris Devices
+P SUNWcsl Core Solaris Libraries
+P SUNWiscsidmr Sun iSCSI Data Mover (Root)
+P SUNWiscsidmu Sun iSCSI Data Mover (Usr)
diff --git a/usr/src/pkgdefs/SUNWiscsitr/pkginfo.tmpl b/usr/src/pkgdefs/SUNWiscsitr/pkginfo.tmpl
new file mode 100644
index 0000000000..6e9df5bd41
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsitr/pkginfo.tmpl
@@ -0,0 +1,48 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# This required package information file describes characteristics of the
+# package, such as package abbreviation, full package name, package version,
+# and package architecture.
+#
+PKG="SUNWiscsitr"
+NAME="Sun iSCSI COMSTAR Port Provider (root)"
+ARCH="ISA"
+CATEGORY="system"
+BASEDIR=/
+SUNW_PKGVERS="1.0"
+SUNW_PKGTYPE="root"
+CLASSES="manifest none"
+DESC="Sun iSCSI COMSTAR Port Provider"
+SUNW_PRODNAME="SunOS"
+SUNW_PRODVERS="RELEASE/VERSION"
+VERSION="ONVERS,REV=0.0.0"
+VENDOR="Sun Microsystems, Inc."
+HOTLINE="Please contact your local service provider"
+EMAIL=""
+MAXINST="1000"
+SUNW_PKG_ALLZONES="true"
+SUNW_PKG_HOLLOW="true"
+SUNW_PKG_THISZONE="false"
diff --git a/usr/src/pkgdefs/SUNWiscsitr/postinstall b/usr/src/pkgdefs/SUNWiscsitr/postinstall
new file mode 100644
index 0000000000..c3e5b6753d
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsitr/postinstall
@@ -0,0 +1,40 @@
+#!/bin/sh
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+
+PATH="/usr/bin:/usr/sbin:${PATH}"; export PATH
+
+# Driver definitions
+DRVR_NAME=iscsit; export DRVR_NAME
+DRVR_PERM='* 0600 root sys'; export DRVR_PERM
+
+if [ "${BASEDIR}" = "/" ]; then
+ add_drv -m "${DRVR_PERM}" ${DRVR_NAME}
+else
+ add_drv -b "${BASEDIR}" -m "${DRVR_PERM}" ${DRVR_NAME}
+fi
+
+exit 0
diff --git a/usr/src/pkgdefs/SUNWiscsitr/preremove b/usr/src/pkgdefs/SUNWiscsitr/preremove
new file mode 100644
index 0000000000..b8315bb5b6
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsitr/preremove
@@ -0,0 +1,36 @@
+#!/bin/sh
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+
+PATH="/usr/bin:/usr/sbin:${PATH}"
+export PATH
+
+DRVR_NAME=iscsit
+
+# Remove the driver entries but leave it attached.
+/usr/sbin/rem_drv -b ${BASEDIR} ${DRVR_NAME}
+
+exit 0
diff --git a/usr/src/pkgdefs/SUNWiscsitr/prototype_com b/usr/src/pkgdefs/SUNWiscsitr/prototype_com
new file mode 100644
index 0000000000..ebb06bdc9e
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsitr/prototype_com
@@ -0,0 +1,60 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# This required package information file contains a list of package contents.
+# The 'pkgmk' command uses this file to identify the contents of a package
+# and their location on the development machine when building the package.
+# Can be created via a text editor or through use of the 'pkgproto' command.
+
+#!search <pathname pathname ...> # where to find pkg objects
+#!include <filename> # include another 'prototype' file
+#!default <mode> <owner> <group> # default used if not specified on entry
+#!<param>=<value> # puts parameter in pkg environment
+
+#
+#
+i copyright
+i pkginfo
+i depend
+i postinstall
+i preremove
+i i.manifest
+i r.manifest
+#
+# SUNWiscsitr files
+#
+d none kernel 0755 root sys
+d none kernel/drv 0755 root sys
+f none kernel/drv/iscsit.conf 0644 root sys
+d none var 755 root sys
+d none var/svc 755 root sys
+d none var/svc/manifest 755 root sys
+d none var/svc/manifest/network 755 root sys
+d none var/svc/manifest/network/iscsi 755 root sys
+f manifest var/svc/manifest/network/iscsi/iscsi-target.xml 0444 root sys
+d none lib 755 root bin
+d none lib/svc 755 root bin
+d none lib/svc/method 755 root bin
+f none lib/svc/method/iscsi-target 0555 root bin
diff --git a/usr/src/pkgdefs/SUNWiscsitr/prototype_i386 b/usr/src/pkgdefs/SUNWiscsitr/prototype_i386
new file mode 100644
index 0000000000..c0891f36a2
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsitr/prototype_i386
@@ -0,0 +1,51 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# This required package information file contains a list of package contents.
+# The 'pkgmk' command uses this file to identify the contents of a package
+# and their location on the development machine when building the package.
+# Can be created via a text editor or through use of the 'pkgproto' command.
+
+#!search <pathname pathname ...> # where to find pkg objects
+#!include <filename> # include another 'prototype' file
+#!default <mode> <owner> <group> # default used if not specified on entry
+#!<param>=<value> # puts parameter in pkg environment
+
+#
+# Include ISA independent files (prototype_com)
+#
+!include prototype_com
+#
+#
+# List files which are Intel specific here
+#
+# source locations relative to the prototype file
+#
+#
+# SUNWiscsitr
+#
+f none kernel/drv/iscsit 0755 root sys
+d none kernel/drv/amd64 0755 root sys
+f none kernel/drv/amd64/iscsit 0755 root sys
diff --git a/usr/src/pkgdefs/SUNWiscsitr/prototype_sparc b/usr/src/pkgdefs/SUNWiscsitr/prototype_sparc
new file mode 100644
index 0000000000..f626a7da5a
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsitr/prototype_sparc
@@ -0,0 +1,49 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# This required package information file contains a list of package contents.
+# The 'pkgmk' command uses this file to identify the contents of a package
+# and their location on the development machine when building the package.
+# Can be created via a text editor or through use of the 'pkgproto' command.
+
+#!search <pathname pathname ...> # where to find pkg objects
+#!include <filename> # include another 'prototype' file
+#!default <mode> <owner> <group> # default used if not specified on entry
+#!<param>=<value> # puts parameter in pkg environment
+#
+# Include ISA independent files (prototype_com)
+#
+!include prototype_com
+#
+#
+# List files which are SPARC specific here
+#
+# source locations relative to the prototype file
+#
+#
+# SUNWiscsitr
+#
+d none kernel/drv/sparcv9 0755 root sys
+f none kernel/drv/sparcv9/iscsit 0755 root sys
diff --git a/usr/src/pkgdefs/SUNWiscsitu/Makefile b/usr/src/pkgdefs/SUNWiscsitu/Makefile
new file mode 100644
index 0000000000..a5e2160367
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsitu/Makefile
@@ -0,0 +1,35 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+
+include ../Makefile.com
+
+.KEEP_STATE:
+
+all: $(FILES) depend
+
+install: all pkg
+
+include ../Makefile.targ
+
diff --git a/usr/src/pkgdefs/SUNWiscsitu/depend b/usr/src/pkgdefs/SUNWiscsitu/depend
new file mode 100644
index 0000000000..fd563fed32
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsitu/depend
@@ -0,0 +1,49 @@
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# This package information file defines software dependencies associated
+# with the pkg. You can define three types of pkg dependencies with this file:
+# P indicates a prerequisite for installation
+# I indicates an incompatible package
+# R indicates a reverse dependency
+# <pkg.abbr> see pkginfo(4), PKG parameter
+# <name> see pkginfo(4), NAME parameter
+# <version> see pkginfo(4), VERSION parameter
+# <arch> see pkginfo(4), ARCH parameter
+# <type> <pkg.abbr> <name>
+# (<arch>)<version>
+# (<arch>)<version>
+# ...
+# <type> <pkg.abbr> <name>
+# ...
+#
+
+P SUNWcar Core Architecture, (Root)
+P SUNWcakr Core Solaris Kernel Architecture (Root)
+P SUNWkvm Core Architecture, (Kvm)
+P SUNWcsr Core Solaris, (Root)
+P SUNWckr Core Solaris Kernel (Root)
+P SUNWcnetr Core Solaris Network Infrastructure (Root)
+P SUNWcsu Core Solaris, (Usr)
+P SUNWcsd Core Solaris Devices
+P SUNWcsl Core Solaris Libraries
+P SUNWiscsitr Sun iSCSI COMSTAR port provider (Root)
diff --git a/usr/src/pkgdefs/SUNWiscsitu/pkginfo.tmpl b/usr/src/pkgdefs/SUNWiscsitu/pkginfo.tmpl
new file mode 100644
index 0000000000..bb9f962ae2
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsitu/pkginfo.tmpl
@@ -0,0 +1,48 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# This required package information file describes characteristics of the
+# package, such as package abbreviation, full package name, package version,
+# and package architecture.
+#
+PKG="SUNWiscsitu"
+NAME="Sun iSCSI COMSTAR Port Provider"
+ARCH="ISA"
+CATEGORY="system"
+BASEDIR=/
+SUNW_PKGVERS="1.0"
+SUNW_PKGTYPE="usr"
+CLASSES="none"
+DESC="Sun iSCSI COMSTAR Port Provider"
+SUNW_PRODNAME="SunOS"
+SUNW_PRODVERS="RELEASE/VERSION"
+VERSION="ONVERS,REV=0.0.0"
+VENDOR="Sun Microsystems, Inc."
+HOTLINE="Please contact your local service provider"
+EMAIL=""
+MAXINST="1000"
+SUNW_PKG_ALLZONES="true"
+SUNW_PKG_HOLLOW="false"
+SUNW_PKG_THISZONE="false"
diff --git a/usr/src/pkgdefs/SUNWiscsitu/prototype_com b/usr/src/pkgdefs/SUNWiscsitu/prototype_com
new file mode 100644
index 0000000000..41d30d931b
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsitu/prototype_com
@@ -0,0 +1,54 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# This required package information file contains a list of package contents.
+# The 'pkgmk' command uses this file to identify the contents of a package
+# and their location on the development machine when building the package.
+# Can be created via a text editor or through use of the 'pkgproto' command.
+
+#!search <pathname pathname ...> # where to find pkg objects
+#!include <filename> # include another 'prototype' file
+#!default <mode> <owner> <group> # default used if not specified on entry
+#!<param>=<value> # puts parameter in pkg environment
+
+i copyright
+i pkginfo
+i depend
+#
+# SUNWiscsitu files
+#
+d none usr 0755 root sys
+d none usr/lib 0755 root bin
+f none usr/lib/libiscsit.so.1 0755 root bin
+s none usr/lib/libiscsit.so=libiscsit.so.1
+f none usr/lib/llib-liscsit 644 root bin
+f none usr/lib/llib-liscsit.ln 644 root bin
+d none usr/sbin 0755 root bin
+f none usr/sbin/itadm 0555 root bin
+d none usr/include 0755 root bin
+f none usr/include/libiscsit.h 644 root bin
+d none usr/include/sys 0755 root bin
+d none usr/include/sys/iscsit 0755 root bin
+f none usr/include/sys/iscsit/iscsit_common.h 644 root bin
diff --git a/usr/src/pkgdefs/SUNWiscsitu/prototype_i386 b/usr/src/pkgdefs/SUNWiscsitu/prototype_i386
new file mode 100644
index 0000000000..431563a7bb
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsitu/prototype_i386
@@ -0,0 +1,51 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# This required package information file contains a list of package contents.
+# The 'pkgmk' command uses this file to identify the contents of a package
+# and their location on the development machine when building the package.
+# Can be created via a text editor or through use of the 'pkgproto' command.
+
+#!search <pathname pathname ...> # where to find pkg objects
+#!include <filename> # include another 'prototype' file
+#!default <mode> <owner> <group> # default used if not specified on entry
+#!<param>=<value> # puts parameter in pkg environment
+
+#
+# Include ISA independent files (prototype_com)
+#
+!include prototype_com
+#
+#
+# List files which are Intel specific here
+#
+# source locations relative to the prototype file
+#
+# SUNWiscsitu
+#
+d none usr/lib/amd64 0755 root bin
+f none usr/lib/amd64/libiscsit.so.1 0755 root bin
+s none usr/lib/amd64/libiscsit.so=libiscsit.so.1
+f none usr/lib/amd64/llib-liscsit.ln 644 root bin
diff --git a/usr/src/pkgdefs/SUNWiscsitu/prototype_sparc b/usr/src/pkgdefs/SUNWiscsitu/prototype_sparc
new file mode 100644
index 0000000000..8f63eb24e5
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWiscsitu/prototype_sparc
@@ -0,0 +1,51 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# This required package information file contains a list of package contents.
+# The 'pkgmk' command uses this file to identify the contents of a package
+# and their location on the development machine when building the package.
+# Can be created via a text editor or through use of the 'pkgproto' command.
+
+#!search <pathname pathname ...> # where to find pkg objects
+#!include <filename> # include another 'prototype' file
+#!default <mode> <owner> <group> # default used if not specified on entry
+#!<param>=<value> # puts parameter in pkg environment
+#
+# Include ISA independent files (prototype_com)
+#
+!include prototype_com
+#
+#
+# List files which are SPARC specific here
+#
+# source locations relative to the prototype file
+#
+#
+# SUNWiscsitu
+#
+d none usr/lib/sparcv9 0755 root bin
+f none usr/lib/sparcv9/libiscsit.so.1 0755 root bin
+s none usr/lib/sparcv9/libiscsit.so=libiscsit.so.1
+f none usr/lib/sparcv9/llib-liscsit.ln 644 root bin
diff --git a/usr/src/pkgdefs/etc/exception_list_i386 b/usr/src/pkgdefs/etc/exception_list_i386
index 2d2a0a5f37..a4320e8aac 100644
--- a/usr/src/pkgdefs/etc/exception_list_i386
+++ b/usr/src/pkgdefs/etc/exception_list_i386
@@ -876,6 +876,21 @@ lib/llib-liscsitgt.ln i386
lib/amd64/libiscsitgt.so i386
lib/amd64/llib-liscsitgt.ln i386
#
+# These files are used by the COMSTAR iSCSI target port provider
+#
+usr/include/sys/idm i386
+usr/include/sys/idm/idm.h i386
+usr/include/sys/idm/idm_conn_sm.h i386
+usr/include/sys/idm/idm_impl.h i386
+usr/include/sys/idm/idm_so.h i386
+usr/include/sys/idm/idm_text.h i386
+usr/include/sys/idm/idm_transport.h i386
+usr/include/sys/iscsit/chap.h i386
+usr/include/sys/iscsit/iscsi_if.h i386
+usr/include/sys/iscsit/isns_protocol.h i386
+usr/include/sys/iscsit/radius_packet.h i386
+usr/include/sys/iscsit/radius_protocol.h i386
+#
# libshare is private and the 64-bit sharemgr is not delivered.
#
usr/lib/libshare.so i386
diff --git a/usr/src/pkgdefs/etc/exception_list_sparc b/usr/src/pkgdefs/etc/exception_list_sparc
index 21bb574f2c..1144b94dcd 100644
--- a/usr/src/pkgdefs/etc/exception_list_sparc
+++ b/usr/src/pkgdefs/etc/exception_list_sparc
@@ -952,6 +952,21 @@ lib/llib-liscsitgt.ln sparc
lib/sparcv9/libiscsitgt.so sparc
lib/sparcv9/llib-liscsitgt.ln sparc
#
+# These files are used by the COMSTAR iSCSI target port provider
+#
+usr/include/sys/idm sparc
+usr/include/sys/idm/idm.h sparc
+usr/include/sys/idm/idm_conn_sm.h sparc
+usr/include/sys/idm/idm_impl.h sparc
+usr/include/sys/idm/idm_so.h sparc
+usr/include/sys/idm/idm_text.h sparc
+usr/include/sys/idm/idm_transport.h sparc
+usr/include/sys/iscsit/chap.h sparc
+usr/include/sys/iscsit/iscsi_if.h sparc
+usr/include/sys/iscsit/isns_protocol.h sparc
+usr/include/sys/iscsit/radius_packet.h sparc
+usr/include/sys/iscsit/radius_protocol.h sparc
+#
# libshare is private and the 64-bit sharemg is not delivered.
#
usr/lib/libshare.so sparc
diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files
index 87df03b0b4..fd16747a60 100644
--- a/usr/src/uts/common/Makefile.files
+++ b/usr/src/uts/common/Makefile.files
@@ -815,6 +815,14 @@ FCT_OBJS += discovery.o fct.o
QLT_OBJS += 2400.o 2500.o qlt.o qlt_dma.o
+ISCSIT_SHARED_OBJS += \
+ iscsit_common.o
+
+ISCSIT_OBJS += $(ISCSIT_SHARED_OBJS) \
+ iscsit.o iscsit_tgt.o iscsit_sess.o iscsit_login.o \
+ iscsit_text.o iscsit_isns.o iscsit_radiusauth.o \
+ iscsit_radiuspacket.o iscsit_auth.o iscsit_authclient.o
+
STMF_OBJS += lun_map.o stmf.o
STMF_SBD_OBJS += filedisk.o memdisk.o sbd.o sbd_scsi.o
@@ -1631,6 +1639,11 @@ DDA_OBJS += dda.o
DMD_OBJS += dmd.o
+IDM_SHARED_OBJS += base64.o
+
+IDM_OBJS += $(IDM_SHARED_OBJS) \
+ idm.o idm_impl.o idm_text.o idm_conn_sm.o idm_so.o
+
#
# Build up defines and paths.
#
diff --git a/usr/src/uts/common/Makefile.rules b/usr/src/uts/common/Makefile.rules
index f131d68608..2aa5f49e5a 100644
--- a/usr/src/uts/common/Makefile.rules
+++ b/usr/src/uts/common/Makefile.rules
@@ -565,6 +565,14 @@ $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/comstar/port/qlt/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
+$(OBJS_DIR)/%.o: $(COMMONBASE)/iscsit/%.c
+ $(COMPILE.c) -o $@ $<
+ $(CTFCONVERT_O)
+
+$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/comstar/port/iscsit/%.c
+ $(COMPILE.c) -o $@ $<
+ $(CTFCONVERT_O)
+
$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/comstar/lu/stmf_sbd/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
@@ -633,6 +641,14 @@ $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/ib/ibtl/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
+$(OBJS_DIR)/%.o: $(COMMONBASE)/iscsi/%.c
+ $(COMPILE.c) -o $@ $<
+ $(CTFCONVERT_O)
+
+$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/idm/%.c
+ $(COMPILE.c) -o $@ $<
+ $(CTFCONVERT_O)
+
$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/ipw/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
@@ -1613,6 +1629,12 @@ $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/comstar/port/fct/%.c
$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/comstar/port/qlt/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
+$(LINTS_DIR)/%.ln: $(COMMONBASE)/iscsit/%.c
+ @($(LHEAD) $(LINT.c) $< $(LTAIL))
+
+$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/comstar/port/iscsit/%.c
+ @($(LHEAD) $(LINT.c) $< $(LTAIL))
+
$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/comstar/stmf/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
@@ -1664,6 +1686,12 @@ $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/ib/ibnex/%.c
$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/ib/ibtl/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
+$(LINTS_DIR)/%.ln: $(COMMONBASE)/iscsi/%.c
+ @($(LHEAD) $(LINT.c) $< $(LTAIL))
+
+$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/idm/%.c
+ @($(LHEAD) $(LINT.c) $< $(LTAIL))
+
$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/ipw/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit.c
new file mode 100644
index 0000000000..59412b159e
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit.c
@@ -0,0 +1,2480 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/cpuvar.h>
+#include <sys/types.h>
+#include <sys/conf.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/modctl.h>
+#include <sys/sysmacros.h>
+#include <sys/socket.h>
+#include <sys/strsubr.h>
+#include <sys/nvpair.h>
+
+#include <sys/stmf.h>
+#include <sys/stmf_ioctl.h>
+#include <sys/portif.h>
+#include <sys/idm/idm.h>
+#include <sys/idm/idm_conn_sm.h>
+#include <iscsit_isns.h>
+#include <iscsit.h>
+
+#define ISCSIT_VERSION BUILD_DATE "-1.18dev"
+#define ISCSIT_NAME_VERSION "COMSTAR ISCSIT v" ISCSIT_VERSION
+
+/*
+ * DDI entry points.
+ */
+static int iscsit_drv_attach(dev_info_t *, ddi_attach_cmd_t);
+static int iscsit_drv_detach(dev_info_t *, ddi_detach_cmd_t);
+static int iscsit_drv_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
+static int iscsit_drv_open(dev_t *, int, int, cred_t *);
+static int iscsit_drv_close(dev_t, int, int, cred_t *);
+static boolean_t iscsit_drv_busy(void);
+static int iscsit_drv_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
+static boolean_t iscsit_cmdsn_in_window(iscsit_conn_t *ict, uint32_t cmdsn);
+static void iscsit_send_direct_scsi_resp(iscsit_conn_t *ict, idm_pdu_t *rx_pdu,
+ uint8_t response, uint8_t cmd_status);
+static void iscsit_send_task_mgmt_resp(idm_pdu_t *tm_resp_pdu,
+ uint8_t tm_status);
+
+extern struct mod_ops mod_miscops;
+
+
+static struct cb_ops iscsit_cb_ops = {
+ iscsit_drv_open, /* cb_open */
+ iscsit_drv_close, /* cb_close */
+ nodev, /* cb_strategy */
+ nodev, /* cb_print */
+ nodev, /* cb_dump */
+ nodev, /* cb_read */
+ nodev, /* cb_write */
+ iscsit_drv_ioctl, /* cb_ioctl */
+ nodev, /* cb_devmap */
+ nodev, /* cb_mmap */
+ nodev, /* cb_segmap */
+ nochpoll, /* cb_chpoll */
+ ddi_prop_op, /* cb_prop_op */
+ NULL, /* cb_streamtab */
+ D_MP, /* cb_flag */
+ CB_REV, /* cb_rev */
+ nodev, /* cb_aread */
+ nodev, /* cb_awrite */
+};
+
+static struct dev_ops iscsit_dev_ops = {
+ DEVO_REV, /* devo_rev */
+ 0, /* devo_refcnt */
+ iscsit_drv_getinfo, /* devo_getinfo */
+ nulldev, /* devo_identify */
+ nulldev, /* devo_probe */
+ iscsit_drv_attach, /* devo_attach */
+ iscsit_drv_detach, /* devo_detach */
+ nodev, /* devo_reset */
+ &iscsit_cb_ops, /* devo_cb_ops */
+ NULL, /* devo_bus_ops */
+ NULL, /* devo_power */
+};
+
+static struct modldrv modldrv = {
+ &mod_driverops,
+ "iSCSI Target",
+ &iscsit_dev_ops,
+};
+
+static struct modlinkage modlinkage = {
+ MODREV_1,
+ &modldrv,
+ NULL,
+};
+
+
+iscsit_global_t iscsit_global;
+
+kmem_cache_t *iscsit_status_pdu_cache;
+
+boolean_t iscsit_sm_logging = B_FALSE;
+
+static idm_status_t iscsit_init(dev_info_t *dip);
+static idm_status_t iscsit_enable_svc(iscsit_hostinfo_t *hostinfo);
+static void iscsit_disable_svc(void);
+
+static void
+iscsit_op_scsi_task_mgmt(iscsit_conn_t *ict, idm_pdu_t *rx_pdu);
+
+static void
+iscsit_pdu_op_noop(iscsit_conn_t *ict, idm_pdu_t *rx_pdu);
+
+static void
+iscsit_pdu_op_login_cmd(iscsit_conn_t *ict, idm_pdu_t *rx_pdu);
+
+void
+iscsit_pdu_op_text_cmd(iscsit_conn_t *ict, idm_pdu_t *rx_pdu);
+
+static void
+iscsit_pdu_op_logout_cmd(iscsit_conn_t *ict, idm_pdu_t *rx_pdu);
+
+int iscsit_cmd_window();
+
+void
+iscsit_set_cmdsn(iscsit_conn_t *ict, idm_pdu_t *rx_pdu);
+
+static void
+iscsit_calc_rspsn(iscsit_conn_t *ict, idm_pdu_t *resp);
+
+static void
+iscsit_deferred_dispatch(idm_pdu_t *rx_pdu);
+
+static void
+iscsit_deferred(void *rx_pdu_void);
+
+static idm_status_t
+iscsit_conn_accept(idm_conn_t *ic);
+
+static idm_status_t
+iscsit_ffp_enabled(idm_conn_t *ic);
+
+static idm_status_t
+iscsit_ffp_disabled(idm_conn_t *ic, idm_ffp_disable_t disable_class);
+
+static idm_status_t
+iscsit_conn_lost(idm_conn_t *ic);
+
+static idm_status_t
+iscsit_conn_destroy(idm_conn_t *ic);
+
+static stmf_data_buf_t *
+iscsit_dbuf_alloc(scsi_task_t *task, uint32_t size, uint32_t *pminsize,
+ uint32_t flags);
+
+static void
+iscsit_dbuf_free(stmf_dbuf_store_t *ds, stmf_data_buf_t *dbuf);
+
+static void
+iscsit_buf_xfer_cb(idm_buf_t *idb, idm_status_t status);
+
+static void
+iscsit_send_good_status_done(idm_pdu_t *pdu, idm_status_t status);
+
+static void
+iscsit_send_status_done(idm_pdu_t *pdu, idm_status_t status);
+
+static stmf_status_t
+iscsit_idm_to_stmf(idm_status_t idmrc);
+
+static iscsit_task_t *
+iscsit_task_alloc(iscsit_conn_t *ict);
+
+static void
+iscsit_task_free(iscsit_task_t *itask);
+
+static iscsit_task_t *
+iscsit_tm_task_alloc(iscsit_conn_t *ict);
+
+static void
+iscsit_tm_task_free(iscsit_task_t *itask);
+
+static int
+iscsit_status_pdu_constructor(void *pdu_void, void *arg, int flags);
+
+static void
+iscsit_pp_cb(struct stmf_port_provider *pp, int cmd, void *arg, uint32_t flags);
+
+static it_cfg_status_t
+iscsit_config_merge(it_config_t *cfg);
+
+static idm_status_t
+iscsit_login_fail(idm_conn_t *ic);
+
+int
+_init(void)
+{
+ int rc;
+
+ rw_init(&iscsit_global.global_rwlock, NULL, RW_DRIVER, NULL);
+ iscsit_global.global_svc_state = ISE_DETACHED;
+
+ if ((rc = mod_install(&modlinkage)) != 0) {
+ rw_destroy(&iscsit_global.global_rwlock);
+ return (rc);
+ }
+
+ return (rc);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
+
+int
+_fini(void)
+{
+ int rc;
+
+ rc = mod_remove(&modlinkage);
+
+ if (rc == 0) {
+ rw_destroy(&iscsit_global.global_rwlock);
+ }
+
+ return (rc);
+}
+
+/*
+ * DDI entry points.
+ */
+
+/* ARGSUSED */
+static int
+iscsit_drv_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,
+ void **result)
+{
+ ulong_t instance = getminor((dev_t)arg);
+
+ switch (cmd) {
+ case DDI_INFO_DEVT2DEVINFO:
+ *result = iscsit_global.global_dip;
+ return (DDI_SUCCESS);
+
+ case DDI_INFO_DEVT2INSTANCE:
+ *result = (void *)instance;
+ return (DDI_SUCCESS);
+
+ default:
+ break;
+ }
+
+ return (DDI_FAILURE);
+}
+
+static int
+iscsit_drv_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+ if (cmd != DDI_ATTACH) {
+ return (DDI_FAILURE);
+ }
+
+ if (ddi_get_instance(dip) != 0) {
+ /* we only allow instance 0 to attach */
+ return (DDI_FAILURE);
+ }
+
+ /* create the minor node */
+ if (ddi_create_minor_node(dip, ISCSIT_MODNAME, S_IFCHR, 0,
+ DDI_PSEUDO, 0) != DDI_SUCCESS) {
+ cmn_err(CE_WARN, "iscsit_drv_attach: "
+ "failed creating minor node");
+ return (DDI_FAILURE);
+ }
+
+ if (iscsit_init(dip) != IDM_STATUS_SUCCESS) {
+ cmn_err(CE_WARN, "iscsit_drv_attach: "
+ "failed to initialize");
+ ddi_remove_minor_node(dip, NULL);
+ return (DDI_FAILURE);
+ }
+
+ iscsit_global.global_svc_state = ISE_DISABLED;
+ iscsit_global.global_dip = dip;
+
+ return (DDI_SUCCESS);
+}
+
+/*ARGSUSED*/
+static int
+iscsit_drv_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ if (cmd != DDI_DETACH)
+ return (DDI_FAILURE);
+
+ ISCSIT_GLOBAL_LOCK(RW_WRITER);
+ if (iscsit_drv_busy()) {
+ ISCSIT_GLOBAL_UNLOCK();
+ return (EBUSY);
+ }
+
+ iscsit_global.global_dip = NULL;
+ ddi_remove_minor_node(dip, NULL);
+
+ ldi_ident_release(iscsit_global.global_li);
+ iscsit_global.global_svc_state = ISE_DETACHED;
+
+ ISCSIT_GLOBAL_UNLOCK();
+
+ return (DDI_SUCCESS);
+}
+
+/*ARGSUSED*/
+static int
+iscsit_drv_open(dev_t *devp, int flag, int otyp, cred_t *credp)
+{
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+iscsit_drv_close(dev_t dev, int flag, int otyp, cred_t *credp)
+{
+ return (0);
+}
+
+static boolean_t
+iscsit_drv_busy(void)
+{
+ switch (iscsit_global.global_svc_state) {
+ case ISE_DISABLED:
+ case ISE_DETACHED:
+ return (B_FALSE);
+ default:
+ return (B_TRUE);
+ }
+ /* NOTREACHED */
+}
+
+/* ARGSUSED */
+static int
+iscsit_drv_ioctl(dev_t drv, int cmd, intptr_t argp, int flag, cred_t *cred,
+ int *retval)
+{
+ iscsit_ioc_set_config_t setcfg;
+ iscsit_ioc_set_config32_t setcfg32;
+ /* iscsit_ioc_get_config_t getcfg; */
+ char *cfg_pnvlist;
+ nvlist_t *cfg_nvlist;
+ it_config_t *cfg;
+ idm_status_t idmrc;
+ int rc = 0;
+
+ if (drv_priv(cred) != 0) {
+ return (EPERM);
+ }
+
+ ISCSIT_GLOBAL_LOCK(RW_WRITER);
+
+ /*
+ * Validate ioctl requests against global service state
+ */
+ switch (iscsit_global.global_svc_state) {
+ case ISE_ENABLED:
+ if (cmd == ISCSIT_IOC_DISABLE_SVC) {
+ iscsit_global.global_svc_state = ISE_DISABLING;
+ } else if (cmd == ISCSIT_IOC_ENABLE_SVC) {
+ /* Already enabled */
+ ISCSIT_GLOBAL_UNLOCK();
+ return (0);
+ } else {
+ iscsit_global.global_svc_state = ISE_BUSY;
+ }
+ break;
+ case ISE_DISABLED:
+ if (cmd == ISCSIT_IOC_ENABLE_SVC) {
+ iscsit_global.global_svc_state = ISE_ENABLING;
+ } else if (cmd == ISCSIT_IOC_DISABLE_SVC) {
+ /* Already disabled */
+ ISCSIT_GLOBAL_UNLOCK();
+ return (0);
+ } else {
+ rc = EFAULT;
+ }
+ break;
+ case ISE_ENABLING:
+ case ISE_DISABLING:
+ rc = EAGAIN;
+ break;
+ case ISE_DETACHED:
+ default:
+ rc = EFAULT;
+ break;
+ }
+
+ ISCSIT_GLOBAL_UNLOCK();
+ if (rc != 0)
+ return (rc);
+
+ /* Handle ioctl request (enable/disable have already been handled) */
+ switch (cmd) {
+ case ISCSIT_IOC_SET_CONFIG:
+ switch (ddi_model_convert_from(flag & FMODELS)) {
+ case DDI_MODEL_ILP32:
+ if (ddi_copyin((void *)argp, &setcfg32,
+ sizeof (iscsit_ioc_set_config32_t), flag) != 0)
+ return (EFAULT);
+
+ setcfg.set_cfg_pnvlist =
+ (char *)((uintptr_t)setcfg32.set_cfg_pnvlist);
+ setcfg.set_cfg_vers = setcfg32.set_cfg_vers;
+ setcfg.set_cfg_pnvlist_len =
+ setcfg32.set_cfg_pnvlist_len;
+ break;
+ case DDI_MODEL_NONE:
+ if (ddi_copyin((void *)argp, &setcfg,
+ sizeof (iscsit_ioc_set_config_t), flag) != 0)
+ return (EFAULT);
+ break;
+ }
+
+ /* Check API version */
+ if (setcfg.set_cfg_vers != ISCSIT_API_VERS0) {
+ return (EINVAL);
+ }
+
+ /* Config is in packed nvlist format so unpack it */
+ cfg_pnvlist = kmem_alloc(setcfg.set_cfg_pnvlist_len,
+ KM_SLEEP);
+ ASSERT(cfg_pnvlist != NULL);
+
+ if (ddi_copyin(setcfg.set_cfg_pnvlist, cfg_pnvlist,
+ setcfg.set_cfg_pnvlist_len, flag) != 0) {
+ kmem_free(cfg_pnvlist, setcfg.set_cfg_pnvlist_len);
+ return (EFAULT);
+ }
+
+ if (nvlist_unpack(cfg_pnvlist, setcfg.set_cfg_pnvlist_len,
+ &cfg_nvlist, KM_SLEEP) != 0) {
+ kmem_free(cfg_pnvlist, setcfg.set_cfg_pnvlist_len);
+ return (EINVAL);
+ }
+
+ /* Translate nvlist */
+ if (it_nv_to_config(cfg_nvlist, &cfg) != 0) {
+ cmn_err(CE_WARN, "Configuration is invalid");
+ kmem_free(cfg_pnvlist, setcfg.set_cfg_pnvlist_len);
+ nvlist_free(cfg_nvlist);
+ return (EINVAL);
+ }
+
+ /* Update config */
+ if (iscsit_config_merge(cfg) != 0) {
+ kmem_free(cfg_pnvlist, setcfg.set_cfg_pnvlist_len);
+ nvlist_free(cfg_nvlist);
+ return (EIO);
+ }
+
+ it_config_free_cmn(cfg);
+ kmem_free(cfg_pnvlist, setcfg.set_cfg_pnvlist_len);
+ nvlist_free(cfg_nvlist);
+
+ /*
+ * Now that the reconfig is complete set our state back to
+ * enabled.
+ */
+ ISCSIT_GLOBAL_LOCK(RW_WRITER);
+ iscsit_global.global_svc_state = ISE_ENABLED;
+ ISCSIT_GLOBAL_UNLOCK();
+ break;
+ case ISCSIT_IOC_ENABLE_SVC: {
+ iscsit_hostinfo_t hostinfo;
+
+ if (ddi_copyin((void *)argp, &hostinfo.length,
+ sizeof (hostinfo.length), flag) != 0) {
+ iscsit_global.global_svc_state = ISE_DISABLED;
+ return (EFAULT);
+ }
+
+ if (hostinfo.length > sizeof (hostinfo.fqhn))
+ hostinfo.length = sizeof (hostinfo.fqhn);
+
+ if (ddi_copyin((void *)((caddr_t)argp +
+ sizeof (hostinfo.length)), &hostinfo.fqhn,
+ hostinfo.length, flag) != 0) {
+ iscsit_global.global_svc_state = ISE_DISABLED;
+ return (EFAULT);
+ }
+
+ idmrc = iscsit_enable_svc(&hostinfo);
+ ISCSIT_GLOBAL_LOCK(RW_WRITER);
+ if (idmrc == IDM_STATUS_SUCCESS) {
+ iscsit_global.global_svc_state = ISE_ENABLED;
+ } else {
+ rc = EIO;
+ iscsit_global.global_svc_state = ISE_DISABLED;
+ }
+ ISCSIT_GLOBAL_UNLOCK();
+ break;
+ }
+ case ISCSIT_IOC_DISABLE_SVC:
+ iscsit_disable_svc();
+ ISCSIT_GLOBAL_LOCK(RW_WRITER);
+ iscsit_global.global_svc_state = ISE_DISABLED;
+ ISCSIT_GLOBAL_UNLOCK();
+ break;
+ default:
+ rc = EINVAL;
+ }
+
+ /* Don't forget to clear ISE_BUSY state */
+ ASSERT(iscsit_global.global_svc_state != ISE_BUSY);
+
+ return (rc);
+}
+
+static idm_status_t
+iscsit_init(dev_info_t *dip)
+{
+ int rc;
+
+ rc = ldi_ident_from_dip(dip, &iscsit_global.global_li);
+ ASSERT(rc == 0); /* Failure indicates invalid argument */
+
+ iscsit_global.global_svc_state = ISE_DISABLED;
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+/*
+ * iscsit_enable_svc
+ *
+ * registers all the configured targets and target portals with STMF
+ */
+static idm_status_t
+iscsit_enable_svc(iscsit_hostinfo_t *hostinfo)
+{
+ stmf_port_provider_t *pp;
+ stmf_dbuf_store_t *dbuf_store;
+ boolean_t did_iscsit_isns_init;
+ idm_status_t retval = IDM_STATUS_SUCCESS;
+
+ ASSERT(iscsit_global.global_svc_state == ISE_ENABLING);
+
+ /*
+ * Make sure that can tell if we have partially allocated
+ * in case we need to exit and tear down anything allocated.
+ */
+ iscsit_global.global_tsih_pool = NULL;
+ iscsit_global.global_dbuf_store = NULL;
+ iscsit_status_pdu_cache = NULL;
+ pp = NULL;
+ iscsit_global.global_pp = NULL;
+ iscsit_global.global_default_tpg = NULL;
+ did_iscsit_isns_init = B_FALSE;
+ iscsit_global.global_dispatch_taskq = NULL;
+
+ /* Setup remaining fields in iscsit_global_t */
+ idm_refcnt_init(&iscsit_global.global_refcnt,
+ &iscsit_global);
+
+ avl_create(&iscsit_global.global_discovery_sessions,
+ iscsit_sess_avl_compare, sizeof (iscsit_sess_t),
+ offsetof(iscsit_sess_t, ist_tgt_ln));
+
+ avl_create(&iscsit_global.global_target_list,
+ iscsit_tgt_avl_compare, sizeof (iscsit_tgt_t),
+ offsetof(iscsit_tgt_t, target_global_ln));
+
+ list_create(&iscsit_global.global_deleted_target_list,
+ sizeof (iscsit_tgt_t),
+ offsetof(iscsit_tgt_t, target_global_deleted_ln));
+
+ avl_create(&iscsit_global.global_tpg_list,
+ iscsit_tpg_avl_compare, sizeof (iscsit_tpg_t),
+ offsetof(iscsit_tpg_t, tpg_global_ln));
+
+ avl_create(&iscsit_global.global_ini_list,
+ iscsit_ini_avl_compare, sizeof (iscsit_ini_t),
+ offsetof(iscsit_ini_t, ini_global_ln));
+
+ iscsit_global.global_tsih_pool = vmem_create("iscsit_tsih_pool",
+ (void *)1, ISCSI_MAX_TSIH, 1, NULL, NULL, NULL, 0,
+ VM_SLEEP | VMC_IDENTIFIER);
+
+ /*
+ * Setup STMF dbuf store. Our buffers are bound to a specific
+ * connection so we really can't let STMF cache buffers for us.
+ * Consequently we'll just allocate one global buffer store.
+ */
+ dbuf_store = stmf_alloc(STMF_STRUCT_DBUF_STORE, 0, 0);
+ if (dbuf_store == NULL) {
+ retval = IDM_STATUS_FAIL;
+ goto tear_down_and_return;
+ }
+ dbuf_store->ds_alloc_data_buf = iscsit_dbuf_alloc;
+ dbuf_store->ds_free_data_buf = iscsit_dbuf_free;
+ dbuf_store->ds_port_private = NULL;
+ iscsit_global.global_dbuf_store = dbuf_store;
+
+ /* Status PDU cache */
+ iscsit_status_pdu_cache = kmem_cache_create("iscsit_status_pdu_cache",
+ sizeof (idm_pdu_t) + sizeof (iscsi_scsi_rsp_hdr_t), 8,
+ &iscsit_status_pdu_constructor,
+ NULL, NULL, NULL, NULL, KM_SLEEP);
+
+ /* Default TPG and portal */
+ iscsit_global.global_default_tpg = iscsit_tpg_createdefault();
+ if (iscsit_global.global_default_tpg == NULL) {
+ retval = IDM_STATUS_FAIL;
+ goto tear_down_and_return;
+ }
+
+ /* initialize isns client */
+ (void) iscsit_isns_init(hostinfo);
+ did_iscsit_isns_init = B_TRUE;
+
+ /* Register port provider */
+ pp = stmf_alloc(STMF_STRUCT_PORT_PROVIDER, 0, 0);
+ if (pp == NULL) {
+ retval = IDM_STATUS_FAIL;
+ goto tear_down_and_return;
+ }
+
+ pp->pp_portif_rev = PORTIF_REV_1;
+ pp->pp_instance = 0;
+ pp->pp_name = ISCSIT_MODNAME;
+ pp->pp_cb = iscsit_pp_cb;
+
+ iscsit_global.global_pp = pp;
+
+
+ if (stmf_register_port_provider(pp) != STMF_SUCCESS) {
+ retval = IDM_STATUS_FAIL;
+ goto tear_down_and_return;
+ }
+
+ iscsit_global.global_dispatch_taskq = taskq_create("iscsit_dispatch",
+ 1, minclsyspri, 16, 16, TASKQ_PREPOPULATE);
+
+ return (IDM_STATUS_SUCCESS);
+
+tear_down_and_return:
+
+ if (iscsit_global.global_dispatch_taskq) {
+ taskq_destroy(iscsit_global.global_dispatch_taskq);
+ iscsit_global.global_dispatch_taskq = NULL;
+ }
+
+ if (did_iscsit_isns_init)
+ iscsit_isns_fini();
+
+ if (iscsit_global.global_default_tpg) {
+ iscsit_tpg_destroydefault(iscsit_global.global_default_tpg);
+ iscsit_global.global_default_tpg = NULL;
+ }
+
+ if (iscsit_global.global_pp)
+ iscsit_global.global_pp = NULL;
+
+ if (pp)
+ stmf_free(pp);
+
+ if (iscsit_status_pdu_cache) {
+ kmem_cache_destroy(iscsit_status_pdu_cache);
+ iscsit_status_pdu_cache = NULL;
+ }
+
+ if (iscsit_global.global_dbuf_store) {
+ stmf_free(iscsit_global.global_dbuf_store);
+ iscsit_global.global_dbuf_store = NULL;
+ }
+
+ if (iscsit_global.global_tsih_pool) {
+ vmem_destroy(iscsit_global.global_tsih_pool);
+ iscsit_global.global_tsih_pool = NULL;
+ }
+
+ avl_destroy(&iscsit_global.global_ini_list);
+ avl_destroy(&iscsit_global.global_tpg_list);
+ list_destroy(&iscsit_global.global_deleted_target_list);
+ avl_destroy(&iscsit_global.global_target_list);
+ avl_destroy(&iscsit_global.global_discovery_sessions);
+
+ idm_refcnt_destroy(&iscsit_global.global_refcnt);
+
+ return (retval);
+}
+
+/*
+ * iscsit_disable_svc
+ *
+ * clean up all existing connections and deregister targets from STMF
+ */
+static void
+iscsit_disable_svc(void)
+{
+ iscsit_sess_t *sess;
+
+ ASSERT(iscsit_global.global_svc_state == ISE_DISABLING);
+
+ /* tear down discovery sessions */
+ for (sess = avl_first(&iscsit_global.global_discovery_sessions);
+ sess != NULL;
+ sess = AVL_NEXT(&iscsit_global.global_discovery_sessions, sess))
+ iscsit_sess_close(sess);
+
+ /*
+ * Passing NULL to iscsit_config_merge tells it to go to an empty
+ * config.
+ */
+ (void) iscsit_config_merge(NULL);
+
+ /*
+ * Wait until there are no more global references
+ */
+ idm_refcnt_wait_ref(&iscsit_global.global_refcnt);
+ idm_refcnt_destroy(&iscsit_global.global_refcnt);
+
+ /*
+ * Default TPG must be destroyed after global_refcnt is 0.
+ */
+ iscsit_tpg_destroydefault(iscsit_global.global_default_tpg);
+
+ avl_destroy(&iscsit_global.global_discovery_sessions);
+ list_destroy(&iscsit_global.global_deleted_target_list);
+ avl_destroy(&iscsit_global.global_target_list);
+ avl_destroy(&iscsit_global.global_tpg_list);
+ avl_destroy(&iscsit_global.global_ini_list);
+
+ taskq_destroy(iscsit_global.global_dispatch_taskq);
+
+ iscsit_isns_fini();
+
+ stmf_free(iscsit_global.global_dbuf_store);
+ iscsit_global.global_dbuf_store = NULL;
+
+ (void) stmf_deregister_port_provider(iscsit_global.global_pp);
+ stmf_free(iscsit_global.global_pp);
+ iscsit_global.global_pp = NULL;
+
+ kmem_cache_destroy(iscsit_status_pdu_cache);
+ iscsit_status_pdu_cache = NULL;
+
+ vmem_destroy(iscsit_global.global_tsih_pool);
+ iscsit_global.global_tsih_pool = NULL;
+}
+
+void
+iscsit_global_hold()
+{
+ idm_refcnt_hold(&iscsit_global.global_refcnt);
+}
+
+void
+iscsit_global_rele()
+{
+ idm_refcnt_rele(&iscsit_global.global_refcnt);
+}
+
+void
+iscsit_global_wait_ref()
+{
+ idm_refcnt_wait_ref(&iscsit_global.global_refcnt);
+}
+
+/*
+ * IDM callbacks
+ */
+
+/*ARGSUSED*/
+void
+iscsit_rx_pdu(idm_conn_t *ic, idm_pdu_t *rx_pdu)
+{
+ iscsit_conn_t *ict = ic->ic_handle;
+ switch (IDM_PDU_OPCODE(rx_pdu)) {
+ case ISCSI_OP_SCSI_CMD:
+ ASSERT(0); /* Shouldn't happen */
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+ break;
+ case ISCSI_OP_SNACK_CMD:
+ /*
+ * We'll need to handle this when we support ERL1/2. For
+ * now we treat it as a protocol error.
+ */
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+ idm_conn_event(ic, CE_TRANSPORT_FAIL, NULL);
+ break;
+ case ISCSI_OP_SCSI_TASK_MGT_MSG:
+ iscsit_set_cmdsn(ict, rx_pdu);
+ iscsit_op_scsi_task_mgmt(ict, rx_pdu);
+ break;
+ case ISCSI_OP_NOOP_OUT:
+ case ISCSI_OP_LOGIN_CMD:
+ case ISCSI_OP_TEXT_CMD:
+ case ISCSI_OP_LOGOUT_CMD:
+ /*
+ * If/when we switch to userland processing these PDU's
+ * will be handled by iscsitd.
+ */
+ iscsit_deferred_dispatch(rx_pdu);
+ break;
+ default:
+ /* Protocol error */
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+ idm_conn_event(ic, CE_TRANSPORT_FAIL, NULL);
+ break;
+ }
+}
+
+/*ARGSUSED*/
+void
+iscsit_rx_pdu_error(idm_conn_t *ic, idm_pdu_t *rx_pdu, idm_status_t status)
+{
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+}
+
+void
+iscsit_task_aborted(idm_task_t *idt, idm_status_t status)
+{
+ iscsit_task_t *itask = idt->idt_private;
+
+ switch (status) {
+ case IDM_STATUS_SUSPENDED:
+ break;
+ case IDM_STATUS_ABORTED:
+ mutex_enter(&itask->it_mutex);
+ itask->it_aborted = B_TRUE;
+ if (itask->it_stmf_abort) {
+ mutex_exit(&itask->it_mutex);
+ /*
+ * STMF has already asked for this task to be aborted.
+ * Cleanup the task and call STMF to let it know that
+ * the abort is complete.
+ */
+ idm_task_cleanup(idt);
+
+ /*
+ * STMF specification is wrong... says to return
+ * STMF_ABORTED, the code actually looks for
+ * STMF_ABORT_SUCCESS.
+ */
+ stmf_task_lport_aborted(itask->it_stmf_task,
+ STMF_ABORT_SUCCESS, STMF_IOF_LPORT_DONE);
+ return;
+ } else {
+ mutex_exit(&itask->it_mutex);
+ /*
+ * Tell STMF to stop processing the task. We will
+ * cleanup the task in the context of iscsit_abort
+ * (lport_abort).
+ */
+ idm_task_cleanup(idt);
+ stmf_abort(STMF_QUEUE_TASK_ABORT, itask->it_stmf_task,
+ STMF_ABORTED, NULL);
+ return;
+ }
+ /*NOTREACHED*/
+ default:
+ ASSERT(0);
+ }
+}
+
+/*ARGSUSED*/
+idm_status_t
+iscsit_client_notify(idm_conn_t *ic, idm_client_notify_t icn,
+ uintptr_t data)
+{
+ idm_status_t rc = IDM_STATUS_SUCCESS;
+
+ /*
+ * IDM client notifications will never occur at interrupt level
+ * since they are generated from the connection state machine which
+ * running on taskq threads.
+ *
+ */
+ switch (icn) {
+ case CN_CONNECT_ACCEPT:
+ rc = iscsit_conn_accept(ic); /* No data */
+ break;
+ case CN_FFP_ENABLED:
+ rc = iscsit_ffp_enabled(ic); /* No data */
+ break;
+ case CN_FFP_DISABLED:
+ /*
+ * Data indicates whether this was the result of an
+ * explicit logout request.
+ */
+ rc = iscsit_ffp_disabled(ic, (idm_ffp_disable_t)data);
+ break;
+ case CN_CONNECT_LOST:
+ rc = iscsit_conn_lost(ic);
+ break;
+ case CN_CONNECT_DESTROY:
+ rc = iscsit_conn_destroy(ic);
+ break;
+ case CN_LOGIN_FAIL:
+ /*
+ * Force the login state machine to completion
+ */
+ rc = iscsit_login_fail(ic);
+ break;
+ default:
+ rc = IDM_STATUS_REJECT;
+ break;
+ }
+
+ return (rc);
+}
+
+
+void
+iscsit_build_hdr(idm_task_t *idm_task, idm_pdu_t *pdu, uint8_t opcode)
+{
+ iscsit_task_t *itask = idm_task->idt_private;
+ iscsi_data_rsp_hdr_t *dh = (iscsi_data_rsp_hdr_t *)pdu->isp_hdr;
+
+ /*
+ * We acquired iscsit_sess_t.ist_sn_rwlock in iscsit_xfer_scsi_data
+ * in reader mode so we expect to be locked here
+ */
+
+ /*
+ * Lun is only required if the opcode == ISCSI_OP_SCSI_DATA_RSP
+ * and the 'A' bit is to be set
+ */
+ dh->opcode = opcode;
+ dh->itt = itask->it_itt;
+ dh->ttt = itask->it_ttt;
+ /* Statsn is only set during phase collapse */
+ dh->expcmdsn = htonl(itask->it_ict->ict_sess->ist_expcmdsn);
+ dh->maxcmdsn = htonl(itask->it_ict->ict_sess->ist_maxcmdsn);
+
+ /*
+ * IDM must set:
+ *
+ * data.flags and rtt.flags
+ * data.dlength
+ * data.datasn
+ * data.offset
+ * residual_count and cmd_status (if we ever implement phase collapse)
+ * rtt.rttsn
+ * rtt.data_offset
+ * rtt.data_length
+ */
+}
+
+static idm_status_t
+iscsit_conn_accept(idm_conn_t *ic)
+{
+ iscsit_conn_t *ict;
+
+ /*
+ * Allocate an associated iscsit structure to represent this
+ * connection. We shouldn't really create a session until we
+ * get the first login PDU.
+ */
+ ict = kmem_zalloc(sizeof (*ict), KM_SLEEP);
+
+ ict->ict_ic = ic;
+ ict->ict_statsn = 1;
+ ic->ic_handle = ict;
+ mutex_init(&ict->ict_mutex, NULL, MUTEX_DRIVER, NULL);
+ idm_refcnt_init(&ict->ict_refcnt, ict);
+
+ /*
+ * Initialize login state machine
+ */
+ if (iscsit_login_sm_init(ict) != IDM_STATUS_SUCCESS) {
+ return (IDM_STATUS_FAIL);
+ }
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+idm_status_t
+iscsit_conn_reinstate(iscsit_conn_t *reinstate_ict, iscsit_conn_t *new_ict)
+{
+ idm_status_t result;
+
+ /*
+ * Note in new connection state that this connection is
+ * reinstating an existing connection.
+ */
+ new_ict->ict_reinstating = B_TRUE;
+ new_ict->ict_reinstate_conn = reinstate_ict;
+ new_ict->ict_statsn = reinstate_ict->ict_statsn;
+
+ /*
+ * Now generate connection state machine event to existing connection
+ * so that it starts the cleanup process.
+ */
+ result = idm_conn_reinstate_event(reinstate_ict->ict_ic,
+ new_ict->ict_ic);
+
+ return (result);
+}
+
+void
+iscsit_conn_hold(iscsit_conn_t *ict)
+{
+ idm_refcnt_hold(&ict->ict_refcnt);
+}
+
+void
+iscsit_conn_rele(iscsit_conn_t *ict)
+{
+ idm_refcnt_rele(&ict->ict_refcnt);
+}
+
+void
+iscsit_conn_dispatch_hold(iscsit_conn_t *ict)
+{
+ idm_refcnt_hold(&ict->ict_dispatch_refcnt);
+}
+
+void
+iscsit_conn_dispatch_rele(iscsit_conn_t *ict)
+{
+ idm_refcnt_rele(&ict->ict_dispatch_refcnt);
+}
+
+static idm_status_t
+iscsit_login_fail(idm_conn_t *ic)
+{
+ iscsit_conn_t *ict = ic->ic_handle;
+
+ /* Generate login state machine event */
+ iscsit_login_sm_event(ict, ILE_LOGIN_CONN_ERROR, NULL);
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+static idm_status_t
+iscsit_ffp_enabled(idm_conn_t *ic)
+{
+ iscsit_conn_t *ict = ic->ic_handle;
+
+ /* Generate session state machine event */
+ iscsit_sess_sm_event(ict->ict_sess, SE_CONN_LOGGED_IN, ict);
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+static idm_status_t
+iscsit_ffp_disabled(idm_conn_t *ic, idm_ffp_disable_t disable_class)
+{
+ iscsit_conn_t *ict = ic->ic_handle;
+
+ /* Generate session state machine event */
+ switch (disable_class) {
+ case FD_CONN_FAIL:
+ iscsit_sess_sm_event(ict->ict_sess, SE_CONN_FFP_FAIL, ict);
+ break;
+ case FD_CONN_LOGOUT:
+ iscsit_sess_sm_event(ict->ict_sess, SE_CONN_FFP_DISABLE, ict);
+ break;
+ case FD_SESS_LOGOUT:
+ iscsit_sess_sm_event(ict->ict_sess, SE_SESSION_CLOSE, ict);
+ break;
+ default:
+ ASSERT(0);
+ }
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+static idm_status_t
+iscsit_conn_lost(idm_conn_t *ic)
+{
+ iscsit_conn_t *ict = ic->ic_handle;
+
+ mutex_enter(&ict->ict_mutex);
+ ict->ict_lost = B_TRUE;
+ mutex_exit(&ict->ict_mutex);
+
+ /*
+ * Make sure there aren't any PDU's transitioning from the receive
+ * handler to the dispatch taskq.
+ */
+ idm_refcnt_wait_ref(&ict->ict_dispatch_refcnt);
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+static idm_status_t
+iscsit_conn_destroy(idm_conn_t *ic)
+{
+ iscsit_conn_t *ict = ic->ic_handle;
+
+ mutex_enter(&ict->ict_mutex);
+ ict->ict_destroyed = B_TRUE;
+ mutex_exit(&ict->ict_mutex);
+
+ /* Generate session state machine event */
+ if (ict->ict_sess != NULL) {
+ /*
+ * Session state machine will call iscsit_conn_destroy_done()
+ * when it has removed references to this connection.
+ */
+ iscsit_sess_sm_event(ict->ict_sess, SE_CONN_FAIL, ict);
+ }
+
+ ict->ict_ic = NULL;
+
+ idm_refcnt_wait_ref(&ict->ict_refcnt);
+
+ mutex_destroy(&ict->ict_mutex);
+ idm_refcnt_destroy(&ict->ict_refcnt);
+ kmem_free(ict, sizeof (*ict));
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+/*
+ * STMF-related functions
+ *
+ * iSCSI to STMF mapping
+ *
+ * Session == ?
+ * Connection == bound to local port but not itself a local port
+ * Target
+ * Target portal (group?) == local port (really but we're not going to do this)
+ * iscsit needs to map connections to local ports (whatever we decide
+ * they are)
+ * Target == ?
+ */
+
+/*ARGSUSED*/
+static stmf_data_buf_t *
+iscsit_dbuf_alloc(scsi_task_t *task, uint32_t size, uint32_t *pminsize,
+ uint32_t flags)
+{
+ iscsit_task_t *itask = task->task_port_private;
+ idm_buf_t *idm_buffer;
+ iscsit_buf_t *ibuf;
+ stmf_data_buf_t *result;
+
+ /*
+ * Once the iSER picture is better understood it might make sense
+ * to pre-allocate some registered buffers, similar to what
+ * fct/qlt are doing. In the meantime hopefully stmf can allocate
+ * these things quickly.
+ *
+ * We can't support a transfer larger than MaxBurstLength bytes.
+ */
+ if (size > itask->it_ict->ict_op.op_max_burst_length) {
+ *pminsize = itask->it_ict->ict_op.op_max_burst_length;
+ return (NULL);
+ }
+
+ /* Alloc buffer */
+ idm_buffer = idm_buf_alloc(itask->it_ict->ict_ic, NULL, size);
+ if (idm_buffer != NULL) {
+ result = stmf_alloc(STMF_STRUCT_DATA_BUF,
+ sizeof (iscsit_buf_t), 0);
+ if (result != NULL) {
+ /* Fill in stmf_data_buf_t */
+ ibuf = result->db_port_private;
+ ibuf->ibuf_idm_buf = idm_buffer;
+ ibuf->ibuf_stmf_buf = result;
+ ibuf->ibuf_is_immed = B_FALSE;
+ result->db_flags = DB_DONT_CACHE;
+ result->db_buf_size = size;
+ result->db_data_size = size;
+ result->db_sglist_length = 1;
+ result->db_sglist[0].seg_addr = idm_buffer->idb_buf;
+ result->db_sglist[0].seg_length =
+ idm_buffer->idb_buflen;
+ return (result);
+ }
+
+ /* Couldn't get the stmf_data_buf_t so free the buffer */
+ idm_buf_free(idm_buffer);
+ }
+
+ return (NULL);
+}
+
+/*ARGSUSED*/
+static void
+iscsit_dbuf_free(stmf_dbuf_store_t *ds, stmf_data_buf_t *dbuf)
+{
+ iscsit_buf_t *ibuf = dbuf->db_port_private;
+
+ if (ibuf->ibuf_is_immed) {
+ /*
+ * The iscsit_buf_t structure itself will be freed with its
+ * associated task. Here we just need to free the PDU that
+ * held the immediate data.
+ */
+ idm_pdu_complete(ibuf->ibuf_immed_data_pdu, IDM_STATUS_SUCCESS);
+ ibuf->ibuf_immed_data_pdu = 0;
+ } else {
+ idm_buf_free(ibuf->ibuf_idm_buf);
+ stmf_free(dbuf);
+ }
+}
+
+/*ARGSUSED*/
+stmf_status_t
+iscsit_xfer_scsi_data(scsi_task_t *task, stmf_data_buf_t *dbuf,
+ uint32_t ioflags)
+{
+ iscsit_task_t *iscsit_task = task->task_port_private;
+ iscsit_buf_t *ibuf = dbuf->db_port_private;
+ int idm_rc;
+
+ /*
+ * If it's not immediate data then start the transfer
+ */
+ ASSERT(ibuf->ibuf_is_immed == B_FALSE);
+ if (dbuf->db_flags & DB_DIRECTION_TO_RPORT) {
+
+ /*
+ * IDM will call iscsit_build_hdr so lock now to serialize
+ * access to the SN values. We need to lock here to enforce
+ * lock ordering
+ */
+ rw_enter(&iscsit_task->it_ict->ict_sess->ist_sn_rwlock,
+ RW_READER);
+ idm_rc = idm_buf_tx_to_ini(iscsit_task->it_idm_task,
+ ibuf->ibuf_idm_buf, dbuf->db_relative_offset,
+ dbuf->db_data_size, &iscsit_buf_xfer_cb, dbuf);
+ rw_exit(&iscsit_task->it_ict->ict_sess->ist_sn_rwlock);
+
+ return (iscsit_idm_to_stmf(idm_rc));
+ } else if (dbuf->db_flags & DB_DIRECTION_FROM_RPORT) {
+ /* Grab the SN lock (see comment above) */
+ rw_enter(&iscsit_task->it_ict->ict_sess->ist_sn_rwlock,
+ RW_READER);
+ idm_rc = idm_buf_rx_from_ini(iscsit_task->it_idm_task,
+ ibuf->ibuf_idm_buf, dbuf->db_relative_offset,
+ dbuf->db_data_size, &iscsit_buf_xfer_cb, dbuf);
+ rw_exit(&iscsit_task->it_ict->ict_sess->ist_sn_rwlock);
+
+ return (iscsit_idm_to_stmf(idm_rc));
+ }
+
+ /* What are we supposed to do if there is no direction? */
+ return (STMF_INVALID_ARG);
+}
+
+static void
+iscsit_buf_xfer_cb(idm_buf_t *idb, idm_status_t status)
+{
+ iscsit_task_t *itask = idb->idb_task_binding->idt_private;
+ stmf_data_buf_t *dbuf = idb->idb_cb_arg;
+
+ dbuf->db_xfer_status = iscsit_idm_to_stmf(status);
+
+ /*
+ * COMSTAR currently requires port providers to support
+ * the DB_SEND_STATUS_GOOD flag even if phase collapse is
+ * not supported. So we will roll our own... pretend we are
+ * COMSTAR and ask for a status PDU.
+ */
+ if ((dbuf->db_flags & DB_SEND_STATUS_GOOD) &&
+ status == IDM_STATUS_SUCCESS) {
+ /*
+ * If iscsit_send_scsi_status succeeds then the TX PDU
+ * callback will call stmf_send_status_done and set
+ * STMF_IOF_LPORT_DONE. Consequently we don't need
+ * to call stmf_data_xfer_done in that case. We
+ * still need to call it if we get a failure.
+ *
+ * To elaborate on this some more, upon successful return
+ * from iscsit_send_scsi_status it's possible that itask
+ * and idb have been freed and are no longer valid.
+ */
+ if (iscsit_send_scsi_status(itask->it_stmf_task, 0)
+ != IDM_STATUS_SUCCESS) {
+ /* Failed to send status */
+ dbuf->db_xfer_status = STMF_FAILURE;
+ stmf_data_xfer_done(itask->it_stmf_task, dbuf,
+ STMF_IOF_LPORT_DONE);
+ }
+ } else {
+ stmf_data_xfer_done(itask->it_stmf_task, dbuf, 0);
+ }
+}
+
+/*ARGSUSED*/
+stmf_status_t
+iscsit_send_scsi_status(scsi_task_t *task, uint32_t ioflags)
+{
+ iscsit_task_t *itask = task->task_port_private;
+ iscsi_scsi_rsp_hdr_t *rsp;
+ idm_pdu_t *pdu;
+ int resp_datalen;
+
+ /*
+ * If this task is aborted then we don't need to respond.
+ */
+ if (itask->it_stmf_abort) {
+ stmf_send_status_done(task, STMF_SUCCESS,
+ STMF_IOF_LPORT_DONE);
+ return (STMF_SUCCESS);
+ }
+
+ /*
+ * If this is a task management status, handle it elsewhere.
+ */
+ if (task->task_mgmt_function != TM_NONE) {
+ /*
+ * Don't wait for the PDU completion to tell STMF
+ * the task is done -- it doesn't really matter and
+ * it makes life complicated if STMF later asks us to
+ * abort the request and we don't know whether the
+ * status has been sent or not.
+ */
+ itask->it_tm_responded = B_TRUE;
+ iscsit_send_task_mgmt_resp(itask->it_tm_pdu,
+ (task->task_completion_status == STMF_SUCCESS) ?
+ SCSI_TCP_TM_RESP_COMPLETE : SCSI_TCP_TM_RESP_FUNC_NOT_SUPP);
+ stmf_send_status_done(task, STMF_SUCCESS,
+ STMF_IOF_LPORT_DONE);
+ return (STMF_SUCCESS);
+ }
+
+ mutex_enter(&itask->it_idm_task->idt_mutex);
+ if ((itask->it_idm_task->idt_state == TASK_ACTIVE) &&
+ (task->task_completion_status == STMF_SUCCESS) &&
+ (task->task_sense_length == 0) &&
+ (task->task_resid == 0)) {
+ itask->it_idm_task->idt_state = TASK_COMPLETE;
+ /* PDU callback releases task hold */
+ idm_task_hold(itask->it_idm_task);
+ mutex_exit(&itask->it_idm_task->idt_mutex);
+ /*
+ * Fast path. Cached status PDU's are already
+ * initialized. We just need to fill in
+ * connection and task information.
+ */
+ pdu = kmem_cache_alloc(iscsit_status_pdu_cache, KM_SLEEP);
+ pdu->isp_ic = itask->it_ict->ict_ic;
+ pdu->isp_private = itask;
+
+ rsp = (iscsi_scsi_rsp_hdr_t *)pdu->isp_hdr;
+ rsp->itt = itask->it_itt;
+ rsp->cmd_status = task->task_scsi_status;
+ iscsit_pdu_tx(pdu);
+ return (STMF_SUCCESS);
+ } else {
+ if (itask->it_idm_task->idt_state != TASK_ACTIVE) {
+ mutex_exit(&itask->it_idm_task->idt_mutex);
+ return (IDM_STATUS_FAIL);
+ }
+ itask->it_idm_task->idt_state = TASK_COMPLETE;
+ /* PDU callback releases task hold */
+ idm_task_hold(itask->it_idm_task);
+ mutex_exit(&itask->it_idm_task->idt_mutex);
+
+ resp_datalen = (task->task_sense_length == 0) ? 0 :
+ (task->task_sense_length + sizeof (uint16_t));
+
+ pdu = idm_pdu_alloc(sizeof (iscsi_hdr_t), resp_datalen);
+ idm_pdu_init(pdu, itask->it_ict->ict_ic, itask,
+ iscsit_send_status_done);
+
+ rsp = (iscsi_scsi_rsp_hdr_t *)pdu->isp_hdr;
+ bzero(rsp, sizeof (*rsp));
+ rsp->opcode = ISCSI_OP_SCSI_RSP;
+
+ rsp->flags = ISCSI_FLAG_FINAL;
+ if (task->task_status_ctrl & TASK_SCTRL_OVER) {
+ rsp->flags |= ISCSI_FLAG_CMD_OVERFLOW;
+ } else if (task->task_status_ctrl & TASK_SCTRL_UNDER) {
+ rsp->flags |= ISCSI_FLAG_CMD_UNDERFLOW;
+ }
+
+ rsp->bi_residual_count = 0;
+ rsp->residual_count = htonl(task->task_resid);
+ rsp->itt = itask->it_itt;
+ rsp->response = ISCSI_STATUS_CMD_COMPLETED;
+ rsp->cmd_status = task->task_scsi_status;
+ if (task->task_sense_length != 0) {
+ /*
+ * Add a byte to provide the sense length in
+ * the response
+ */
+ *(uint16_t *)((void *)pdu->isp_data) =
+ htons(task->task_sense_length);
+ bcopy(task->task_sense_data,
+ (uint8_t *)pdu->isp_data +
+ sizeof (uint16_t),
+ task->task_sense_length);
+ hton24(rsp->dlength, resp_datalen);
+ }
+
+ iscsit_pdu_tx(pdu);
+
+ return (STMF_SUCCESS);
+ }
+}
+
+/*ARGSUSED*/
+static void
+iscsit_send_good_status_done(idm_pdu_t *pdu, idm_status_t status)
+{
+ iscsit_task_t *itask;
+
+ itask = pdu->isp_private;
+ idm_task_rele(itask->it_idm_task);
+ stmf_send_status_done(itask->it_stmf_task,
+ iscsit_idm_to_stmf(pdu->isp_status), STMF_IOF_LPORT_DONE);
+ kmem_cache_free(iscsit_status_pdu_cache, pdu);
+}
+
+/*ARGSUSED*/
+static void
+iscsit_send_status_done(idm_pdu_t *pdu, idm_status_t status)
+{
+ iscsit_task_t *itask;
+
+ itask = pdu->isp_private;
+ idm_task_rele(itask->it_idm_task);
+ stmf_send_status_done(itask->it_stmf_task,
+ iscsit_idm_to_stmf(pdu->isp_status), STMF_IOF_LPORT_DONE);
+ idm_pdu_free(pdu);
+}
+
+
+void
+iscsit_lport_task_free(scsi_task_t *task)
+{
+ iscsit_task_t *itask = task->task_port_private;
+
+ /* We only call idm_task_start for regular tasks, not task management */
+ if (task->task_mgmt_function == TM_NONE) {
+ idm_task_done(itask->it_idm_task);
+ iscsit_task_free(itask);
+ return;
+ } else {
+ iscsit_tm_task_free(itask);
+ }
+}
+
+/*ARGSUSED*/
+stmf_status_t
+iscsit_abort(stmf_local_port_t *lport, int abort_cmd, void *arg, uint32_t flags)
+{
+ scsi_task_t *st = (scsi_task_t *)arg;
+ iscsit_task_t *iscsit_task;
+ idm_task_t *idt;
+
+ /*
+ * If this is a task management request then there's really not much to
+ * do.
+ */
+ if (st->task_mgmt_function != TM_NONE) {
+ return (STMF_ABORT_SUCCESS);
+ }
+
+ /*
+ * Regular task, start cleaning up
+ */
+ iscsit_task = st->task_port_private;
+ idt = iscsit_task->it_idm_task;
+ mutex_enter(&iscsit_task->it_mutex);
+ iscsit_task->it_stmf_abort = B_TRUE;
+ if (iscsit_task->it_aborted) {
+ mutex_exit(&iscsit_task->it_mutex);
+ /*
+ * STMF specification is wrong... says to return
+ * STMF_ABORTED, the code actually looks for
+ * STMF_ABORT_SUCCESS.
+ */
+ return (STMF_ABORT_SUCCESS);
+ } else {
+ mutex_exit(&iscsit_task->it_mutex);
+ /*
+ * Call IDM to abort the task. Due to a variety of
+ * circumstances the task may already be in the process of
+ * aborting.
+ * We'll let IDM worry about rationalizing all that except
+ * for one particular instance. If the state of the task
+ * is TASK_COMPLETE, we need to indicate to the framework
+ * that we are in fact done. This typically happens with
+ * framework-initiated task management type requests
+ * (e.g. abort task).
+ */
+ if (idt->idt_state == TASK_COMPLETE) {
+ idm_refcnt_wait_ref(&idt->idt_refcnt);
+ return (STMF_ABORT_SUCCESS);
+ } else {
+ idm_task_abort(idt->idt_ic, idt, AT_TASK_MGMT_ABORT);
+ return (STMF_SUCCESS);
+ }
+ }
+
+ /*NOTREACHED*/
+}
+
+/*ARGSUSED*/
+void
+iscsit_ctl(stmf_local_port_t *lport, int cmd, void *arg)
+{
+ iscsit_tgt_t *iscsit_tgt;
+
+ ASSERT((cmd == STMF_CMD_LPORT_ONLINE) ||
+ (cmd == STMF_ACK_LPORT_ONLINE_COMPLETE) ||
+ (cmd == STMF_CMD_LPORT_OFFLINE) ||
+ (cmd == STMF_ACK_LPORT_OFFLINE_COMPLETE));
+
+ iscsit_tgt = (iscsit_tgt_t *)lport->lport_port_private;
+
+ switch (cmd) {
+ case STMF_CMD_LPORT_ONLINE:
+ iscsit_tgt_sm_event(iscsit_tgt, TE_STMF_ONLINE_REQ);
+ break;
+ case STMF_CMD_LPORT_OFFLINE:
+ iscsit_tgt_sm_event(iscsit_tgt, TE_STMF_OFFLINE_REQ);
+ break;
+ case STMF_ACK_LPORT_ONLINE_COMPLETE:
+ iscsit_tgt_sm_event(iscsit_tgt, TE_STMF_ONLINE_COMPLETE_ACK);
+ break;
+ case STMF_ACK_LPORT_OFFLINE_COMPLETE:
+ iscsit_tgt_sm_event(iscsit_tgt, TE_STMF_OFFLINE_COMPLETE_ACK);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static stmf_status_t
+iscsit_idm_to_stmf(idm_status_t idmrc)
+{
+ switch (idmrc) {
+ case IDM_STATUS_SUCCESS:
+ return (STMF_SUCCESS);
+ default:
+ return (STMF_FAILURE);
+ }
+ /*NOTREACHED*/
+}
+
+
+/*
+ * ISCSI protocol
+ */
+
+void
+iscsit_op_scsi_cmd(idm_conn_t *ic, idm_pdu_t *rx_pdu)
+{
+ iscsit_conn_t *ict;
+ iscsit_task_t *itask;
+ scsi_task_t *task;
+ iscsit_buf_t *ibuf;
+ iscsi_scsi_cmd_hdr_t *iscsi_scsi =
+ (iscsi_scsi_cmd_hdr_t *)rx_pdu->isp_hdr;
+ iscsi_addl_hdr_t *ahs_hdr;
+ uint16_t addl_cdb_len = 0;
+
+ ict = ic->ic_handle;
+
+ itask = iscsit_task_alloc(ict);
+ if (itask == NULL) {
+ iscsit_send_direct_scsi_resp(ict, rx_pdu,
+ ISCSI_STATUS_CMD_COMPLETED, STATUS_BUSY);
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+ return;
+ }
+
+ /* Finish processing request */
+ iscsit_set_cmdsn(ict, rx_pdu);
+
+ /*
+ * Note CmdSN and ITT in task. IDM will have already validated this
+ * request against the connection state so we don't need to check
+ * that (the connection may have changed state in the meantime but
+ * we will catch that when we try to send a response)
+ */
+ itask->it_cmdsn = ntohl(iscsi_scsi->cmdsn);
+ itask->it_itt = iscsi_scsi->itt;
+
+ /*
+ * Check for extended CDB AHS
+ */
+ if (iscsi_scsi->hlength > 0) {
+ ahs_hdr = (iscsi_addl_hdr_t *)iscsi_scsi;
+ addl_cdb_len = ((ahs_hdr->ahs_hlen_hi << 8) |
+ ahs_hdr->ahs_hlen_lo) - 1; /* Adjust for reserved byte */
+ if (((addl_cdb_len + 4) / sizeof (uint32_t)) >
+ iscsi_scsi->hlength) {
+ /* Mangled header info, drop it */
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+ return;
+ }
+ }
+
+ ict = rx_pdu->isp_ic->ic_handle; /* IDM client private */
+
+ itask->it_stmf_task = stmf_task_alloc(
+ itask->it_ict->ict_sess->ist_lport,
+ itask->it_ict->ict_sess->ist_stmf_sess, iscsi_scsi->lun,
+ 16 + addl_cdb_len, 0);
+ if (itask->it_stmf_task == NULL) {
+ /*
+ * Either stmf really couldn't get memory for a task or,
+ * more likely, the LU is currently in reset. Either way
+ * we have no choice but to fail the request.
+ */
+ iscsit_task_free(itask);
+ iscsit_send_direct_scsi_resp(ict, rx_pdu,
+ ISCSI_STATUS_CMD_COMPLETED, STATUS_BUSY);
+ idm_pdu_complete(rx_pdu, IDM_STATUS_FAIL);
+ return;
+ }
+
+ task = itask->it_stmf_task;
+ task->task_port_private = itask;
+
+ bcopy(iscsi_scsi->lun, task->task_lun_no, sizeof (task->task_lun_no));
+
+ /*
+ * iSCSI and Comstar use the same values. Should we rely on this
+ * or translate them bit-wise?
+ */
+
+ task->task_flags =
+ (((iscsi_scsi->flags & ISCSI_FLAG_CMD_READ) ? TF_READ_DATA : 0) |
+ ((iscsi_scsi->flags & ISCSI_FLAG_CMD_WRITE) ? TF_WRITE_DATA : 0) |
+ ((rx_pdu->isp_datalen == 0) ? 0 : TF_INITIAL_BURST));
+
+ switch (iscsi_scsi->flags & ISCSI_FLAG_CMD_ATTR_MASK) {
+ case ISCSI_ATTR_UNTAGGED:
+ break;
+ case ISCSI_ATTR_SIMPLE:
+ task->task_additional_flags |= TF_ATTR_SIMPLE_QUEUE;
+ break;
+ case ISCSI_ATTR_ORDERED:
+ task->task_additional_flags |= TF_ATTR_ORDERED_QUEUE;
+ break;
+ case ISCSI_ATTR_HEAD_OF_QUEUE:
+ task->task_additional_flags |= TF_ATTR_HEAD_OF_QUEUE;
+ break;
+ case ISCSI_ATTR_ACA:
+ task->task_additional_flags |= TF_ATTR_ACA;
+ break;
+ default:
+ /* Protocol error but just take it, treat as untagged */
+ break;
+ }
+
+
+ task->task_additional_flags = 0;
+ task->task_priority = 0;
+ task->task_mgmt_function = TM_NONE;
+
+ /*
+ * This "task_max_nbufs" doesn't map well to BIDI. We probably need
+ * parameter for each direction. "MaxOutstandingR2T" may very well
+ * be set to one which could prevent us from doing simultaneous
+ * transfers in each direction.
+ */
+ task->task_max_nbufs = (iscsi_scsi->flags & ISCSI_FLAG_CMD_WRITE) ?
+ ict->ict_op.op_max_outstanding_r2t : STMF_BUFS_MAX;
+ task->task_cmd_seq_no = ntohl(iscsi_scsi->itt);
+ task->task_expected_xfer_length = ntohl(iscsi_scsi->data_length);
+
+ /* Copy CDB */
+ bcopy(iscsi_scsi->scb, task->task_cdb, 16);
+ if (addl_cdb_len > 0) {
+ bcopy(ahs_hdr->ahs_extscb, task->task_cdb + 16, addl_cdb_len);
+ }
+
+ /*
+ * Copy the transport header into the task handle from the PDU
+ * handle. The transport header describes this task's remote tagged
+ * buffer.
+ */
+ if (rx_pdu->isp_transport_hdrlen != 0) {
+ bcopy(rx_pdu->isp_transport_hdr,
+ itask->it_idm_task->idt_transport_hdr,
+ rx_pdu->isp_transport_hdrlen);
+ }
+
+ /*
+ * Tell IDM about our new active task
+ */
+ idm_task_start(itask->it_idm_task, (uintptr_t)itask->it_itt);
+
+ /*
+ * If we have any immediate data then setup the immediate buffer
+ * context that comes with the task
+ */
+ if (rx_pdu->isp_datalen) {
+ ibuf = itask->it_immed_data;
+ ibuf->ibuf_immed_data_pdu = rx_pdu;
+ ibuf->ibuf_stmf_buf->db_data_size = rx_pdu->isp_datalen;
+ ibuf->ibuf_stmf_buf->db_buf_size = rx_pdu->isp_datalen;
+ ibuf->ibuf_stmf_buf->db_relative_offset = 0;
+ ibuf->ibuf_stmf_buf->db_sglist[0].seg_length =
+ rx_pdu->isp_datalen;
+ ibuf->ibuf_stmf_buf->db_sglist[0].seg_addr = rx_pdu->isp_data;
+
+ stmf_post_task(task, ibuf->ibuf_stmf_buf);
+ } else {
+ stmf_post_task(task, NULL);
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+ }
+}
+
+/*ARGSUSED*/
+void
+iscsit_deferred_dispatch(idm_pdu_t *rx_pdu)
+{
+ iscsit_conn_t *ict = rx_pdu->isp_ic->ic_handle;
+
+ /*
+ * If the connection has been lost then ignore new PDU's
+ */
+ mutex_enter(&ict->ict_mutex);
+ if (ict->ict_lost) {
+ mutex_exit(&ict->ict_mutex);
+ idm_pdu_complete(rx_pdu, IDM_STATUS_FAIL);
+ return;
+ }
+
+ /*
+ * Grab a hold on the connection to prevent it from going away
+ * between now and when the taskq function is called.
+ */
+ iscsit_conn_dispatch_hold(ict);
+ mutex_exit(&ict->ict_mutex);
+
+ if (taskq_dispatch(iscsit_global.global_dispatch_taskq,
+ iscsit_deferred, rx_pdu, DDI_NOSLEEP) == NULL) {
+ /*
+ * In the unlikely scenario that we couldn't get the resources
+ * to dispatch the PDU then just drop it.
+ */
+ idm_pdu_complete(rx_pdu, IDM_STATUS_FAIL);
+ idm_conn_event(ict->ict_ic, CE_TRANSPORT_FAIL, NULL);
+ iscsit_conn_dispatch_rele(ict);
+ }
+}
+
+static void
+iscsit_deferred(void *rx_pdu_void)
+{
+ idm_pdu_t *rx_pdu = rx_pdu_void;
+ idm_conn_t *ic = rx_pdu->isp_ic;
+ iscsit_conn_t *ict = ic->ic_handle;
+
+ switch (IDM_PDU_OPCODE(rx_pdu)) {
+ case ISCSI_OP_NOOP_OUT:
+ iscsit_set_cmdsn(ict, rx_pdu);
+ iscsit_pdu_op_noop(ict, rx_pdu);
+ break;
+ case ISCSI_OP_LOGIN_CMD:
+ iscsit_pdu_op_login_cmd(ict, rx_pdu);
+ break;
+ case ISCSI_OP_TEXT_CMD:
+ iscsit_set_cmdsn(ict, rx_pdu);
+ iscsit_pdu_op_text_cmd(ict, rx_pdu);
+ break;
+ case ISCSI_OP_LOGOUT_CMD:
+ iscsit_set_cmdsn(ict, rx_pdu);
+ iscsit_pdu_op_logout_cmd(ict, rx_pdu);
+ break;
+ default:
+ /* Protocol error. IDM should have caught this */
+ idm_pdu_complete(rx_pdu, IDM_STATUS_FAIL);
+ ASSERT(0);
+ break;
+ }
+
+ iscsit_conn_dispatch_rele(ict);
+}
+
+static void
+iscsit_send_direct_scsi_resp(iscsit_conn_t *ict, idm_pdu_t *rx_pdu,
+ uint8_t response, uint8_t cmd_status)
+{
+ idm_pdu_t *rsp_pdu;
+ idm_conn_t *ic;
+ iscsi_scsi_rsp_hdr_t *resp;
+
+ ic = ict->ict_ic;
+
+ rsp_pdu = idm_pdu_alloc(sizeof (iscsi_scsi_rsp_hdr_t), 0);
+ idm_pdu_init(rsp_pdu, ic, NULL, NULL);
+ resp = (iscsi_scsi_rsp_hdr_t *)rsp_pdu->isp_hdr;
+
+ resp->opcode = ISCSI_OP_SCSI_RSP;
+ resp->flags = ISCSI_FLAG_FINAL;
+ resp->response = response;
+ resp->cmd_status = cmd_status;
+ resp->itt = rx_pdu->isp_hdr->itt;
+
+ iscsit_pdu_tx(rsp_pdu);
+}
+
+void
+iscsit_send_task_mgmt_resp(idm_pdu_t *tm_resp_pdu, uint8_t tm_status)
+{
+ iscsi_scsi_task_mgt_rsp_hdr_t *tm_resp;
+
+ tm_resp = (iscsi_scsi_task_mgt_rsp_hdr_t *)tm_resp_pdu->isp_hdr;
+ tm_resp->response = tm_status;
+ iscsit_pdu_tx(tm_resp_pdu);
+}
+
+void
+iscsit_op_scsi_task_mgmt(iscsit_conn_t *ict, idm_pdu_t *rx_pdu)
+{
+ idm_pdu_t *tm_resp_pdu;
+ iscsit_task_t *itask;
+ iscsit_task_t *tm_itask;
+ scsi_task_t *task;
+ iscsi_scsi_task_mgt_hdr_t *iscsi_tm =
+ (iscsi_scsi_task_mgt_hdr_t *)rx_pdu->isp_hdr;
+ iscsi_scsi_task_mgt_rsp_hdr_t *iscsi_tm_rsp =
+ (iscsi_scsi_task_mgt_rsp_hdr_t *)rx_pdu->isp_hdr;
+ uint32_t rtt, cmdsn, refcmdsn;
+ uint8_t tm_func;
+
+ /*
+ * Setup response PDU (response field will get filled in later)
+ */
+ tm_resp_pdu = idm_pdu_alloc(sizeof (iscsi_scsi_task_mgt_rsp_hdr_t), 0);
+ if (tm_resp_pdu == NULL) {
+ /* Can't respond, just drop it */
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+ return;
+ }
+ idm_pdu_init(tm_resp_pdu, ict->ict_ic, NULL, NULL);
+ iscsi_tm_rsp = (iscsi_scsi_task_mgt_rsp_hdr_t *)tm_resp_pdu->isp_hdr;
+ bzero(iscsi_tm_rsp, sizeof (iscsi_scsi_task_mgt_rsp_hdr_t));
+ iscsi_tm_rsp->opcode = ISCSI_OP_SCSI_TASK_MGT_RSP;
+ iscsi_tm_rsp->flags = ISCSI_FLAG_FINAL;
+ iscsi_tm_rsp->itt = rx_pdu->isp_hdr->itt;
+
+ /*
+ * Figure out what we're being asked to do.
+ */
+ switch (iscsi_tm->function & ISCSI_FLAG_TASK_MGMT_FUNCTION_MASK) {
+ case ISCSI_TM_FUNC_ABORT_TASK:
+ /*
+ * STMF doesn't currently support the "abort task" task
+ * management command although it does support aborting
+ * an individual task. We'll get STMF to abort the task
+ * for us but handle the details of the task management
+ * command ourselves.
+ *
+ * Find the task associated with the referenced task tag.
+ */
+ rtt = iscsi_tm->rtt;
+ itask = (iscsit_task_t *)idm_task_find_by_handle(ict->ict_ic,
+ (uintptr_t)rtt);
+
+ if (itask == NULL) {
+ cmdsn = ntohl(iscsi_tm->cmdsn);
+ refcmdsn = ntohl(iscsi_tm->refcmdsn);
+
+ /*
+ * Task was not found. If RefCmdSN is within the CmdSN
+ * window and less than CmdSN of the TM function, return
+ * "Function Complete". Otherwise, return
+ * "Task Does Not Exist".
+ */
+
+ if (iscsit_cmdsn_in_window(ict, refcmdsn) &&
+ (refcmdsn < cmdsn)) {
+ iscsit_send_task_mgmt_resp(tm_resp_pdu,
+ SCSI_TCP_TM_RESP_COMPLETE);
+ } else {
+ iscsit_send_task_mgmt_resp(tm_resp_pdu,
+ SCSI_TCP_TM_RESP_NO_TASK);
+ }
+ } else {
+
+ /*
+ * Tell STMF to abort the task. This will do no harm
+ * if the task is already complete.
+ */
+ stmf_abort(STMF_QUEUE_TASK_ABORT, itask->it_stmf_task,
+ STMF_ABORTED, NULL);
+
+ /*
+ * Make sure the task hasn't already completed
+ */
+ mutex_enter(&itask->it_idm_task->idt_mutex);
+ if ((itask->it_idm_task->idt_state == TASK_COMPLETE) ||
+ (itask->it_idm_task->idt_state == TASK_IDLE)) {
+ /*
+ * Task is complete, return "Task Does Not
+ * Exist"
+ */
+ mutex_exit(&itask->it_idm_task->idt_mutex);
+ iscsit_send_task_mgmt_resp(tm_resp_pdu,
+ SCSI_TCP_TM_RESP_NO_TASK);
+ } else {
+ /*
+ * STMF is now aborting the task, return
+ * "Function Complete"
+ */
+ mutex_exit(&itask->it_idm_task->idt_mutex);
+ iscsit_send_task_mgmt_resp(tm_resp_pdu,
+ SCSI_TCP_TM_RESP_COMPLETE);
+ }
+ idm_task_rele(itask->it_idm_task);
+ }
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+ return;
+
+ case ISCSI_TM_FUNC_ABORT_TASK_SET:
+ tm_func = TM_ABORT_TASK_SET;
+ break;
+
+ case ISCSI_TM_FUNC_CLEAR_ACA:
+ tm_func = TM_CLEAR_ACA;
+ break;
+
+ case ISCSI_TM_FUNC_CLEAR_TASK_SET:
+ tm_func = TM_CLEAR_TASK_SET;
+ break;
+
+ case ISCSI_TM_FUNC_LOGICAL_UNIT_RESET:
+ tm_func = TM_LUN_RESET;
+ break;
+
+ case ISCSI_TM_FUNC_TARGET_WARM_RESET:
+ tm_func = TM_TARGET_WARM_RESET;
+ break;
+
+ case ISCSI_TM_FUNC_TARGET_COLD_RESET:
+ tm_func = TM_TARGET_COLD_RESET;
+ break;
+
+ case ISCSI_TM_FUNC_TASK_REASSIGN:
+ /*
+ * We do not currently support allegiance reassignment. When
+ * we start supporting ERL1+, we will need to.
+ */
+ iscsit_send_task_mgmt_resp(tm_resp_pdu,
+ SCSI_TCP_TM_RESP_NO_ALLG_REASSN);
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+ return;
+
+ default:
+ iscsit_send_task_mgmt_resp(tm_resp_pdu,
+ SCSI_TCP_TM_RESP_REJECTED);
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+ return;
+ }
+
+ tm_itask = iscsit_tm_task_alloc(ict);
+ if (tm_itask == NULL) {
+ iscsit_send_task_mgmt_resp(tm_resp_pdu,
+ SCSI_TCP_TM_RESP_REJECTED);
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+ return;
+ }
+
+
+ task = stmf_task_alloc(ict->ict_sess->ist_lport,
+ ict->ict_sess->ist_stmf_sess, iscsi_tm->lun,
+ 0, STMF_TASK_EXT_NONE);
+ if (task == NULL) {
+ /*
+ * If this happens, either the LU is in reset, couldn't
+ * get memory, or some other condition in which we simply
+ * can't complete this request. It would be nice to return
+ * an error code like "busy" but the closest we have is
+ * "rejected".
+ */
+ iscsit_send_task_mgmt_resp(tm_resp_pdu,
+ SCSI_TCP_TM_RESP_REJECTED);
+ iscsit_tm_task_free(tm_itask);
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+ return;
+ }
+
+ tm_itask->it_tm_pdu = tm_resp_pdu;
+ tm_itask->it_stmf_task = task;
+ task->task_port_private = tm_itask;
+ task->task_mgmt_function = tm_func;
+ task->task_additional_flags = TASK_AF_NO_EXPECTED_XFER_LENGTH;
+ task->task_priority = 0;
+ task->task_max_nbufs = STMF_BUFS_MAX;
+ task->task_cmd_seq_no = iscsi_tm->itt;
+ task->task_expected_xfer_length = 0;
+
+ stmf_post_task(task, NULL);
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+}
+
+static void
+iscsit_pdu_op_noop(iscsit_conn_t *ict, idm_pdu_t *rx_pdu)
+{
+ iscsi_nop_out_hdr_t *out = (iscsi_nop_out_hdr_t *)rx_pdu->isp_hdr;
+ iscsi_nop_in_hdr_t *in;
+ int resp_datalen;
+ idm_pdu_t *resp;
+
+ /* Get iSCSI session handle */
+ /* Ignore the response from initiator */
+ if (out->ttt != ISCSI_RSVD_TASK_TAG)
+ return;
+
+ /* Allocate a PDU to respond */
+ resp_datalen = ntoh24(out->dlength);
+ resp = idm_pdu_alloc(sizeof (iscsi_hdr_t), resp_datalen);
+ idm_pdu_init(resp, ict->ict_ic, NULL, NULL);
+ if (resp_datalen > 0) {
+ bcopy(rx_pdu->isp_data, resp->isp_data, resp_datalen);
+ }
+
+ in = (iscsi_nop_in_hdr_t *)resp->isp_hdr;
+ bzero(in, sizeof (*in));
+ in->opcode = ISCSI_OP_NOOP_IN;
+ in->flags = ISCSI_FLAG_FINAL;
+ bcopy(out->lun, in->lun, 8);
+ in->itt = out->itt;
+ in->ttt = ISCSI_RSVD_TASK_TAG;
+ hton24(in->dlength, resp_datalen);
+
+ /* Any other field in resp to be set? */
+ iscsit_pdu_tx(resp);
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+}
+
+static void
+iscsit_pdu_op_login_cmd(iscsit_conn_t *ict, idm_pdu_t *rx_pdu)
+{
+
+ /*
+ * Submit PDU to login state machine. State machine will free the
+ * PDU.
+ */
+ iscsit_login_sm_event(ict, ILE_LOGIN_RCV, rx_pdu);
+}
+
+void
+iscsit_pdu_op_logout_cmd(iscsit_conn_t *ict, idm_pdu_t *rx_pdu)
+{
+ iscsi_logout_hdr_t *logout_req =
+ (iscsi_logout_hdr_t *)rx_pdu->isp_hdr;
+ iscsi_logout_rsp_hdr_t *logout_rsp;
+ idm_pdu_t *resp;
+
+ /* Allocate a PDU to respond */
+ resp = idm_pdu_alloc(sizeof (iscsi_hdr_t), 0);
+ idm_pdu_init(resp, ict->ict_ic, NULL, NULL);
+
+ /*
+ * Logout results in the immediate termination of all tasks except
+ * if the logout reason is ISCSI_LOGOUT_REASON_RECOVERY. The
+ * connection state machine will drive this task cleanup automatically
+ * so we don't need to handle that here.
+ */
+ logout_rsp = (iscsi_logout_rsp_hdr_t *)resp->isp_hdr;
+ bzero(logout_rsp, sizeof (*logout_rsp));
+ logout_rsp->opcode = ISCSI_OP_LOGOUT_RSP;
+ logout_rsp->flags = ISCSI_FLAG_FINAL;
+ logout_rsp->itt = logout_req->itt;
+ if ((logout_req->flags & ISCSI_FLAG_LOGOUT_REASON_MASK) >
+ ISCSI_LOGOUT_REASON_RECOVERY) {
+ logout_rsp->response = ISCSI_LOGOUT_RECOVERY_UNSUPPORTED;
+ } else {
+ logout_rsp->response = ISCSI_LOGOUT_SUCCESS;
+ }
+
+ iscsit_pdu_tx(resp);
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+}
+
+/*
+ * Calculate the number of outstanding commands we can process
+ */
+int
+iscsit_cmd_window()
+{
+ /* Will be better later */
+ return (1024);
+}
+
+/*
+ * Set local registers based on incoming PDU
+ */
+void
+iscsit_set_cmdsn(iscsit_conn_t *ict, idm_pdu_t *rx_pdu)
+{
+ iscsit_sess_t *ist;
+ iscsi_scsi_cmd_hdr_t *req;
+
+ ist = ict->ict_sess;
+
+ req = (iscsi_scsi_cmd_hdr_t *)rx_pdu->isp_hdr;
+
+ rw_enter(&ist->ist_sn_rwlock, RW_WRITER);
+ ist->ist_expcmdsn = ntohl(req->cmdsn) + 1;
+ ist->ist_maxcmdsn = ntohl(req->cmdsn) + iscsit_cmd_window();
+ rw_exit(&ist->ist_sn_rwlock);
+}
+
+/*
+ * Update local StatSN and set SNs in response
+ */
+static void
+iscsit_calc_rspsn(iscsit_conn_t *ict, idm_pdu_t *resp)
+{
+ iscsit_sess_t *ist;
+ iscsi_scsi_rsp_hdr_t *rsp;
+
+ /* Get iSCSI session handle */
+ ist = ict->ict_sess;
+
+ rsp = (iscsi_scsi_rsp_hdr_t *)resp->isp_hdr;
+
+ /* Update StatSN */
+ rsp->statsn = htonl(ict->ict_statsn);
+ switch (IDM_PDU_OPCODE(resp)) {
+ case ISCSI_OP_RTT_RSP:
+ /* Do nothing */
+ break;
+ case ISCSI_OP_NOOP_IN:
+ /*
+ * Refer to section 10.19.1, RFC3720.
+ * Advance only if target is responding initiator
+ */
+ if (((iscsi_nop_in_hdr_t *)rsp)->ttt == ISCSI_RSVD_TASK_TAG)
+ ict->ict_statsn++;
+ break;
+ case ISCSI_OP_SCSI_DATA_RSP:
+ if (rsp->flags & ISCSI_FLAG_DATA_STATUS)
+ ict->ict_statsn++;
+ else
+ rsp->statsn = 0;
+ break;
+ default:
+ ict->ict_statsn++;
+ break;
+ }
+
+ /* Set ExpCmdSN and MaxCmdSN */
+ rsp->maxcmdsn = htonl(ist->ist_maxcmdsn);
+ rsp->expcmdsn = htonl(ist->ist_expcmdsn);
+}
+
+/*
+ * Wrapper funtion, calls iscsi_calc_rspsn and idm_pdu_tx
+ */
+void
+iscsit_pdu_tx(idm_pdu_t *pdu)
+{
+ iscsit_conn_t *ict = pdu->isp_ic->ic_handle;
+
+ /*
+ * Protect ict->ict_statsn, ist->ist_maxcmdsn, and ist->ist_expcmdsn
+ * (which are used by iscsit_calc_rspsn) with the session mutex
+ * (ist->ist_sn_mutex).
+ */
+ rw_enter(&ict->ict_sess->ist_sn_rwlock, RW_WRITER);
+ iscsit_calc_rspsn(ict, pdu);
+ idm_pdu_tx(pdu);
+ rw_exit(&ict->ict_sess->ist_sn_rwlock);
+}
+
+/*
+ * Internal functions
+ */
+
+void
+iscsit_send_async_event(iscsit_conn_t *ict, uint8_t event)
+{
+ idm_pdu_t *abt;
+ iscsi_async_evt_hdr_t *async_abt;
+
+ /*
+ * Get a PDU to build the abort request.
+ */
+ abt = idm_pdu_alloc(sizeof (iscsi_hdr_t), 0);
+ if (abt == NULL) {
+ idm_conn_event(ict->ict_ic, CE_TRANSPORT_FAIL, NULL);
+ return;
+ }
+ idm_pdu_init(abt, ict->ict_ic, NULL, NULL);
+
+ ASSERT(abt != NULL);
+ abt->isp_datalen = 0;
+
+ async_abt = (iscsi_async_evt_hdr_t *)abt->isp_hdr;
+ bzero(async_abt, sizeof (*async_abt));
+ async_abt->opcode = ISCSI_OP_ASYNC_EVENT;
+ async_abt->async_event = event;
+ async_abt->flags = ISCSI_FLAG_FINAL;
+ async_abt->rsvd4[0] = 0xff;
+ async_abt->rsvd4[1] = 0xff;
+ async_abt->rsvd4[2] = 0xff;
+ async_abt->rsvd4[3] = 0xff;
+
+ switch (event) {
+ case ISCSI_ASYNC_EVENT_REQUEST_LOGOUT:
+ async_abt->param3 = htons(IDM_LOGOUT_SECONDS);
+ break;
+ case ISCSI_ASYNC_EVENT_SCSI_EVENT:
+ case ISCSI_ASYNC_EVENT_DROPPING_CONNECTION:
+ case ISCSI_ASYNC_EVENT_DROPPING_ALL_CONNECTIONS:
+ case ISCSI_ASYNC_EVENT_PARAM_NEGOTIATION:
+ default:
+ ASSERT(0);
+ }
+
+ iscsit_pdu_tx(abt);
+}
+
+
+static iscsit_task_t *
+iscsit_task_alloc(iscsit_conn_t *ict)
+{
+ iscsit_task_t *itask;
+ iscsit_buf_t *immed_ibuf;
+
+ /*
+ * Possible items to pre-alloc if we cache iscsit_task_t's:
+ *
+ * Status PDU w/ sense buffer
+ * stmf_data_buf_t for immediate data
+ */
+ itask = kmem_alloc(sizeof (iscsit_task_t) + sizeof (iscsit_buf_t) +
+ sizeof (stmf_data_buf_t), KM_NOSLEEP);
+ if (itask != NULL) {
+ mutex_init(&itask->it_mutex, NULL, MUTEX_DRIVER, NULL);
+ itask->it_aborted = itask->it_stmf_abort =
+ itask->it_tm_task = 0;
+
+ immed_ibuf = (iscsit_buf_t *)(itask + 1);
+ bzero(immed_ibuf, sizeof (*immed_ibuf));
+ immed_ibuf->ibuf_is_immed = B_TRUE;
+ immed_ibuf->ibuf_stmf_buf = (stmf_data_buf_t *)(immed_ibuf + 1);
+
+ bzero(immed_ibuf->ibuf_stmf_buf, sizeof (stmf_data_buf_t));
+ immed_ibuf->ibuf_stmf_buf->db_port_private = immed_ibuf;
+ immed_ibuf->ibuf_stmf_buf->db_sglist_length = 1;
+ immed_ibuf->ibuf_stmf_buf->db_flags = DB_DIRECTION_FROM_RPORT |
+ DB_DONT_CACHE;
+ itask->it_immed_data = immed_ibuf;
+ itask->it_idm_task = idm_task_alloc(ict->ict_ic);
+ if (itask->it_idm_task != NULL) {
+ itask->it_idm_task->idt_private = itask;
+ itask->it_ict = ict;
+ itask->it_ttt = itask->it_idm_task->idt_tt;
+ return (itask);
+ } else {
+ kmem_free(itask, sizeof (iscsit_task_t) +
+ sizeof (iscsit_buf_t) + sizeof (stmf_data_buf_t));
+ }
+ }
+
+ return (NULL);
+}
+
+static void
+iscsit_task_free(iscsit_task_t *itask)
+{
+ idm_task_free(itask->it_idm_task);
+ mutex_destroy(&itask->it_mutex);
+ kmem_free(itask, sizeof (iscsit_task_t) +
+ sizeof (iscsit_buf_t) + sizeof (stmf_data_buf_t));
+}
+
+static iscsit_task_t *
+iscsit_tm_task_alloc(iscsit_conn_t *ict)
+{
+ iscsit_task_t *itask;
+
+ itask = kmem_zalloc(sizeof (iscsit_task_t), KM_NOSLEEP);
+ if (itask != NULL) {
+ idm_conn_hold(ict->ict_ic);
+ mutex_init(&itask->it_mutex, NULL, MUTEX_DRIVER, NULL);
+ itask->it_aborted = itask->it_stmf_abort =
+ itask->it_tm_responded = 0;
+ itask->it_tm_pdu = NULL;
+ itask->it_tm_task = 1;
+ itask->it_ict = ict;
+ }
+
+ return (itask);
+}
+
+static void
+iscsit_tm_task_free(iscsit_task_t *itask)
+{
+ /*
+ * If we responded then the call to idm_pdu_complete will free the
+ * PDU. Otherwise we got aborted before the TM function could
+ * complete and we need to free the PDU explicitly.
+ */
+ if (itask->it_tm_pdu != NULL && !itask->it_tm_responded)
+ idm_pdu_free(itask->it_tm_pdu);
+ idm_conn_rele(itask->it_ict->ict_ic);
+ mutex_destroy(&itask->it_mutex);
+ kmem_free(itask, sizeof (iscsit_task_t));
+}
+
+/*
+ * iscsit status PDU cache
+ */
+
+/*ARGSUSED*/
+static int
+iscsit_status_pdu_constructor(void *pdu_void, void *arg, int flags)
+{
+ idm_pdu_t *pdu = pdu_void;
+ iscsi_scsi_rsp_hdr_t *rsp;
+
+ bzero(pdu, sizeof (idm_pdu_t));
+ pdu->isp_callback = iscsit_send_good_status_done;
+ pdu->isp_magic = IDM_PDU_MAGIC;
+ pdu->isp_hdr = (iscsi_hdr_t *)(pdu + 1); /* Ptr arithmetic */
+ pdu->isp_hdrlen = sizeof (iscsi_hdr_t);
+
+ /* Setup status response */
+ rsp = (iscsi_scsi_rsp_hdr_t *)pdu->isp_hdr;
+ bzero(rsp, sizeof (*rsp));
+ rsp->opcode = ISCSI_OP_SCSI_RSP;
+ rsp->flags = ISCSI_FLAG_FINAL;
+ rsp->response = ISCSI_STATUS_CMD_COMPLETED;
+
+ return (0);
+}
+
+/*
+ * iscsit private data handler
+ */
+
+/*ARGSUSED*/
+static void
+iscsit_pp_cb(struct stmf_port_provider *pp, int cmd, void *arg, uint32_t flags)
+{
+ it_config_t *cfg;
+ nvlist_t *nvl;
+
+ if ((cmd != STMF_PROVIDER_DATA_UPDATED) || (arg == NULL)) {
+ return;
+ }
+
+ nvl = (nvlist_t *)arg;
+
+ /* Translate nvlist */
+ if (it_nv_to_config(nvl, &cfg) != 0) {
+ cmn_err(CE_WARN, "Configuration is invalid");
+ return;
+ }
+
+ /* Update config */
+ (void) iscsit_config_merge(cfg);
+
+ it_config_free_cmn(cfg);
+}
+
+
+static it_cfg_status_t
+iscsit_config_merge(it_config_t *in_cfg)
+{
+ it_cfg_status_t status;
+ it_config_t *cfg;
+ it_config_t tmp_cfg;
+ list_t tpg_del_list;
+
+ if (in_cfg) {
+ cfg = in_cfg;
+ } else {
+ /* Make empty config */
+ bzero(&tmp_cfg, sizeof (tmp_cfg));
+ cfg = &tmp_cfg;
+ }
+
+ list_create(&tpg_del_list, sizeof (iscsit_tpg_t),
+ offsetof(iscsit_tpg_t, tpg_delete_ln));
+
+ /*
+ * Update targets, initiator contexts, target portal groups,
+ * and iSNS client
+ */
+ ISCSIT_GLOBAL_LOCK(RW_WRITER);
+ if (((status = iscsit_config_merge_tpg(cfg, &tpg_del_list))
+ != 0) ||
+ ((status = iscsit_config_merge_tgt(cfg)) != 0) ||
+ ((status = iscsit_config_merge_ini(cfg)) != 0) ||
+ ((status = isnst_config_merge(cfg)) != 0)) {
+ ISCSIT_GLOBAL_UNLOCK();
+ return (status);
+ }
+
+ /* Update other global config parameters */
+ if (iscsit_global.global_props) {
+ nvlist_free(iscsit_global.global_props);
+ iscsit_global.global_props = NULL;
+ }
+ if (in_cfg) {
+ (void) nvlist_dup(cfg->config_global_properties,
+ &iscsit_global.global_props, KM_SLEEP);
+ }
+ ISCSIT_GLOBAL_UNLOCK();
+
+ iscsit_config_destroy_tpgs(&tpg_del_list);
+
+ list_destroy(&tpg_del_list);
+
+ return (ITCFG_SUCCESS);
+}
+
+/*
+ * iscsit_sna_lt[e]
+ *
+ * Compare serial numbers using serial number arithmetic as defined in
+ * RFC 1982.
+ *
+ * NOTE: This code is duplicated in the isns server as well as iscsitgtd. It
+ * ought to be common.
+ */
+
+static int
+iscsit_sna_lt(uint32_t sn1, uint32_t sn2)
+{
+ return ((sn1 != sn2) &&
+ (((sn1 < sn2) && ((sn2 - sn1) < ISCSIT_SNA32_CHECK)) ||
+ ((sn1 > sn2) && ((sn1 - sn2) > ISCSIT_SNA32_CHECK))));
+}
+
+static int
+iscsit_sna_lte(uint32_t sn1, uint32_t sn2)
+{
+ return ((sn1 == sn2) ||
+ (((sn1 < sn2) && ((sn2 - sn1) < ISCSIT_SNA32_CHECK)) ||
+ ((sn1 > sn2) && ((sn1 - sn2) > ISCSIT_SNA32_CHECK))));
+}
+
+
+static boolean_t
+iscsit_cmdsn_in_window(iscsit_conn_t *ict, uint32_t cmdsn)
+{
+ iscsit_sess_t *ist = ict->ict_sess;
+ int rval = B_TRUE;
+
+ ist = ict->ict_sess;
+
+ rw_enter(&ist->ist_sn_rwlock, RW_READER);
+
+ /*
+ * If cmdsn is less than ist_expcmdsn - iscsit_cmd_window() or
+ * greater than ist_expcmdsn, it's not in the window.
+ */
+
+ if (iscsit_sna_lt(cmdsn, (ist->ist_expcmdsn - iscsit_cmd_window())) ||
+ !iscsit_sna_lte(cmdsn, ist->ist_expcmdsn)) {
+ rval = B_FALSE;
+ }
+
+ rw_exit(&ist->ist_sn_rwlock);
+
+ return (rval);
+}
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit.conf b/usr/src/uts/common/io/comstar/port/iscsit/iscsit.conf
new file mode 100644
index 0000000000..14698062f6
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit.conf
@@ -0,0 +1,27 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+
+name="iscsit" parent="/pseudo";
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit.h b/usr/src/uts/common/io/comstar/port/iscsit/iscsit.h
new file mode 100644
index 0000000000..2fcee4fa1f
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit.h
@@ -0,0 +1,799 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+#ifndef _ISCSIT_H_
+#define _ISCSIT_H_
+
+#include <sys/iscsit/iscsi_if.h>
+#include <iscsit_authclient.h>
+#include <sys/iscsit/iscsit_common.h>
+
+/*
+ * For some reason iscsi_protocol.h lists the max version as "0x02" and the
+ * min version as "0x00". RFC3720 clearly states that the current version
+ * number is 0x00 so that is what we will use.
+ */
+#define ISCSIT_MIN_VERSION 0x00
+#define ISCSIT_MAX_VERSION 0x00
+#define ISCSIT_MAX_CONNECTIONS 1 /* No MC/S support */
+#define ISCSIT_MAX_RECV_DATA_SEGMENT_LENGTH (32*1024)
+#define ISCSIT_MAX_BURST_LENGTH (512*1024)
+#define ISCSIT_MAX_FIRST_BURST_LENGTH ISCSI_DEFAULT_FIRST_BURST_LENGTH
+#define ISCSIT_MAX_TIME2WAIT ISCSI_DEFAULT_TIME_TO_WAIT
+#define ISCSIT_MAX_TIME2RETAIN ISCSI_DEFAULT_TIME_TO_RETAIN
+#define ISCSIT_MAX_OUTSTANDING_R2T ISCSI_DEFAULT_MAX_OUT_R2T
+#define ISCSIT_MAX_ERROR_RECOVERY_LEVEL 0
+
+#define ISCSIT_DEFAULT_TPG "iscsit-default-tpg"
+#define ISCSIT_DEFAULT_TPGT 1
+
+#define ISCSI_MAX_TSIH 0xffff
+#define ISCSI_UNSPEC_TSIH 0
+
+/* Max targets per system */
+#define ISCSIT_MAX_TARGETS 1024
+
+/* Time in seconds to wait between calls to stmf_deregister_local_port */
+#define TGT_DEREG_RETRY_SECONDS 1
+
+#define ISCSIT_GLOBAL_LOCK(rw) rw_enter(&iscsit_global.global_rwlock, (rw))
+#define ISCSIT_GLOBAL_UNLOCK() rw_exit(&iscsit_global.global_rwlock)
+
+/*
+ * Used for serial number arithmetic (RFC 1982)
+ */
+#define ISCSIT_SNA32_CHECK 0x80000000
+
+typedef struct {
+ char tpg_name[MAX_TPG_NAMELEN];
+ kmutex_t tpg_mutex;
+ idm_refcnt_t tpg_refcnt;
+ int tpg_online;
+ avl_tree_t tpg_portal_list;
+ avl_node_t tpg_global_ln;
+ list_node_t tpg_delete_ln;
+} iscsit_tpg_t;
+
+#define IS_DEFAULT_TPGT(TPGT) \
+ (((TPGT) != NULL) && \
+ ((TPGT)->tpgt_tpg == iscsit_global.global_default_tpg))
+
+typedef struct {
+ iscsit_tpg_t *tpgt_tpg;
+ idm_refcnt_t tpgt_refcnt;
+ avl_node_t tpgt_tgt_ln;
+ list_node_t tpgt_delete_ln;
+ uint16_t tpgt_tag;
+ boolean_t tpgt_needs_tpg_offline;
+} iscsit_tpgt_t;
+
+typedef struct {
+ struct sockaddr_storage portal_addr;
+ int portal_online;
+ idm_refcnt_t portal_refcnt;
+ avl_node_t portal_tpg_ln;
+ iscsit_tpg_t *portal_tpg;
+ idm_svc_t *portal_svc;
+} iscsit_portal_t;
+
+
+/* Target states and events, update iscsit_ts_name table whenever modified */
+typedef enum {
+ TS_UNDEFINED = 0,
+ TS_CREATED,
+ TS_ONLINING,
+ TS_ONLINE,
+ TS_STMF_ONLINE,
+ TS_DELETING_NEED_OFFLINE,
+ TS_OFFLINING,
+ TS_OFFLINE,
+ TS_STMF_OFFLINE,
+ TS_DELETING_STMF_DEREG,
+ TS_DELETING_STMF_DEREG_FAIL,
+ TS_DELETING,
+ TS_MAX_STATE
+} iscsit_tgt_state_t;
+
+#ifdef ISCSIT_TGT_SM_STRINGS
+static const char *iscsit_ts_name[TS_MAX_STATE+1] = {
+ "TS_UNDEFINED",
+ "TS_CREATED",
+ "TS_ONLINING",
+ "TS_ONLINE",
+ "TS_STMF_ONLINE",
+ "TS_DELETING_NEED_OFFLINE",
+ "TS_OFFLINING",
+ "TS_OFFLINE",
+ "TS_STMF_OFFLINE",
+ "TS_DELETING_STMF_DEREG",
+ "TS_DELETING_STMF_DEREG_FAIL",
+ "TS_DELETING",
+ "TS_MAX_STATE"
+};
+#endif
+
+typedef enum {
+ TE_UNDEFINED = 0,
+ TE_STMF_ONLINE_REQ,
+ TE_ONLINE_SUCCESS,
+ TE_ONLINE_FAIL,
+ TE_STMF_ONLINE_COMPLETE_ACK,
+ TE_STMF_OFFLINE_REQ,
+ TE_OFFLINE_COMPLETE,
+ TE_STMF_OFFLINE_COMPLETE_ACK,
+ TE_DELETE,
+ TE_STMF_DEREG_SUCCESS,
+ TE_STMF_DEREG_FAIL,
+ TE_STMF_DEREG_RETRY,
+ TE_WAIT_REF_COMPLETE,
+ TE_MAX_EVENT
+} iscsit_tgt_event_t;
+
+#ifdef ISCSIT_TGT_SM_STRINGS
+static const char *iscsit_te_name[TE_MAX_EVENT+1] = {
+ "TE_UNDEFINED",
+ "TE_STMF_ONLINE_REQ",
+ "TE_ONLINE_SUCCESS",
+ "TE_ONLINE_FAIL",
+ "TE_STMF_ONLINE_COMPLETE_ACK",
+ "TE_STMF_OFFLINE_REQ",
+ "TE_OFFLINE_COMPLETE",
+ "TE_STMF_OFFLINE_COMPLETE_ACK",
+ "TE_DELETE",
+ "TE_STMF_DEREG_SUCCESS",
+ "TE_STMF_DEREG_FAIL",
+ "TE_STMF_DEREG_RETRY",
+ "TE_WAIT_REF_COMPLETE",
+ "TE_MAX_EVENT"
+};
+#endif
+
+typedef struct {
+ char *target_name;
+ nvlist_t *target_props;
+ kmutex_t target_mutex;
+ idm_refcnt_t target_refcnt;
+ idm_refcnt_t target_sess_refcnt;
+ avl_tree_t target_tpgt_list;
+ avl_tree_t target_sess_list;
+ avl_node_t target_global_ln;
+ avl_node_t target_global_deleted_ln;
+ /* STMF lport == iSCSI target */
+ scsi_devid_desc_t *target_devid;
+ stmf_local_port_t *target_stmf_lport;
+ uint8_t target_stmf_lport_registered;
+
+ /* Target state */
+ boolean_t target_sm_busy;
+ boolean_t target_deleting;
+ iscsit_tgt_state_t target_state;
+ iscsit_tgt_state_t target_last_state;
+ sm_audit_buf_t target_state_audit;
+ list_t target_events;
+ uint64_t target_generation;
+} iscsit_tgt_t;
+
+typedef struct {
+ char ini_name[MAX_ISCSI_NODENAMELEN];
+ nvlist_t *ini_props;
+ avl_node_t ini_global_ln;
+} iscsit_ini_t;
+
+/*
+ * iSCSI Auth Information
+ */
+typedef struct conn_auth {
+ char ca_tgt_chapuser[iscsiAuthStringMaxLength];
+ uint8_t ca_tgt_chapsecret[iscsiAuthStringMaxLength];
+ int ca_tgt_chapsecretlen;
+
+ char ca_ini_chapuser[iscsiAuthStringMaxLength];
+ uint8_t ca_ini_chapsecret[iscsiAuthStringMaxLength];
+ int ca_ini_chapsecretlen;
+
+ /* RADIUS authentication information */
+ boolean_t ca_use_radius;
+ struct sockaddr_storage ca_radius_server;
+ uint8_t ca_radius_secret[iscsiAuthStringMaxLength];
+ int ca_radius_secretlen;
+
+ /* authentication method list */
+ iscsit_auth_method_t ca_method_valid_list[iscsiAuthMethodMaxCount];
+
+ /* Target alias */
+ char ca_tgt_alias[MAX_ISCSI_NODENAMELEN];
+} conn_auth_t;
+
+/*
+ * We have three state machines (so far) between the IDM connection state
+ * machine, the session state machine, and the login state machine. All
+ * of these states have some concept of "full feature mode". It's going
+ * to be obnoxious if we use a mixture of these "ffp" representations
+ * since it will be difficult to ensure the three state machines
+ * transition at exactly the same time. We should drive decisions that
+ * depend on FFP from the IDM state machine which is actually snooping
+ * the iSCSI PDU's and will always transition at the correct time.
+ *
+ * A consequence of this approach is that there is a window just after
+ * login completes where we may get a SCSI request but the session
+ * or login state machine has not quite transitioned to "FFP". Whether
+ * this is a problem depends on how we use those state machines. This
+ * is what we should use them for:
+ *
+ * IDM Connection state machine - Decisions related to command processing
+ * including whether a connection is in FFP
+ *
+ * Session state machine - Summarize the state of all available connections
+ * for the purposes of ERL1, ERL2 and MC/S. A session in LOGGED_IN state
+ * should always have at least one FFP connection but there may be a brief
+ * window where a session in ACTIVE might have one or more FFP connections
+ * even though ACTIVE is not strictly an FFP state according to the RFC.
+ *
+ * Login state machine -- drive the login process, collect negotiated
+ * parameters. Another side effect of this approach is that we may get
+ * the "notify ffp" callback from the IDM connection state machine before
+ * the login state machine has actually transitioned to FFP state.
+ */
+
+struct iscsit_conn_s;
+
+/* Update iscsit_ss_name table whenever session states are modified */
+typedef enum {
+ SS_UNDEFINED = 0,
+ SS_Q1_FREE,
+ SS_Q2_ACTIVE,
+ SS_Q3_LOGGED_IN,
+ SS_Q4_FAILED,
+ SS_Q5_CONTINUE,
+ SS_Q6_DONE,
+ SS_Q7_ERROR,
+ /* Add new session states above SS_MAX_STATE */
+ SS_MAX_STATE
+} iscsit_session_state_t;
+
+#ifdef ISCSIT_SESS_SM_STRINGS
+/* An array of state text values, for use in logging state transitions */
+static const char *iscsit_ss_name[SS_MAX_STATE+1] = {
+ "SS_UNDEFINED",
+ "SS_Q1_FREE",
+ "SS_Q2_ACTIVE",
+ "SS_Q3_LOGGED_IN",
+ "SS_Q4_FAILED",
+ "SS_Q5_CONTINUE",
+ "SS_Q6_DONE",
+ "SS_Q7_ERROR",
+ "SS_MAX_STATE"
+};
+#endif
+
+/* Update iscsit_se_name table whenever session events are modified */
+typedef enum {
+ SE_UNDEFINED = 0,
+ SE_CONN_IN_LOGIN, /* From login state machine */
+ SE_CONN_LOGGED_IN, /* FFP enabled client notification */
+ SE_CONN_FFP_FAIL, /* FFP disabled client notification */
+ SE_CONN_FFP_DISABLE, /* FFP disabled client notification */
+ SE_CONN_FAIL, /* Conn destroy client notification */
+ SE_SESSION_CLOSE, /* FFP disabled client notification */
+ SE_SESSION_REINSTATE, /* From login state machine */
+ SE_SESSION_TIMEOUT, /* Internal */
+ SE_SESSION_CONTINUE, /* From login state machine */
+ SE_SESSION_CONTINUE_FAIL, /* From login state machine? */
+ /* Add new events above SE_MAX_EVENT */
+ SE_MAX_EVENT
+} iscsit_session_event_t;
+
+#ifdef ISCSIT_SESS_SM_STRINGS
+/* An array of event text values, for use in logging events */
+static const char *iscsit_se_name[SE_MAX_EVENT+1] = {
+ "SE_UNDEFINED",
+ "SE_CONN_IN_LOGIN",
+ "SE_CONN_LOGGED_IN",
+ "SE_CONN_FFP_FAIL",
+ "SE_CONN_FFP_DISABLE",
+ "SE_CONN_FAIL",
+ "SE_SESSION_CLOSE",
+ "SE_SESSION_REINSTATE",
+ "SE_SESSION_TIMEOUT",
+ "SE_SESSION_CONTINUE",
+ "SE_SESSION_CONTINUE_FAIL",
+ "SE_MAX_EVENT"
+};
+#endif
+
+/*
+ * Set in ist_tgt after iscsit_tgt_unbind_sess to differentiate an unbound
+ * session from a discovery session.
+ */
+#define SESS_UNBOUND_FROM_TGT -1
+
+typedef struct {
+ stmf_scsi_session_t *ist_stmf_sess;
+ stmf_local_port_t *ist_lport;
+ iscsit_tgt_t *ist_tgt;
+ idm_refcnt_t ist_refcnt;
+ kmem_cache_t *ist_task_cache;
+ krwlock_t ist_sn_rwlock;
+ kmutex_t ist_mutex;
+ kcondvar_t ist_cv;
+ iscsit_session_state_t ist_state;
+ iscsit_session_state_t ist_last_state;
+ sm_audit_buf_t ist_state_audit;
+ boolean_t ist_sm_busy;
+ boolean_t ist_sm_complete;
+ boolean_t ist_admin_close;
+ list_t ist_events;
+ int ist_conn_count;
+ int ist_ffp_conn_count;
+ struct iscsit_conn_s *ist_failed_conn;
+ timeout_id_t ist_state_timeout;
+ list_t ist_conn_list;
+ avl_node_t ist_tgt_ln;
+ char *ist_initiator_name;
+ char *ist_initiator_alias;
+ char *ist_target_name;
+ char *ist_target_alias;
+ uint8_t ist_isid[ISCSI_ISID_LEN];
+ uint16_t ist_tsih;
+ uint16_t ist_tpgt_tag;
+ uint32_t ist_expcmdsn;
+ uint32_t ist_maxcmdsn;
+} iscsit_sess_t;
+
+/* Update iscsit_ils_name table whenever login states are modified */
+typedef enum {
+ ILS_UNDEFINED = 0,
+ ILS_LOGIN_INIT,
+ ILS_LOGIN_WAITING, /* Waiting for more login PDU's */
+ ILS_LOGIN_PROCESSING, /* Processing login request */
+ ILS_LOGIN_RESPONDING, /* Sending login response */
+ ILS_LOGIN_RESPONDED, /* Sent login response (no trans. to FFP) */
+ ILS_LOGIN_FFP, /* Sending last login PDU for final response */
+ ILS_LOGIN_DONE, /* Last login PDU sent (so we can free it) */
+ ILS_LOGIN_ERROR, /* Login error, login failed */
+ /* Add new login states above ILS_MAX_STATE */
+ ILS_MAX_STATE
+} iscsit_login_state_t;
+
+#ifdef ISCSIT_LOGIN_SM_STRINGS
+/* An array of login state text values, for use in logging login progress */
+static const char *iscsit_ils_name[ILS_MAX_STATE+1] = {
+ "ILS_UNDEFINED",
+ "ILS_LOGIN_INIT",
+ "ILS_LOGIN_WAITING",
+ "ILS_LOGIN_PROCESSING",
+ "ILS_LOGIN_RESPONDING",
+ "ILS_LOGIN_RESPONDED",
+ "ILS_LOGIN_FFP",
+ "ILS_LOGIN_DONE",
+ "ILS_LOGIN_ERROR",
+ "ILS_MAX_STATE"
+};
+#endif
+
+/* Update iscsit_ile_name table whenever login events are modified */
+typedef enum {
+ ILE_UNDEFINED = 0,
+ ILE_LOGIN_RCV,
+ ILE_LOGIN_RESP_READY,
+ ILE_LOGIN_FFP,
+ ILE_LOGIN_RESP_COMPLETE,
+ ILE_LOGIN_ERROR,
+ ILE_LOGIN_CONN_ERROR,
+ /* Add new login events above ILE_MAX_EVENT */
+ ILE_MAX_EVENT
+} iscsit_login_event_t;
+
+#ifdef ISCSIT_LOGIN_SM_STRINGS
+/* An array of login event text values, for use in logging login events */
+static const char *iscsit_ile_name[ILE_MAX_EVENT+1] = {
+ "ILE_UNDEFINED",
+ "ILE_LOGIN_RCV",
+ "ILE_LOGIN_RESP_READY",
+ "ILE_LOGIN_FFP",
+ "ILE_LOGIN_RESP_COMPLETE",
+ "ILE_LOGIN_ERROR",
+ "ILE_LOGIN_CONN_ERROR",
+ "ILE_MAX_EVENT"
+};
+#endif
+
+typedef struct {
+ uint32_t op_initial_params_set:1,
+ op_discovery_session:1,
+ op_initial_r2t:1,
+ op_immed_data:1,
+ op_data_pdu_in_order:1,
+ op_data_sequence_in_order:1;
+ uint64_t op_max_connections;
+ uint64_t op_max_recv_data_segment_length;
+ uint64_t op_max_burst_length;
+ uint64_t op_first_burst_length;
+ uint64_t op_default_time_2_wait;
+ uint64_t op_default_time_2_retain;
+ uint64_t op_max_outstanding_r2t;
+ uint64_t op_error_recovery_level;
+} iscsit_op_params_t;
+
+typedef struct {
+ iscsit_login_state_t icl_login_state;
+ iscsit_login_state_t icl_login_last_state;
+ sm_audit_buf_t icl_state_audit;
+ boolean_t icl_busy;
+ boolean_t icl_login_complete;
+ kmutex_t icl_mutex;
+ uint32_t icl_login_itt;
+ uint8_t icl_login_csg;
+ uint8_t icl_login_nsg;
+ boolean_t icl_login_transit;
+ conn_auth_t icl_auth;
+ iscsit_auth_client_t icl_auth_client;
+ int icl_auth_pass;
+ list_t icl_login_events;
+ list_t icl_pdu_list;
+ uint16_t icl_tsih;
+ uint8_t icl_isid[ISCSI_ISID_LEN];
+ uint32_t icl_cmdsn;
+ uint16_t icl_tpgt_tag;
+ char *icl_target_name;
+ char *icl_target_alias;
+ char *icl_initiator_name;
+ char *icl_login_resp_buf;
+ void *icl_login_resp_itb; /* mult-pdu idm buf */
+ int icl_login_resp_len; /* For kmem_free */
+ int icl_login_resp_valid_len;
+ uint8_t icl_login_resp_err_class;
+ uint8_t icl_login_resp_err_detail;
+ iscsi_login_rsp_hdr_t *icl_login_resp_tmpl;
+ idm_pdu_t *icl_login_resp;
+ nvlist_t *icl_request_nvlist;
+ nvlist_t *icl_response_nvlist;
+ nvlist_t *icl_negotiated_values;
+} iscsit_conn_login_t;
+
+#define SET_LOGIN_ERROR(SLE_ICT, SLE_CLASS, SLE_DETAIL) \
+ (SLE_ICT)->ict_login_sm.icl_login_resp_err_class = (SLE_CLASS); \
+ (SLE_ICT)->ict_login_sm.icl_login_resp_err_detail = (SLE_DETAIL);
+
+typedef struct iscsit_conn_s {
+ idm_conn_t *ict_ic;
+ iscsit_sess_t *ict_sess;
+ kmutex_t ict_mutex;
+ idm_refcnt_t ict_refcnt;
+ idm_refcnt_t ict_dispatch_refcnt;
+ list_node_t ict_sess_ln;
+ iscsit_conn_login_t ict_login_sm;
+ iscsit_op_params_t ict_op;
+ uint16_t ict_cid;
+ uint32_t ict_statsn;
+ struct iscsit_conn_s *ict_reinstate_conn;
+ uint32_t ict_reinstating:1,
+ ict_lost:1,
+ ict_destroyed:1;
+} iscsit_conn_t;
+
+#define ICT_FLAGS_DISCOVERY 0x00000001
+
+typedef struct {
+ idm_buf_t *ibuf_idm_buf;
+ stmf_data_buf_t *ibuf_stmf_buf;
+ idm_pdu_t *ibuf_immed_data_pdu;
+ boolean_t ibuf_is_immed;
+} iscsit_buf_t;
+
+typedef struct {
+ scsi_task_t *it_stmf_task;
+ idm_task_t *it_idm_task;
+ iscsit_buf_t *it_immed_data;
+ iscsit_conn_t *it_ict;
+ kmutex_t it_mutex;
+ idm_pdu_t *it_tm_pdu;
+ uint32_t it_stmf_abort:1,
+ it_aborted:1,
+ it_tm_task:1,
+ it_tm_responded:1;
+ uint32_t it_cmdsn;
+ uint32_t it_itt;
+ uint32_t it_ttt;
+} iscsit_task_t;
+
+typedef struct iscsit_isns_cfg {
+ kmutex_t isns_mutex;
+ boolean_t isns_state;
+ list_t isns_svrs;
+} iscsit_isns_cfg_t;
+
+/*
+ * State values for the iscsit service
+ */
+typedef enum {
+ ISE_UNDEFINED = 0,
+ ISE_DETACHED,
+ ISE_DISABLED,
+ ISE_ENABLING,
+ ISE_ENABLED,
+ ISE_BUSY,
+ ISE_DISABLING
+} iscsit_service_enabled_t;
+
+
+typedef struct {
+ iscsit_service_enabled_t global_svc_state;
+ dev_info_t *global_dip;
+ ldi_ident_t global_li;
+ nvlist_t *global_props;
+ stmf_port_provider_t *global_pp;
+ stmf_dbuf_store_t *global_dbuf_store;
+ taskq_t *global_dispatch_taskq;
+ idm_refcnt_t global_refcnt;
+ avl_tree_t global_discovery_sessions;
+ avl_tree_t global_target_list;
+ list_t global_deleted_target_list;
+ avl_tree_t global_tpg_list;
+ avl_tree_t global_ini_list;
+ iscsit_tpg_t *global_default_tpg;
+ vmem_t *global_tsih_pool;
+ iscsit_isns_cfg_t global_isns_cfg;
+ iscsi_radius_props_t global_radius_server;
+ krwlock_t global_rwlock;
+} iscsit_global_t;
+
+extern iscsit_global_t iscsit_global;
+
+void
+iscsit_global_hold();
+
+void
+iscsit_global_rele();
+
+void
+iscsit_global_wait_ref();
+
+idm_status_t
+iscsit_login_sm_init(iscsit_conn_t *ict);
+
+void
+iscsit_login_sm_fini(iscsit_conn_t *ict);
+
+void
+iscsit_login_sm_event(iscsit_conn_t *ic, iscsit_login_event_t event,
+ idm_pdu_t *pdu);
+
+void
+iscsit_login_sm_event_locked(iscsit_conn_t *ic, iscsit_login_event_t event,
+ idm_pdu_t *pdu);
+
+void
+iscsit_send_async_event(iscsit_conn_t *ict, uint8_t async_event);
+
+void
+iscsit_pdu_tx(idm_pdu_t *pdu);
+
+/*
+ * IDM conn ops
+ */
+
+idm_rx_pdu_cb_t iscsit_op_scsi_cmd;
+idm_rx_pdu_cb_t iscsit_rx_pdu;
+idm_rx_pdu_error_cb_t iscsit_rx_pdu_error;
+idm_task_cb_t iscsit_task_aborted;
+idm_client_notify_cb_t iscsit_client_notify;
+idm_build_hdr_cb_t iscsit_build_hdr;
+
+/*
+ * lport entry points
+ */
+stmf_status_t
+iscsit_xfer_scsi_data(scsi_task_t *task, stmf_data_buf_t *dbuf,
+ uint32_t ioflags);
+
+stmf_status_t
+iscsit_send_scsi_status(scsi_task_t *task, uint32_t ioflags);
+
+void
+iscsit_lport_task_free(scsi_task_t *task);
+
+stmf_status_t
+iscsit_abort(stmf_local_port_t *lport, int abort_cmd, void *arg,
+ uint32_t flags);
+
+void
+iscsit_ctl(stmf_local_port_t *lport, int cmd, void *arg);
+
+/*
+ * Connection functions
+ */
+idm_status_t
+iscsit_conn_reinstate(iscsit_conn_t *existing_ict, iscsit_conn_t *ict);
+
+void
+iscsit_conn_destroy_done(iscsit_conn_t *ict);
+
+void
+iscsit_conn_set_auth(iscsit_conn_t *ict);
+
+void
+iscsit_conn_hold(iscsit_conn_t *ict);
+
+void
+iscsit_conn_rele(iscsit_conn_t *ict);
+
+/*
+ * Session functions
+ */
+int
+iscsit_sess_avl_compare(const void *void_sess1, const void *void_sess2);
+
+iscsit_sess_t *
+iscsit_sess_create(iscsit_tgt_t *tgt, iscsit_conn_t *ict,
+ uint32_t cmdsn, uint8_t *isid, uint16_t tag,
+ char *initiator_name, char *target_name,
+ uint8_t *error_class, uint8_t *error_detail);
+
+void
+iscsit_sess_destroy(iscsit_sess_t *ist);
+
+void
+iscsit_sess_hold(iscsit_sess_t *ist);
+
+void
+iscsit_sess_rele(iscsit_sess_t *ist);
+
+iscsit_conn_t *
+iscsit_sess_lookup_conn(iscsit_sess_t *ist, uint16_t cid);
+
+void
+iscsit_sess_bind_conn(iscsit_sess_t *ist, iscsit_conn_t *ict);
+
+void
+iscsit_sess_unbind_conn(iscsit_sess_t *ist, iscsit_conn_t *ict);
+
+void
+iscsit_sess_close(iscsit_sess_t *ist);
+
+iscsit_sess_t *
+iscsit_sess_reinstate(iscsit_tgt_t *tgt, iscsit_sess_t *ist, iscsit_conn_t *ict,
+ uint8_t *error_class, uint8_t *error_detail);
+
+void
+iscsit_sess_sm_event(iscsit_sess_t *ist, iscsit_session_event_t event,
+ iscsit_conn_t *ict);
+
+/*
+ * Target, TPGT, TPGT and portal functions
+ */
+
+void
+iscsit_tgt_sm_event(iscsit_tgt_t *tgt, iscsit_tgt_event_t event);
+
+void
+tgt_sm_event_locked(iscsit_tgt_t *tgt, iscsit_tgt_event_t event);
+
+it_cfg_status_t
+iscsit_config_merge_tgt(it_config_t *cfg);
+
+void
+iscsit_config_destroy_tgts(list_t *tgt_del_list);
+
+void
+iscsit_config_destroy_tpgts(list_t *tpgt_del_list);
+
+iscsit_tgt_t *
+iscsit_tgt_lookup(char *target_name);
+
+iscsit_tgt_t *
+iscsit_tgt_lookup_locked(char *target_name);
+
+int
+iscsit_tgt_avl_compare(const void *void_tgt1, const void *void_tgt2);
+
+int
+iscsit_tpgt_avl_compare(const void *void_tpgt1, const void *void_tpgt2);
+
+void
+iscsit_tgt_hold(iscsit_tgt_t *tgt);
+
+void
+iscsit_tgt_rele(iscsit_tgt_t *tgt);
+
+iscsit_tpgt_t *
+iscsit_tgt_lookup_tpgt(iscsit_tgt_t *tgt, uint16_t tag);
+
+void
+iscsit_tpgt_hold(iscsit_tpgt_t *tpgt);
+
+void
+iscsit_tpgt_rele(iscsit_tpgt_t *tpgt);
+
+iscsit_portal_t *
+iscsit_tgt_lookup_portal(iscsit_tgt_t *tgt, struct sockaddr_storage *sa,
+ iscsit_tpgt_t **output_tpgt);
+
+iscsit_sess_t *
+iscsit_tgt_lookup_sess(iscsit_tgt_t *tgt, char *initiator_name,
+ uint8_t *isid, uint16_t tsih, uint16_t tag);
+
+void
+iscsit_tgt_bind_sess(iscsit_tgt_t *tgt, iscsit_sess_t *sess);
+
+void
+iscsit_tgt_unbind_sess(iscsit_tgt_t *tgt, iscsit_sess_t *sess);
+
+it_cfg_status_t
+iscsit_config_merge_tpg(it_config_t *cfg, list_t *tpg_del_list);
+
+void
+iscsit_config_destroy_tpgs(list_t *tpg_del_list);
+
+iscsit_tpg_t *
+iscsit_tpg_lookup(char *tpg_name);
+
+int
+iscsit_tpg_avl_compare(const void *void_tpg1, const void *void_tpg2);
+
+void
+iscsit_tpg_hold(iscsit_tpg_t *tpg);
+
+void
+iscsit_tpg_rele(iscsit_tpg_t *tpg);
+
+iscsit_tpg_t *
+iscsit_tpg_createdefault();
+
+void
+iscsit_tpg_destroydefault(iscsit_tpg_t *tpg);
+
+idm_status_t
+iscsit_tpg_online(iscsit_tpg_t *tpg);
+
+void
+iscsit_tpg_offline(iscsit_tpg_t *tpg);
+
+iscsit_portal_t *
+iscsit_tpg_portal_lookup(iscsit_tpg_t *tpg, struct sockaddr_storage *sa);
+
+void
+iscsit_portal_hold(iscsit_portal_t *portal);
+
+void
+iscsit_portal_rele(iscsit_portal_t *portal);
+
+it_cfg_status_t
+iscsit_config_merge_ini(it_config_t *cfg);
+
+int
+iscsit_ini_avl_compare(const void *void_ini1, const void *void_ini2);
+
+iscsit_ini_t *
+iscsit_ini_lookup_locked(char *ini_name);
+
+int
+iscsit_portal_avl_compare(const void *void_portal1, const void *void_portal2);
+
+int
+iscsit_verify_chap_resp(iscsit_conn_login_t *lsm,
+ unsigned int chap_i, uchar_t *chap_c, unsigned int challenge_len,
+ uchar_t *chap_r, unsigned int resp_len);
+
+#endif /* _ISCSIT_H_ */
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.c
new file mode 100644
index 0000000000..762081e5b6
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.c
@@ -0,0 +1,751 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/cpuvar.h>
+#include <sys/types.h>
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/modctl.h>
+
+#include <sys/socket.h>
+#include <sys/strsubr.h>
+#include <sys/sysmacros.h>
+
+#include <sys/stmf.h>
+#include <sys/stmf_ioctl.h>
+#include <sys/portif.h>
+#include <sys/idm/idm.h>
+#include <sys/idm/idm_text.h>
+#include <iscsit.h>
+#include <iscsit_auth.h>
+
+static kv_status_t
+iscsit_select_auth(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx);
+
+static kv_status_t
+auth_propose_chap(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx);
+
+static kv_status_t
+auth_chap_select_alg(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx);
+
+static kv_status_t
+auth_chap_recv_n(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx);
+
+static kv_status_t
+auth_chap_recv_r(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx);
+
+static kv_status_t
+auth_chap_recv_i(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx);
+
+static kv_status_t
+auth_chap_recv_c(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx);
+
+static kv_status_t
+iscsit_auth_propose(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx);
+
+static kv_status_t
+iscsit_auth_expect_key(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx);
+
+static kv_status_t
+auth_chap_expect_r(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx);
+
+static kv_status_t
+auth_chap_done(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx);
+
+static kv_status_t
+iscsit_auth_gen_challenge(iscsit_conn_t *ict);
+
+static kv_status_t
+iscsit_auth_gen_response(iscsit_conn_t *ict);
+
+typedef struct {
+ iscsit_auth_phase_t phase;
+ iscsikey_id_t kv_id;
+ iscsit_auth_handler_t handler;
+} auth_phase_entry_t;
+
+/*
+ * This table defines all authentication phases which have valid
+ * handler. The entries which have a non-zero key index are for
+ * a key/value pair handling when a key/value is being received,
+ * the rest of entries are for target checking the authentication
+ * phase after all key/value pair(s) are handled.
+ */
+static const auth_phase_entry_t apet[] = {
+ /* by key */
+ { AP_AM_UNDECIDED, KI_AUTH_METHOD, iscsit_select_auth },
+ { AP_AM_PROPOSED, KI_CHAP_A, auth_propose_chap },
+
+ { AP_CHAP_A_WAITING, KI_CHAP_A, auth_chap_select_alg },
+ { AP_CHAP_R_WAITING, KI_CHAP_N, auth_chap_recv_n },
+ { AP_CHAP_R_WAITING, KI_CHAP_R, auth_chap_recv_r },
+ { AP_CHAP_R_WAITING, KI_CHAP_I, auth_chap_recv_i },
+ { AP_CHAP_R_WAITING, KI_CHAP_C, auth_chap_recv_c },
+ { AP_CHAP_R_RCVD, KI_CHAP_N, auth_chap_recv_n },
+ { AP_CHAP_R_RCVD, KI_CHAP_R, auth_chap_recv_r },
+ { AP_CHAP_R_RCVD, KI_CHAP_I, auth_chap_recv_i },
+ { AP_CHAP_R_RCVD, KI_CHAP_C, auth_chap_recv_c },
+
+ /* by target */
+ { AP_AM_UNDECIDED, 0, iscsit_auth_propose },
+ { AP_AM_DECIDED, 0, iscsit_auth_expect_key },
+
+ { AP_CHAP_A_RCVD, 0, auth_chap_expect_r },
+ { AP_CHAP_R_RCVD, 0, auth_chap_done }
+};
+
+typedef struct {
+ iscsit_auth_method_t am_id;
+ char *am_name;
+} auth_id_name_t;
+
+/*
+ * a table of mapping from the authentication index to name.
+ */
+static const auth_id_name_t aint[] = {
+ { AM_CHAP, "CHAP" },
+ { AM_NONE, "None" },
+ /* { AM_KRB5, "KRB5" }, */ /* Not supported */
+ /* { AM_SPKM1, "SPKM1" }, */ /* Not supported */
+ /* { AM_SPKM2, "SPKM2" }, */ /* Not supported */
+ /* { AM_SRP, "SRP" }, */ /* Not supported */
+};
+
+#define ARRAY_LENGTH(ARRAY) (sizeof (ARRAY) / sizeof (ARRAY[0]))
+
+/*
+ * get the authentication method name for the method id.
+ */
+static const char *
+am_id_to_name(int id)
+{
+ int i;
+ const auth_id_name_t *p;
+ i = 0;
+ while (i < ARRAY_LENGTH(aint)) {
+ p = &(aint[i]);
+ if (id == p->am_id) {
+ return (p->am_name);
+ }
+ i ++;
+ }
+
+ return (NULL);
+}
+
+/*
+ * Look for an apporiate function handler which is defined for
+ * current authentication phase and matches the key which is
+ * being handled. The key index is passed in as zero when it
+ * is looking for an handler for checking the authentication phase
+ * after all security keys are handled.
+ */
+iscsit_auth_handler_t
+iscsit_auth_get_handler(iscsit_auth_client_t *client, iscsikey_id_t kv_id)
+{
+ iscsit_auth_phase_t phase = client->phase;
+ int i;
+ const auth_phase_entry_t *p;
+
+ i = 0;
+ p = NULL;
+ while (i < ARRAY_LENGTH(apet)) {
+ p = &(apet[i]);
+ if (phase == p->phase &&
+ kv_id == p->kv_id) {
+ return (p->handler);
+ }
+ i ++;
+ }
+
+ /* No handler can be found, it must be an invalid requst. */
+ return (NULL);
+}
+
+/*
+ * Select an authentication method from a list of values proposed
+ * by initiator. After a valid method is selected, shift the
+ * authentication phase to AP_AM_DECIDED.
+ */
+static kv_status_t
+iscsit_select_auth(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ conn_auth_t *auth = &lsm->icl_auth;
+ iscsit_auth_method_t *am_list = &auth->ca_method_valid_list[0];
+ iscsit_auth_client_t *client = &lsm->icl_auth_client;
+ int nvrc;
+ kv_status_t kvrc;
+ nvpair_t *am_choice;
+ char *am;
+ const char *am_name;
+ const char *text;
+ iscsit_auth_method_t am_id;
+ int i;
+
+ client->phase = AP_AM_DECIDED;
+
+ /* select a valid authentication method */
+ am_choice = idm_get_next_listvalue(nvp, NULL);
+ while (am_choice != NULL) {
+ nvrc = nvpair_value_string(am_choice, &am);
+ ASSERT(nvrc == 0);
+
+ i = 0;
+ am_id = am_list[i];
+ while (am_id != 0) {
+ am_name = am_id_to_name(am_id);
+ if (strcasecmp(am, am_name) == 0) {
+ text = am;
+ goto am_decided;
+ }
+ i++;
+ am_id = am_list[i];
+ }
+ am_choice = idm_get_next_listvalue(nvp, am_choice);
+ }
+
+ /* none of authentication method is valid */
+ am_id = 0;
+ text = ISCSI_TEXT_REJECT;
+
+am_decided:
+ client->negotiatedMethod = am_id;
+ /* add the selected method to the response nvlist */
+ nvrc = nvlist_add_string(lsm->icl_response_nvlist,
+ ikvx->ik_key_name, text);
+ kvrc = idm_nvstat_to_kvstat(nvrc);
+
+ return (kvrc);
+}
+
+/*
+ * Initiator chooses to use CHAP after target proposed a list of
+ * authentication method. Set the authentication method to CHAP and
+ * continue on chap authentication phase.
+ */
+static kv_status_t
+auth_propose_chap(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsit_auth_client_t *client = &lsm->icl_auth_client;
+
+ client->negotiatedMethod = AM_CHAP;
+ client->phase = AP_AM_DECIDED;
+
+ return (auth_chap_select_alg(ict, nvp, ikvx));
+}
+
+/*
+ * Select a CHAP algorithm from a list of values proposed by
+ * initiator and shift the authentication phase to AP_CHAP_A_RCVD.
+ */
+static kv_status_t
+auth_chap_select_alg(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsit_auth_client_t *client = &lsm->icl_auth_client;
+ int nvrc, rc;
+ kv_status_t kvrc;
+ nvpair_t *alg_choice;
+ char *alg_string;
+ uint64_t alg;
+ const char *text;
+
+ client->phase = AP_CHAP_A_RCVD;
+
+ alg_choice = idm_get_next_listvalue(nvp, NULL);
+ while (alg_choice != NULL) {
+ nvrc = nvpair_value_string(alg_choice, &alg_string);
+ ASSERT(nvrc == 0);
+ rc = idm_strtoull(alg_string, NULL, 0, (u_longlong_t *)&alg);
+ if (rc == 0 && alg == 5) {
+ /* only MD5 is supported */
+ text = alg_string;
+ goto alg_selected;
+ }
+
+ alg_choice = idm_get_next_listvalue(nvp, alg_choice);
+ }
+
+ /* none of algorithm is selected */
+ alg = 0;
+ text = ISCSI_TEXT_REJECT;
+
+alg_selected:
+ /* save the selected algorithm or zero for none is selected */
+ client_set_numeric_data(
+ &client->recvKeyBlock,
+ AKT_CHAP_A,
+ (uint32_t)alg);
+
+ /* add the selected algorithm to the response nvlist */
+ nvrc = nvlist_add_string(lsm->icl_response_nvlist,
+ ikvx->ik_key_name, text);
+ if (alg == 0) {
+ kvrc = KV_AUTH_FAILED; /* No algorithm selected */
+ } else {
+ kvrc = idm_nvstat_to_kvstat(nvrc);
+ if (kvrc == 0) {
+ kvrc = iscsit_auth_gen_challenge(ict);
+ }
+ }
+
+ return (kvrc);
+}
+
+/*
+ * Validate and save the the chap name which is sent by initiator
+ * and shift the authentication phase to AP_CHAP_R_RCVD.
+ *
+ * Note: the CHAP_N, CHAP_R, optionally CHAP_I and CHAP_C key/value
+ * pairs need to be received in one packet, we handle each of them
+ * separately, in order to track the authentication phase, we set
+ * the authentication phase to AP_CHAP_R_RCVD once one of them is
+ * handled. So both of AP_CHAP_R_WAITING and AP_CHAP_R_RCVD phases
+ * are valid for these keys. The function auth_chap_done is going
+ * to detect if any of these keys is missing.
+ */
+
+/*ARGSUSED*/
+static kv_status_t
+auth_chap_recv_n(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsit_auth_client_t *client = &lsm->icl_auth_client;
+ int nvrc;
+ char *chap_name;
+
+ nvrc = nvpair_value_string(nvp, &chap_name);
+ ASSERT(nvrc == 0);
+
+ client_set_string_data(&client->recvKeyBlock,
+ AKT_CHAP_N,
+ chap_name);
+
+ client->phase = AP_CHAP_R_RCVD;
+
+ return (KV_HANDLED);
+}
+
+/*
+ * Validate and save the the chap response which is sent by initiator
+ * and shift the authentication phase to AP_CHAP_R_RCVD.
+ *
+ * Note: see function auth_chap_recv_n.
+ */
+
+/*ARGSUSED*/
+static kv_status_t
+auth_chap_recv_r(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsit_auth_client_t *client = &lsm->icl_auth_client;
+ int nvrc;
+ unsigned char *chap_resp;
+ uint_t len;
+
+ nvrc = nvpair_value_byte_array(nvp, &chap_resp, &len);
+ ASSERT(nvrc == 0);
+
+ client_set_binary_data(&client->recvKeyBlock,
+ AKT_CHAP_R,
+ chap_resp, len);
+
+ client->phase = AP_CHAP_R_RCVD;
+
+ return (KV_HANDLED);
+}
+
+/*
+ * Validate and save the the chap identifier which is sent by initiator
+ * and shift the authentication phase to AP_CHAP_R_RCVD.
+ *
+ * Note: see function auth_chap_recv_n.
+ */
+
+/*ARGSUSED*/
+static kv_status_t
+auth_chap_recv_i(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsit_auth_client_t *client = &lsm->icl_auth_client;
+ int nvrc;
+ uint64_t chap_id;
+
+ nvrc = nvpair_value_uint64(nvp, &chap_id);
+ ASSERT(nvrc == 0);
+
+ client_set_numeric_data(&client->recvKeyBlock,
+ AKT_CHAP_I,
+ chap_id);
+
+ client->phase = AP_CHAP_R_RCVD;
+
+ return (KV_HANDLED);
+}
+
+/*
+ * Validate and save the the chap challenge which is sent by initiator
+ * and shift the authentication phase to AP_CHAP_R_RCVD.
+ *
+ * Note: see function auth_chap_recv_n.
+ */
+
+/*ARGSUSED*/
+static kv_status_t
+auth_chap_recv_c(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsit_auth_client_t *client = &lsm->icl_auth_client;
+ int nvrc;
+ unsigned char *chap_challenge;
+ uint_t len;
+
+ nvrc = nvpair_value_byte_array(nvp, &chap_challenge, &len);
+ ASSERT(nvrc == 0);
+
+ client_set_binary_data(
+ &client->recvKeyBlock,
+ AKT_CHAP_C,
+ chap_challenge, len);
+
+ client->phase = AP_CHAP_R_RCVD;
+
+ return (KV_HANDLED);
+}
+
+/*
+ * Shift the authentication phase to AP_CHAP_R_WAITING after target
+ * has successfully selected a chap algorithm.
+ */
+
+/*ARGSUSED*/
+static kv_status_t
+auth_chap_expect_r(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsit_auth_client_t *client = &lsm->icl_auth_client;
+
+ uint32_t alg;
+
+ client_get_numeric_data(&client->recvKeyBlock,
+ AKT_CHAP_A,
+ &alg);
+
+ if (alg != 0) {
+ client->phase = AP_CHAP_R_WAITING;
+ } else {
+ /* none of proposed algorithm is supported or understood. */
+ client->phase = AP_CHAP_A_WAITING;
+ }
+
+ return (KV_HANDLED);
+}
+
+/*
+ * Initiator does not propose security negotiation, target needs to
+ * verify if we can bypass the security negotiation phase or propose
+ * a security negotiation for the initiator.
+ */
+
+/*ARGSUSED*/
+static kv_status_t
+iscsit_auth_propose(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ conn_auth_t *auth = &lsm->icl_auth;
+ iscsit_auth_method_t *am_list = &auth->ca_method_valid_list[0];
+ iscsit_auth_client_t *client = &lsm->icl_auth_client;
+
+ int nvrc;
+ kv_status_t kvrc;
+ const char *am_name;
+
+ if (am_list[0] == AM_NONE || am_list[0] == 0) {
+ lsm->icl_auth_pass = 1;
+ }
+
+ if (lsm->icl_auth_pass == 0) {
+ /*
+ * It should be noted that the negotiation might also
+ * be directed by the target if the initiator does
+ * support security, but is not ready to direct the
+ * negotiation (propose options).
+ * - RFC3720 section 5.3.2.
+ */
+ am_name = am_id_to_name(am_list[0]);
+ nvrc = nvlist_add_string(
+ lsm->icl_response_nvlist,
+ "AuthMethod", am_name);
+ kvrc = idm_nvstat_to_kvstat(nvrc);
+
+ client->phase = AP_AM_PROPOSED;
+ } else {
+ kvrc = KV_HANDLED;
+
+ client->phase = AP_DONE;
+ }
+
+ return (kvrc);
+}
+
+/*
+ * Shift the authentication phase according to the authentication
+ * method once it is selected.
+ */
+
+/*ARGSUSED*/
+static kv_status_t
+iscsit_auth_expect_key(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsit_auth_client_t *client = &lsm->icl_auth_client;
+
+ if (client->negotiatedMethod != 0) {
+ /* Shift security negotiation phase. */
+ switch (client->negotiatedMethod) {
+ case AM_CHAP:
+ client->phase = AP_CHAP_A_WAITING;
+ break;
+ case AM_NONE:
+ client->phase = AP_DONE;
+ lsm->icl_auth_pass = 1;
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+ } else {
+ /* None of proposed method is supported or understood. */
+ client->phase = AP_AM_UNDECIDED;
+ }
+
+ return (KV_HANDLED);
+}
+
+/*
+ * The last step of the chap authentication. We will validate the
+ * chap parameters we received and authenticate the client here.
+ */
+
+/*ARGSUSED*/
+static kv_status_t
+auth_chap_done(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsit_auth_client_t *client = &lsm->icl_auth_client;
+ kv_status_t kvrc = KV_HANDLED;
+
+ conn_auth_t *auth = &lsm->icl_auth;
+ char *username_in;
+
+ uint32_t chap_id;
+ unsigned char *chap_challenge;
+ unsigned int challenge_len;
+ char *chap_name;
+ unsigned char *chap_resp;
+ unsigned int resp_len;
+
+ int bi_auth;
+
+ username_in = auth->ca_ini_chapuser;
+ if (username_in[0] == '\0')
+ return (KV_AUTH_FAILED);
+
+ /*
+ * Check if we have received a valid list of response keys.
+ */
+ if (!client_auth_key_present(&client->recvKeyBlock, AKT_CHAP_N) ||
+ !client_auth_key_present(&client->recvKeyBlock, AKT_CHAP_R) ||
+ ((bi_auth =
+ client_auth_key_present(&client->recvKeyBlock, AKT_CHAP_I)) ^
+ client_auth_key_present(&client->recvKeyBlock, AKT_CHAP_C))) {
+ return (KV_MISSING_FIELDS);
+ }
+
+ client->phase = AP_DONE;
+
+ client_get_string_data(&client->recvKeyBlock,
+ AKT_CHAP_N,
+ &chap_name);
+
+ /* check username */
+ if (strcmp(username_in, chap_name) != 0) {
+ return (KV_AUTH_FAILED);
+ }
+
+ client_get_numeric_data(&client->sendKeyBlock,
+ AKT_CHAP_I,
+ &chap_id);
+
+ client_get_binary_data(&client->sendKeyBlock,
+ AKT_CHAP_C,
+ &chap_challenge, &challenge_len);
+
+ client_get_binary_data(&client->recvKeyBlock,
+ AKT_CHAP_R,
+ &chap_resp, &resp_len);
+
+ if (iscsit_verify_chap_resp(lsm,
+ chap_id, chap_challenge, challenge_len,
+ chap_resp, resp_len) != ISCSI_AUTH_PASSED) {
+ return (KV_AUTH_FAILED);
+ }
+
+ /* bi-direction authentication is required */
+ if (bi_auth != 0) {
+ kvrc = iscsit_auth_gen_response(ict);
+ }
+
+ lsm->icl_auth_pass = 1;
+
+ return (kvrc);
+}
+
+static kv_status_t
+iscsit_auth_gen_challenge(iscsit_conn_t *ict)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsit_auth_client_t *client = &lsm->icl_auth_client;
+ int nvrc;
+ kv_status_t kvrc;
+
+ unsigned char idData[1];
+ unsigned char *bin;
+ int len;
+
+ auth_random_set_data(idData, 1);
+ client_set_numeric_data(&client->sendKeyBlock,
+ AKT_CHAP_I,
+ idData[0]);
+
+ /* send chap identifier */
+ nvrc = nvlist_add_uint64(
+ lsm->icl_response_nvlist,
+ "CHAP_I", idData[0]);
+ kvrc = idm_nvstat_to_kvstat(nvrc);
+ if (kvrc != 0) {
+ return (kvrc);
+ }
+
+ bin = &(client->auth_send_binary_block.largeBinary[0]);
+ len = iscsiAuthChapResponseLength;
+ auth_random_set_data(bin, len);
+ client_set_binary_data(&client->sendKeyBlock,
+ AKT_CHAP_C,
+ bin, len);
+
+ /* send chap challenge */
+ nvrc = nvlist_add_byte_array(
+ lsm->icl_response_nvlist,
+ "CHAP_C", bin, len);
+ kvrc = idm_nvstat_to_kvstat(nvrc);
+
+ return (kvrc);
+}
+
+static kv_status_t
+iscsit_auth_gen_response(iscsit_conn_t *ict)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsit_auth_client_t *client = &lsm->icl_auth_client;
+ int nvrc;
+ kv_status_t kvrc;
+
+ conn_auth_t *auth = &lsm->icl_auth;
+ char *tgt_username;
+ uint8_t *tgt_password;
+ int tgt_password_length;
+
+ uint32_t chap_id;
+ unsigned char *chap_challenge;
+ unsigned int challenge_len;
+ uchar_t resp[iscsiAuthChapResponseLength];
+
+ tgt_username = auth->ca_tgt_chapuser;
+ tgt_password = auth->ca_tgt_chapsecret;
+ tgt_password_length = auth->ca_tgt_chapsecretlen;
+
+ /*
+ * We can't know in advance whether the initiator will attempt
+ * mutual authentication, so now we need to check whether we
+ * have a target CHAP secret configured.
+ */
+ if (tgt_password_length == 0) {
+ return (KV_AUTH_FAILED);
+ }
+
+ client_get_numeric_data(&client->recvKeyBlock,
+ AKT_CHAP_I,
+ &chap_id);
+
+ client_get_binary_data(&client->recvKeyBlock,
+ AKT_CHAP_C,
+ &chap_challenge, &challenge_len);
+
+ client_compute_chap_resp(
+ &resp[0],
+ chap_id,
+ tgt_password, tgt_password_length,
+ chap_challenge, challenge_len);
+
+ nvrc = nvlist_add_string(
+ lsm->icl_response_nvlist,
+ "CHAP_N", tgt_username);
+
+ if (nvrc == 0) {
+ nvrc = nvlist_add_byte_array(
+ lsm->icl_response_nvlist,
+ "CHAP_R", resp, sizeof (resp));
+ }
+ kvrc = idm_nvstat_to_kvstat(nvrc);
+
+ return (kvrc);
+}
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.h b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.h
new file mode 100644
index 0000000000..2734fd64b3
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_auth.h
@@ -0,0 +1,34 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+#ifndef _ISCSIT_AUTH_H_
+#define _ISCSIT_AUTH_H_
+
+typedef kv_status_t (*iscsit_auth_handler_t)(iscsit_conn_t *, nvpair_t *,
+ const idm_kv_xlate_t *);
+
+iscsit_auth_handler_t
+iscsit_auth_get_handler(iscsit_auth_client_t *, iscsikey_id_t);
+
+#endif /* _ISCSIT_AUTH_H_ */
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.c
new file mode 100644
index 0000000000..3843362368
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.c
@@ -0,0 +1,261 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/types.h>
+#include <sys/random.h>
+#include <sys/conf.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+
+#include <sys/socket.h>
+#include <inet/tcp.h>
+
+#include <sys/stmf.h>
+#include <sys/stmf_ioctl.h>
+#include <sys/portif.h>
+#include <sys/idm/idm.h>
+
+#include <iscsit.h>
+
+#include <sys/iscsit/chap.h>
+#include <radius_auth.h>
+
+void
+client_set_numeric_data(auth_key_block_t *keyBlock,
+ int key_type,
+ uint32_t numeric)
+{
+ auth_key_t *p;
+
+ ASSERT(key_type < AUTH_KEY_TYPE_MAX);
+
+ p = &keyBlock->key[key_type];
+ p->value.numeric = numeric;
+ p->present = 1;
+}
+
+void
+client_set_string_data(auth_key_block_t *keyBlock,
+ int key_type,
+ char *string)
+{
+ auth_key_t *p;
+
+ ASSERT(key_type < AUTH_KEY_TYPE_MAX);
+
+ p = &keyBlock->key[key_type];
+ p->value.string = string;
+ p->present = 1;
+}
+
+void
+client_set_binary_data(auth_key_block_t *keyBlock,
+ int key_type,
+ unsigned char *binary, unsigned int len)
+{
+ auth_key_t *p;
+
+ ASSERT(key_type < AUTH_KEY_TYPE_MAX);
+
+ p = &keyBlock->key[key_type];
+ p->value.binary = binary;
+ p->len = len;
+ p->present = 1;
+}
+
+void
+client_get_numeric_data(auth_key_block_t *keyBlock,
+ int key_type,
+ uint32_t *numeric)
+{
+ auth_key_t *p;
+
+ ASSERT(key_type < AUTH_KEY_TYPE_MAX);
+
+ p = &keyBlock->key[key_type];
+ *numeric = p->value.numeric;
+}
+
+void
+client_get_string_data(auth_key_block_t *keyBlock,
+ int key_type,
+ char **string)
+{
+ auth_key_t *p;
+
+ ASSERT(key_type < AUTH_KEY_TYPE_MAX);
+
+ p = &keyBlock->key[key_type];
+ *string = p->value.string;
+}
+
+void
+client_get_binary_data(auth_key_block_t *keyBlock,
+ int key_type,
+ unsigned char **binary, unsigned int *len)
+{
+ auth_key_t *p;
+
+ ASSERT(key_type < AUTH_KEY_TYPE_MAX);
+
+ p = &keyBlock->key[key_type];
+ *binary = p->value.binary;
+ *len = p->len;
+}
+
+int
+client_auth_key_present(auth_key_block_t *keyBlock,
+ int key_type)
+{
+ auth_key_t *p;
+
+ ASSERT(key_type < AUTH_KEY_TYPE_MAX);
+
+ p = &keyBlock->key[key_type];
+
+ return (p->present != 0 ? 1 : 0);
+}
+
+/*ARGSUSED*/
+void
+client_compute_chap_resp(uchar_t *resp,
+ unsigned int chap_i,
+ uint8_t *password, int password_len,
+ uchar_t *chap_c, unsigned int challenge_len)
+{
+ MD5_CTX context;
+
+ MD5Init(&context);
+
+ /*
+ * id byte
+ */
+ resp[0] = (uchar_t)chap_i;
+ MD5Update(&context, resp, 1);
+
+ /*
+ * shared secret
+ */
+ MD5Update(&context, (uchar_t *)password, password_len);
+
+ /*
+ * challenge value
+ */
+ MD5Update(&context, chap_c, challenge_len);
+
+ MD5Final(resp, &context);
+}
+
+int
+iscsit_verify_chap_resp(iscsit_conn_login_t *lsm,
+ unsigned int chap_i,
+ uchar_t *chap_c, unsigned int challenge_len,
+ uchar_t *chap_r, unsigned int resp_len)
+{
+ uchar_t verifyData[iscsiAuthChapResponseLength];
+ conn_auth_t *auth = &lsm->icl_auth;
+
+ /* Check if RADIUS access is enabled */
+ if (auth->ca_use_radius == B_TRUE) {
+ chap_validation_status_type chap_valid_status;
+ RADIUS_CONFIG radius_cfg;
+ struct sockaddr_storage *sa = &auth->ca_radius_server;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+
+ /* Use RADIUS server to authentication target */
+ sin = (struct sockaddr_in *)sa;
+ radius_cfg.rad_svr_port = ntohs(sin->sin_port);
+ if (sa->ss_family == AF_INET) {
+ /* IPv4 */
+ radius_cfg.rad_svr_addr.i_addr.in4.s_addr =
+ sin->sin_addr.s_addr;
+ radius_cfg.rad_svr_addr.i_insize = sizeof (in_addr_t);
+ } else if (sa->ss_family == AF_INET6) {
+ /* IPv6 */
+ sin6 = (struct sockaddr_in6 *)sa;
+ bcopy(sin6->sin6_addr.s6_addr,
+ radius_cfg.rad_svr_addr.i_addr.in6.s6_addr,
+ sizeof (struct in6_addr));
+ radius_cfg.rad_svr_addr.i_insize = sizeof (in6_addr_t);
+ } else {
+ return (ISCSI_AUTH_FAILED);
+ }
+
+ bcopy(auth->ca_radius_secret,
+ radius_cfg.rad_svr_shared_secret,
+ MAX_RAD_SHARED_SECRET_LEN);
+ radius_cfg.rad_svr_shared_secret_len =
+ auth->ca_radius_secretlen;
+
+ chap_valid_status = iscsit_radius_chap_validate(
+ auth->ca_ini_chapuser,
+ auth->ca_tgt_chapuser,
+ chap_c,
+ challenge_len,
+ chap_r,
+ resp_len,
+ chap_i,
+ radius_cfg.rad_svr_addr,
+ radius_cfg.rad_svr_port,
+ radius_cfg.rad_svr_shared_secret,
+ radius_cfg.rad_svr_shared_secret_len);
+
+ if (chap_valid_status == CHAP_VALIDATION_PASSED) {
+ return (ISCSI_AUTH_PASSED);
+ }
+ return (ISCSI_AUTH_FAILED);
+ }
+
+ /* Empty chap secret is not allowed */
+ if (auth->ca_ini_chapsecretlen == 0) {
+ return (ISCSI_AUTH_FAILED);
+ }
+
+ /* only MD5 is supported */
+ if (resp_len != sizeof (verifyData)) {
+ return (ISCSI_AUTH_FAILED);
+ }
+
+ client_compute_chap_resp(
+ &verifyData[0],
+ chap_i,
+ auth->ca_ini_chapsecret, auth->ca_ini_chapsecretlen,
+ chap_c, challenge_len);
+
+ if (bcmp(chap_r, verifyData,
+ sizeof (verifyData)) != 0) {
+ return (ISCSI_AUTH_FAILED);
+ }
+
+ /* chap response OK */
+ return (ISCSI_AUTH_PASSED);
+}
+
+void
+auth_random_set_data(uchar_t *data, unsigned int length)
+{
+ (void) random_get_pseudo_bytes(data, length);
+}
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.h b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.h
new file mode 100644
index 0000000000..dd5a3b40d4
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_authclient.h
@@ -0,0 +1,160 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+#ifndef _ISCSIT_AUTHCLIENT_H_
+#define _ISCSIT_AUTHCLIENT_H_
+
+#define ISCSI_AUTH_PASSED 0
+#define ISCSI_AUTH_FAILED 1
+
+enum { iscsiAuthStringMaxLength = 256 };
+
+enum { AuthStringMaxLength = 256 };
+enum { AuthStringBlockMaxLength = 1024 };
+enum { AuthLargeBinaryMaxLength = 1024 };
+
+enum { iscsiAuthChapResponseLength = 16 };
+
+enum { iscsiAuthMethodMaxCount = 2 };
+
+enum { iscsiAuthChapAlgorithmMd5 = 5 };
+
+enum {
+ AKT_CHAP_A = 0,
+ AKT_CHAP_I,
+ AKT_CHAP_C,
+ AKT_CHAP_N,
+ AKT_CHAP_R,
+ AUTH_KEY_TYPE_MAX
+};
+
+typedef union auth_value {
+ uint32_t numeric;
+ char *string;
+ unsigned char *binary;
+} auth_value_t;
+
+typedef struct auth_key {
+ unsigned char present;
+ unsigned int len;
+ auth_value_t value;
+} auth_key_t;
+
+typedef struct iscsit_auth_key_block {
+ auth_key_t key[AUTH_KEY_TYPE_MAX];
+} auth_key_block_t;
+
+typedef struct auth_large_binary {
+ unsigned char largeBinary[AuthLargeBinaryMaxLength];
+} auth_large_binary_t;
+
+typedef enum {
+ AM_CHAP = 1, /* keep 0 as invalid */
+ AM_KRB5,
+ AM_SPKM1,
+ AM_SPKM2,
+ AM_SRP,
+ AM_NONE
+} iscsit_auth_method_t;
+
+typedef enum {
+ /* authentication phase start status */
+ AP_AM_UNDECIDED = 0,
+ AP_AM_PROPOSED,
+ AP_AM_DECIDED,
+
+ /* authentication phase for chap */
+ AP_CHAP_A_WAITING,
+ AP_CHAP_A_RCVD,
+ AP_CHAP_R_WAITING,
+ AP_CHAP_R_RCVD,
+
+ /* authentication phase for kerberos */
+ AP_KRB_REQ_WAITING,
+ AP_KRB_REQ_RCVD,
+
+ /* authentication phase done */
+ AP_DONE
+} iscsit_auth_phase_t;
+
+typedef struct iscsit_auth_client {
+ iscsit_auth_phase_t phase;
+ iscsit_auth_method_t negotiatedMethod;
+
+ auth_large_binary_t auth_send_binary_block;
+
+ auth_key_block_t recvKeyBlock;
+ auth_key_block_t sendKeyBlock;
+} iscsit_auth_client_t;
+
+void
+client_set_numeric_data(auth_key_block_t *keyBlock,
+ int key_type,
+ uint32_t numeric);
+
+void
+client_set_string_data(auth_key_block_t *keyBlock,
+ int key_type,
+ char *string);
+
+void
+client_set_binary_data(auth_key_block_t *keyBlock,
+ int key_type,
+ unsigned char *binary, unsigned int len);
+
+void
+client_get_numeric_data(auth_key_block_t *keyBlock,
+ int key_type,
+ uint32_t *numeric);
+
+void
+client_get_string_data(auth_key_block_t *keyBlock,
+ int key_type,
+ char **string);
+
+void
+client_get_binary_data(auth_key_block_t *keyBlock,
+ int key_type,
+ unsigned char **binary, unsigned int *len);
+
+int
+client_auth_key_present(auth_key_block_t *keyBlock,
+ int key_type);
+
+void
+client_compute_chap_resp(uchar_t *resp,
+ unsigned int chap_i,
+ uint8_t *password, int password_len,
+ uchar_t *chap_c, unsigned int challenge_len);
+
+int
+client_verify_chap_resp(char *target_chap_name, char *initiator_chap_name,
+ uint8_t *password, int password_len,
+ unsigned int chap_i, uchar_t *chap_c, unsigned int challenge_len,
+ uchar_t *chap_r, unsigned int resp_len);
+
+void
+auth_random_set_data(uchar_t *data, unsigned int length);
+
+#endif /* _ISCSIT_AUTHCLIENT_H_ */
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.c
new file mode 100644
index 0000000000..7aec02edef
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.c
@@ -0,0 +1,2531 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/cpuvar.h>
+#include <sys/types.h>
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/socket.h>
+#include <inet/tcp.h>
+#include <sys/sdt.h>
+
+#include <sys/stmf.h>
+#include <sys/stmf_ioctl.h>
+#include <sys/portif.h>
+#include <sys/idm/idm.h>
+#include <sys/idm/idm_so.h>
+#include <sys/iscsit/iscsit_common.h>
+#include <sys/iscsit/isns_protocol.h>
+#include <iscsit.h>
+#include <iscsit_isns.h>
+
+/* local defines */
+#define MAX_XID (2^16)
+#define ISNS_IDLE_TIME 60
+#define MAX_RETRY (3)
+
+#define VALID_NAME(NAME, LEN) \
+((LEN) > 0 && (NAME)[0] != 0 && (NAME)[(LEN) - 1] == 0)
+
+static kmutex_t isns_mutex;
+static kthread_t *isns_monitor_thr_id;
+
+static kcondvar_t isns_idle_cv;
+static boolean_t isns_idle;
+
+static uint16_t xid;
+#define GET_XID() atomic_inc_16_nv(&xid)
+
+static clock_t monitor_idle_interval;
+
+#define ISNS_GLOBAL_LOCK() \
+ mutex_enter(&iscsit_global.global_isns_cfg.isns_mutex)
+
+#define ISNS_GLOBAL_LOCK_HELD() \
+ MUTEX_HELD(&iscsit_global.global_isns_cfg.isns_mutex)
+
+#define ISNS_GLOBAL_UNLOCK() \
+ mutex_exit(&iscsit_global.global_isns_cfg.isns_mutex)
+
+/*
+ * iSNS ESI thread state
+ */
+
+static kmutex_t isns_esi_mutex;
+static kcondvar_t isns_esi_cv;
+static list_t esi_list;
+static uint32_t isns_esi_max_interval = 0;
+
+/*
+ * List of portals.
+ */
+
+static list_t portal_list;
+static uint32_t portal_list_count = 0;
+
+/* How many of our portals are not "default"? */
+static uint32_t nondefault_portals = 0;
+
+/*
+ * Our entity identifier (fully-qualified hostname)
+ */
+static char *isns_eid = NULL;
+
+/*
+ * Our list of targets
+ */
+static avl_tree_t isns_target_list;
+
+static void
+isnst_start();
+
+static void
+isnst_stop();
+
+static void
+iscsit_set_isns(boolean_t state);
+
+static int
+iscsit_add_isns(it_portal_t *cfg_svr);
+
+static void
+iscsit_delete_isns(iscsit_isns_svr_t *svr);
+
+static iscsit_isns_svr_t *
+iscsit_isns_svr_lookup(struct sockaddr_storage *sa);
+
+static void
+isnst_monitor(void *arg);
+
+static int
+isnst_monitor_one_server(iscsit_isns_svr_t *svr, boolean_t enabled);
+
+static int
+isnst_update_target(iscsit_tgt_t *target, isns_reg_type_t reg);
+
+static int
+isnst_update_one_server(iscsit_isns_svr_t *svr, iscsit_tgt_t *target,
+ isns_reg_type_t reg);
+
+static int isnst_register(iscsit_isns_svr_t *svr, iscsit_tgt_t *target,
+ isns_reg_type_t regtype);
+static int isnst_deregister(iscsit_isns_svr_t *svr, char *node);
+
+static size_t
+isnst_make_dereg_pdu(isns_pdu_t **pdu, char *node);
+
+static int
+isnst_verify_rsp(isns_pdu_t *pdu, isns_pdu_t *rsp);
+
+static uint16_t
+isnst_pdu_get_op(isns_pdu_t *pdu, uint8_t **pp);
+
+static size_t
+isnst_make_reg_pdu(isns_pdu_t **pdu, iscsit_tgt_t *target,
+ boolean_t svr_registered, isns_reg_type_t regtype);
+
+static size_t
+isnst_create_pdu_header(uint16_t func_id, isns_pdu_t **pdu, uint16_t flags);
+
+static int
+isnst_add_attr(isns_pdu_t *pdu,
+ size_t max_pdu_size,
+ uint32_t attr_id,
+ uint32_t attr_len,
+ void *attr_data,
+ uint32_t attr_numeric_data);
+
+static int
+isnst_send_pdu(void *so, isns_pdu_t *pdu);
+
+static size_t
+isnst_rcv_pdu(void *so, isns_pdu_t **pdu);
+
+static void *
+isnst_open_so(struct sockaddr_storage *sa);
+
+static void
+isnst_close_so(void *);
+
+static void
+isnst_esi_thread(void *arg);
+
+static boolean_t
+isnst_handle_esi_req(struct sonode *so, isns_pdu_t *pdu, size_t pl_size);
+
+static void isnst_esi_start(isns_portal_list_t *portal);
+static void isnst_esi_stop();
+static void isnst_esi_stop_thread(isns_esi_tinfo_t *tinfop);
+static void isnst_esi_check();
+static void isnst_esi_start_thread(isns_esi_tinfo_t *tinfop);
+static isns_target_t *isnst_add_to_target_list(iscsit_tgt_t *target);
+int isnst_tgt_avl_compare(const void *t1, const void *t2);
+static void isnst_get_target_list(void);
+static void isnst_set_server_status(iscsit_isns_svr_t *svr,
+ boolean_t registered);
+static void isnst_wakeup_monitor(void);
+static void isns_remove_portal(isns_portal_list_t *p);
+static void isnst_add_default_portals();
+static int isnst_add_default_portal_attrs(isns_pdu_t *pdu, size_t pdu_size);
+static void isnst_remove_default_portals();
+static boolean_t isnst_retry_registration(int rsp_status_code);
+
+it_cfg_status_t
+isnst_config_merge(it_config_t *cfg)
+{
+ boolean_t new_isns_state = B_FALSE;
+ iscsit_isns_svr_t *isns_svr, *next_isns_svr;
+ it_portal_t *cfg_isns_svr;
+
+ /*
+ * Determine whether iSNS is enabled in the new config.
+ * Isns property may not be set up yet.
+ */
+ (void) nvlist_lookup_boolean_value(cfg->config_global_properties,
+ PROP_ISNS_ENABLED, &new_isns_state);
+
+ ISNS_GLOBAL_LOCK();
+
+ /* Delete iSNS servers that are no longer part of the config */
+ for (isns_svr = list_head(&iscsit_global.global_isns_cfg.isns_svrs);
+ isns_svr != NULL;
+ isns_svr = next_isns_svr) {
+ next_isns_svr = list_next(
+ &iscsit_global.global_isns_cfg.isns_svrs, isns_svr);
+ if (it_sns_svr_lookup(cfg, &isns_svr->svr_sa) == NULL)
+ iscsit_delete_isns(isns_svr);
+ }
+
+ /* Add new iSNS servers */
+ for (cfg_isns_svr = cfg->config_isns_svr_list;
+ cfg_isns_svr != NULL;
+ cfg_isns_svr = cfg_isns_svr->next) {
+ isns_svr = iscsit_isns_svr_lookup(&cfg_isns_svr->portal_addr);
+ if (isns_svr == NULL) {
+ if (iscsit_add_isns(cfg_isns_svr) != 0) {
+ /* Shouldn't happen */
+ ISNS_GLOBAL_UNLOCK();
+ return (ITCFG_MISC_ERR);
+ }
+ }
+ }
+
+ /* Start/Stop iSNS if necessary */
+ if (iscsit_global.global_isns_cfg.isns_state != new_isns_state) {
+ iscsit_set_isns(new_isns_state);
+ }
+
+ ISNS_GLOBAL_UNLOCK();
+
+ /*
+ * There is no "modify case" since the user specifies a complete
+ * server list each time. A modify is the same as a remove+add.
+ */
+
+ return (0);
+}
+
+int
+iscsit_isns_init(iscsit_hostinfo_t *hostinfo)
+{
+ mutex_init(&iscsit_global.global_isns_cfg.isns_mutex, NULL,
+ MUTEX_DEFAULT, NULL);
+
+ ISNS_GLOBAL_LOCK();
+ iscsit_global.global_isns_cfg.isns_state = B_FALSE;
+ list_create(&iscsit_global.global_isns_cfg.isns_svrs,
+ sizeof (iscsit_isns_svr_t), offsetof(iscsit_isns_svr_t, svr_ln));
+ list_create(&portal_list, sizeof (isns_portal_list_t),
+ offsetof(isns_portal_list_t, portal_ln));
+ list_create(&esi_list, sizeof (isns_esi_tinfo_t),
+ offsetof(isns_esi_tinfo_t, esi_ln));
+ portal_list_count = 0;
+ isns_eid = kmem_alloc(hostinfo->length, KM_SLEEP);
+ if (hostinfo->length > ISCSIT_MAX_HOSTNAME_LEN)
+ hostinfo->length = ISCSIT_MAX_HOSTNAME_LEN;
+ (void) strlcpy(isns_eid, hostinfo->fqhn, hostinfo->length);
+ avl_create(&isns_target_list, isnst_tgt_avl_compare,
+ sizeof (isns_target_t), offsetof(isns_target_t, target_node));
+ /*
+ * The iscsi global lock is not held here, but it is held when
+ * isnst_start is called, so we need to acquire it only in this
+ * case.
+ */
+ ISCSIT_GLOBAL_LOCK(RW_READER);
+ isnst_get_target_list();
+ ISCSIT_GLOBAL_UNLOCK();
+
+ /* initialize isns client */
+ mutex_init(&isns_mutex, NULL, MUTEX_DEFAULT, NULL);
+ mutex_init(&isns_esi_mutex, NULL, MUTEX_DEFAULT, NULL);
+ isns_monitor_thr_id = NULL;
+ monitor_idle_interval = ISNS_IDLE_TIME * drv_usectohz(1000000);
+ cv_init(&isns_idle_cv, NULL, CV_DEFAULT, NULL);
+ cv_init(&isns_esi_cv, NULL, CV_DEFAULT, NULL);
+ isns_idle = B_TRUE;
+ xid = 0;
+ ISNS_GLOBAL_UNLOCK();
+
+ return (0);
+}
+
+static void
+isnst_esi_stop_thread(isns_esi_tinfo_t *tinfop)
+{
+ ASSERT(ISNS_GLOBAL_LOCK_HELD());
+ ASSERT(mutex_owned(&isns_esi_mutex));
+
+ list_remove(&esi_list, tinfop);
+
+ /*
+ * The only way to break a thread waiting in soaccept() is to signal
+ * it with EINTR. See idm_so_tgt_svc_offline for more detail.
+ */
+ tinfop->esi_so->so_error = EINTR;
+ cv_signal(&tinfop->esi_so->so_connind_cv);
+
+ /*
+ * Must also drop the global lock in case the esi thread is running
+ * and trying to update the server timestamps.
+ */
+ mutex_exit(&isns_esi_mutex);
+ ISNS_GLOBAL_UNLOCK();
+ thread_join(tinfop->esi_thread_did);
+ ISNS_GLOBAL_LOCK();
+ mutex_enter(&isns_esi_mutex);
+
+ tinfop->esi_portal->portal_esi = NULL;
+ kmem_free(tinfop, sizeof (isns_esi_tinfo_t));
+}
+
+static void
+isnst_esi_stop()
+{
+ /*
+ * Basically, we just wait for all the threads to stop. They
+ * should already be in the process of shutting down.
+ */
+
+ ASSERT(ISNS_GLOBAL_LOCK_HELD());
+
+ ISNS_GLOBAL_UNLOCK();
+ mutex_enter(&isns_esi_mutex);
+ while (!list_is_empty(&esi_list)) {
+ cv_wait(&isns_esi_cv, &isns_esi_mutex);
+ }
+ mutex_exit(&isns_esi_mutex);
+ ISNS_GLOBAL_LOCK();
+}
+
+void
+iscsit_isns_fini()
+{
+ ISNS_GLOBAL_LOCK();
+ iscsit_set_isns(B_FALSE);
+ ISNS_GLOBAL_UNLOCK();
+
+ mutex_enter(&isns_mutex);
+ while (isns_monitor_thr_id != NULL) {
+ cv_wait(&isns_idle_cv, &isns_mutex);
+ }
+ mutex_exit(&isns_mutex);
+
+ ISNS_GLOBAL_LOCK();
+ mutex_destroy(&isns_mutex);
+ cv_destroy(&isns_idle_cv);
+ list_destroy(&esi_list);
+ mutex_destroy(&isns_esi_mutex);
+ cv_destroy(&isns_esi_cv);
+
+ /*
+ * Free our EID and target list.
+ */
+
+ if (isns_eid) {
+ kmem_free(isns_eid, strlen(isns_eid) + 1);
+ isns_eid = NULL;
+ }
+
+ iscsit_global.global_isns_cfg.isns_state = B_FALSE;
+ avl_destroy(&isns_target_list);
+ list_destroy(&iscsit_global.global_isns_cfg.isns_svrs);
+ list_destroy(&portal_list);
+ portal_list_count = 0;
+ ISNS_GLOBAL_UNLOCK();
+
+ mutex_destroy(&iscsit_global.global_isns_cfg.isns_mutex);
+}
+
+static void
+iscsit_set_isns(boolean_t state)
+{
+ iscsit_isns_svr_t *svr;
+
+ ASSERT(ISNS_GLOBAL_LOCK_HELD());
+
+ /* reset retry count for all servers */
+ for (svr = list_head(&iscsit_global.global_isns_cfg.isns_svrs);
+ svr != NULL;
+ svr = list_next(&iscsit_global.global_isns_cfg.isns_svrs, svr)) {
+ svr->svr_retry_count = 0;
+ }
+
+ /*
+ * Update state and isns stop flag
+ */
+ iscsit_global.global_isns_cfg.isns_state = state;
+
+ if (state) {
+ isnst_start();
+ } else {
+ isnst_stop();
+ }
+}
+
+int
+iscsit_add_isns(it_portal_t *cfg_svr)
+{
+ iscsit_isns_svr_t *svr;
+
+ ASSERT(ISNS_GLOBAL_LOCK_HELD());
+
+ svr = kmem_zalloc(sizeof (iscsit_isns_svr_t), KM_SLEEP);
+ bcopy(&cfg_svr->portal_addr, &svr->svr_sa,
+ sizeof (struct sockaddr_storage));
+
+ /* put it on the global isns server list */
+ list_insert_tail(&iscsit_global.global_isns_cfg.isns_svrs, svr);
+
+ /*
+ * Register targets with this server if iSNS is enabled.
+ */
+
+ if (iscsit_global.global_isns_cfg.isns_state &&
+ (isnst_update_one_server(svr, NULL, ISNS_REGISTER_ALL) == 0)) {
+ isnst_set_server_status(svr, B_TRUE);
+ }
+
+ return (0);
+}
+
+void
+iscsit_delete_isns(iscsit_isns_svr_t *svr)
+{
+ boolean_t need_dereg;
+
+ ASSERT(ISNS_GLOBAL_LOCK_HELD());
+
+ list_remove(&iscsit_global.global_isns_cfg.isns_svrs, svr);
+
+ /* talk to this server if isns monitor is running */
+ mutex_enter(&isns_mutex);
+ if (isns_monitor_thr_id != NULL) {
+ need_dereg = B_TRUE;
+ } else {
+ need_dereg = B_FALSE;
+ }
+ mutex_exit(&isns_mutex);
+
+ if (need_dereg) {
+ (void) isnst_monitor_one_server(svr, B_FALSE);
+ }
+
+ /* free the memory */
+ kmem_free(svr, sizeof (*svr));
+}
+
+static iscsit_isns_svr_t *
+iscsit_isns_svr_lookup(struct sockaddr_storage *sa)
+{
+ iscsit_isns_svr_t *svr;
+ it_portal_t portal1;
+
+ ASSERT(ISNS_GLOBAL_LOCK_HELD());
+
+ bcopy(sa, &portal1.portal_addr, sizeof (struct sockaddr_storage));
+
+ for (svr = list_head(&iscsit_global.global_isns_cfg.isns_svrs);
+ svr != NULL;
+ svr = list_next(&iscsit_global.global_isns_cfg.isns_svrs, svr)) {
+ if (it_sa_compare(&svr->svr_sa, sa) == 0)
+ return (svr);
+ }
+
+ return (NULL);
+}
+
+int
+iscsit_isns_register(iscsit_tgt_t *target)
+{
+ int rc = 0;
+
+ ISNS_GLOBAL_LOCK();
+
+ (void) isnst_add_to_target_list(target);
+
+ if (iscsit_global.global_isns_cfg.isns_state == B_FALSE) {
+ ISNS_GLOBAL_UNLOCK();
+ return (rc);
+ }
+
+ rc = isnst_update_target(target, ISNS_REGISTER_TARGET);
+
+ ISNS_GLOBAL_UNLOCK();
+
+ return (rc);
+}
+
+int
+iscsit_isns_deregister(iscsit_tgt_t *target)
+{
+ void *itarget;
+ isns_target_t tmptgt;
+ iscsit_isns_svr_t *svr;
+ list_t *global;
+
+ ISNS_GLOBAL_LOCK();
+
+ if (iscsit_global.global_isns_cfg.isns_state == B_FALSE) {
+ tmptgt.target = target;
+
+ if ((itarget = avl_find(&isns_target_list, &tmptgt, NULL))
+ != NULL) {
+ avl_remove(&isns_target_list, itarget);
+ kmem_free(itarget, sizeof (isns_target_t));
+ }
+
+ ISNS_GLOBAL_UNLOCK();
+ return (0);
+ }
+
+ /*
+ * Don't worry about dereg failures.
+ */
+ (void) isnst_update_target(target, ISNS_DEREGISTER_TARGET);
+
+ /*
+ * Remove the target from the list regardless of the status.
+ */
+
+ tmptgt.target = target;
+ if ((itarget = avl_find(&isns_target_list, &tmptgt, NULL)) != NULL) {
+ avl_remove(&isns_target_list, itarget);
+ kmem_free(itarget, sizeof (isns_target_t));
+ }
+
+ /*
+ * If there are no more targets, mark the server as
+ * unregistered.
+ */
+
+ if (avl_numnodes(&isns_target_list) == 0) {
+ global = &iscsit_global.global_isns_cfg.isns_svrs;
+ for (svr = list_head(global); svr != NULL;
+ svr = list_next(global, svr)) {
+ isnst_set_server_status(svr, B_FALSE);
+ }
+ }
+
+ ISNS_GLOBAL_UNLOCK();
+
+ return (0);
+}
+
+/*
+ * This function is called by iscsit when a target's configuration
+ * has changed.
+ */
+
+void
+iscsit_isns_target_update(iscsit_tgt_t *target)
+{
+ ISNS_GLOBAL_LOCK();
+
+ if (iscsit_global.global_isns_cfg.isns_state == B_FALSE) {
+ ISNS_GLOBAL_UNLOCK();
+ return;
+ }
+
+ (void) isnst_update_target(target, ISNS_UPDATE_TARGET);
+
+ ISNS_GLOBAL_UNLOCK();
+}
+
+static void
+isnst_start()
+{
+ ASSERT(ISNS_GLOBAL_LOCK_HELD());
+
+ /*
+ * Get target and portal lists, then start ESI threads for each portal.
+ */
+
+ isnst_get_target_list();
+ isnst_add_default_portals();
+
+ /*
+ * Create a thread for monitoring server communications
+ */
+ mutex_enter(&isns_mutex);
+ if (isns_monitor_thr_id == NULL) {
+ isns_monitor_thr_id = thread_create(NULL, 0,
+ isnst_monitor, NULL, 0, &p0, TS_RUN, minclsyspri);
+ }
+ mutex_exit(&isns_mutex);
+}
+
+static void
+isnst_wakeup_monitor(void)
+{
+ mutex_enter(&isns_mutex);
+ isns_idle = B_FALSE;
+ cv_signal(&isns_idle_cv);
+ mutex_exit(&isns_mutex);
+}
+
+static void
+isnst_stop()
+{
+ isns_target_t *itarget;
+
+ while ((itarget = avl_first(&isns_target_list)) != NULL) {
+ avl_remove(&isns_target_list, itarget);
+ kmem_free(itarget, sizeof (isns_target_t));
+ }
+
+ isnst_remove_default_portals();
+ isnst_esi_stop();
+ isnst_wakeup_monitor();
+}
+
+/*
+ * isnst_update_server_timestamp
+ *
+ * When we receive an ESI request, update the timestamp for the server.
+ * If we don't receive one for the specified period of time, we'll attempt
+ * to re-register.
+ */
+
+static void
+isnst_update_server_timestamp(struct sonode *so)
+{
+ iscsit_isns_svr_t *svr;
+ struct in_addr *sin = NULL, *svr_in;
+ struct in6_addr *sin6 = NULL, *svr_in6;
+
+ if (so->so_faddr_sa->sa_family == AF_INET) {
+ sin = &((struct sockaddr_in *)
+ ((void *)so->so_faddr_sa))->sin_addr;
+ } else {
+ sin6 = &((struct sockaddr_in6 *)
+ ((void *)so->so_faddr_sa))->sin6_addr;
+ }
+
+ /*
+ * Find the server and update the timestamp
+ */
+
+ ISNS_GLOBAL_LOCK();
+ for (svr = list_head(&iscsit_global.global_isns_cfg.isns_svrs);
+ svr != NULL;
+ svr = list_next(&iscsit_global.global_isns_cfg.isns_svrs, svr)) {
+ if (sin6 == NULL) {
+ if (svr->svr_sa.ss_family == AF_INET) {
+ svr_in = &((struct sockaddr_in *)&svr->svr_sa)->
+ sin_addr;
+ if (bcmp(svr_in, sin, sizeof (in_addr_t))
+ == 0) {
+ break;
+ }
+ }
+ } else {
+ if (svr->svr_sa.ss_family == AF_INET6) {
+ svr_in6 = &((struct sockaddr_in6 *)
+ &svr->svr_sa)->sin6_addr;
+ if (bcmp(svr_in6, sin6,
+ sizeof (in6_addr_t)) == 0) {
+ break;
+ }
+ }
+ }
+ }
+
+ if (svr != NULL) {
+ svr->svr_last_msg = ddi_get_lbolt();
+ }
+ ISNS_GLOBAL_UNLOCK();
+}
+
+/*
+ * isnst_monitor
+ *
+ * This function monitors registration status for each server.
+ */
+
+/*ARGSUSED*/
+static void
+isnst_monitor(void *arg)
+{
+ iscsit_isns_svr_t *svr;
+ list_t *svr_list;
+ boolean_t enabled;
+
+ svr_list = &iscsit_global.global_isns_cfg.isns_svrs;
+
+ do {
+ ISNS_GLOBAL_LOCK();
+ enabled = iscsit_global.global_isns_cfg.isns_state;
+ for (svr = list_head(svr_list); svr != NULL;
+ svr = list_next(svr_list, svr)) {
+ if (isnst_monitor_one_server(svr, enabled) != 0) {
+ svr->svr_retry_count++;
+ } else {
+ svr->svr_retry_count = 0;
+ }
+ }
+
+ /*
+ * Attempted registrations, especially to unreachable
+ * servers, can take time. Thus, we check the isns_state
+ * again here.
+ */
+ enabled = iscsit_global.global_isns_cfg.isns_state;
+ ISNS_GLOBAL_UNLOCK();
+
+ /*
+ * Keep the while loop running while isns is enabled.
+ * If isns_idle is FALSE, we have more work to do, so
+ * no waiting.
+ */
+
+ mutex_enter(&isns_mutex);
+
+ if (enabled && isns_idle) {
+ (void) cv_timedwait(&isns_idle_cv, &isns_mutex,
+ ddi_get_lbolt() + monitor_idle_interval);
+ }
+
+ isns_idle = B_TRUE;
+ mutex_exit(&isns_mutex);
+ } while (enabled);
+
+ mutex_enter(&isns_mutex);
+ isns_monitor_thr_id = NULL;
+ cv_signal(&isns_idle_cv);
+ mutex_exit(&isns_mutex);
+
+ /* terminate the thread at the last */
+ thread_exit();
+}
+
+static int
+isnst_monitor_one_server(iscsit_isns_svr_t *svr, boolean_t enabled)
+{
+ int rc = 0;
+ struct sonode *so;
+
+ ASSERT(ISNS_GLOBAL_LOCK_HELD());
+
+ /*
+ * First, take care of the case where iSNS is no longer enabled.
+ *
+ * If we're still registered, deregister. Regardless, mark the
+ * server as not registered.
+ */
+
+ if (enabled == B_FALSE) {
+ if (svr->svr_registered == B_TRUE) {
+ /*
+ * Doesn't matter if this fails. We're disabled.
+ */
+ so = isnst_open_so(&svr->svr_sa);
+ if (so != NULL) {
+ (void) isnst_update_one_server(svr, NULL,
+ ISNS_DEREGISTER_ALL);
+ isnst_close_so(so);
+ }
+ }
+
+ isnst_set_server_status(svr, B_FALSE);
+ return (0);
+ }
+
+ /*
+ * If there are no targets, we're done.
+ */
+
+ if (avl_numnodes(&isns_target_list) == 0) {
+ return (0);
+ }
+
+ /*
+ * At this point, we know iSNS is enabled.
+ *
+ * If we've received an ESI request from the server recently
+ * (within MAX_ESI_INTERVALS * the max interval length),
+ * no need to continue.
+ */
+
+ if (svr->svr_registered == B_TRUE) {
+ if (ddi_get_lbolt() < (svr->svr_last_msg +
+ drv_usectohz(isns_esi_max_interval * 1000000 *
+ MAX_ESI_INTERVALS))) {
+ return (0);
+ }
+ } else {
+ /*
+ * We're not registered... Try to register now.
+ */
+ if ((rc = isnst_update_one_server(svr, NULL,
+ ISNS_REGISTER_ALL)) == 0) {
+ isnst_set_server_status(svr, B_TRUE);
+ }
+ }
+
+ return (rc);
+}
+
+static int
+isnst_update_target(iscsit_tgt_t *target, isns_reg_type_t reg)
+{
+ iscsit_isns_svr_t *svr;
+ int rc = 0, curr_rc;
+
+ ASSERT(ISNS_GLOBAL_LOCK_HELD());
+ ASSERT(iscsit_global.global_isns_cfg.isns_state == B_TRUE);
+
+ for (svr = list_head(&iscsit_global.global_isns_cfg.isns_svrs);
+ svr != NULL;
+ svr = list_next(&iscsit_global.global_isns_cfg.isns_svrs, svr)) {
+ /*
+ * Only return success if they all succeed. Let the caller
+ * deal with any failure.
+ */
+
+ curr_rc = isnst_update_one_server(svr, target, reg);
+
+ if (curr_rc == 0) {
+ if (reg == ISNS_REGISTER_TARGET) {
+ isnst_set_server_status(svr, B_TRUE);
+ }
+ } else if (rc == 0) {
+ rc = curr_rc;
+ }
+ }
+
+ return (rc);
+}
+
+static int
+isnst_update_one_server(iscsit_isns_svr_t *svr, iscsit_tgt_t *target,
+ isns_reg_type_t reg)
+{
+ int rc = 0;
+
+ switch (reg) {
+ case ISNS_DEREGISTER_TARGET:
+ rc = isnst_deregister(svr, target->target_name);
+ break;
+
+ case ISNS_DEREGISTER_ALL:
+ rc = isnst_deregister(svr, NULL);
+ break;
+
+ case ISNS_UPDATE_TARGET:
+ case ISNS_REGISTER_TARGET:
+ rc = isnst_register(svr, target, reg);
+ break;
+
+ case ISNS_REGISTER_ALL:
+ rc = isnst_register(svr, NULL, reg);
+ break;
+
+ default:
+ ASSERT(0);
+ /* NOTREACHED */
+ }
+
+ return (rc);
+}
+
+/*
+ * isnst_retry_registration
+ *
+ * This function checks the return value from a registration pdu and
+ * determines whether or not we should retry this request. If the
+ * request is retried, it will do so as an "update", which means we
+ * re-register everything.
+ */
+
+static boolean_t
+isnst_retry_registration(int rsp_status_code)
+{
+ boolean_t retry;
+
+ /*
+ * Currently, we will attempt to retry for "Invalid Registration",
+ * "Source Unauthorized", or "Busy" errors. Any other errors should
+ * be handled by the caller if necessary.
+ */
+
+ switch (rsp_status_code) {
+ case ISNS_RSP_INVALID_REGIS:
+ case ISNS_RSP_SRC_UNAUTHORIZED:
+ case ISNS_RSP_BUSY:
+ retry = B_TRUE;
+ break;
+ default:
+ retry = B_FALSE;
+ break;
+ }
+
+ return (retry);
+}
+
+static int
+isnst_register(iscsit_isns_svr_t *svr, iscsit_tgt_t *target,
+ isns_reg_type_t regtype)
+{
+ struct sonode *so;
+ int rc = 0;
+ isns_pdu_t *pdu, *rsp;
+ size_t pdu_size, rsp_size;
+ isns_target_t *itarget, tmptgt;
+ boolean_t retry_reg = B_TRUE;
+
+ /*
+ * Registration is a tricky thing. In order to keep things simple,
+ * we don't want to keep track of which targets are registered to
+ * which server. We rely on the target state machine to tell us
+ * when a target is online or offline, which prompts us to either
+ * register or deregister that target.
+ *
+ * When iscsit_isns_init is called, get a list of targets. Those that
+ * are online will need to be registered. In this case, target
+ * will be NULL.
+ *
+ * What this means is that if svr_registered == B_FALSE, that's
+ * when we'll register the network entity as well.
+ */
+
+ if ((avl_numnodes(&isns_target_list) == 0) && (target == NULL)) {
+ return (0);
+ }
+
+ /*
+ * If the target is already registered and we're not doing an
+ * update registration, just return.
+ */
+
+ if (target != NULL) {
+ tmptgt.target = target;
+ itarget = avl_find(&isns_target_list, &tmptgt, NULL);
+ ASSERT(itarget);
+ if ((itarget->target_registered == B_TRUE) &&
+ (regtype != ISNS_UPDATE_TARGET)) {
+ return (0);
+ }
+ }
+
+ isnst_esi_check();
+
+ /* create TCP connection to the isns server */
+ so = isnst_open_so(&svr->svr_sa);
+
+ if (so == NULL) {
+ isnst_set_server_status(svr, B_FALSE);
+ return (-1);
+ }
+
+ while (retry_reg) {
+ pdu_size = isnst_make_reg_pdu(&pdu, target, svr->svr_registered,
+ regtype);
+ if (pdu_size == 0) {
+ isnst_close_so(so);
+ return (-1);
+ }
+
+ rc = isnst_send_pdu(so, pdu);
+ if (rc != 0) {
+ kmem_free(pdu, pdu_size);
+ isnst_close_so(so);
+ return (rc);
+ }
+
+ rsp_size = isnst_rcv_pdu(so, &rsp);
+ if (rsp_size == 0) {
+ kmem_free(pdu, pdu_size);
+ isnst_close_so(so);
+ return (-1);
+ }
+
+ rc = isnst_verify_rsp(pdu, rsp);
+
+ /*
+ * If we got a registration error, the server may be out of
+ * sync. In this case, we may re-try the registration as
+ * a "target update", which causes us to re-register everything.
+ */
+
+ if ((retry_reg = isnst_retry_registration(rc)) == B_TRUE) {
+ if (regtype == ISNS_UPDATE_TARGET) {
+ /*
+ * If registration failed on an update, there
+ * is something terribly wrong, possibly with
+ * the server.
+ */
+ rc = -1;
+ retry_reg = B_FALSE;
+ isnst_set_server_status(svr, B_FALSE);
+ } else {
+ regtype = ISNS_UPDATE_TARGET;
+ }
+ }
+
+ kmem_free(pdu, pdu_size);
+ kmem_free(rsp, rsp_size);
+ }
+
+ isnst_close_so(so);
+
+ /*
+ * If it succeeded, mark all registered targets as such
+ */
+ if (rc == 0) {
+ if ((target != NULL) && (regtype != ISNS_UPDATE_TARGET)) {
+ /* itarget initialized above */
+ itarget->target_registered = B_TRUE;
+ } else {
+ itarget = avl_first(&isns_target_list);
+ while (itarget) {
+ itarget->target_registered = B_TRUE;
+ itarget = AVL_NEXT(&isns_target_list, itarget);
+ }
+ }
+ }
+
+ return (rc);
+}
+
+static isns_portal_list_t *
+isns_lookup_portal(struct sockaddr_storage *p)
+{
+ isns_portal_list_t *portal;
+
+ portal = list_head(&portal_list);
+
+ while (portal != NULL) {
+ if (bcmp(p, &portal->portal_addr,
+ sizeof (struct sockaddr_storage)) == 0) {
+ return (portal);
+ }
+ portal = list_next(&portal_list, portal);
+ }
+
+ return (NULL);
+}
+
+static void
+isns_remove_portal(isns_portal_list_t *p)
+{
+ list_remove(&portal_list, p);
+ kmem_free(p, sizeof (isns_portal_list_t));
+ portal_list_count--;
+}
+
+static isns_target_t *
+isnst_add_to_target_list(iscsit_tgt_t *target)
+{
+ isns_target_t *itarget, tmptgt;
+
+ ASSERT(ISNS_GLOBAL_LOCK_HELD());
+
+ /*
+ * Make sure this target isn't already in our list. If it is,
+ * perhaps it has just moved from offline to online.
+ */
+
+ tmptgt.target = target;
+ if ((itarget = (isns_target_t *)avl_find(&isns_target_list,
+ &tmptgt, NULL)) == NULL) {
+ itarget = kmem_zalloc(sizeof (isns_target_t), KM_NOSLEEP);
+
+ /*
+ * If we can't get memory, we're not going to be able to
+ * register this target. This needs to be fixed up.
+ */
+ if (itarget == NULL)
+ return (NULL);
+
+ itarget->target = target;
+ avl_add(&isns_target_list, itarget);
+ }
+
+ return (itarget);
+}
+
+static int
+isnst_add_default_portal_attrs(isns_pdu_t *pdu, size_t pdu_size)
+{
+ isns_portal_list_t *portal;
+ struct sockaddr_in *in;
+ struct sockaddr_in6 *in6;
+ int idx = 0;
+ uint32_t attr_data;
+ void *inaddrp;
+
+ portal = list_head(&portal_list);
+
+ while (portal) {
+ if (idx == nondefault_portals) {
+ break;
+ }
+
+ if (portal->portal_iscsit == NULL) {
+ in = (struct sockaddr_in *)&portal->portal_addr;
+
+ if (in->sin_family == AF_INET) {
+ attr_data = sizeof (in_addr_t);
+ inaddrp = (void *)&in->sin_addr;
+ } else if (in->sin_family == AF_INET6) {
+ in6 = (struct sockaddr_in6 *)
+ &portal->portal_addr;
+ attr_data = sizeof (in6_addr_t);
+ inaddrp = (void *)&in6->sin6_addr;
+ } else {
+ return (-1);
+ }
+
+ if (isnst_add_attr(pdu, pdu_size,
+ ISNS_PG_PORTAL_IP_ADDR_ATTR_ID, 16, inaddrp,
+ attr_data) != 0) {
+ return (-1);
+ }
+
+ /* Portal Group Portal Port */
+ if (isnst_add_attr(pdu, pdu_size,
+ ISNS_PG_PORTAL_PORT_ATTR_ID, 4, 0,
+ ntohs(in->sin_port)) != 0) {
+ return (-1);
+ }
+
+ idx++;
+ }
+
+ portal = list_next(&portal_list, portal);
+ }
+
+ return (0);
+}
+
+static size_t
+isnst_make_reg_pdu(isns_pdu_t **pdu, iscsit_tgt_t *target,
+ boolean_t svr_registered, isns_reg_type_t regtype)
+{
+ size_t pdu_size;
+ iscsit_tpgt_t *tpgt;
+ iscsit_tpg_t *tpg;
+ iscsit_portal_t *tp;
+ char *str;
+ int len;
+ isns_portal_list_t *portal;
+ isns_esi_tinfo_t *tinfop;
+ isns_target_t *itarget;
+ iscsit_tgt_t *src;
+ boolean_t reg_all = B_FALSE;
+ uint16_t flags = 0;
+
+ ASSERT(ISNS_GLOBAL_LOCK_HELD());
+
+ /*
+ * Find a source attribute for this registration.
+ *
+ * If we're already registered, registering for the first time, or
+ * updating a target, we'll use the target_name of the first target
+ * in our list.
+ *
+ * The alternate case is that we're registering for the first time,
+ * but target is non-NULL. In that case, we have no targets in our
+ * list yet, so we use the passed in target's name.
+ */
+
+ if (svr_registered || (target == NULL) ||
+ (regtype == ISNS_UPDATE_TARGET)) {
+ ASSERT(avl_numnodes(&isns_target_list) != 0);
+ itarget = (isns_target_t *)avl_first(&isns_target_list);
+ src = itarget->target;
+ } else {
+ src = target;
+ }
+
+ /*
+ * No target means we're registering everything. A regtype of
+ * ISNS_UPDATE_TARGET means we're re-registering everything.
+ * Whether we're registering or re-registering depends on if
+ * we're already registered.
+ */
+
+ if ((target == NULL) || (regtype == ISNS_UPDATE_TARGET)) {
+ reg_all = B_TRUE;
+ target = src; /* This will be the 1st tgt in our list */
+
+ /*
+ * If we're already registered, this will be a replacement
+ * registration. In this case, we need to make sure our
+ * source attribute is an already registered target.
+ */
+ if (svr_registered) {
+ flags = ISNS_FLAG_REPLACE_REG;
+ while (itarget->target_registered == B_FALSE) {
+ itarget = AVL_NEXT(&isns_target_list,
+ itarget);
+ }
+ src = itarget->target;
+ /* Reset itarget to the beginning of our list */
+ itarget = (isns_target_t *)avl_first(&isns_target_list);
+ }
+ }
+
+ pdu_size = isnst_create_pdu_header(ISNS_DEV_ATTR_REG, pdu, flags);
+ if (pdu_size == 0) {
+ return (0);
+ }
+
+ len = strlen(src->target_name) + 1;
+ if (isnst_add_attr(*pdu, pdu_size, ISNS_ISCSI_NAME_ATTR_ID,
+ len, src->target_name, 0) != 0) {
+ goto pdu_error;
+ }
+
+ /*
+ * Message Key Attributes - EID
+ */
+ len = strlen(isns_eid) + 1;
+
+ if (isnst_add_attr(*pdu, pdu_size, ISNS_EID_ATTR_ID,
+ len, isns_eid, 0) != 0) {
+ goto pdu_error;
+ }
+
+ /* Delimiter */
+ if (isnst_add_attr(*pdu, pdu_size, ISNS_DELIMITER_ATTR_ID,
+ 0, 0, 0) != 0) {
+ goto pdu_error;
+ }
+
+ /*
+ * Operating Attributes
+ */
+ if (isnst_add_attr(*pdu, pdu_size, ISNS_EID_ATTR_ID, len,
+ isns_eid, 0) != 0) {
+ goto pdu_error;
+ }
+
+ /* ENTITY Protocol - Section 6.2.2 */
+ if (isnst_add_attr(*pdu, pdu_size, ISNS_ENTITY_PROTOCOL_ATTR_ID,
+ 4, 0, ISNS_ENTITY_PROTOCOL_ISCSI) != 0) {
+ goto pdu_error;
+ }
+
+ /*
+ * Network entity portal information - only on the first registration.
+ */
+
+ if (svr_registered == B_FALSE) {
+ struct sockaddr_in *sin;
+ int addrsize;
+
+ portal = list_head(&portal_list);
+
+ while (portal != NULL) {
+ sin = (struct sockaddr_in *)&portal->portal_addr;
+ tinfop = portal->portal_esi;
+
+ if (portal->portal_iscsit == NULL) {
+ if (sin->sin_family == AF_INET) {
+ addrsize = sizeof (struct in_addr);
+ } else {
+ addrsize = sizeof (struct in6_addr);
+ }
+
+ /* Portal IP Address */
+ if (isnst_add_attr(*pdu, pdu_size,
+ ISNS_PORTAL_IP_ADDR_ATTR_ID, 16,
+ &sin->sin_addr, addrsize) != 0) {
+ goto pdu_error;
+ }
+
+ /* Portal Port */
+ if (isnst_add_attr(*pdu, pdu_size,
+ ISNS_PORTAL_PORT_ATTR_ID, 4, 0,
+ ntohs(sin->sin_port)) != 0) {
+ goto pdu_error;
+ }
+
+ if (tinfop && tinfop->esi_port) {
+ /* ESI interval and port */
+ if (isnst_add_attr(*pdu, pdu_size,
+ ISNS_ESI_INTERVAL_ATTR_ID, 4,
+ NULL, 20) != 0) {
+ goto pdu_error;
+ }
+
+ if (isnst_add_attr(*pdu, pdu_size,
+ ISNS_ESI_PORT_ATTR_ID, 4, NULL,
+ tinfop->esi_port) != 0) {
+ goto pdu_error;
+ }
+ }
+ }
+
+ portal = list_next(&portal_list, portal);
+ }
+ }
+
+ do {
+ /* Hold the target mutex */
+ mutex_enter(&target->target_mutex);
+
+ /* iSCSI Name - Section 6.4.1 */
+ str = target->target_name;
+ len = strlen(str) + 1;
+ if (isnst_add_attr(*pdu, pdu_size, ISNS_ISCSI_NAME_ATTR_ID,
+ len, str, 0) != 0) {
+ mutex_exit(&target->target_mutex);
+ goto pdu_error;
+ }
+
+ /* iSCSI Node Type */
+ if (isnst_add_attr(*pdu, pdu_size,
+ ISNS_ISCSI_NODE_TYPE_ATTR_ID, 4, 0,
+ ISNS_TARGET_NODE_TYPE) != 0) {
+ mutex_exit(&target->target_mutex);
+ goto pdu_error;
+ }
+
+ /* iSCSI Alias */
+#if 0
+ str = target->target_alias;
+#else
+ str = "Solaris iSCSI Target";
+#endif
+ if (str != NULL) {
+ len = strlen(str) + 1;
+ if (isnst_add_attr(*pdu, pdu_size,
+ ISNS_ISCSI_ALIAS_ATTR_ID, len, str, 0) != 0) {
+ mutex_exit(&target->target_mutex);
+ goto pdu_error;
+ }
+ }
+
+ /* for each target portal group (start)... */
+ tpgt = avl_first(&target->target_tpgt_list);
+ ASSERT(tpgt != NULL);
+ do {
+ /* no need to explicitly register default PG */
+ if ((tpgt->tpgt_tag == ISCSIT_DEFAULT_TPGT) &&
+ (avl_numnodes(&target->target_tpgt_list) == 1)) {
+ tpgt = AVL_NEXT(&target->target_tpgt_list,
+ tpgt);
+ continue;
+ }
+
+ tpg = tpgt->tpgt_tpg;
+ mutex_enter(&tpg->tpg_mutex);
+
+ tp = avl_first(&tpg->tpg_portal_list);
+
+ /* Portal Group Tag */
+ if (isnst_add_attr(*pdu, pdu_size,
+ ISNS_PG_TAG_ATTR_ID, 4, 0, tpgt->tpgt_tag) != 0) {
+ mutex_exit(&tpg->tpg_mutex);
+ mutex_exit(&target->target_mutex);
+ goto pdu_error;
+ }
+
+ ASSERT(tp != NULL);
+ do {
+ struct sockaddr_storage *ss;
+ struct sockaddr_in *in;
+ struct sockaddr_in6 *in6;
+ uint32_t attr_numeric_data;
+ void *inaddrp;
+
+ ss = &tp->portal_addr;
+ in = (struct sockaddr_in *)ss;
+ in6 = (struct sockaddr_in6 *)ss;
+
+ if (ss->ss_family == AF_INET) {
+ attr_numeric_data = sizeof (in_addr_t);
+ inaddrp = (void *)&in->sin_addr;
+ } else if (ss->ss_family == AF_INET6) {
+ attr_numeric_data = sizeof (in6_addr_t);
+ inaddrp = (void *)&in6->sin6_addr;
+ } else if (ss->ss_family == 0) {
+ /*
+ * Need to add all default portals
+ */
+ attr_numeric_data = 0;
+ } else {
+ cmn_err(CE_WARN, "Unknown address "
+ "family for portal %p", (void *)tp);
+ mutex_exit(&tpg->tpg_mutex);
+ mutex_exit(&target->target_mutex);
+ goto pdu_error;
+ }
+
+ if (attr_numeric_data == 0) {
+ if (isnst_add_default_portal_attrs(*pdu,
+ pdu_size) != 0) {
+ mutex_exit(&tpg->tpg_mutex);
+ mutex_exit(&target->
+ target_mutex);
+ goto pdu_error;
+ }
+ } else {
+ /* Portal Group Portal IP Address */
+ if (isnst_add_attr(*pdu, pdu_size,
+ ISNS_PG_PORTAL_IP_ADDR_ATTR_ID, 16,
+ inaddrp, attr_numeric_data) != 0) {
+ mutex_exit(&tpg->tpg_mutex);
+ mutex_exit(&target->
+ target_mutex);
+ goto pdu_error;
+ }
+
+ /* Portal Group Portal Port */
+ if (isnst_add_attr(*pdu, pdu_size,
+ ISNS_PG_PORTAL_PORT_ATTR_ID,
+ 4, 0, ntohs(in->sin_port)) != 0) {
+ mutex_exit(&tpg->tpg_mutex);
+ mutex_exit(&target->
+ target_mutex);
+ goto pdu_error;
+ }
+ }
+
+ tp = AVL_NEXT(&tpg->tpg_portal_list, tp);
+ } while (tp != NULL);
+
+ mutex_exit(&tpg->tpg_mutex);
+ tpgt = AVL_NEXT(&target->target_tpgt_list, tpgt);
+ } while (tpgt != NULL);
+ /* for each target portal group (end)... */
+
+ mutex_exit(&target->target_mutex);
+
+ if (reg_all) {
+ itarget = AVL_NEXT(&isns_target_list, itarget);
+ if (itarget) {
+ target = itarget->target;
+ } else {
+ target = NULL;
+ }
+ }
+ } while ((reg_all == B_TRUE) && (target != NULL));
+
+ return (pdu_size);
+
+pdu_error:
+ /* packet too large, no memory */
+ kmem_free(*pdu, pdu_size);
+ *pdu = NULL;
+
+ return (0);
+}
+
+static int
+isnst_deregister(iscsit_isns_svr_t *svr, char *node)
+{
+ int rc;
+ isns_pdu_t *pdu, *rsp;
+ size_t pdu_size, rsp_size;
+ struct sonode *so;
+
+ if ((svr->svr_registered == B_FALSE) ||
+ (avl_numnodes(&isns_target_list) == 0)) {
+ return (0);
+ }
+
+ so = isnst_open_so(&svr->svr_sa);
+
+ if (so == NULL) {
+ return (-1);
+ }
+
+ pdu_size = isnst_make_dereg_pdu(&pdu, node);
+ if (pdu_size == 0) {
+ isnst_close_so(so);
+ return (-1);
+ }
+
+ rc = isnst_send_pdu(so, pdu);
+ if (rc != 0) {
+ isnst_close_so(so);
+ kmem_free(pdu, pdu_size);
+ return (rc);
+ }
+
+ rsp_size = isnst_rcv_pdu(so, &rsp);
+ if (rsp_size == 0) {
+ isnst_close_so(so);
+ kmem_free(pdu, pdu_size);
+ return (-1);
+ }
+
+ rc = isnst_verify_rsp(pdu, rsp);
+
+ isnst_close_so(so);
+ kmem_free(pdu, pdu_size);
+ kmem_free(rsp, rsp_size);
+
+ return (rc);
+}
+
+static size_t
+isnst_make_dereg_pdu(isns_pdu_t **pdu, char *node)
+{
+ size_t pdu_size;
+ int len;
+ isns_target_t *itarget;
+ iscsit_tgt_t *target;
+ int num_targets;
+
+ /*
+ * create DevDereg Message with all of target nodes
+ */
+ pdu_size = isnst_create_pdu_header(ISNS_DEV_DEREG, pdu, 0);
+ if (pdu_size == 0) {
+ return (0);
+ }
+
+ /*
+ * Source attribute - Must be a storage node in the same
+ * network entity. We'll just grab the first one in the list.
+ * If it's the only online target, we turn this into a total
+ * deregistration regardless of the value of "node".
+ */
+
+ num_targets = avl_numnodes(&isns_target_list);
+ itarget = avl_first(&isns_target_list);
+ target = itarget->target;
+
+ len = strlen(target->target_name) + 1;
+ if (isnst_add_attr(*pdu, pdu_size, ISNS_ISCSI_NAME_ATTR_ID,
+ len, target->target_name, 0) != 0) {
+ goto dereg_pdu_error;
+ }
+
+ /* Delimiter */
+ if (isnst_add_attr(*pdu, pdu_size, ISNS_DELIMITER_ATTR_ID,
+ 0, 0, 0) != 0) {
+ goto dereg_pdu_error;
+ }
+
+ /*
+ * Operating attributes
+ */
+ if ((node == NULL) || (num_targets == 1)) {
+ /* dereg everything */
+ len = strlen(isns_eid) + 1;
+ if (isnst_add_attr(*pdu, pdu_size, ISNS_EID_ATTR_ID,
+ len, isns_eid, 0) != 0) {
+ goto dereg_pdu_error;
+ }
+ } else {
+ /* dereg one target only */
+ len = strlen(node) + 1;
+ if (isnst_add_attr(*pdu, pdu_size, ISNS_ISCSI_NAME_ATTR_ID,
+ len, node, 0) != 0) {
+ goto dereg_pdu_error;
+ }
+ }
+
+ return (pdu_size);
+
+dereg_pdu_error:
+ kmem_free(*pdu, pdu_size);
+ *pdu = NULL;
+
+ return (0);
+}
+
+static int
+isnst_verify_rsp(isns_pdu_t *pdu, isns_pdu_t *rsp)
+{
+ uint16_t func_id;
+ uint16_t payload_len, rsp_payload_len;
+ isns_resp_t *resp;
+ uint8_t *pp;
+ isns_tlv_t *attr;
+ uint32_t attr_len, attr_id, esi_interval;
+
+ /* validate response function id */
+ func_id = ntohs(rsp->func_id);
+ switch (ntohs(pdu->func_id)) {
+ case ISNS_DEV_ATTR_REG:
+ if (func_id != ISNS_DEV_ATTR_REG_RSP) {
+ return (-1);
+ }
+
+ /*
+ * Get the ESI interval returned by the server. It could
+ * be different than what we asked for. We never know which
+ * portal a request may come in on, and any server could demand
+ * any interval. We'll simply keep track of the largest interval
+ * for use in monitoring.
+ */
+
+ rsp_payload_len = isnst_pdu_get_op(rsp, &pp);
+ attr = (isns_tlv_t *)((void *)pp);
+
+ while (rsp_payload_len) {
+ attr_len = ntohl(attr->attr_len);
+ attr_id = ntohl(attr->attr_id);
+
+ if (attr_id == ISNS_ESI_INTERVAL_ATTR_ID) {
+ esi_interval =
+ ntohl(*((uint32_t *)
+ ((void *)(&attr->attr_value))));
+
+ if (esi_interval > isns_esi_max_interval)
+ isns_esi_max_interval = esi_interval;
+
+ break;
+ }
+
+ rsp_payload_len -= (8 + attr_len);
+ attr = (isns_tlv_t *)
+ ((void *)((uint8_t *)attr + attr_len + 8));
+ }
+
+ break;
+ case ISNS_DEV_DEREG:
+ if (func_id != ISNS_DEV_DEREG_RSP) {
+ return (-1);
+ }
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+
+ /* verify response transaction id */
+ if (ntohs(rsp->xid) != ntohs(pdu->xid)) {
+ return (-1);
+ }
+
+ /* check the error code */
+ payload_len = ntohs(rsp->payload_len);
+ resp = (isns_resp_t *)((void *)&rsp->payload[0]);
+ if (payload_len < 4) {
+ return (-1);
+ }
+
+ return (ntohl(resp->status));
+}
+
+static uint16_t
+isnst_pdu_get_op(isns_pdu_t *pdu, uint8_t **pp)
+{
+ uint8_t *payload;
+ uint16_t payload_len;
+ isns_resp_t *resp;
+ isns_tlv_t *attr;
+ uint32_t attr_id;
+ uint32_t tlv_len;
+
+ /* get payload */
+ payload_len = ntohs(pdu->payload_len);
+ resp = (isns_resp_t *)((void *)&pdu->payload[0]);
+
+ /* find the operating attributes */
+ ASSERT(payload_len >= 4);
+ payload_len -= 4;
+ payload = &resp->data[0];
+
+ while (payload_len >= 8) {
+ attr = (isns_tlv_t *)((void *)payload);
+ tlv_len = 8 + ntohl(attr->attr_len);
+ if (payload_len >= tlv_len) {
+ payload += tlv_len;
+ payload_len -= tlv_len;
+ attr_id = ntohl(attr->attr_id);
+ if (attr_id == ISNS_DELIMITER_ATTR_ID) {
+ break;
+ }
+ } else {
+ /* mal-formed packet */
+ payload = NULL;
+ payload_len = 0;
+ }
+ }
+
+ *pp = payload;
+
+ return (payload_len);
+}
+
+static size_t
+isnst_create_pdu_header(uint16_t func_id, isns_pdu_t **pdu, uint16_t flags)
+{
+ size_t pdu_size = ISNSP_MAX_PDU_SIZE;
+
+ *pdu = (isns_pdu_t *)kmem_zalloc(pdu_size, KM_NOSLEEP);
+ if (*pdu != NULL) {
+ (*pdu)->version = htons((uint16_t)ISNSP_VERSION);
+ (*pdu)->func_id = htons((uint16_t)func_id);
+ (*pdu)->payload_len = htons(0);
+ (*pdu)->flags = htons(flags);
+
+ (*pdu)->xid = htons(GET_XID());
+ (*pdu)->seq = htons(0);
+ } else {
+ pdu_size = 0;
+ }
+
+ return (pdu_size);
+}
+
+static int
+isnst_add_attr(isns_pdu_t *pdu,
+ size_t max_pdu_size,
+ uint32_t attr_id,
+ uint32_t attr_len,
+ void *attr_data,
+ uint32_t attr_numeric_data)
+{
+ isns_tlv_t *attr_tlv;
+ uint8_t *payload_ptr;
+ uint16_t payload_len;
+ uint32_t normalized_attr_len;
+ uint64_t attr_tlv_len;
+
+ /* The attribute length must be 4-byte aligned. Section 5.1.3. */
+ normalized_attr_len = (attr_len % 4) == 0 ?
+ (attr_len) : (attr_len + (4 - (attr_len % 4)));
+ attr_tlv_len = ISNS_TLV_ATTR_ID_LEN +
+ ISNS_TLV_ATTR_LEN_LEN + normalized_attr_len;
+
+ /* Check if we are going to exceed the maximum PDU length. */
+ payload_len = ntohs(pdu->payload_len);
+ if ((payload_len + attr_tlv_len) > max_pdu_size) {
+ return (1);
+ }
+
+ attr_tlv = (isns_tlv_t *)kmem_zalloc(attr_tlv_len, KM_SLEEP);
+
+ attr_tlv->attr_id = htonl(attr_id);
+
+ switch (attr_id) {
+ case ISNS_DELIMITER_ATTR_ID:
+ break;
+
+ case ISNS_PORTAL_IP_ADDR_ATTR_ID:
+ case ISNS_PG_PORTAL_IP_ADDR_ATTR_ID:
+ if (attr_numeric_data == sizeof (in_addr_t)) {
+ /* IPv4 */
+ attr_tlv->attr_value[10] = 0xFF;
+ attr_tlv->attr_value[11] = 0xFF;
+ bcopy(attr_data, ((attr_tlv->attr_value) + 12),
+ sizeof (in_addr_t));
+ } else if (attr_numeric_data == sizeof (in6_addr_t)) {
+ /* IPv6 */
+ bcopy(attr_data, attr_tlv->attr_value,
+ sizeof (in6_addr_t));
+ } else if (attr_numeric_data == 0) {
+ /* EMPTY */
+ /* Do nothing */
+ } else {
+ kmem_free(attr_tlv, attr_tlv_len);
+ attr_tlv = NULL;
+ return (1);
+ }
+ break;
+
+ case ISNS_EID_ATTR_ID:
+ case ISNS_ISCSI_NAME_ATTR_ID:
+ case ISNS_ISCSI_ALIAS_ATTR_ID:
+ case ISNS_PG_ISCSI_NAME_ATTR_ID:
+ if (attr_len && attr_data) {
+ bcopy((char *)attr_data,
+ attr_tlv->attr_value, attr_len);
+ }
+ break;
+
+ default:
+ if (attr_len == 8) {
+ *(uint64_t *)((void *)attr_tlv->attr_value) =
+ BE_64((uint64_t)attr_numeric_data);
+ } else if (attr_len == 4) {
+ *(uint32_t *)((void *)attr_tlv->attr_value) =
+ htonl((uint32_t)attr_numeric_data);
+ }
+ break;
+ }
+
+ attr_tlv->attr_len = htonl(normalized_attr_len);
+ /*
+ * Convert the network byte ordered payload length to host byte
+ * ordered for local address calculation.
+ */
+ payload_len = ntohs(pdu->payload_len);
+ payload_ptr = pdu->payload + payload_len;
+ bcopy(attr_tlv, payload_ptr, attr_tlv_len);
+ payload_len += attr_tlv_len;
+
+ /*
+ * Convert the host byte ordered payload length back to network
+ * byte ordered - it's now ready to be sent on the wire.
+ */
+ pdu->payload_len = htons(payload_len);
+
+ kmem_free(attr_tlv, attr_tlv_len);
+ attr_tlv = NULL;
+
+ return (0);
+}
+
+static int
+isnst_send_pdu(void *so, isns_pdu_t *pdu)
+{
+ size_t total_len, payload_len, send_len;
+ uint8_t *payload;
+ uint16_t flags, seq;
+
+ iovec_t iov[2];
+ int rc;
+
+ /* update pdu flags */
+ flags = ntohs(pdu->flags);
+ flags |= ISNS_FLAG_CLIENT;
+ flags |= ISNS_FLAG_FIRST_PDU;
+
+ /* initalize sequence number */
+ seq = 0;
+
+ payload = pdu->payload;
+
+ /* total payload length */
+ total_len = ntohs(pdu->payload_len);
+
+ /* fill in the pdu header */
+ iov[0].iov_base = (void *)pdu;
+ iov[0].iov_len = ISNSP_HEADER_SIZE;
+
+ do {
+ /* split the payload accordingly */
+ if (total_len > ISNSP_MAX_PAYLOAD_SIZE) {
+ payload_len = ISNSP_MAX_PAYLOAD_SIZE;
+ } else {
+ payload_len = total_len;
+ /* set the last pdu flag */
+ flags |= ISNS_FLAG_LAST_PDU;
+ }
+
+ /* set back the pdu flags */
+ pdu->flags = htons(flags);
+ /* set the sequence number */
+ pdu->seq = htons(seq);
+ /* set the payload length */
+ pdu->payload_len = htons(payload_len);
+
+ /* fill in the payload */
+ iov[1].iov_base = (void *)payload;
+ iov[1].iov_len = payload_len;
+
+ DTRACE_PROBE3(isnst__pdu__send, uint16_t, ntohs(pdu->func_id),
+ uint16_t, ntohs(pdu->payload_len), caddr_t, pdu);
+
+ /* send the pdu */
+ send_len = ISNSP_HEADER_SIZE + payload_len;
+ rc = idm_iov_sosend(so, &iov[0], 2, send_len);
+
+ flags &= ~ISNS_FLAG_FIRST_PDU;
+ payload += payload_len;
+ total_len -= payload_len;
+
+ /* increase the sequence number */
+ seq ++;
+
+ } while (rc == 0 && total_len > 0);
+
+ return (rc);
+}
+
+static size_t
+isnst_rcv_pdu(void *so, isns_pdu_t **pdu)
+{
+ size_t total_pdu_len;
+ size_t total_payload_len;
+ size_t payload_len;
+ size_t combined_len;
+ isns_pdu_t tmp_pdu_hdr;
+ isns_pdu_t *combined_pdu;
+ uint8_t *payload;
+ uint8_t *combined_payload;
+
+ uint16_t flags;
+ uint16_t seq;
+
+ *pdu = NULL;
+ total_pdu_len = total_payload_len = 0;
+ payload = NULL;
+ seq = 0;
+
+ do {
+ /* receive the pdu header */
+ if (idm_sorecv(so, &tmp_pdu_hdr, ISNSP_HEADER_SIZE) != 0 ||
+ ntohs(tmp_pdu_hdr.seq) != seq) {
+ goto rcv_error;
+ }
+
+ /* receive the payload */
+ payload_len = ntohs(tmp_pdu_hdr.payload_len);
+ payload = kmem_alloc(payload_len, KM_SLEEP);
+ if (idm_sorecv(so, payload, payload_len) != 0) {
+ goto rcv_error;
+ }
+
+ /* combine the pdu if it is not the first one */
+ if (total_pdu_len > 0) {
+ combined_len = total_pdu_len + payload_len;
+ combined_pdu = kmem_alloc(combined_len, KM_SLEEP);
+ bcopy(*pdu, combined_pdu, total_pdu_len);
+ combined_payload =
+ &combined_pdu->payload[total_payload_len];
+ bcopy(payload, combined_payload, payload_len);
+ kmem_free(*pdu, total_pdu_len);
+ kmem_free(payload, payload_len);
+ *pdu = combined_pdu;
+ total_payload_len += payload_len;
+ total_pdu_len += payload_len;
+ (*pdu)->payload_len = htons(total_payload_len);
+ } else {
+ total_payload_len = payload_len;
+ total_pdu_len = ISNSP_HEADER_SIZE + payload_len;
+ *pdu = kmem_alloc(total_pdu_len, KM_SLEEP);
+ bcopy(&tmp_pdu_hdr, *pdu, ISNSP_HEADER_SIZE);
+ bcopy(payload, &(*pdu)->payload[0], payload_len);
+ kmem_free(payload, payload_len);
+ }
+ payload = NULL;
+
+ /* the flags of pdu which is just received */
+ flags = ntohs(tmp_pdu_hdr.flags);
+
+ /* increase sequence number by one */
+ seq ++;
+ } while ((flags & ISNS_FLAG_LAST_PDU) == 0);
+
+ DTRACE_PROBE3(isnst__pdu__recv, uint16_t, ntohs((*pdu)->func_id),
+ size_t, total_payload_len, caddr_t, *pdu);
+
+ return (total_pdu_len);
+
+rcv_error:
+ if (*pdu != NULL) {
+ kmem_free(*pdu, total_pdu_len);
+ *pdu = NULL;
+ }
+ if (payload != NULL) {
+ kmem_free(payload, payload_len);
+ }
+ return (0);
+}
+
+static void *
+isnst_open_so(struct sockaddr_storage *sa)
+{
+ int sa_sz;
+ struct sonode *so;
+
+ /* determin local IP address */
+ if (sa->ss_family == AF_INET) {
+ /* IPv4 */
+ sa_sz = sizeof (struct sockaddr_in);
+
+ /* Create socket */
+ so = idm_socreate(AF_INET, SOCK_STREAM, 0);
+ } else {
+ /* IPv6 */
+ sa_sz = sizeof (struct sockaddr_in6);
+
+ /* Create socket */
+ so = idm_socreate(AF_INET6, SOCK_STREAM, 0);
+ }
+
+ if (so != NULL) {
+ if (soconnect(so, (struct sockaddr *)sa, sa_sz, 0, 0) != 0) {
+ /* not calling isnst_close_so() to */
+ /* make dtrace output look clear */
+ idm_soshutdown(so);
+ idm_sodestroy(so);
+ so = NULL;
+ }
+ }
+
+ if (so == NULL) {
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ char s[INET6_ADDRSTRLEN];
+ void *ip;
+ uint16_t port;
+ sin = (struct sockaddr_in *)sa;
+ port = ntohs(sin->sin_port);
+ if (sa->ss_family == AF_INET) {
+ ip = (void *)&sin->sin_addr.s_addr;
+ (void) inet_ntop(AF_INET, ip, s, sizeof (s));
+ } else {
+ sin6 = (struct sockaddr_in6 *)sa;
+ ip = (void *)&sin6->sin6_addr.s6_addr;
+ (void) inet_ntop(AF_INET6, ip, s, sizeof (s));
+ }
+ cmn_err(CE_WARN, "open iSNS Server %s:%u failed", s, port);
+ }
+
+ return (so);
+}
+
+static void
+isnst_close_so(void *so)
+{
+ idm_soshutdown(so);
+ idm_sodestroy(so);
+}
+
+
+/*
+ * ESI handling
+ */
+
+static void
+isnst_esi_start_thread(isns_esi_tinfo_t *tinfop)
+{
+ tinfop->esi_thread_running = B_FALSE;
+ tinfop->esi_thread_failed = B_FALSE;
+ tinfop->esi_registered = B_FALSE;
+ tinfop->esi_thread = thread_create(NULL, 0, isnst_esi_thread,
+ (void *)tinfop, 0, &p0, TS_RUN, minclsyspri);
+
+ mutex_enter(&isns_esi_mutex);
+ list_insert_tail(&esi_list, tinfop);
+
+ /*
+ * Wait for the thread to start
+ */
+
+ while (!tinfop->esi_thread_running && !tinfop->esi_thread_failed) {
+ cv_wait(&isns_esi_cv, &isns_esi_mutex);
+ }
+
+ mutex_exit(&isns_esi_mutex);
+}
+
+static void
+isnst_esi_start(isns_portal_list_t *portal)
+{
+ isns_esi_tinfo_t *tinfop;
+
+ ASSERT(ISNS_GLOBAL_LOCK_HELD());
+
+ /*
+ * Allocate our ESI thread info structure
+ */
+
+ tinfop = (isns_esi_tinfo_t *)
+ kmem_zalloc(sizeof (isns_esi_tinfo_t), KM_NOSLEEP);
+
+ if (tinfop == NULL) {
+ cmn_err(CE_WARN, "isnst_esi_start: Cant alloc ESI");
+ return;
+ }
+
+ tinfop->esi_portal = portal;
+ portal->portal_esi = tinfop;
+ isnst_esi_start_thread(tinfop);
+}
+
+/*
+ * isnst_esi_check
+ *
+ * Verify that all the ESI threads are running and try to restart any that
+ * failed for any reason.
+ */
+
+static void
+isnst_esi_check()
+{
+ isns_portal_list_t *portal;
+ isns_esi_tinfo_t *tinfop;
+
+ /*
+ * Now, threads for new portals or those which stopped for some other
+ * reason will be started.
+ */
+
+ portal = list_head(&portal_list);
+
+ while (portal) {
+ tinfop = portal->portal_esi;
+
+ if (tinfop && (!tinfop->esi_thread_running ||
+ tinfop->esi_thread_failed)) {
+ isnst_esi_start_thread(tinfop);
+ }
+
+ portal = list_next(&portal_list, portal);
+ }
+}
+
+/*
+ * isnst_esi_thread
+ *
+ * This function listens on a socket for incoming connections from an
+ * iSNS server until told to stop.
+ */
+
+static void
+isnst_esi_thread(void *arg)
+{
+ isns_esi_tinfo_t *tinfop;
+ struct sonode *newso;
+ struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
+ uint32_t on;
+ int rc;
+ isns_pdu_t *pdu;
+ size_t pl_size;
+ int family;
+
+ tinfop = (isns_esi_tinfo_t *)arg;
+ tinfop->esi_thread_did = curthread->t_did;
+
+ /*
+ * Create a socket to listen for requests from the iSNS server.
+ */
+
+ if (tinfop->esi_portal->portal_addr.ss_family == AF_INET) {
+ family = AF_INET;
+ } else {
+ family = AF_INET6;
+ }
+
+
+ if ((tinfop->esi_so =
+ idm_socreate(family, SOCK_STREAM, 0)) == NULL) {
+ cmn_err(CE_WARN,
+ "isnst_esi_thread: Unable to create socket");
+ tinfop->esi_thread_failed = B_TRUE;
+ mutex_enter(&isns_esi_mutex);
+ cv_signal(&isns_esi_cv);
+ mutex_exit(&isns_esi_mutex);
+ thread_exit();
+ }
+
+ /*
+ * Set options, bind, and listen until we're told to stop
+ */
+
+ switch (family) {
+ case AF_INET:
+ bzero(&sin, sizeof (sin));
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(0);
+ bcopy(((caddr_t)&tinfop->esi_portal->portal_addr +
+ offsetof(struct sockaddr_in, sin_addr)),
+ &sin.sin_addr.s_addr, sizeof (in_addr_t));
+ on = 1;
+
+ (void) sosetsockopt(tinfop->esi_so, SOL_SOCKET, SO_REUSEADDR,
+ (char *)&on, sizeof (on));
+
+ if (sobind(tinfop->esi_so, (struct sockaddr *)&sin,
+ sizeof (sin), 0, 0) != 0) {
+ idm_sodestroy(tinfop->esi_so);
+ tinfop->esi_so = NULL;
+ tinfop->esi_thread_failed = B_TRUE;
+ } else {
+ tinfop->esi_port = ntohs(((struct sockaddr_in *)
+ ((void *)tinfop->esi_so->so_laddr_sa))->sin_port);
+ }
+
+ break;
+
+ case AF_INET6:
+ bzero(&sin6, sizeof (sin6));
+ sin6.sin6_family = AF_INET6;
+ sin6.sin6_port = htons(0);
+ bcopy(((caddr_t)&tinfop->esi_portal->portal_addr +
+ offsetof(struct sockaddr_in6, sin6_addr)),
+ &sin6.sin6_addr.s6_addr, sizeof (in6_addr_t));
+ on = 1;
+
+ (void) sosetsockopt(tinfop->esi_so, SOL_SOCKET,
+ SO_REUSEADDR, (char *)&on, sizeof (on));
+
+ if (sobind(tinfop->esi_so, (struct sockaddr *)&sin6,
+ sizeof (sin6), 0, 0) != 0) {
+ idm_sodestroy(tinfop->esi_so);
+ tinfop->esi_so = NULL;
+ tinfop->esi_thread_failed = B_TRUE;
+ } else {
+ tinfop->esi_port = ntohs(((struct sockaddr_in6 *)
+ ((void *)tinfop->esi_so->so_laddr_sa))->sin6_port);
+ }
+
+ break;
+ }
+
+ if (tinfop->esi_thread_failed) {
+ cmn_err(CE_WARN, "Unable to bind socket for ESI");
+ goto esi_thread_exit;
+ }
+
+ if ((rc = solisten(tinfop->esi_so, 5)) != 0) {
+ cmn_err(CE_WARN, "isnst_esi_thread: listen failure 0x%x", rc);
+ goto esi_thread_exit;
+ }
+
+ mutex_enter(&isns_esi_mutex);
+ /*
+ * Mark the thread as running and the portal as no longer new.
+ */
+ tinfop->esi_thread_running = B_TRUE;
+ cv_signal(&isns_esi_cv);
+
+ while (tinfop->esi_thread_running && !tinfop->esi_thread_failed) {
+ mutex_exit(&isns_esi_mutex);
+
+ if ((rc = soaccept(tinfop->esi_so, 0, &newso)) != 0) {
+ mutex_enter(&isns_esi_mutex);
+ /*
+ * If we were interrupted with EINTR, it's not
+ * really a failure.
+ */
+ if (rc != EINTR) {
+ cmn_err(CE_WARN, "isnst_esi_thread: "
+ "accept failure (0x%x)", rc);
+ tinfop->esi_thread_failed = B_TRUE;
+ }
+
+ tinfop->esi_thread_running = B_FALSE;
+ continue;
+ }
+
+ mutex_enter(&isns_esi_mutex);
+
+ pl_size = isnst_rcv_pdu(newso, &pdu);
+
+ if (pl_size == 0) {
+ cmn_err(CE_WARN, "isnst_esi_thread: rcv_pdu failure");
+ tinfop->esi_thread_failed = B_TRUE;
+ continue;
+ }
+
+ if (isnst_handle_esi_req(newso, pdu, pl_size) == B_TRUE) {
+ tinfop->esi_registered = B_TRUE;
+ }
+
+ (void) soshutdown(newso, SHUT_RDWR);
+
+ /*
+ * Do not hold the esi mutex during server timestamp
+ * update. It requires the isns global lock, which may
+ * be held during other functions that also require
+ * the esi_mutex (potential deadlock).
+ */
+ mutex_exit(&isns_esi_mutex);
+ isnst_update_server_timestamp(newso);
+ mutex_enter(&isns_esi_mutex);
+ }
+ mutex_exit(&isns_esi_mutex);
+esi_thread_exit:
+ idm_soshutdown(tinfop->esi_so);
+ idm_sodestroy(tinfop->esi_so);
+ mutex_enter(&isns_esi_mutex);
+ tinfop->esi_thread_running = B_FALSE;
+ tinfop->esi_so = NULL;
+ tinfop->esi_port = 0;
+ tinfop->esi_registered = B_FALSE;
+ cv_signal(&isns_esi_cv);
+ mutex_exit(&isns_esi_mutex);
+ thread_exit();
+}
+
+/*
+ * Handle an incoming ESI request
+ */
+
+static boolean_t
+isnst_handle_esi_req(struct sonode *so, isns_pdu_t *pdu, size_t pl_size)
+{
+ isns_pdu_t *rsp_pdu;
+ isns_resp_t *rsp;
+ size_t pl_len, rsp_size;
+ boolean_t esirv = B_TRUE;
+
+ if (ntohs(pdu->func_id) != ISNS_ESI) {
+ cmn_err(CE_WARN, "isnst_handle_esi_req: Unexpected func 0x%x",
+ pdu->func_id);
+ kmem_free(pdu, pl_size);
+ return (B_FALSE);
+ }
+
+ pl_len = ntohs(pdu->payload_len) + 4 /* ISNS_STATUS_SZ */;
+
+ if (pl_len > ISNSP_MAX_PAYLOAD_SIZE) {
+ cmn_err(CE_WARN, "isnst_handle_esi_req: PDU payload too large "
+ " (%ld bytes)", pl_len);
+ kmem_free(pdu, pl_size);
+ return (B_FALSE);
+ }
+
+ rsp_size = isnst_create_pdu_header(ISNS_ESI_RSP, &rsp_pdu, 0);
+
+ if (rsp_size == 0) {
+ cmn_err(CE_WARN, "isnst_handle_esi_req: Can't get rsp pdu");
+ kmem_free(pdu, pl_size);
+ return (B_FALSE);
+ }
+
+ rsp = (isns_resp_t *)((void *)(&rsp_pdu->payload[0]));
+
+ /* Use xid from the request pdu */
+ rsp_pdu->xid = pdu->xid;
+ rsp->status = htonl(ISNS_RSP_SUCCESSFUL);
+
+ /* Copy original data */
+ bcopy(pdu->payload, rsp->data, pl_len - 4);
+ rsp_pdu->payload_len = htons(pl_len);
+
+ if (isnst_send_pdu(so, rsp_pdu) != 0) {
+ cmn_err(CE_WARN, "isnst_handle_esi_req: Send response failed");
+ esirv = B_FALSE;
+ }
+
+ kmem_free(rsp_pdu, rsp_size);
+ kmem_free(pdu, pl_size);
+
+ return (esirv);
+}
+
+int
+isnst_tgt_avl_compare(const void *t1, const void *t2)
+{
+ const isns_target_t *tgt1 = t1;
+ const isns_target_t *tgt2 = t2;
+
+ /*
+ * Sort by target (pointer to iscsit_tgt_t).
+ */
+
+ if (tgt1->target < tgt2->target) {
+ return (-1);
+ } else if (tgt1->target > tgt2->target) {
+ return (1);
+ }
+
+ return (0);
+}
+
+static void
+isnst_get_target_list(void)
+{
+ iscsit_tgt_t *tgt, *next_tgt;
+
+ /*
+ * Initialize our list of targets with those from the global
+ * list that are online.
+ */
+
+ for (tgt = avl_first(&iscsit_global.global_target_list); tgt != NULL;
+ tgt = next_tgt) {
+ next_tgt = AVL_NEXT(&iscsit_global.global_target_list, tgt);
+ if (tgt->target_state == TS_STMF_ONLINE) {
+ (void) isnst_add_to_target_list(tgt);
+ }
+ }
+}
+
+static void
+isnst_set_server_status(iscsit_isns_svr_t *svr, boolean_t registered)
+{
+ ASSERT(ISNS_GLOBAL_LOCK_HELD());
+
+ if (registered == B_TRUE) {
+ svr->svr_registered = B_TRUE;
+ svr->svr_last_msg = ddi_get_lbolt();
+ } else {
+ svr->svr_registered = B_FALSE;
+ }
+}
+
+static void
+isnst_add_default_portals()
+{
+ idm_addr_list_t *default_portal_list;
+ idm_addr_t *dportal;
+ isns_portal_list_t *portal;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ uint32_t dpl_size, idx;
+
+ ASSERT(ISNS_GLOBAL_LOCK_HELD());
+
+ dpl_size = idm_get_ipaddr(&default_portal_list);
+
+ if (dpl_size == 0) {
+ cmn_err(CE_WARN, "isnst_add_default_portals: "
+ "No default portals");
+ return;
+ }
+
+ for (idx = 0; idx < default_portal_list->al_out_cnt; idx++) {
+ dportal = &default_portal_list->al_addrs[idx];
+
+ if (dportal->a_addr.i_insize == 0) {
+ continue;
+ }
+
+ portal = kmem_zalloc(sizeof (isns_portal_list_t), KM_SLEEP);
+ portal->portal_iscsit = NULL; /* Default portal */
+
+ if (dportal->a_addr.i_insize == sizeof (struct in_addr)) {
+ sin = (struct sockaddr_in *)&portal->portal_addr;
+ sin->sin_family = AF_INET;
+ sin->sin_port = htons(ISCSI_LISTEN_PORT);
+ sin->sin_addr = dportal->a_addr.i_addr.in4;
+ } else {
+ sin6 = (struct sockaddr_in6 *)&portal->portal_addr;
+ sin->sin_family = AF_INET6;
+ sin6->sin6_port = htons(ISCSI_LISTEN_PORT);
+ sin6->sin6_addr = dportal->a_addr.i_addr.in6;
+ }
+
+ list_insert_tail(&portal_list, portal);
+ isnst_esi_start(portal);
+ }
+
+ kmem_free(default_portal_list, dpl_size);
+}
+
+static void
+isnst_remove_default_portals()
+{
+ isns_portal_list_t *portal, *next;
+
+ ASSERT(ISNS_GLOBAL_LOCK_HELD());
+
+ portal = list_head(&portal_list);
+
+ while (portal) {
+ next = list_next(&portal_list, portal);
+
+ if (portal->portal_iscsit == NULL) {
+ mutex_enter(&isns_esi_mutex);
+ isnst_esi_stop_thread(portal->portal_esi);
+ mutex_exit(&isns_esi_mutex);
+ isns_remove_portal(portal);
+ }
+
+ portal = next;
+ }
+}
+
+/*
+ * These functions are called by iscsit proper when a portal comes online
+ * or goes offline.
+ */
+
+void
+iscsit_isns_portal_online(iscsit_portal_t *portal)
+{
+ isns_portal_list_t *iportal, *new_portal;
+ struct sockaddr_in *sin;
+
+ ISNS_GLOBAL_LOCK();
+
+ iportal = isns_lookup_portal(&portal->portal_addr);
+ sin = (struct sockaddr_in *)&portal->portal_addr;
+
+ /*
+ * If sin_family is 0, it's a "default" portal. It's possible
+ * sin_family may be non-zero, so check portal_iscsit. If it's NULL,
+ * it's a default portal as well.
+ */
+
+ if ((sin->sin_family == 0) ||
+ (iportal && (iportal->portal_iscsit == NULL))) {
+ ISNS_GLOBAL_UNLOCK();
+ return;
+ }
+
+ ASSERT(iportal == NULL);
+
+ new_portal = kmem_zalloc(sizeof (isns_portal_list_t), KM_SLEEP);
+ new_portal->portal_addr = portal->portal_addr;
+ sin = (struct sockaddr_in *)&new_portal->portal_addr;
+ new_portal->portal_iscsit = portal;
+ list_insert_tail(&portal_list, new_portal);
+ portal_list_count++;
+ nondefault_portals++;
+
+ ISNS_GLOBAL_UNLOCK();
+}
+
+void
+iscsit_isns_portal_offline(iscsit_portal_t *portal)
+{
+ isns_portal_list_t *iportal = NULL;
+ struct sockaddr_in *sin;
+ boolean_t default_portals = B_FALSE;
+
+ ISNS_GLOBAL_LOCK();
+
+ /*
+ * Stop the ESI thread for this portal
+ */
+
+ iportal = isns_lookup_portal(&portal->portal_addr);
+ sin = (struct sockaddr_in *)&portal->portal_addr;
+
+ if ((sin->sin_family == 0) ||
+ (iportal && (iportal->portal_iscsit == NULL))) {
+ default_portals = B_TRUE;
+ } else {
+ iportal = isns_lookup_portal(&portal->portal_addr);
+ ASSERT(iportal);
+ }
+
+ if (!default_portals) {
+ isns_remove_portal(iportal);
+ nondefault_portals--;
+ }
+
+ ISNS_GLOBAL_UNLOCK();
+}
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.h b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.h
new file mode 100644
index 0000000000..40c111f491
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_isns.h
@@ -0,0 +1,109 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+#ifndef _ISNS_CLIENT_H_
+#define _ISNS_CLIENT_H_
+
+#include <iscsit.h>
+
+/*
+ * List of iSNS servers with which we register.
+ */
+
+typedef struct {
+ int svr_retry_count;
+ struct sockaddr_storage svr_sa;
+ clock_t svr_last_msg;
+ list_node_t svr_ln;
+ boolean_t svr_registered;
+} iscsit_isns_svr_t;
+
+/*
+ * Type of registration to perform (deregister, register, update)
+ */
+typedef enum {
+ ISNS_DEREGISTER_TARGET = 0,
+ ISNS_DEREGISTER_ALL,
+ ISNS_REGISTER_TARGET,
+ ISNS_REGISTER_ALL,
+ ISNS_UPDATE_TARGET
+} isns_reg_type_t;
+
+/*
+ * This structure is used to keep state with regard to the RX threads used
+ * for ESI. There must always be a 1:1 correspondence between the entries
+ * in this list and the entries in the portal_list.
+ */
+
+struct isns_portal_list_s;
+
+typedef struct {
+ struct isns_portal_list_s *esi_portal;
+ kthread_t *esi_thread;
+ kt_did_t esi_thread_did;
+ struct sonode *esi_so;
+ uint16_t esi_port;
+ boolean_t esi_thread_running;
+ boolean_t esi_thread_failed;
+ boolean_t esi_registered;
+ boolean_t esi_not_available;
+ list_node_t esi_ln;
+} isns_esi_tinfo_t;
+
+/*
+ * Portal list - comprised of "default" portals (i.e. idm_get_ipaddr) and
+ * portals that are part of target portal groups.
+ */
+
+typedef struct isns_portal_list_s {
+ struct sockaddr_storage portal_addr;
+ isns_esi_tinfo_t *portal_esi;
+ iscsit_portal_t *portal_iscsit;
+ list_node_t portal_ln;
+} isns_portal_list_t;
+
+typedef struct isns_target_s {
+ iscsit_tgt_t *target;
+ avl_node_t target_node;
+ boolean_t target_registered;
+} isns_target_t;
+
+/*
+ * If no ESI request is received within this number of intervals, we'll
+ * try to re-register with the server.
+ */
+#define MAX_ESI_INTERVALS 3
+
+it_cfg_status_t
+isnst_config_merge(it_config_t *cfg);
+
+int iscsit_isns_init(iscsit_hostinfo_t *hostinfo);
+void iscsit_isns_fini();
+int iscsit_isns_register(iscsit_tgt_t *target);
+int iscsit_isns_deregister(iscsit_tgt_t *target);
+void iscsit_isns_target_update(iscsit_tgt_t *target);
+void iscsit_isns_portal_online(iscsit_portal_t *portal);
+void iscsit_isns_portal_offline(iscsit_portal_t *portal);
+
+#endif /* _ISNS_CLIENT_H_ */
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_login.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_login.c
new file mode 100644
index 0000000000..1a24d01d18
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_login.c
@@ -0,0 +1,2534 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/cpuvar.h>
+#include <sys/types.h>
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/modctl.h>
+
+#include <sys/socket.h>
+#include <sys/strsubr.h>
+#include <sys/sysmacros.h>
+#include <sys/note.h>
+#include <sys/sdt.h>
+
+#include <sys/stmf.h>
+#include <sys/stmf_ioctl.h>
+#include <sys/portif.h>
+#include <sys/idm/idm.h>
+#include <sys/idm/idm_text.h>
+
+#define ISCSIT_LOGIN_SM_STRINGS
+#include <iscsit.h>
+#include <iscsit_auth.h>
+
+typedef struct {
+ list_node_t le_ctx_node;
+ iscsit_login_event_t le_ctx_event;
+ idm_pdu_t *le_pdu;
+} login_event_ctx_t;
+
+#ifndef TRUE
+#define TRUE B_TRUE
+#endif
+
+#ifndef FALSE
+#define FALSE B_FALSE
+#endif
+
+#define DEFAULT_RADIUS_PORT 1812
+
+static void
+login_sm_complete(void *ict_void);
+
+static void
+login_sm_event_dispatch(iscsit_conn_login_t *lsm, iscsit_conn_t *ict,
+ login_event_ctx_t *ctx);
+
+static void
+login_sm_init(iscsit_conn_t *ict, login_event_ctx_t *ctx);
+
+static void
+login_sm_waiting(iscsit_conn_t *ict, login_event_ctx_t *ctx);
+
+static void
+login_sm_processing(iscsit_conn_t *ict, login_event_ctx_t *ctx);
+
+static void
+login_sm_responding(iscsit_conn_t *ict, login_event_ctx_t *ctx);
+
+static void
+login_sm_responded(iscsit_conn_t *ict, login_event_ctx_t *ctx);
+
+static void
+login_sm_ffp(iscsit_conn_t *ict, login_event_ctx_t *ctx);
+
+static void
+login_sm_done(iscsit_conn_t *ict, login_event_ctx_t *ctx);
+
+static void
+login_sm_error(iscsit_conn_t *ict, login_event_ctx_t *ctx);
+
+static void
+login_sm_new_state(iscsit_conn_t *ict, login_event_ctx_t *ctx,
+ iscsit_login_state_t new_state);
+
+static void
+login_sm_send_ack(iscsit_conn_t *ict, idm_pdu_t *pdu);
+
+static idm_status_t
+login_sm_validate_ack(iscsit_conn_t *ict, idm_pdu_t *pdu);
+
+static boolean_t
+login_sm_is_last_response(iscsit_conn_t *ict);
+
+static void
+login_sm_handle_initial_login(iscsit_conn_t *ict, idm_pdu_t *pdu);
+
+static void
+login_sm_send_next_response(iscsit_conn_t *ict);
+
+static void
+login_sm_process_request(iscsit_conn_t *ict);
+
+static idm_status_t
+login_sm_req_pdu_check(iscsit_conn_t *ict, idm_pdu_t *pdu);
+
+static idm_status_t
+login_sm_process_nvlist(iscsit_conn_t *ict);
+
+static idm_status_t
+login_sm_check_security(iscsit_conn_t *ict);
+
+static void
+login_sm_build_login_response(iscsit_conn_t *ict);
+
+static void
+login_sm_ffp_actions(iscsit_conn_t *ict);
+
+static idm_status_t
+login_sm_validate_initial_parameters(iscsit_conn_t *ict);
+
+static idm_status_t
+login_sm_session_bind(iscsit_conn_t *ict);
+
+static idm_status_t
+login_sm_set_auth(iscsit_conn_t *ict);
+
+static idm_status_t
+login_sm_session_register(iscsit_conn_t *ict);
+
+static kv_status_t
+iscsit_handle_key(iscsit_conn_t *ict, nvpair_t *nvp, char *nvp_name);
+
+static kv_status_t
+iscsit_handle_common_key(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx);
+
+static kv_status_t
+iscsit_handle_security_key(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx);
+
+static kv_status_t
+iscsit_reply_security_key(iscsit_conn_t *ict);
+
+static kv_status_t
+iscsit_handle_operational_key(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx);
+
+static kv_status_t
+iscsit_reply_numerical(iscsit_conn_t *ict,
+ const char *nvp_name, const uint64_t value);
+
+static kv_status_t
+iscsit_reply_string(iscsit_conn_t *ict,
+ const char *nvp_name, const char *text);
+
+static kv_status_t
+iscsit_handle_digest(iscsit_conn_t *ict, nvpair_t *choices,
+ const idm_kv_xlate_t *ikvx);
+
+static kv_status_t
+iscsit_handle_boolean(iscsit_conn_t *ict, nvpair_t *nvp, boolean_t value,
+ const idm_kv_xlate_t *ikvx, boolean_t iscsit_value);
+
+static kv_status_t
+iscsit_handle_numerical(iscsit_conn_t *ict, nvpair_t *nvp, uint64_t value,
+ const idm_kv_xlate_t *ikvx,
+ uint64_t iscsi_min_value, uint64_t iscsi_max_value,
+ uint64_t iscsit_max_value);
+
+static void
+iscsit_process_negotiated_values(iscsit_conn_t *ict);
+
+static void
+login_resp_complete_cb(idm_pdu_t *pdu, idm_status_t status);
+
+idm_status_t
+iscsit_login_sm_init(iscsit_conn_t *ict)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+
+ bzero(lsm, sizeof (iscsit_conn_login_t));
+
+ /* initialize the response pdu */
+ ict->ict_login_sm.icl_login_resp =
+ idm_pdu_alloc(sizeof (iscsi_hdr_t), 0);
+ if (ict->ict_login_sm.icl_login_resp == NULL) {
+ return (IDM_STATUS_FAIL);
+ }
+ idm_pdu_init(ict->ict_login_sm.icl_login_resp,
+ ict->ict_ic, ict, login_resp_complete_cb);
+ lsm->icl_login_resp->isp_flags |= IDM_PDU_LOGIN_TX;
+
+ (void) nvlist_alloc(&lsm->icl_negotiated_values, NV_UNIQUE_NAME,
+ KM_SLEEP);
+
+ /*
+ * Hold connection until the login state machine completes
+ */
+ iscsit_conn_hold(ict);
+
+ /*
+ * Pre-allocating a login response PDU means we will always be
+ * able to respond to a login request -- even if we can't allocate
+ * a data buffer to hold the text responses we can at least send
+ * a login failure.
+ */
+ lsm->icl_login_resp_tmpl = kmem_zalloc(sizeof (iscsi_login_rsp_hdr_t),
+ KM_SLEEP);
+
+ idm_sm_audit_init(&lsm->icl_state_audit);
+ mutex_init(&lsm->icl_mutex, NULL, MUTEX_DEFAULT, NULL);
+ list_create(&lsm->icl_login_events, sizeof (login_event_ctx_t),
+ offsetof(login_event_ctx_t, le_ctx_node));
+ list_create(&lsm->icl_pdu_list, sizeof (idm_pdu_t),
+ offsetof(idm_pdu_t, isp_client_lnd));
+
+ lsm->icl_login_state = ILS_LOGIN_INIT;
+ lsm->icl_login_last_state = ILS_LOGIN_INIT;
+
+ /*
+ * Initialize operational parameters to default values. Anything
+ * we don't specifically negotiate stays at the default.
+ */
+ ict->ict_op.op_discovery_session = B_FALSE;
+ ict->ict_op.op_initial_r2t = ISCSI_DEFAULT_INITIALR2T;
+ ict->ict_op.op_immed_data = ISCSI_DEFAULT_IMMEDIATE_DATA;
+ ict->ict_op.op_data_pdu_in_order = ISCSI_DEFAULT_DATA_PDU_IN_ORDER;
+ ict->ict_op.op_data_sequence_in_order =
+ ISCSI_DEFAULT_DATA_SEQUENCE_IN_ORDER;
+ ict->ict_op.op_max_connections = ISCSI_DEFAULT_MAX_CONNECTIONS;
+ ict->ict_op.op_max_recv_data_segment_length =
+ ISCSI_DEFAULT_MAX_RECV_SEG_LEN;
+ ict->ict_op.op_max_burst_length = ISCSI_DEFAULT_MAX_BURST_LENGTH;
+ ict->ict_op.op_first_burst_length = ISCSI_DEFAULT_FIRST_BURST_LENGTH;
+ ict->ict_op.op_default_time_2_wait = ISCSI_DEFAULT_TIME_TO_WAIT;
+ ict->ict_op.op_default_time_2_retain = ISCSI_DEFAULT_TIME_TO_RETAIN;
+ ict->ict_op.op_max_outstanding_r2t = ISCSI_DEFAULT_MAX_OUT_R2T;
+ ict->ict_op.op_error_recovery_level =
+ ISCSI_DEFAULT_ERROR_RECOVERY_LEVEL;
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+static void
+login_resp_complete_cb(idm_pdu_t *pdu, idm_status_t status)
+{
+ iscsit_conn_t *ict = pdu->isp_private;
+
+ ASSERT(ict->ict_login_sm.icl_login_resp == pdu);
+ /*
+ * The icl_login_resp response buffer should only ever be used
+ * during the LOGIN phase.
+ */
+ ASSERT((pdu->isp_flags & IDM_PDU_LOGIN_TX) != 0);
+
+ if ((status != IDM_STATUS_SUCCESS) ||
+ (ict->ict_login_sm.icl_login_resp_err_class != 0)) {
+ iscsit_login_sm_event(ict, ILE_LOGIN_ERROR, NULL);
+ } else if (login_sm_is_last_response(ict) == B_TRUE) {
+ iscsit_login_sm_event(ict, ILE_LOGIN_RESP_COMPLETE, NULL);
+ }
+}
+
+void
+iscsit_login_sm_fini(iscsit_conn_t *ict)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+
+ mutex_enter(&lsm->icl_mutex);
+ list_destroy(&lsm->icl_pdu_list);
+ list_destroy(&lsm->icl_login_events);
+ mutex_exit(&lsm->icl_mutex);
+ mutex_destroy(&lsm->icl_mutex);
+
+ kmem_free(lsm->icl_login_resp_tmpl, sizeof (iscsi_login_rsp_hdr_t));
+ idm_pdu_free(lsm->icl_login_resp);
+
+ /* clean up the login response idm text buffer */
+ if (lsm->icl_login_resp_itb != NULL) {
+ idm_itextbuf_free(lsm->icl_login_resp_itb);
+ lsm->icl_login_resp_itb = NULL;
+ }
+
+ nvlist_free(lsm->icl_negotiated_values);
+ iscsit_conn_rele(ict);
+}
+
+void
+iscsit_login_sm_event(iscsit_conn_t *ict, iscsit_login_event_t event,
+ idm_pdu_t *pdu)
+{
+ /*
+ * This is a bit ugly but if we're already in ILS_LOGIN_ERROR
+ * or ILS_LOGIN_DONE then just drop any additional events. They
+ * won't change the state and it's possible we've already called
+ * iscsit_login_sm_fini in which case the mutex is destroyed.
+ */
+ if ((ict->ict_login_sm.icl_login_state == ILS_LOGIN_ERROR) ||
+ (ict->ict_login_sm.icl_login_state == ILS_LOGIN_DONE))
+ return;
+
+ mutex_enter(&ict->ict_login_sm.icl_mutex);
+ iscsit_login_sm_event_locked(ict, event, pdu);
+ mutex_exit(&ict->ict_login_sm.icl_mutex);
+}
+void
+iscsit_login_sm_event_locked(iscsit_conn_t *ict, iscsit_login_event_t event,
+ idm_pdu_t *pdu)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ login_event_ctx_t *ctx;
+
+ ctx = kmem_zalloc(sizeof (*ctx), KM_SLEEP);
+
+ ctx->le_ctx_event = event;
+ ctx->le_pdu = pdu;
+
+ list_insert_tail(&lsm->icl_login_events, ctx);
+
+ /*
+ * Use the icl_busy flag to keep the state machine single threaded.
+ * This also serves as recursion avoidance since this flag will
+ * always be set if we call login_sm_event from within the
+ * state machine code.
+ */
+ if (!lsm->icl_busy) {
+ lsm->icl_busy = B_TRUE;
+ while (!list_is_empty(&lsm->icl_login_events)) {
+ ctx = list_head(&lsm->icl_login_events);
+ list_remove(&lsm->icl_login_events, ctx);
+ idm_sm_audit_event(&lsm->icl_state_audit,
+ SAS_ISCSIT_LOGIN, (int)lsm->icl_login_state,
+ (int)ctx->le_ctx_event, (uintptr_t)pdu);
+
+ mutex_exit(&lsm->icl_mutex);
+ login_sm_event_dispatch(lsm, ict, ctx);
+ mutex_enter(&lsm->icl_mutex);
+ }
+ lsm->icl_busy = B_FALSE;
+
+ /*
+ * When the state machine reaches ILS_LOGIN_DONE or
+ * ILS_LOGIN_ERROR state the login process has completed
+ * and it's time to cleanup. The state machine code will
+ * mark itself "complete" when this happens.
+ *
+ * To protect against spurious events (which shouldn't
+ * happen) set icl_busy again.
+ */
+ if (lsm->icl_login_complete) {
+ lsm->icl_busy = B_TRUE;
+ if (taskq_dispatch(iscsit_global.global_dispatch_taskq,
+ login_sm_complete, ict, DDI_SLEEP) == NULL) {
+ cmn_err(CE_WARN, "iscsit_login_sm_event_locked:"
+ " Failed to dispatch task");
+ }
+ }
+ }
+}
+
+static void
+login_sm_complete(void *ict_void)
+{
+ iscsit_conn_t *ict = ict_void;
+
+ /*
+ * State machine has run to completion, release state machine resources
+ */
+ iscsit_login_sm_fini(ict);
+}
+
+static void
+login_sm_event_dispatch(iscsit_conn_login_t *lsm, iscsit_conn_t *ict,
+ login_event_ctx_t *ctx)
+{
+ idm_pdu_t *pdu = ctx->le_pdu; /* Only valid for some events */
+
+ DTRACE_PROBE2(login__event, iscsit_conn_t *, ict,
+ login_event_ctx_t *, ctx);
+
+ IDM_SM_LOG(CE_NOTE, "login_sm_event_dispatch: ict %p event %s(%d)",
+ (void *)ict,
+ iscsit_ile_name[ctx->le_ctx_event], ctx->le_ctx_event);
+
+ /* State independent actions */
+ switch (ctx->le_ctx_event) {
+ case ILE_LOGIN_RCV:
+ /* Perform basic sanity checks on the header */
+ if (login_sm_req_pdu_check(ict, pdu) != IDM_STATUS_SUCCESS) {
+ SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR,
+ ISCSI_LOGIN_STATUS_INVALID_REQUEST);
+ /*
+ * If we haven't processed any PDU's yet then use
+ * this one as a template for the response
+ */
+ if (ict->ict_login_sm.icl_login_resp_tmpl->opcode == 0)
+ login_sm_handle_initial_login(ict, pdu);
+ login_sm_build_login_response(ict);
+ login_sm_send_next_response(ict);
+ idm_pdu_complete(pdu, IDM_STATUS_SUCCESS);
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ /* State dependent actions */
+ switch (lsm->icl_login_state) {
+ case ILS_LOGIN_INIT:
+ login_sm_init(ict, ctx);
+ break;
+ case ILS_LOGIN_WAITING:
+ login_sm_waiting(ict, ctx);
+ break;
+ case ILS_LOGIN_PROCESSING:
+ login_sm_processing(ict, ctx);
+ break;
+ case ILS_LOGIN_RESPONDING:
+ login_sm_responding(ict, ctx);
+ break;
+ case ILS_LOGIN_RESPONDED:
+ login_sm_responded(ict, ctx);
+ break;
+ case ILS_LOGIN_FFP:
+ login_sm_ffp(ict, ctx);
+ break;
+ case ILS_LOGIN_DONE:
+ login_sm_done(ict, ctx);
+ break;
+ case ILS_LOGIN_ERROR:
+ login_sm_error(ict, ctx);
+ break;
+ }
+
+ kmem_free(ctx, sizeof (*ctx));
+}
+
+static void
+login_sm_init(iscsit_conn_t *ict, login_event_ctx_t *ctx)
+{
+ idm_pdu_t *pdu;
+
+ switch (ctx->le_ctx_event) {
+ case ILE_LOGIN_RCV:
+ pdu = ctx->le_pdu;
+
+ /*
+ * This is the first login PDU we've received so use
+ * it to build the login response template and set our CSG.
+ */
+ login_sm_handle_initial_login(ict, pdu);
+
+ /*
+ * Accumulate all the login PDU's that make up this
+ * request on a queue.
+ */
+ mutex_enter(&ict->ict_login_sm.icl_mutex);
+ list_insert_tail(&ict->ict_login_sm.icl_pdu_list, pdu);
+ mutex_exit(&ict->ict_login_sm.icl_mutex);
+
+ if (pdu->isp_hdr->flags & ISCSI_FLAG_LOGIN_CONTINUE) {
+ login_sm_send_ack(ict, pdu);
+ login_sm_new_state(ict, ctx, ILS_LOGIN_WAITING);
+ } else {
+ login_sm_new_state(ict, ctx, ILS_LOGIN_PROCESSING);
+ }
+ break;
+ case ILE_LOGIN_CONN_ERROR:
+ case ILE_LOGIN_ERROR:
+ login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR);
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+static void
+login_sm_waiting(iscsit_conn_t *ict, login_event_ctx_t *ctx)
+{
+ idm_pdu_t *pdu;
+
+ switch (ctx->le_ctx_event) {
+ case ILE_LOGIN_RCV:
+ pdu = ctx->le_pdu;
+ mutex_enter(&ict->ict_login_sm.icl_mutex);
+ list_insert_tail(&ict->ict_login_sm.icl_pdu_list, pdu);
+ mutex_exit(&ict->ict_login_sm.icl_mutex);
+ if (!(pdu->isp_hdr->flags & ISCSI_FLAG_LOGIN_CONTINUE)) {
+ login_sm_new_state(ict, ctx, ILS_LOGIN_PROCESSING);
+ } else {
+ login_sm_send_ack(ict, pdu);
+ }
+ break;
+ case ILE_LOGIN_ERROR:
+ login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR);
+ break;
+ case ILE_LOGIN_RESP_COMPLETE:
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+static void
+login_sm_processing(iscsit_conn_t *ict, login_event_ctx_t *ctx)
+{
+ switch (ctx->le_ctx_event) {
+ case ILE_LOGIN_RESP_READY:
+ login_sm_new_state(ict, ctx, ILS_LOGIN_RESPONDING);
+ break;
+ case ILE_LOGIN_RCV:
+ idm_pdu_complete(ctx->le_pdu, IDM_STATUS_SUCCESS);
+ /*FALLTHROUGH*/
+ case ILE_LOGIN_CONN_ERROR:
+ case ILE_LOGIN_ERROR:
+ login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR);
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+static void
+login_sm_responding(iscsit_conn_t *ict, login_event_ctx_t *ctx)
+{
+ idm_pdu_t *pdu;
+
+ switch (ctx->le_ctx_event) {
+ case ILE_LOGIN_RCV:
+ pdu = ctx->le_pdu;
+ /*
+ * We should only be in "responding" state if we have not
+ * sent the last PDU of a multi-PDU login response sequence.
+ * In that case we expect this received PDU to be an
+ * acknowledgement from the initiator (login PDU with C
+ * bit cleared and no data). If it's the acknowledgement
+ * we are expecting then we send the next PDU in the login
+ * response sequence. Otherwise it's a protocol error and
+ * the login fails.
+ */
+ if (login_sm_validate_ack(ict, pdu) == IDM_STATUS_SUCCESS) {
+ login_sm_build_login_response(ict);
+ login_sm_send_next_response(ict);
+ } else {
+ login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR);
+ }
+ idm_pdu_complete(pdu, IDM_STATUS_SUCCESS);
+ break;
+ case ILE_LOGIN_FFP:
+ login_sm_new_state(ict, ctx, ILS_LOGIN_FFP);
+ break;
+ case ILE_LOGIN_RESP_COMPLETE:
+ login_sm_new_state(ict, ctx, ILS_LOGIN_RESPONDED);
+ break;
+ case ILE_LOGIN_CONN_ERROR:
+ case ILE_LOGIN_ERROR:
+ login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR);
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+static void
+login_sm_responded(iscsit_conn_t *ict, login_event_ctx_t *ctx)
+{
+ idm_pdu_t *pdu;
+ iscsi_login_hdr_t *lh;
+
+ switch (ctx->le_ctx_event) {
+ case ILE_LOGIN_RCV:
+ pdu = ctx->le_pdu;
+ lh = (iscsi_login_hdr_t *)pdu->isp_hdr;
+ /*
+ * Set the CSG, NSG and Transit bits based on the this PDU.
+ * The CSG already validated in login_sm_req_pdu_check().
+ * We'll clear the transit bit if we encounter any login
+ * parameters in the request that required an additional
+ * login transfer (i.e. no acceptable
+ * choices in range or we needed to change a boolean
+ * value from "Yes" to "No").
+ */
+ ict->ict_login_sm.icl_login_csg =
+ ISCSI_LOGIN_CURRENT_STAGE(lh->flags);
+ ict->ict_login_sm.icl_login_nsg =
+ ISCSI_LOGIN_NEXT_STAGE(lh->flags);
+ ict->ict_login_sm.icl_login_transit =
+ lh->flags & ISCSI_FLAG_LOGIN_TRANSIT;
+ mutex_enter(&ict->ict_login_sm.icl_mutex);
+ list_insert_tail(&ict->ict_login_sm.icl_pdu_list, pdu);
+ mutex_exit(&ict->ict_login_sm.icl_mutex);
+ if (pdu->isp_hdr->flags & ISCSI_FLAG_LOGIN_CONTINUE) {
+ login_sm_send_ack(ict, pdu);
+ login_sm_new_state(ict, ctx, ILS_LOGIN_WAITING);
+ } else {
+ login_sm_new_state(ict, ctx, ILS_LOGIN_PROCESSING);
+ }
+ break;
+ case ILE_LOGIN_CONN_ERROR:
+ case ILE_LOGIN_ERROR:
+ login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR);
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+static void
+login_sm_ffp(iscsit_conn_t *ict, login_event_ctx_t *ctx)
+{
+ switch (ctx->le_ctx_event) {
+ case ILE_LOGIN_RESP_COMPLETE:
+ login_sm_new_state(ict, ctx, ILS_LOGIN_DONE);
+ break;
+ case ILE_LOGIN_CONN_ERROR:
+ case ILE_LOGIN_ERROR:
+ login_sm_new_state(ict, ctx, ILS_LOGIN_ERROR);
+ break;
+ default:
+ ASSERT(0);
+ }
+
+}
+
+/*ARGSUSED*/
+static void
+login_sm_done(iscsit_conn_t *ict, login_event_ctx_t *ctx)
+{
+ /* Terminal state, we should get no events */
+ switch (ctx->le_ctx_event) {
+ case ILE_LOGIN_RCV:
+ /*
+ * We've already processed everything we're going to
+ * process. Drop any additional login PDU's.
+ */
+ idm_pdu_complete(ctx->le_pdu, IDM_STATUS_SUCCESS);
+ break;
+ case ILE_LOGIN_CONN_ERROR:
+ /* Don't care */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+/*ARGSUSED*/
+static void
+login_sm_error(iscsit_conn_t *ict, login_event_ctx_t *ctx)
+{
+ switch (ctx->le_ctx_event) {
+ case ILE_LOGIN_RCV:
+ /*
+ * We've already processed everything we're going to
+ * process. Drop any additional login PDU's.
+ */
+ idm_pdu_complete(ctx->le_pdu, IDM_STATUS_SUCCESS);
+ break;
+ case ILE_LOGIN_CONN_ERROR:
+ /* Don't care */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+static void
+login_sm_new_state(iscsit_conn_t *ict, login_event_ctx_t *ctx,
+ iscsit_login_state_t new_state)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+
+ /*
+ * Validate new state
+ */
+ ASSERT(new_state != ILS_UNDEFINED);
+ ASSERT3U(new_state, <, ILS_MAX_STATE);
+
+ new_state = (new_state < ILS_MAX_STATE) ?
+ new_state : ILS_UNDEFINED;
+
+ IDM_SM_LOG(CE_NOTE, "login_sm_new_state: conn %p "
+ "%s (%d) --> %s (%d)\n", (void *)ict->ict_ic,
+ iscsit_ils_name[lsm->icl_login_state], lsm->icl_login_state,
+ iscsit_ils_name[new_state], new_state);
+
+ DTRACE_PROBE3(login__state__change,
+ iscsit_conn_t *, ict, login_event_ctx_t *, ctx,
+ iscsit_login_state_t, new_state);
+
+ mutex_enter(&lsm->icl_mutex);
+ idm_sm_audit_state_change(&lsm->icl_state_audit, SAS_ISCSIT_LOGIN,
+ (int)lsm->icl_login_state, (int)new_state);
+ lsm->icl_login_last_state = lsm->icl_login_state;
+ lsm->icl_login_state = new_state;
+ mutex_exit(&lsm->icl_mutex);
+
+ switch (lsm->icl_login_state) {
+ case ILS_LOGIN_WAITING:
+ /* Do nothing, waiting for more login PDU's */
+ break;
+ case ILS_LOGIN_PROCESSING:
+ /* All login PDU's received, process login request */
+ login_sm_process_request(ict);
+ break;
+ case ILS_LOGIN_RESPONDING:
+ login_sm_send_next_response(ict);
+ break;
+ case ILS_LOGIN_RESPONDED:
+ /* clean up the login response idm text buffer */
+ if (lsm->icl_login_resp_itb != NULL) {
+ idm_itextbuf_free(lsm->icl_login_resp_itb);
+ lsm->icl_login_resp_itb = NULL;
+ }
+ break;
+ case ILS_LOGIN_FFP:
+ login_sm_ffp_actions(ict);
+ break;
+ case ILS_LOGIN_DONE:
+ case ILS_LOGIN_ERROR:
+ /* Free login SM resources */
+ lsm->icl_login_complete = B_TRUE;
+ break;
+ case ILS_LOGIN_INIT: /* Initial state, can't return */
+ default:
+ ASSERT(0);
+ /*NOTREACHED*/
+ }
+}
+
+/*ARGSUSED*/
+static void
+login_sm_send_ack(iscsit_conn_t *ict, idm_pdu_t *pdu)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+
+ ASSERT((lsm->icl_login_resp->isp_flags & IDM_PDU_LOGIN_TX) != 0);
+ bcopy(lsm->icl_login_resp_tmpl,
+ lsm->icl_login_resp->isp_hdr, sizeof (iscsi_hdr_t));
+ idm_pdu_tx(lsm->icl_login_resp);
+}
+
+/*ARGSUSED*/
+static idm_status_t
+login_sm_validate_ack(iscsit_conn_t *ict, idm_pdu_t *pdu)
+{
+ iscsi_hdr_t *ihp = pdu->isp_hdr;
+ if (ihp->flags & ISCSI_FLAG_TEXT_CONTINUE) {
+ return (IDM_STATUS_FAIL);
+ }
+ if (ntoh24(ihp->dlength) != 0) {
+ return (IDM_STATUS_FAIL);
+ }
+ return (IDM_STATUS_SUCCESS);
+}
+
+static boolean_t
+login_sm_is_last_response(iscsit_conn_t *ict)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+
+ if (lsm->icl_login_resp->isp_hdr->flags & ISCSI_FLAG_LOGIN_CONTINUE) {
+ return (B_FALSE);
+ }
+ return (B_TRUE);
+}
+
+
+static void
+login_sm_handle_initial_login(iscsit_conn_t *ict, idm_pdu_t *pdu)
+{
+ iscsi_login_hdr_t *lh_req = (iscsi_login_hdr_t *)pdu->isp_hdr;
+ iscsi_login_rsp_hdr_t *lh_resp =
+ ict->ict_login_sm.icl_login_resp_tmpl;
+
+ /*
+ * First login PDU, this connection should not have a sesssion
+ * associated.
+ */
+ ASSERT(ict->ict_sess == NULL);
+
+ /*
+ * Save off TSIH and ISID for later use in finding a session
+ */
+ ict->ict_login_sm.icl_cmdsn = ntohl(lh_req->cmdsn);
+ ict->ict_login_sm.icl_tsih = ntohs(lh_req->tsid);
+ bcopy(lh_req->isid, ict->ict_login_sm.icl_isid, ISCSI_ISID_LEN);
+
+ /*
+ * We'll need the CID as well
+ */
+ ict->ict_cid = ntohs(lh_req->cid);
+
+ /*
+ * Set the CSG, NSG and Transit bits based on the first PDU
+ * in the login sequence. The CSG already validated in
+ * login_sm_req_pdu_check(). We'll clear the transit bit if
+ * we encounter any login parameters in the request that
+ * required an additional login transfer (i.e. no acceptable
+ * choices in range or we needed to change a boolean
+ * value from "Yes" to "No").
+ */
+ ict->ict_login_sm.icl_login_csg =
+ ISCSI_LOGIN_CURRENT_STAGE(lh_req->flags);
+ ict->ict_login_sm.icl_login_nsg =
+ ISCSI_LOGIN_NEXT_STAGE(lh_req->flags);
+ ict->ict_login_sm.icl_login_transit =
+ lh_req->flags & ISCSI_FLAG_LOGIN_TRANSIT;
+
+ /*
+ * Initialize header for login reject response. This will also
+ * be copied for use as a template for other login responses
+ */
+ lh_resp->opcode = ISCSI_OP_LOGIN_RSP;
+ lh_resp->max_version = ISCSIT_MAX_VERSION;
+
+ /*
+ * We already validated that we can support one of the initiator's
+ * versions in login_sm_req_pdu_check().
+ */
+#if (ISCSIT_MAX_VERSION > 0)
+ if (ISCSIT_MAX_VERSION >= lh_req->min_version) {
+ lh_resp->active_version =
+ MIN(lh_req->max_version, ISCSIT_MAX_VERSION);
+ } else {
+ ASSERT(ISCSIT_MAX_VERSION <= lh_req->max_version);
+ lh_resp->active_version = ISCSIT_MAX_VERSION;
+ }
+#endif
+
+ lh_resp->hlength = 0; /* No AHS */
+ bcopy(lh_req->isid, lh_resp->isid, ISCSI_ISID_LEN);
+ lh_resp->tsid = lh_req->tsid;
+ lh_resp->itt = lh_req->itt;
+
+ /*
+ * StatSn, ExpCmdSn and MaxCmdSn will be set immediately before
+ * transmission
+ */
+}
+
+static void
+login_sm_send_next_response(iscsit_conn_t *ict)
+{
+ idm_pdu_t *pdu = ict->ict_login_sm.icl_login_resp;
+ iscsi_login_rsp_hdr_t *lh_resp = (iscsi_login_rsp_hdr_t *)pdu->isp_hdr;
+
+ /* Tell the IDM layer this PDU is part of the login phase */
+ ASSERT((pdu->isp_flags & IDM_PDU_LOGIN_TX) != 0);
+
+ /*
+ * Fill in header values
+ */
+ hton24(lh_resp->dlength, pdu->isp_datalen);
+
+ /*
+ * If this is going to be the last PDU of a login response
+ * that moves us to FFP then generate the ILE_LOGIN_FFP event.
+ */
+ if (lh_resp->status_class == ISCSI_STATUS_CLASS_SUCCESS) {
+ ASSERT(ict->ict_sess != NULL);
+
+ if ((lh_resp->flags & ISCSI_FLAG_LOGIN_TRANSIT) &&
+ (ISCSI_LOGIN_NEXT_STAGE(lh_resp->flags) ==
+ ISCSI_FULL_FEATURE_PHASE) &&
+ !(lh_resp->flags & ISCSI_FLAG_LOGIN_CONTINUE)) {
+ iscsit_login_sm_event_locked(ict, ILE_LOGIN_FFP, NULL);
+ }
+
+ iscsit_pdu_tx(pdu);
+ } else {
+ /*
+ * If status_class != ISCSI_STATUS_CLASS_SUCCESS then
+ * StatSN is not valid and we can call idm_pdu_tx instead
+ * of iscsit_pdu_tx. This is very good thing since in
+ * some cases of login failure we may not have a session.
+ * Since iscsit_calc_rspsn grabs the session mutex while
+ * it is retrieving values for expcmdsn and maxcmdsn this
+ * would cause a panic.
+ *
+ * Since we still want a value for expcmdsn, fill in an
+ * appropriate value based on the login request before
+ * sending the response.
+ */
+ lh_resp->expcmdsn = htonl(ict->ict_login_sm.icl_cmdsn + 1);
+ lh_resp->maxcmdsn = htonl(ict->ict_login_sm.icl_cmdsn + 2);
+
+ idm_pdu_tx(ict->ict_login_sm.icl_login_resp);
+ }
+
+}
+
+static void
+login_sm_process_request(iscsit_conn_t *ict)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ uint8_t error_class = 0;
+ uint8_t error_detail = 0;
+
+ /*
+ * First walk all the PDU's that make up this login request
+ * and compile all the iSCSI key-value pairs into nvlist format.
+ */
+
+ ASSERT(lsm->icl_request_nvlist == NULL);
+ /* create an nvlist for request key/value pairs */
+ if (idm_pdu_list_to_nvlist(&lsm->icl_pdu_list,
+ &lsm->icl_request_nvlist, &error_detail) != IDM_STATUS_SUCCESS) {
+ error_class = ISCSI_STATUS_CLASS_TARGET_ERR;
+ SET_LOGIN_ERROR(ict, error_class, error_detail);
+ goto request_fail;
+ }
+
+ /* Allocate a new nvlist for response key/value pairs */
+ ASSERT(lsm->icl_response_nvlist == NULL);
+ if (nvlist_alloc(&lsm->icl_response_nvlist, NV_UNIQUE_NAME,
+ KM_NOSLEEP) != 0) {
+ error_class = ISCSI_STATUS_CLASS_TARGET_ERR;
+ error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES;
+ SET_LOGIN_ERROR(ict, error_class, error_detail);
+ goto request_fail;
+ }
+
+ /*
+ * This would be a very good time to make sure we have
+ * negotiated the required values for the login phase. For
+ * example we definitely should have defined InitiatorName,
+ * and Target name regardless of our current login phase.
+ */
+ if (!ict->ict_op.op_initial_params_set) {
+ if (login_sm_validate_initial_parameters(ict) !=
+ IDM_STATUS_SUCCESS) {
+ goto request_fail;
+ }
+
+ /*
+ * Now setup our session association. This includes
+ * create a new session or looking up an existing session,
+ * and if this is not a discovery session then we will
+ * also register this session with STMF.
+ */
+ if (login_sm_session_bind(ict) != IDM_STATUS_SUCCESS) {
+ goto request_fail;
+ }
+
+ if (login_sm_set_auth(ict) != IDM_STATUS_SUCCESS) {
+ goto request_fail;
+ }
+
+ /*
+ * Prepend TargetAlias and PortalGroupTag
+ */
+ if (ict->ict_op.op_discovery_session == B_FALSE) {
+ if ((lsm->icl_auth.ca_tgt_alias[0]) != '\0') {
+ (void) iscsit_reply_string(ict,
+ "TargetAlias",
+ &lsm->icl_auth.ca_tgt_alias[0]);
+ }
+ (void) iscsit_reply_numerical(ict,
+ "TargetPortalGroupTag",
+ (uint64_t)lsm->icl_tpgt_tag);
+ }
+
+ ict->ict_op.op_initial_params_set = B_TRUE;
+ }
+
+ if (login_sm_process_nvlist(ict) != IDM_STATUS_SUCCESS) {
+ goto request_fail;
+ }
+
+ if (login_sm_check_security(ict) != IDM_STATUS_SUCCESS) {
+ goto request_fail;
+ }
+
+request_fail:
+ login_sm_build_login_response(ict);
+ iscsit_login_sm_event(ict, ILE_LOGIN_RESP_READY, NULL);
+
+ /* clean up request_nvlist and response_nvlist */
+ if (lsm->icl_request_nvlist != NULL) {
+ nvlist_free(lsm->icl_request_nvlist);
+ lsm->icl_request_nvlist = NULL;
+ }
+ if (lsm->icl_response_nvlist != NULL) {
+ nvlist_free(lsm->icl_response_nvlist);
+ lsm->icl_response_nvlist = NULL;
+ }
+}
+
+
+static void
+login_sm_ffp_actions(iscsit_conn_t *ict)
+{
+ iscsit_process_negotiated_values(ict);
+}
+
+static idm_status_t
+login_sm_validate_initial_parameters(iscsit_conn_t *ict)
+{
+ int nvrc;
+ char *string_val;
+ uint8_t error_class = ISCSI_STATUS_CLASS_INITIATOR_ERR;
+ uint8_t error_detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS;
+ idm_status_t status = IDM_STATUS_FAIL;
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+
+ /*
+ * Make sure we received the required information from the initial
+ * login. Add these declaratives to the negotiated list and
+ * remove them from the request list as we go. If anything fails,
+ * the caller will clean-up the nvlists.
+ */
+
+ /*
+ * Initiator name
+ */
+ if ((nvrc = nvlist_lookup_string(lsm->icl_request_nvlist,
+ "InitiatorName", &string_val)) != 0) {
+ goto initial_params_done;
+ }
+ if ((nvrc = nvlist_add_string(lsm->icl_negotiated_values,
+ "InitiatorName", string_val)) != 0) {
+ goto initial_params_done;
+ }
+ if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values,
+ "InitiatorName", &string_val)) != 0) {
+ goto initial_params_done;
+ }
+ lsm->icl_initiator_name = string_val;
+ if ((nvrc = nvlist_remove(lsm->icl_request_nvlist,
+ "InitiatorName", DATA_TYPE_STRING)) != 0) {
+ goto initial_params_done;
+ }
+
+ /*
+ * Session type
+ */
+ ict->ict_op.op_discovery_session = B_FALSE;
+ nvrc = nvlist_lookup_string(lsm->icl_request_nvlist,
+ "SessionType", &string_val);
+ if (nvrc != ENOENT && nvrc != 0) {
+ goto initial_params_done;
+ }
+ if (nvrc == 0) {
+ if (strcmp(string_val, "Discovery") == 0) {
+ ict->ict_op.op_discovery_session = B_TRUE;
+ } else if (strcmp(string_val, "Normal") != 0) {
+ goto initial_params_done;
+ }
+ if ((nvrc = nvlist_add_string(lsm->icl_negotiated_values,
+ "SessionType", string_val)) != 0) {
+ goto initial_params_done;
+ }
+ if ((nvrc = nvlist_remove(lsm->icl_request_nvlist,
+ "SessionType", DATA_TYPE_STRING)) != 0) {
+ goto initial_params_done;
+ }
+ }
+
+ /*
+ * Must have either TargetName or SessionType==Discovery
+ */
+ lsm->icl_target_name = NULL;
+ nvrc = nvlist_lookup_string(lsm->icl_request_nvlist,
+ "TargetName", &string_val);
+ if (nvrc != ENOENT && nvrc != 0) {
+ goto initial_params_done;
+ }
+ if (nvrc == 0) {
+ if ((nvrc = nvlist_add_string(lsm->icl_negotiated_values,
+ "TargetName", string_val)) != 0) {
+ goto initial_params_done;
+ }
+ if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values,
+ "TargetName", &string_val)) != 0) {
+ goto initial_params_done;
+ }
+ lsm->icl_target_name = string_val;
+ if ((nvrc = nvlist_remove(lsm->icl_request_nvlist,
+ "TargetName", DATA_TYPE_STRING)) != 0) {
+ goto initial_params_done;
+ }
+ } else if (ict->ict_op.op_discovery_session == B_FALSE) {
+ /*
+ * Missing target name
+ */
+ goto initial_params_done;
+ }
+
+ IDM_SM_LOG(CE_NOTE, "conn %p: initiator=%s", (void *)ict->ict_ic,
+ (lsm->icl_initiator_name == NULL) ? "N/A" :
+ lsm->icl_initiator_name);
+ IDM_SM_LOG(CE_NOTE, "conn %p: target=%s", (void *)ict->ict_ic,
+ (lsm->icl_target_name == NULL) ? "N/A" :
+ lsm->icl_target_name);
+ IDM_SM_LOG(CE_NOTE, "conn %p: sessiontype=%s", (void *)ict->ict_ic,
+ ict->ict_op.op_discovery_session ? "Discovery" : "Normal");
+
+ /* Sucess */
+ status = IDM_STATUS_SUCCESS;
+ error_class = ISCSI_STATUS_CLASS_SUCCESS;
+ error_detail = ISCSI_LOGIN_STATUS_ACCEPT;
+
+initial_params_done:
+ SET_LOGIN_ERROR(ict, error_class, error_detail);
+ return (status);
+}
+
+
+/*
+ * login_sm_session_bind
+ *
+ * This function looks at the data from the initial login request
+ * of a new connection and either looks up and existing session,
+ * creates a new session, or returns an error. RFC3720 section 5.3.1
+ * defines these rules:
+ *
+ * +------------------------------------------------------------------+
+ * |ISID | TSIH | CID | Target action |
+ * +------------------------------------------------------------------+
+ * |new | non-zero | any | fail the login |
+ * | | | | ("session does not exist") |
+ * +------------------------------------------------------------------+
+ * |new | zero | any | instantiate a new session |
+ * +------------------------------------------------------------------+
+ * |existing | zero | any | do session reinstatement |
+ * | | | | (see section 5.3.5) |
+ * +------------------------------------------------------------------+
+ * |existing | non-zero | new | add a new connection to |
+ * | | existing | | the session |
+ * +------------------------------------------------------------------+
+ * |existing | non-zero |existing| do connection reinstatement|
+ * | | existing | | (see section 5.3.4) |
+ * +------------------------------------------------------------------+
+ * |existing | non-zero | any | fail the login |
+ * | | new | | ("session does not exist") |
+ * +------------------------------------------------------------------+
+ *
+ */
+
+/*
+ * Map an <ipv6,port> address to an <ipv4,port> address if possible.
+ * Returns:
+ * 1 - success
+ * 0 - address not mapable
+ */
+
+static int
+iscsit_is_v4_mapped(struct sockaddr_storage *sa, struct sockaddr_storage *v4sa)
+{
+ struct sockaddr_in *sin;
+ struct in_addr *in;
+ struct sockaddr_in6 *sin6;
+ struct in6_addr *in6;
+ int ret = 0;
+
+ sin6 = (struct sockaddr_in6 *)sa;
+ in6 = &sin6->sin6_addr;
+ if ((sa->ss_family == AF_INET6) &&
+ (IN6_IS_ADDR_V4MAPPED(in6) || IN6_IS_ADDR_V4COMPAT(in6))) {
+ sin = (struct sockaddr_in *)v4sa;
+ in = &sin->sin_addr;
+ v4sa->ss_family = AF_INET;
+ sin->sin_port = sin6->sin6_port;
+ IN6_V4MAPPED_TO_INADDR(in6, in);
+ ret = 1;
+ }
+ return (ret);
+}
+
+static idm_status_t
+login_sm_session_bind(iscsit_conn_t *ict)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsit_tgt_t *tgt = NULL;
+ iscsit_tpgt_t *tpgt = NULL;
+ iscsit_portal_t *portal = NULL;
+ iscsit_sess_t *existing_sess = NULL;
+ iscsit_sess_t *new_sess = NULL;
+ iscsit_conn_t *existing_ict = NULL;
+ uint8_t error_class;
+ uint8_t error_detail;
+
+ /*
+ * Look up target and then check if there are sessions or connections
+ * that match this request (see below). Any holds taken on objects
+ * must be released at the end of the function (let's keep things
+ * simple).
+ *
+ * If target name is set then we should have a corresponding target
+ * context configured.
+ */
+ if (lsm->icl_target_name != NULL) {
+ /*
+ * iscsit_tgt_lookup implicitly takes a ref on the target
+ */
+ ISCSIT_GLOBAL_LOCK(RW_READER);
+ tgt = iscsit_tgt_lookup_locked(lsm->icl_target_name);
+ if (tgt == NULL) {
+ ISCSIT_GLOBAL_UNLOCK();
+ SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR,
+ ISCSI_LOGIN_STATUS_TGT_NOT_FOUND);
+ goto session_bind_error;
+ } else {
+ mutex_enter(&tgt->target_mutex);
+ tpgt = avl_first(&tgt->target_tpgt_list);
+
+ if (IS_DEFAULT_TPGT(tpgt)) {
+ lsm->icl_tpgt_tag = ISCSIT_DEFAULT_TPGT;
+ } else {
+ /*
+ * Find the portal group tag for the
+ * login response.
+ */
+ struct sockaddr_storage v4sa, *sa;
+
+ sa = &ict->ict_ic->ic_laddr;
+ portal = iscsit_tgt_lookup_portal(tgt,
+ sa, &tpgt);
+ if (portal == NULL &&
+ iscsit_is_v4_mapped(sa, &v4sa)) {
+ /*
+ * Try again if the local address
+ * was v6 mappable to v4.
+ */
+ portal = iscsit_tgt_lookup_portal(tgt,
+ &v4sa, &tpgt);
+
+ }
+ if (portal == NULL) {
+ /*
+ * Initiator came in on wrong address
+ */
+ SET_LOGIN_ERROR(ict,
+ ISCSI_STATUS_CLASS_INITIATOR_ERR,
+ ISCSI_LOGIN_STATUS_TGT_NOT_FOUND);
+ mutex_exit(&tgt->target_mutex);
+ ISCSIT_GLOBAL_UNLOCK();
+ goto session_bind_error;
+ }
+
+ /*
+ * Need to release holds on the portal and
+ * tpgt after processing is complete.
+ */
+ lsm->icl_tpgt_tag = tpgt->tpgt_tag;
+ iscsit_portal_rele(portal);
+ iscsit_tpgt_rele(tpgt);
+ }
+
+ if ((tgt->target_state != TS_STMF_ONLINE) ||
+ ((iscsit_global.global_svc_state != ISE_ENABLED) &&
+ ((iscsit_global.global_svc_state != ISE_BUSY)))) {
+ SET_LOGIN_ERROR(ict,
+ ISCSI_STATUS_CLASS_INITIATOR_ERR,
+ ISCSI_LOGIN_STATUS_TGT_REMOVED);
+ mutex_exit(&tgt->target_mutex);
+ ISCSIT_GLOBAL_UNLOCK();
+ goto session_bind_error;
+ }
+ mutex_exit(&tgt->target_mutex);
+ ISCSIT_GLOBAL_UNLOCK();
+ }
+ }
+
+ ASSERT((tgt != NULL) || (ict->ict_op.op_discovery_session == B_TRUE));
+
+ /*
+ * Check if there is an existing session matching this ISID. If
+ * tgt == NULL then we'll look for the session on the global list
+ * of discovery session. If we find a session then the ISID
+ * exists.
+ */
+ existing_sess = iscsit_tgt_lookup_sess(tgt, lsm->icl_initiator_name,
+ lsm->icl_isid, lsm->icl_tsih, lsm->icl_tpgt_tag);
+ if (existing_sess != NULL) {
+ existing_ict = iscsit_sess_lookup_conn(existing_sess,
+ ict->ict_cid);
+ }
+
+ /*
+ * If this is a discovery session, make sure it has appropriate
+ * parameters.
+ */
+ if ((ict->ict_op.op_discovery_session == B_TRUE) &&
+ ((lsm->icl_tsih != ISCSI_UNSPEC_TSIH) || (existing_sess != NULL))) {
+ /* XXX Do we need to check for existing ISID (sess != NULL)? */
+ SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR,
+ ISCSI_LOGIN_STATUS_INVALID_REQUEST);
+ goto session_bind_error;
+ }
+
+ /*
+ * Check the two error conditions from the table.
+ *
+ * ISID=new, TSIH=non-zero
+ */
+ if ((existing_sess == NULL) && (lsm->icl_tsih != ISCSI_UNSPEC_TSIH)) {
+ /* fail the login */
+ SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR,
+ ISCSI_LOGIN_STATUS_NO_SESSION);
+ goto session_bind_error;
+ }
+
+ /* ISID=existing, TSIH=non-zero new */
+ if ((existing_sess != NULL) && (lsm->icl_tsih != 0) &&
+ (existing_sess->ist_tsih != lsm->icl_tsih)) {
+ /* fail the login */
+ SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR,
+ ISCSI_LOGIN_STATUS_NO_SESSION);
+ goto session_bind_error;
+ }
+
+ /*
+ * Handle the remaining table cases in order
+ */
+ if (existing_sess == NULL) {
+ /* Should have caught this above */
+ ASSERT(lsm->icl_tsih == ISCSI_UNSPEC_TSIH);
+ /*
+ * ISID=new, TSIH=zero --> instantiate a new session
+ */
+ new_sess = iscsit_sess_create(tgt, ict, lsm->icl_cmdsn,
+ lsm->icl_isid, lsm->icl_tpgt_tag, lsm->icl_initiator_name,
+ lsm->icl_target_name, &error_class, &error_detail);
+ ASSERT(new_sess != NULL);
+
+ /* Session create may have failed even if it returned a value */
+ if (error_class != ISCSI_STATUS_CLASS_SUCCESS) {
+ SET_LOGIN_ERROR(ict, error_class, error_detail);
+ goto session_bind_error;
+ }
+
+ /*
+ * If we don't already have an STMF session and this is not
+ * a discovery session then we need to allocate and register
+ * one.
+ */
+ if (!ict->ict_op.op_discovery_session) {
+ if (login_sm_session_register(ict) !=
+ IDM_STATUS_SUCCESS) {
+ /* login_sm_session_register sets error codes */
+ goto session_bind_error;
+ }
+ }
+
+ } else {
+ if (lsm->icl_tsih == ISCSI_UNSPEC_TSIH) {
+ /*
+ * ISID=existing, TSIH=zero --> Session reinstatement
+ */
+ new_sess = iscsit_sess_reinstate(tgt, existing_sess,
+ ict, &error_class, &error_detail);
+ ASSERT(new_sess != NULL);
+
+ if (error_class != ISCSI_STATUS_CLASS_SUCCESS) {
+ SET_LOGIN_ERROR(ict, error_class, error_detail);
+ goto session_bind_error;
+ }
+
+ /*
+ * If we don't already have an STMF session and this is
+ * not a discovery session then we need to allocate and
+ * register one.
+ */
+ if (!ict->ict_op.op_discovery_session) {
+ if (login_sm_session_register(ict) !=
+ IDM_STATUS_SUCCESS) {
+ /*
+ * login_sm_session_register sets
+ * error codes
+ */
+ goto session_bind_error;
+ }
+ }
+ } else {
+ /*
+ * The following code covers these two cases:
+ * ISID=existing, TSIH=non-zero existing, CID=new
+ * --> add new connection to MC/S session
+ * ISID=existing, TSIH=non-zero existing, CID=existing
+ * --> do connection reinstatement
+ *
+ * Session continuation uses this path as well
+ */
+ cmn_err(CE_NOTE, "login_sm_session_bind: add new "
+ "conn/sess continue");
+ if (existing_ict != NULL) {
+ /*
+ * ISID=existing, TSIH=non-zero existing,
+ * CID=existing --> do connection reinstatement
+ */
+ if (iscsit_conn_reinstate(existing_ict, ict) !=
+ IDM_STATUS_SUCCESS) {
+ /*
+ * Most likely this means the connection
+ * the initiator is trying to reinstate
+ * is not in an acceptable state.
+ */
+ SET_LOGIN_ERROR(ict,
+ ISCSI_STATUS_CLASS_INITIATOR_ERR,
+ ISCSI_LOGIN_STATUS_INIT_ERR);
+ goto session_bind_error;
+ }
+ }
+
+ iscsit_sess_sm_event(existing_sess, SE_CONN_IN_LOGIN,
+ ict);
+ }
+ }
+
+ if (tgt != NULL)
+ iscsit_tgt_rele(tgt);
+ if (existing_sess != NULL)
+ iscsit_sess_rele(existing_sess);
+ if (existing_ict != NULL)
+ iscsit_conn_rele(existing_ict);
+
+ return (IDM_STATUS_SUCCESS);
+
+session_bind_error:
+ if (tgt != NULL)
+ iscsit_tgt_rele(tgt);
+ if (existing_sess != NULL)
+ iscsit_sess_rele(existing_sess);
+ if (existing_ict != NULL)
+ iscsit_conn_rele(existing_ict);
+
+ /*
+ * If session bind fails we will fail the login but don't destroy
+ * the session until later.
+ */
+ return (IDM_STATUS_FAIL);
+}
+
+
+static idm_status_t
+login_sm_set_auth(iscsit_conn_t *ict)
+{
+ idm_status_t idmrc = IDM_STATUS_SUCCESS;
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsit_ini_t *ini;
+ iscsit_tgt_t *tgt;
+ char *auth = "";
+ char *radiusserver = "";
+ char *radiussecret = "";
+ char *chapuser = "";
+ char *chapsecret = "";
+ char *targetchapuser = "";
+ char *targetchapsecret = "";
+ char *targetalias = "";
+ int i;
+
+ ISCSIT_GLOBAL_LOCK(RW_READER);
+
+ /*
+ * Set authentication method to none for discovery session.
+ */
+ if (ict->ict_op.op_discovery_session == B_TRUE) {
+ lsm->icl_auth.ca_method_valid_list[0] = AM_NONE;
+ ISCSIT_GLOBAL_UNLOCK();
+ return (idmrc);
+ }
+
+ /*
+ * Get all the authentication parameters we need -- since we hold
+ * the global config lock we guarantee that the parameters will
+ * be consistent with each other.
+ */
+ (void) nvlist_lookup_string(iscsit_global.global_props,
+ PROP_AUTH, &auth);
+ (void) nvlist_lookup_string(iscsit_global.global_props,
+ PROP_RADIUS_SERVER, &radiusserver);
+ (void) nvlist_lookup_string(iscsit_global.global_props,
+ PROP_RADIUS_SECRET, &radiussecret);
+
+ ini = iscsit_ini_lookup_locked(lsm->icl_initiator_name);
+ if (ini != NULL) {
+ /* Get Initiator CHAP parameters */
+ (void) nvlist_lookup_string(ini->ini_props, PROP_CHAP_USER,
+ &chapuser);
+ (void) nvlist_lookup_string(ini->ini_props, PROP_CHAP_SECRET,
+ &chapsecret);
+ }
+
+ tgt = ict->ict_sess->ist_tgt;
+ if (tgt != NULL) {
+ /* See if we have a target-specific authentication setting */
+ (void) nvlist_lookup_string(tgt->target_props, PROP_AUTH,
+ &auth);
+ /* Get target CHAP parameters */
+ (void) nvlist_lookup_string(tgt->target_props,
+ PROP_TARGET_CHAP_USER, &targetchapuser);
+ (void) nvlist_lookup_string(tgt->target_props,
+ PROP_TARGET_CHAP_SECRET, &targetchapsecret);
+ /* Get alias */
+ (void) nvlist_lookup_string(tgt->target_props,
+ PROP_ALIAS, &targetalias);
+ }
+
+ /* Set authentication method */
+ i = 0;
+ if (strcmp(auth, PA_AUTH_RADIUS) == 0) {
+ /* CHAP authentication using RADIUS server */
+ lsm->icl_auth.ca_method_valid_list[i++] = AM_CHAP;
+ lsm->icl_auth.ca_use_radius = B_TRUE;
+ } else if (strcmp(auth, PA_AUTH_CHAP) == 0) {
+ /* Local CHAP authentication */
+ lsm->icl_auth.ca_method_valid_list[i++] = AM_CHAP;
+ lsm->icl_auth.ca_use_radius = B_FALSE;
+ } else if ((strcmp(auth, PA_AUTH_NONE) == 0) ||
+ (strcmp(auth, "") == 0)) {
+ /* No authentication */
+ lsm->icl_auth.ca_method_valid_list[i++] = AM_NONE;
+ }
+
+ /*
+ * If initiator/target CHAP username is not set then use the
+ * node name. If lsm->icl_target_name == NULL then this is
+ * a discovery session so we don't need to work about the target.
+ */
+ if (strcmp(chapuser, "") == 0) {
+ (void) strlcpy(lsm->icl_auth.ca_ini_chapuser,
+ lsm->icl_initiator_name,
+ min(iscsiAuthStringMaxLength, MAX_ISCSI_NODENAMELEN));
+ } else {
+ (void) strlcpy(lsm->icl_auth.ca_ini_chapuser, chapuser,
+ iscsiAuthStringMaxLength);
+ }
+ if ((lsm->icl_target_name != NULL) &&
+ (strcmp(targetchapuser, "") == 0)) {
+ (void) strlcpy(lsm->icl_auth.ca_tgt_chapuser,
+ lsm->icl_target_name,
+ min(iscsiAuthStringMaxLength, MAX_ISCSI_NODENAMELEN));
+ } else {
+ (void) strlcpy(lsm->icl_auth.ca_tgt_chapuser,
+ targetchapuser, iscsiAuthStringMaxLength);
+ }
+
+ /*
+ * Secrets are stored in base64-encoded format so we need to
+ * decode them into binary form
+ */
+ if (strcmp(chapsecret, "") == 0) {
+ lsm->icl_auth.ca_ini_chapsecretlen = 0;
+ } else {
+ if (iscsi_base64_str_to_binary(chapsecret,
+ strnlen(chapsecret, iscsiAuthStringMaxLength),
+ lsm->icl_auth.ca_ini_chapsecret, iscsiAuthStringMaxLength,
+ &lsm->icl_auth.ca_ini_chapsecretlen) != 0) {
+ cmn_err(CE_WARN, "Corrupted CHAP secret"
+ " for initiator %s", lsm->icl_initiator_name);
+ lsm->icl_auth.ca_ini_chapsecretlen = 0;
+ }
+ }
+ if (strcmp(targetchapsecret, "") == 0) {
+ lsm->icl_auth.ca_tgt_chapsecretlen = 0;
+ } else {
+ if (iscsi_base64_str_to_binary(targetchapsecret,
+ strnlen(targetchapsecret, iscsiAuthStringMaxLength),
+ lsm->icl_auth.ca_tgt_chapsecret, iscsiAuthStringMaxLength,
+ &lsm->icl_auth.ca_tgt_chapsecretlen) != 0) {
+ cmn_err(CE_WARN, "Corrupted CHAP secret"
+ " for target %s", lsm->icl_target_name);
+ lsm->icl_auth.ca_tgt_chapsecretlen = 0;
+ }
+ }
+ if (strcmp(radiussecret, "") == 0) {
+ lsm->icl_auth.ca_radius_secretlen = 0;
+ } else {
+ if (iscsi_base64_str_to_binary(radiussecret,
+ strnlen(radiussecret, iscsiAuthStringMaxLength),
+ lsm->icl_auth.ca_radius_secret, iscsiAuthStringMaxLength,
+ &lsm->icl_auth.ca_radius_secretlen) != 0) {
+ cmn_err(CE_WARN, "Corrupted RADIUS secret");
+ lsm->icl_auth.ca_radius_secretlen = 0;
+ }
+ }
+
+ /*
+ * Set alias
+ */
+ (void) strlcpy(lsm->icl_auth.ca_tgt_alias, targetalias,
+ MAX_ISCSI_NODENAMELEN);
+
+ /*
+ * Now that authentication parameters are setup, validate the parameters
+ * against the authentication mode
+ * Decode RADIUS server value int lsm->icl_auth.ca_radius_server
+ */
+ if ((strcmp(auth, PA_AUTH_RADIUS) == 0) &&
+ ((lsm->icl_auth.ca_radius_secretlen == 0) ||
+ (strcmp(radiusserver, "") == 0) ||
+ it_common_convert_sa(radiusserver,
+ &lsm->icl_auth.ca_radius_server,
+ DEFAULT_RADIUS_PORT) == NULL)) {
+ cmn_err(CE_WARN, "RADIUS authentication selected "
+ "for target %s but RADIUS parameters are not "
+ "configured.", lsm->icl_target_name);
+ SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_TARGET_ERR,
+ ISCSI_LOGIN_STATUS_TARGET_ERROR);
+ idmrc = IDM_STATUS_FAIL;
+ } else if ((strcmp(auth, PA_AUTH_CHAP) == 0) &&
+ (lsm->icl_auth.ca_ini_chapsecretlen == 0)) {
+ SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR,
+ ISCSI_LOGIN_STATUS_AUTH_FAILED);
+ idmrc = IDM_STATUS_FAIL;
+ }
+
+ ISCSIT_GLOBAL_UNLOCK();
+
+ return (idmrc);
+}
+
+
+static idm_status_t
+login_sm_session_register(iscsit_conn_t *ict)
+{
+ iscsit_sess_t *ist = ict->ict_sess;
+ stmf_scsi_session_t *ss;
+
+ /*
+ * Hold target mutex until we have finished registering with STMF
+ */
+ mutex_enter(&ist->ist_tgt->target_mutex);
+ if (ist->ist_tgt->target_state != TS_STMF_ONLINE) {
+ mutex_exit(&ist->ist_tgt->target_mutex);
+ SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_INITIATOR_ERR,
+ ISCSI_LOGIN_STATUS_TGT_REMOVED);
+ return (IDM_STATUS_FAIL);
+ }
+
+ ss = stmf_alloc(STMF_STRUCT_SCSI_SESSION, 0,
+ 0);
+ if (ss == NULL) {
+ mutex_exit(&ist->ist_tgt->target_mutex);
+ SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_TARGET_ERR,
+ ISCSI_LOGIN_STATUS_NO_RESOURCES);
+ return (IDM_STATUS_FAIL);
+ }
+
+ ss->ss_rport_id = kmem_zalloc(sizeof (scsi_devid_desc_t) +
+ strlen(ist->ist_initiator_name) + 1, KM_SLEEP);
+ (void) strcpy((char *)ss->ss_rport_id->ident, ist->ist_initiator_name);
+ ss->ss_rport_id->ident_length = strlen(ist->ist_initiator_name);
+ ss->ss_rport_id->protocol_id = PROTOCOL_iSCSI;
+ ss->ss_rport_id->piv = 1;
+ ss->ss_rport_id->code_set = CODE_SET_ASCII;
+ ss->ss_rport_id->association = ID_IS_TARGET_PORT;
+
+ ss->ss_lport = ist->ist_lport;
+
+ if (stmf_register_scsi_session(ict->ict_sess->ist_lport, ss) !=
+ STMF_SUCCESS) {
+ mutex_exit(&ist->ist_tgt->target_mutex);
+ kmem_free(ss->ss_rport_id,
+ sizeof (scsi_devid_desc_t) +
+ strlen(ist->ist_initiator_name) + 1);
+ stmf_free(ss);
+ SET_LOGIN_ERROR(ict, ISCSI_STATUS_CLASS_TARGET_ERR,
+ ISCSI_LOGIN_STATUS_TARGET_ERROR);
+ return (IDM_STATUS_FAIL);
+ }
+
+ ss->ss_port_private = ict->ict_sess;
+ ict->ict_sess->ist_stmf_sess = ss;
+ mutex_exit(&ist->ist_tgt->target_mutex);
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+
+static idm_status_t
+login_sm_req_pdu_check(iscsit_conn_t *ict, idm_pdu_t *pdu)
+{
+ uint8_t csg_req;
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsi_login_hdr_t *lh = (iscsi_login_hdr_t *)pdu->isp_hdr;
+ iscsi_login_rsp_hdr_t *lh_resp = lsm->icl_login_resp_tmpl;
+
+ /*
+ * Check CSG
+ */
+ csg_req = ISCSI_LOGIN_CURRENT_STAGE(lh->flags);
+ switch (csg_req) {
+ case ISCSI_SECURITY_NEGOTIATION_STAGE:
+ case ISCSI_OP_PARMS_NEGOTIATION_STAGE:
+ if ((csg_req != lsm->icl_login_csg) &&
+ (lsm->icl_login_state != ILS_LOGIN_INIT)) {
+ /*
+ * Inappropriate CSG change. Initiator can only
+ * change CSG after we've responded with the
+ * transit bit set. If we had responded with
+ * a CSG change previous we would have updated
+ * our copy of CSG.
+ *
+ * The exception is when we are in ILS_LOGIN_INIT
+ * state since we haven't determined our initial
+ * CSG value yet.
+ */
+ goto pdu_check_fail;
+ }
+ break;
+ case ISCSI_FULL_FEATURE_PHASE:
+ default:
+ goto pdu_check_fail;
+ }
+
+ /*
+ * If this is the first login PDU for a new connection then
+ * the session will be NULL.
+ */
+ if (ict->ict_sess != NULL) {
+ /*
+ * We've already created a session on a previous PDU. Make
+ * sure this PDU is consistent with what we've already seen
+ */
+ if ((ict->ict_cid != ntohs(lh->cid)) ||
+ (bcmp(ict->ict_sess->ist_isid, lh->isid,
+ ISCSI_ISID_LEN) != 0)) {
+ goto pdu_check_fail;
+ }
+ }
+
+ /*
+ * Make sure we are compatible with the version range
+ */
+#if (ISCSIT_MAX_VERSION > 0)
+ if ((lh->min_version > ISCSIT_MAX_VERSION) ||
+ (lh->max_version < ISCSIT_MIN_VERSION)) {
+ goto pdu_check_fail;
+ }
+#endif
+
+ /*
+ * Just in case the initiator changes things up on us along the way
+ * check against our active_version -- we can't change the active
+ * version and the initiator is not *supposed* to change its
+ * min_version and max_version values so this should never happen.
+ * Of course we only do this if the response header template has
+ * been built.
+ */
+ if ((lh_resp->opcode == ISCSI_OP_LOGIN_RSP) && /* header valid */
+ ((lh->min_version > lh_resp->active_version) ||
+ (lh->max_version < lh_resp->active_version))) {
+ goto pdu_check_fail;
+ }
+
+ return (IDM_STATUS_SUCCESS);
+
+pdu_check_fail:
+ return (IDM_STATUS_FAIL);
+}
+
+static idm_status_t
+login_sm_process_nvlist(iscsit_conn_t *ict)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ char *nvp_name;
+ nvpair_t *nvp;
+ nvpair_t *next_nvp;
+ nvpair_t *negotiated_nvp;
+ kv_status_t kvrc;
+ uint8_t error_class;
+ uint8_t error_detail;
+ idm_status_t idm_status;
+
+ error_class = ISCSI_STATUS_CLASS_SUCCESS;
+ error_detail = ISCSI_LOGIN_STATUS_ACCEPT;
+
+ /* First, request that the transport process the list */
+ kvrc = idm_negotiate_key_values(ict->ict_ic, lsm->icl_request_nvlist,
+ lsm->icl_response_nvlist, lsm->icl_negotiated_values);
+ idm_kvstat_to_error(kvrc, &error_class, &error_detail);
+ if (error_class != ISCSI_STATUS_CLASS_SUCCESS) {
+ SET_LOGIN_ERROR(ict, error_class, error_detail);
+ idm_status = IDM_STATUS_FAIL;
+ return (idm_status);
+ }
+
+ /* Ensure we clear transit bit if the transport layer has countered */
+ if (kvrc == KV_HANDLED_NO_TRANSIT) {
+ lsm->icl_login_transit = B_FALSE;
+ }
+
+ /* Now, move on and process the rest of the pairs */
+ nvp = nvlist_next_nvpair(lsm->icl_request_nvlist, NULL);
+ while (nvp != NULL) {
+ next_nvp = nvlist_next_nvpair(lsm->icl_request_nvlist, nvp);
+ nvp_name = nvpair_name(nvp);
+ /*
+ * If we've already agreed upon a value then make sure this
+ * is not attempting to change that value. From RFC3270
+ * section 5.3:
+ *
+ * "Neither the initiator nor the target should attempt to
+ * declare or negotiate a parameter more than once during
+ * login except for responses to specific keys that
+ * explicitly allow repeated key declarations (e.g.,
+ * TargetAddress). An attempt to renegotiate/redeclare
+ * parameters not specifically allowed MUST be detected
+ * by the initiator and target. If such an attempt is
+ * detected by the target, the target MUST respond
+ * with Login reject (initiator error); ..."
+ */
+ if (nvlist_lookup_nvpair(lsm->icl_negotiated_values,
+ nvp_name, &negotiated_nvp) == 0) {
+ kvrc = KV_HANDLED;
+ } else {
+ kvrc = iscsit_handle_key(ict, nvp, nvp_name);
+ }
+
+ idm_kvstat_to_error(kvrc, &error_class, &error_detail);
+ if (error_class != ISCSI_STATUS_CLASS_SUCCESS) {
+ break;
+ }
+
+ nvp = next_nvp;
+ }
+
+ if (error_class == ISCSI_STATUS_CLASS_SUCCESS) {
+ idm_status = IDM_STATUS_SUCCESS;
+ } else {
+ /* supply login class/detail for login errors */
+ SET_LOGIN_ERROR(ict, error_class, error_detail);
+ idm_status = IDM_STATUS_FAIL;
+ }
+
+ return (idm_status);
+}
+
+static idm_status_t
+login_sm_check_security(iscsit_conn_t *ict)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ conn_auth_t *auth = &lsm->icl_auth;
+ iscsit_auth_method_t *am_list = &auth->ca_method_valid_list[0];
+ kv_status_t kvrc;
+ uint8_t error_class;
+ uint8_t error_detail;
+ idm_status_t idm_status;
+
+ error_class = ISCSI_STATUS_CLASS_SUCCESS;
+ error_detail = ISCSI_LOGIN_STATUS_ACCEPT;
+
+ /* Check authentication status. */
+ if (lsm->icl_login_csg == ISCSI_SECURITY_NEGOTIATION_STAGE) {
+ /*
+ * We should have some authentication key/value pair(s)
+ * received from initiator and the authentication phase
+ * has been shifted when the key/value pair(s) are being
+ * handled in the previous call iscsit_handle_security_key.
+ * Now it turns to target to check the authentication phase
+ * and shift it after taking some authentication action.
+ */
+ kvrc = iscsit_reply_security_key(ict);
+ idm_kvstat_to_error(kvrc, &error_class, &error_detail);
+ } else if (!ict->ict_login_sm.icl_auth_pass) {
+ /*
+ * Check to see if the target allows initiators to bypass the
+ * security check. If the target is configured to require
+ * authentication, we reject the connection.
+ */
+ if (am_list[0] == AM_NONE || am_list[0] == 0) {
+ ict->ict_login_sm.icl_auth_pass = 1;
+ } else {
+ error_class = ISCSI_STATUS_CLASS_INITIATOR_ERR;
+ error_detail = ISCSI_LOGIN_STATUS_AUTH_FAILED;
+ }
+ }
+
+ if (error_class == ISCSI_STATUS_CLASS_SUCCESS) {
+ idm_status = IDM_STATUS_SUCCESS;
+ } else {
+ /* supply login class/detail for login errors */
+ SET_LOGIN_ERROR(ict, error_class, error_detail);
+ idm_status = IDM_STATUS_FAIL;
+ }
+
+ return (idm_status);
+}
+
+static void
+login_sm_build_login_response(iscsit_conn_t *ict)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsi_login_rsp_hdr_t *lh;
+ int transit, text_transit = 1;
+
+ /*
+ * 1. Convert response nvlist to an idm text buffer that holds
+ * response key-value pairs.
+ * 2. Build a PDU to transmit the first login response PDU
+ * 3. If there is more data, wait for an ack then goto step 2.
+ */
+ ASSERT(lsm->icl_login_resp != NULL);
+
+ if (lsm->icl_response_nvlist) {
+ if (lsm->icl_login_resp_itb == NULL) {
+ /* initialze the idm text buf to send pdus */
+ lsm->icl_login_resp_itb = idm_nvlist_to_itextbuf(
+ lsm->icl_response_nvlist);
+ if (lsm->icl_login_resp_itb == NULL) {
+ SET_LOGIN_ERROR(ict,
+ ISCSI_STATUS_CLASS_TARGET_ERR,
+ ISCSI_LOGIN_STATUS_NO_RESOURCES);
+ /* Still need to send the resp so continue */
+ } else {
+ lsm->icl_login_resp_buf =
+ idm_pdu_init_text_data(lsm->icl_login_resp,
+ lsm->icl_login_resp_itb,
+ ISCSI_DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH,
+ lsm->icl_login_resp_buf, &text_transit);
+ }
+ } else {
+ lsm->icl_login_resp_buf = idm_pdu_init_text_data(
+ lsm->icl_login_resp, lsm->icl_login_resp_itb,
+ ISCSI_DEFAULT_MAX_RECV_DATA_SEGMENT_LENGTH,
+ lsm->icl_login_resp_buf, &text_transit);
+ }
+ } else {
+ lsm->icl_login_resp->isp_data = NULL;
+ lsm->icl_login_resp->isp_datalen = 0;
+ }
+
+ /*
+ * Use the BHS header values from the response template
+ */
+ bcopy(lsm->icl_login_resp_tmpl,
+ lsm->icl_login_resp->isp_hdr, sizeof (iscsi_login_rsp_hdr_t));
+
+ lh = (iscsi_login_rsp_hdr_t *)lsm->icl_login_resp->isp_hdr;
+
+ /* Set error class/detail */
+ lh->status_class = lsm->icl_login_resp_err_class;
+ lh->status_detail = lsm->icl_login_resp_err_detail;
+ /* Set CSG, NSG and Transit */
+ lh->flags = 0;
+ lh->flags |= lsm->icl_login_csg << 2;
+
+
+ if (lh->status_class == ISCSI_STATUS_CLASS_SUCCESS) {
+ if (lsm->icl_login_transit &&
+ lsm->icl_auth_pass != 0) {
+ transit = 1;
+ } else {
+ transit = 0;
+ }
+ /*
+ * inititalize the text data
+ */
+ if (transit == 1 && text_transit == 1) {
+ lh->flags |= lsm->icl_login_nsg;
+ lsm->icl_login_csg = lsm->icl_login_nsg;
+ lh->flags |= ISCSI_FLAG_LOGIN_TRANSIT;
+ } else {
+ lh->flags &= ~ISCSI_FLAG_LOGIN_TRANSIT;
+ }
+
+ /* If we are transitioning to FFP then set TSIH */
+ if (transit && (lh->flags & ISCSI_FLAG_LOGIN_TRANSIT) &&
+ lsm->icl_login_csg == ISCSI_FULL_FEATURE_PHASE) {
+ lh->tsid = htons(ict->ict_sess->ist_tsih);
+ }
+ } else {
+ lsm->icl_login_resp->isp_data = 0;
+ lsm->icl_login_resp->isp_datalen = 0;
+ }
+}
+
+static kv_status_t
+iscsit_handle_key(iscsit_conn_t *ict, nvpair_t *nvp, char *nvp_name)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ kv_status_t kvrc;
+ const idm_kv_xlate_t *ikvx;
+
+ ikvx = idm_lookup_kv_xlate(nvp_name, strlen(nvp_name));
+ if (ikvx->ik_key_id == KI_MAX_KEY) {
+ /*
+ * Any key not understood by the acceptor may be igonred
+ * by the acceptor without affecting the basic function.
+ * However, the answer for a key not understood MUST be
+ * key=NotUnderstood.
+ */
+ kvrc = iscsit_reply_string(ict, nvp_name,
+ ISCSI_TEXT_NOTUNDERSTOOD);
+ } else {
+ kvrc = iscsit_handle_common_key(ict, nvp, ikvx);
+ if (kvrc == KV_UNHANDLED) {
+ switch (lsm->icl_login_csg) {
+ case ISCSI_SECURITY_NEGOTIATION_STAGE:
+ kvrc = iscsit_handle_security_key(
+ ict, nvp, ikvx);
+ break;
+ case ISCSI_OP_PARMS_NEGOTIATION_STAGE:
+ kvrc = iscsit_handle_operational_key(
+ ict, nvp, ikvx);
+ break;
+ case ISCSI_FULL_FEATURE_PHASE:
+ default:
+ /* What are we doing here? */
+ ASSERT(0);
+ kvrc = KV_UNHANDLED;
+ }
+ }
+ }
+
+ return (kvrc);
+}
+
+static kv_status_t
+iscsit_handle_common_key(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ kv_status_t kvrc;
+ char *string_val;
+ int nvrc;
+
+ switch (ikvx->ik_key_id) {
+ case KI_INITIATOR_NAME:
+ case KI_INITIATOR_ALIAS:
+ nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, nvp);
+ kvrc = idm_nvstat_to_kvstat(nvrc);
+ break;
+ case KI_TARGET_NAME:
+ /* We'll validate the target during login_sm_session_bind() */
+ nvrc = nvpair_value_string(nvp, &string_val);
+ ASSERT(nvrc == 0); /* We built this nvlist */
+
+ nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, nvp);
+ kvrc = idm_nvstat_to_kvstat(nvrc);
+ break;
+ case KI_TARGET_ALIAS:
+ case KI_TARGET_ADDRESS:
+ case KI_TARGET_PORTAL_GROUP_TAG:
+ kvrc = KV_TARGET_ONLY; /* Only the target can declare this */
+ break;
+ case KI_SESSION_TYPE:
+ /*
+ * If we don't receive this key on the initial login
+ * we assume this is a normal session.
+ */
+ nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values, nvp);
+ kvrc = idm_nvstat_to_kvstat(nvrc);
+ nvrc = nvpair_value_string(nvp, &string_val);
+ ASSERT(nvrc == 0); /* We built this nvlist */
+ ict->ict_op.op_discovery_session =
+ strcmp(string_val, "Discovery") == 0 ? B_TRUE : B_FALSE;
+ break;
+ default:
+ /*
+ * This is not really an error but we should
+ * leave this nvpair on the list since we
+ * didn't do anything with it. Either
+ * the security or operational phase
+ * handling functions should process it.
+ */
+ kvrc = KV_UNHANDLED;
+ break;
+ }
+
+ return (kvrc);
+}
+
+static kv_status_t
+iscsit_handle_security_key(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ iscsit_auth_client_t *client = &lsm->icl_auth_client;
+ iscsikey_id_t kv_id;
+ kv_status_t kvrc;
+ iscsit_auth_handler_t handler;
+
+ /*
+ * After all of security keys are handled, this function will
+ * be called again to verify current authentication status
+ * and perform some actual authentication work. At this time,
+ * the nvp and ikvx will be passed in as NULLs.
+ */
+ if (ikvx != NULL) {
+ kv_id = ikvx->ik_key_id;
+ } else {
+ kv_id = 0;
+ }
+
+ handler = iscsit_auth_get_handler(client, kv_id);
+ if (handler) {
+ kvrc = handler(ict, nvp, ikvx);
+ } else {
+ kvrc = KV_UNHANDLED; /* invalid request */
+ }
+
+ return (kvrc);
+}
+
+static kv_status_t
+iscsit_reply_security_key(iscsit_conn_t *ict)
+{
+ return (iscsit_handle_security_key(ict, NULL, NULL));
+}
+
+static kv_status_t
+iscsit_handle_operational_key(iscsit_conn_t *ict, nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx)
+{
+ kv_status_t kvrc = KV_UNHANDLED;
+ boolean_t bool_val;
+ uint64_t num_val;
+ int nvrc;
+
+ /*
+ * Retrieve values. All value lookups are expected to succeed
+ * since we build the nvlist while decoding the text buffer. This
+ * step is intended to eliminate some duplication of code (for example
+ * we only need to code the numerical value lookup once). We will
+ * handle the values (if necessary) below.
+ */
+ switch (ikvx->ik_key_id) {
+ /* Lists */
+ case KI_HEADER_DIGEST:
+ case KI_DATA_DIGEST:
+ break;
+ /* Booleans */
+ case KI_INITIAL_R2T:
+ case KI_IMMEDIATE_DATA:
+ case KI_DATA_PDU_IN_ORDER:
+ case KI_DATA_SEQUENCE_IN_ORDER:
+ case KI_IFMARKER:
+ case KI_OFMARKER:
+ nvrc = nvpair_value_boolean_value(nvp, &bool_val);
+ ASSERT(nvrc == 0); /* We built this nvlist */
+ break;
+ /* Numericals */
+ case KI_MAX_CONNECTIONS:
+ case KI_MAX_RECV_DATA_SEGMENT_LENGTH:
+ case KI_MAX_BURST_LENGTH:
+ case KI_FIRST_BURST_LENGTH:
+ case KI_DEFAULT_TIME_2_WAIT:
+ case KI_DEFAULT_TIME_2_RETAIN:
+ case KI_MAX_OUTSTANDING_R2T:
+ case KI_ERROR_RECOVERY_LEVEL:
+ nvrc = nvpair_value_uint64(nvp, &num_val);
+ ASSERT(nvrc == 0);
+ break;
+ /* Ranges */
+ case KI_OFMARKERINT:
+ case KI_IFMARKERINT:
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Now handle the values according to the key name. Sometimes we
+ * don't care what the value is -- in that case we just add the nvpair
+ * to the negotiated values list.
+ */
+ switch (ikvx->ik_key_id) {
+ case KI_HEADER_DIGEST:
+ kvrc = iscsit_handle_digest(ict, nvp, ikvx);
+ break;
+ case KI_DATA_DIGEST:
+ kvrc = iscsit_handle_digest(ict, nvp, ikvx);
+ break;
+ case KI_INITIAL_R2T:
+ /* We *require* INITIAL_R2T=yes */
+ kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx,
+ B_TRUE);
+ break;
+ case KI_IMMEDIATE_DATA:
+ /*
+ * For now we *require* IMMEDIATE_DATA=no.
+ */
+ kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx,
+ B_FALSE);
+ break;
+ case KI_DATA_PDU_IN_ORDER:
+ kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx,
+ B_TRUE);
+ break;
+ case KI_DATA_SEQUENCE_IN_ORDER:
+ /* We allow any value for DATA_SEQUENCE_IN_ORDER */
+ kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx,
+ bool_val);
+ break;
+ case KI_OFMARKER:
+ case KI_IFMARKER:
+ /* We don't support markers */
+ kvrc = iscsit_handle_boolean(ict, nvp, bool_val, ikvx,
+ B_FALSE);
+ break;
+ case KI_MAX_CONNECTIONS:
+ kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
+ ISCSI_MIN_CONNECTIONS,
+ ISCSI_MAX_CONNECTIONS,
+ ISCSIT_MAX_CONNECTIONS);
+ break;
+ case KI_MAX_RECV_DATA_SEGMENT_LENGTH:
+ kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
+ ISCSI_MIN_RECV_DATA_SEGMENT_LENGTH,
+ ISCSI_MAX_RECV_DATA_SEGMENT_LENGTH,
+ ISCSIT_MAX_RECV_DATA_SEGMENT_LENGTH);
+ break;
+ case KI_MAX_BURST_LENGTH:
+ kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
+ ISCSI_MIN_MAX_BURST_LENGTH,
+ ISCSI_MAX_BURST_LENGTH,
+ ISCSIT_MAX_BURST_LENGTH);
+ break;
+ case KI_FIRST_BURST_LENGTH:
+ kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
+ ISCSI_MIN_FIRST_BURST_LENGTH,
+ ISCSI_MAX_FIRST_BURST_LENGTH,
+ ISCSIT_MAX_FIRST_BURST_LENGTH);
+ break;
+ case KI_DEFAULT_TIME_2_WAIT:
+ kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
+ ISCSI_MIN_TIME2WAIT,
+ ISCSI_MAX_TIME2WAIT,
+ ISCSIT_MAX_TIME2WAIT);
+ break;
+ case KI_DEFAULT_TIME_2_RETAIN:
+ kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
+ ISCSI_MIN_TIME2RETAIN,
+ ISCSI_MAX_TIME2RETAIN,
+ ISCSIT_MAX_TIME2RETAIN);
+ break;
+ case KI_MAX_OUTSTANDING_R2T:
+ kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
+ ISCSI_MIN_MAX_OUTSTANDING_R2T,
+ ISCSI_MAX_OUTSTANDING_R2T,
+ ISCSIT_MAX_OUTSTANDING_R2T);
+ break;
+ case KI_ERROR_RECOVERY_LEVEL:
+ kvrc = iscsit_handle_numerical(ict, nvp, num_val, ikvx,
+ ISCSI_MIN_ERROR_RECOVERY_LEVEL,
+ ISCSI_MAX_ERROR_RECOVERY_LEVEL,
+ ISCSIT_MAX_ERROR_RECOVERY_LEVEL);
+ break;
+ case KI_OFMARKERINT:
+ case KI_IFMARKERINT:
+ kvrc = iscsit_reply_string(ict, ikvx->ik_key_name,
+ ISCSI_TEXT_IRRELEVANT);
+ break;
+ default:
+ kvrc = KV_UNHANDLED; /* invalid request */
+ break;
+ }
+
+ return (kvrc);
+}
+
+static kv_status_t
+iscsit_reply_numerical(iscsit_conn_t *ict,
+ const char *nvp_name, const uint64_t value)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ kv_status_t kvrc;
+ int nvrc;
+
+ nvrc = nvlist_add_uint64(lsm->icl_response_nvlist,
+ nvp_name, value);
+ kvrc = idm_nvstat_to_kvstat(nvrc);
+
+ return (kvrc);
+}
+
+static kv_status_t
+iscsit_reply_string(iscsit_conn_t *ict,
+ const char *nvp_name, const char *text)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ kv_status_t kvrc;
+ int nvrc;
+
+ nvrc = nvlist_add_string(lsm->icl_response_nvlist,
+ nvp_name, text);
+ kvrc = idm_nvstat_to_kvstat(nvrc);
+
+ return (kvrc);
+}
+
+static kv_status_t
+iscsit_handle_digest(iscsit_conn_t *ict, nvpair_t *choices,
+ const idm_kv_xlate_t *ikvx)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ kv_status_t kvrc = KV_VALUE_ERROR;
+ int nvrc;
+ nvpair_t *digest_choice;
+ char *digest_choice_string;
+
+ /*
+ * Need to add persistent config here if we want users to allow
+ * disabling of digests on the target side. You could argue that
+ * this makes things too complicated... just let the initiator state
+ * what it wants and we'll take it. For now that's exactly what
+ * we'll do.
+ *
+ * Basic digest negotiation happens here at iSCSI level. IDM
+ * can override this during negotiate_key_values phase to
+ * decline to set up any digest processing.
+ */
+ digest_choice = idm_get_next_listvalue(choices, NULL);
+
+ /*
+ * Loop through all choices. As soon as we find a choice
+ * that we support add the value to our negotiated values list
+ * and respond with that value in the login response.
+ */
+ while (digest_choice != NULL) {
+ nvrc = nvpair_value_string(digest_choice,
+ &digest_choice_string);
+ ASSERT(nvrc == 0);
+
+ if ((strcasecmp(digest_choice_string, "crc32c") == 0) ||
+ (strcasecmp(digest_choice_string, "none") == 0)) {
+ /* Add to negotiated values list */
+ nvrc = nvlist_add_string(lsm->icl_negotiated_values,
+ ikvx->ik_key_name, digest_choice_string);
+ kvrc = idm_nvstat_to_kvstat(nvrc);
+ if (nvrc == 0) {
+ /* Add to login response list */
+ nvrc = nvlist_add_string(
+ lsm->icl_response_nvlist,
+ ikvx->ik_key_name, digest_choice_string);
+ kvrc = idm_nvstat_to_kvstat(nvrc);
+ }
+ break;
+ }
+ digest_choice = idm_get_next_listvalue(choices,
+ digest_choice);
+ }
+
+ if (digest_choice == NULL)
+ kvrc = KV_VALUE_ERROR;
+
+ return (kvrc);
+}
+
+static kv_status_t
+iscsit_handle_boolean(iscsit_conn_t *ict, nvpair_t *nvp, boolean_t value,
+ const idm_kv_xlate_t *ikvx, boolean_t iscsit_value)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ kv_status_t kvrc;
+ int nvrc;
+
+ if (value != iscsit_value) {
+ /* Respond back to initiator with our value */
+ value = iscsit_value;
+ lsm->icl_login_transit = B_FALSE;
+ nvrc = 0;
+ } else {
+ /* Add this to our negotiated values */
+ nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values,
+ nvp);
+ }
+
+ /* Response of Simple-value Negotiation */
+ if (nvrc == 0 && !ikvx->ik_declarative) {
+ nvrc = nvlist_add_boolean_value(
+ lsm->icl_response_nvlist, ikvx->ik_key_name, value);
+ }
+ kvrc = idm_nvstat_to_kvstat(nvrc);
+
+ return (kvrc);
+}
+
+static kv_status_t
+iscsit_handle_numerical(iscsit_conn_t *ict, nvpair_t *nvp, uint64_t value,
+ const idm_kv_xlate_t *ikvx,
+ uint64_t iscsi_min_value, uint64_t iscsi_max_value,
+ uint64_t iscsit_max_value)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ kv_status_t kvrc;
+ int nvrc;
+
+ /* Validate against standard */
+ if ((value < iscsi_min_value) || (value > iscsi_max_value)) {
+ kvrc = KV_VALUE_ERROR;
+ } else {
+ if (value > iscsit_max_value) {
+ /* Respond back to initiator with our value */
+ value = iscsit_max_value;
+ lsm->icl_login_transit = B_FALSE;
+ nvrc = 0;
+ } else {
+ /* Add this to our negotiated values */
+ nvrc = nvlist_add_nvpair(lsm->icl_negotiated_values,
+ nvp);
+ }
+
+ /* Response of Simple-value Negotiation */
+ if (nvrc == 0 && !ikvx->ik_declarative) {
+ nvrc = nvlist_add_uint64(lsm->icl_response_nvlist,
+ ikvx->ik_key_name, value);
+ }
+ kvrc = idm_nvstat_to_kvstat(nvrc);
+ }
+
+ return (kvrc);
+}
+
+
+static void
+iscsit_process_negotiated_values(iscsit_conn_t *ict)
+{
+ iscsit_conn_login_t *lsm = &ict->ict_login_sm;
+ char *string_val;
+ boolean_t boolean_val;
+ uint64_t uint64_val;
+ int nvrc;
+ idm_status_t idmrc;
+
+ /* Let the IDM level activate its parameters first */
+ idmrc = idm_notice_key_values(ict->ict_ic, lsm->icl_negotiated_values);
+ ASSERT(idmrc == IDM_STATUS_SUCCESS);
+
+ /*
+ * Initiator alias and target alias
+ */
+ if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values,
+ "InitiatorAlias", &string_val)) != ENOENT) {
+ ASSERT(nvrc == 0);
+ ict->ict_sess->ist_initiator_alias =
+ kmem_alloc(strlen(string_val) + 1, KM_SLEEP);
+ (void) strcpy(ict->ict_sess->ist_initiator_alias, string_val);
+ }
+
+ if ((nvrc = nvlist_lookup_string(lsm->icl_negotiated_values,
+ "TargetAlias", &string_val)) != ENOENT) {
+ ASSERT(nvrc == 0);
+ ict->ict_sess->ist_target_alias =
+ kmem_alloc(strlen(string_val) + 1, KM_SLEEP);
+ (void) strcpy(ict->ict_sess->ist_target_alias, string_val);
+ }
+
+ /*
+ * Operational parameters. We process SessionType when it is
+ * initially received since it is required on the initial login.
+ */
+ if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values,
+ "InitialR2T", &boolean_val)) != ENOENT) {
+ ASSERT(nvrc == 0);
+ ict->ict_op.op_initial_r2t = boolean_val;
+ }
+
+ if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values,
+ "ImmediateData", &boolean_val)) != ENOENT) {
+ ASSERT(nvrc == 0);
+ ict->ict_op.op_immed_data = boolean_val;
+ }
+
+ if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values,
+ "DataPDUInOrder", &boolean_val)) != ENOENT) {
+ ASSERT(nvrc == 0);
+ ict->ict_op.op_data_pdu_in_order = boolean_val;
+ }
+
+ if ((nvrc = nvlist_lookup_boolean_value(lsm->icl_negotiated_values,
+ "DataSequenceInOrder", &boolean_val)) != ENOENT) {
+ ASSERT(nvrc == 0);
+ ict->ict_op.op_data_sequence_in_order = boolean_val;
+ }
+
+ if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
+ "MaxConnections", &uint64_val)) != ENOENT) {
+ ASSERT(nvrc == 0);
+ ict->ict_op.op_max_connections = uint64_val;
+ }
+
+ if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
+ "MaxRecvDataSegmentLength", &uint64_val)) != ENOENT) {
+ ASSERT(nvrc == 0);
+ ict->ict_op.op_max_recv_data_segment_length = uint64_val;
+ }
+
+ if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
+ "MaxBurstLength", &uint64_val)) != ENOENT) {
+ ASSERT(nvrc == 0);
+ ict->ict_op.op_max_burst_length = uint64_val;
+ }
+
+ if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
+ "FirstBurstLength", &uint64_val)) != ENOENT) {
+ ASSERT(nvrc == 0);
+ ict->ict_op.op_first_burst_length = uint64_val;
+ }
+
+ if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
+ "DefaultTime2Wait", &uint64_val)) != ENOENT) {
+ ASSERT(nvrc == 0);
+ ict->ict_op.op_default_time_2_wait = uint64_val;
+ }
+
+ if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
+ "DefaultTime2Retain", &uint64_val)) != ENOENT) {
+ ASSERT(nvrc == 0);
+ ict->ict_op.op_default_time_2_retain = uint64_val;
+ }
+
+ if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
+ "MaxOutstandingR2T", &uint64_val)) != ENOENT) {
+ ASSERT(nvrc == 0);
+ ict->ict_op.op_max_outstanding_r2t = uint64_val;
+ }
+
+ if ((nvrc = nvlist_lookup_uint64(lsm->icl_negotiated_values,
+ "ErrorRecoveryLevel", &uint64_val)) != ENOENT) {
+ ASSERT(nvrc == 0);
+ ict->ict_op.op_error_recovery_level = uint64_val;
+ }
+}
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiusauth.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiusauth.c
new file mode 100644
index 0000000000..ba515c2b59
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiusauth.c
@@ -0,0 +1,183 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/random.h>
+#include <sys/ddi.h>
+#include <sys/md5.h>
+
+#include <sys/iscsit/iscsi_if.h>
+#include <sys/idm/idm.h>
+#include <sys/idm/idm_so.h>
+#include <sys/iscsit/radius_packet.h>
+#include <sys/iscsit/radius_protocol.h>
+#include <radius_auth.h>
+
+/* Forward declaration */
+/*
+ * Annotate the radius_attr_t objects with authentication data.
+ */
+static
+void
+set_radius_attrs(radius_packet_data_t *req,
+ char *target_chap_name,
+ unsigned char *target_response,
+ uint32_t response_length,
+ uint8_t *challenge,
+ uint32_t challenge_length);
+
+/*
+ * See radius_auth.h.
+ */
+/* ARGSUSED */
+chap_validation_status_type
+iscsit_radius_chap_validate(char *target_chap_name,
+ char *initiator_chap_name,
+ uint8_t *challenge,
+ uint32_t challenge_length,
+ uint8_t *target_response,
+ uint32_t response_length,
+ uint8_t identifier,
+ iscsi_ipaddr_t rad_svr_ip_addr,
+ uint32_t rad_svr_port,
+ uint8_t *rad_svr_shared_secret,
+ uint32_t rad_svr_shared_secret_len)
+{
+ chap_validation_status_type validation_status;
+ char lbolt[64];
+ int rcv_status;
+ void *socket;
+ radius_packet_data_t req;
+ radius_packet_data_t resp;
+ MD5_CTX context;
+ uint8_t md5_digest[16]; /* MD5 digest length 16 */
+ uint8_t random_number[16];
+
+ if (rad_svr_shared_secret_len == 0) {
+ /* The secret must not be empty (section 3, RFC 2865) */
+ cmn_err(CE_WARN, "empty RADIUS shared secret");
+ return (CHAP_VALIDATION_BAD_RADIUS_SECRET);
+ }
+
+ bzero(&req, sizeof (radius_packet_data_t));
+
+ req.identifier = identifier;
+ req.code = RAD_ACCESS_REQ;
+ set_radius_attrs(&req,
+ target_chap_name,
+ target_response,
+ response_length,
+ challenge,
+ challenge_length);
+
+ /* Prepare the request authenticator */
+ MD5Init(&context);
+ bzero(&md5_digest, 16);
+ /* First, the shared secret */
+ MD5Update(&context, rad_svr_shared_secret, rad_svr_shared_secret_len);
+ /* Then a unique number - use lbolt plus a random number */
+ bzero(&lbolt, sizeof (lbolt));
+ (void) snprintf(lbolt, sizeof (lbolt), "%lx", ddi_get_lbolt());
+ MD5Update(&context, (uint8_t *)lbolt, strlen(lbolt));
+ bzero(&random_number, sizeof (random_number));
+ (void) random_get_pseudo_bytes(random_number, sizeof (random_number));
+ MD5Update(&context, random_number, sizeof (random_number));
+ MD5Final(md5_digest, &context);
+ bcopy(md5_digest, &req.authenticator, RAD_AUTHENTICATOR_LEN);
+
+ socket = idm_socreate(PF_INET, SOCK_DGRAM, 0);
+ if (socket == NULL) {
+ /* Error obtaining socket for RADIUS use */
+ return (CHAP_VALIDATION_RADIUS_ACCESS_ERROR);
+ }
+
+ /* Send the authentication access request to the RADIUS server */
+ if (iscsit_snd_radius_request(socket,
+ rad_svr_ip_addr,
+ rad_svr_port,
+ &req) != 0) {
+ idm_soshutdown(socket);
+ idm_sodestroy(socket);
+ return (CHAP_VALIDATION_RADIUS_ACCESS_ERROR);
+ }
+
+ bzero(&resp, sizeof (radius_packet_data_t));
+ /* Analyze the response coming through from the same socket. */
+ rcv_status = iscsit_rcv_radius_response(socket,
+ rad_svr_shared_secret,
+ rad_svr_shared_secret_len,
+ req.authenticator, &resp);
+ if (rcv_status == RAD_RSP_RCVD_SUCCESS) {
+ if (resp.code == RAD_ACCESS_ACPT) {
+ validation_status = CHAP_VALIDATION_PASSED;
+ } else if (resp.code == RAD_ACCESS_REJ) {
+ validation_status = CHAP_VALIDATION_INVALID_RESPONSE;
+ } else {
+ validation_status =
+ CHAP_VALIDATION_UNKNOWN_RADIUS_CODE;
+ }
+ } else if (rcv_status == RAD_RSP_RCVD_AUTH_FAILED) {
+ validation_status = CHAP_VALIDATION_BAD_RADIUS_SECRET;
+ } else {
+ validation_status = CHAP_VALIDATION_RADIUS_ACCESS_ERROR;
+ }
+
+ /* Done! Close the socket. */
+ idm_soshutdown(socket);
+ idm_sodestroy(socket);
+
+ return (validation_status);
+}
+
+/* See forward declaration. */
+static void
+set_radius_attrs(radius_packet_data_t *req,
+ char *target_chap_name,
+ unsigned char *target_response,
+ uint32_t response_length,
+ uint8_t *challenge,
+ uint32_t challenge_length)
+{
+ req->attrs[0].attr_type_code = RAD_USER_NAME;
+ (void) strncpy((char *)req->attrs[0].attr_value,
+ (const char *)target_chap_name,
+ strlen(target_chap_name));
+ req->attrs[0].attr_value_len = strlen(target_chap_name);
+
+ req->attrs[1].attr_type_code = RAD_CHAP_PASSWORD;
+ bcopy(target_response,
+ (char *)req->attrs[1].attr_value,
+ min(response_length, sizeof (req->attrs[1].attr_value)));
+ /* A target response is an MD5 hash thus its length has to be 16. */
+ req->attrs[1].attr_value_len = 16;
+
+ req->attrs[2].attr_type_code = RAD_CHAP_CHALLENGE;
+ bcopy(challenge,
+ (char *)req->attrs[2].attr_value,
+ min(challenge_length, sizeof (req->attrs[2].attr_value)));
+ req->attrs[2].attr_value_len = challenge_length;
+
+ /* 3 attributes associated with each RADIUS packet. */
+ req->num_of_attrs = 3;
+}
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiuspacket.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiuspacket.c
new file mode 100644
index 0000000000..2441e3b65c
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_radiuspacket.c
@@ -0,0 +1,390 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/iscsit/iscsi_if.h>
+#include <sys/md5.h>
+
+#include <sys/idm/idm.h>
+#include <sys/idm/idm_so.h>
+#include <sys/iscsit/radius_packet.h>
+#include <sys/iscsit/radius_protocol.h>
+
+static void encode_chap_password(int identifier, int chap_passwd_len,
+ uint8_t *chap_passwd, uint8_t *result);
+
+static size_t iscsit_net_recvmsg(void *socket, struct msghdr *msg,
+ int timeout);
+
+/*
+ * See radius_packet.h.
+ */
+int
+iscsit_snd_radius_request(void *socket, iscsi_ipaddr_t rsvr_ip_addr,
+ uint32_t rsvr_port, radius_packet_data_t *req_data)
+{
+ int i; /* Loop counter. */
+ int data_len;
+ int len;
+ ushort_t total_length; /* Has to be 2 octets in size */
+ uint8_t *ptr; /* Pointer to RADIUS packet data */
+ uint8_t *length_ptr; /* Points to the Length field of the */
+ /* packet. */
+ uint8_t *data; /* RADIUS data to be sent */
+ radius_attr_t *req_attr; /* Request attributes */
+ radius_packet_t *packet; /* Outbound RADIUS packet */
+ union {
+ struct sockaddr_in s_in4;
+ struct sockaddr_in6 s_in6;
+ } sa_rsvr; /* Socket address of the server */
+ int err;
+
+ /*
+ * Create a RADIUS packet with minimal length for now.
+ */
+ total_length = MIN_RAD_PACKET_LEN;
+ data = kmem_zalloc(MAX_RAD_PACKET_LEN, KM_SLEEP);
+ packet = (radius_packet_t *)data;
+ packet->code = req_data->code;
+ packet->identifier = req_data->identifier;
+ bcopy(req_data->authenticator, packet->authenticator,
+ RAD_AUTHENTICATOR_LEN);
+ ptr = packet->data;
+
+ /* Loop over all attributes of the request. */
+ for (i = 0; i < req_data->num_of_attrs; i++) {
+ if (total_length > MAX_RAD_PACKET_LEN) {
+ /* The packet has exceed its maximum size. */
+ kmem_free(data, MAX_RAD_PACKET_LEN);
+ return (-1);
+ }
+
+ req_attr = &req_data->attrs[i];
+ *ptr++ = (req_attr->attr_type_code & 0xFF);
+ length_ptr = ptr;
+ /* Length is 2 octets - RFC 2865 section 3 */
+ *ptr++ = 2;
+ total_length += 2;
+
+ /* If the attribute is CHAP-Password, encode it. */
+ if (req_attr->attr_type_code == RAD_CHAP_PASSWORD) {
+ /*
+ * Identifier plus CHAP response. RFC 2865
+ * section 5.3.
+ */
+ uint8_t encoded_chap_passwd[
+ RAD_CHAP_PASSWD_STR_LEN + RAD_IDENTIFIER_LEN + 1];
+ encode_chap_password(
+ req_data->identifier,
+ req_attr->attr_value_len,
+ req_attr->attr_value,
+ encoded_chap_passwd);
+
+ req_attr->attr_value_len =
+ RAD_CHAP_PASSWD_STR_LEN + RAD_IDENTIFIER_LEN;
+
+ bcopy(encoded_chap_passwd,
+ req_attr->attr_value,
+ req_attr->attr_value_len);
+ }
+
+ len = req_attr->attr_value_len;
+ *length_ptr += len;
+
+ bcopy(req_attr->attr_value, ptr, req_attr->attr_value_len);
+ ptr += req_attr->attr_value_len;
+
+ total_length += len;
+ } /* Done looping over all attributes */
+
+ data_len = total_length;
+ total_length = htons(total_length);
+ bcopy(&total_length, packet->length, sizeof (ushort_t));
+
+ /*
+ * Send the packet to the RADIUS server.
+ */
+ bzero((char *)&sa_rsvr, sizeof (sa_rsvr));
+ if (rsvr_ip_addr.i_insize == sizeof (in_addr_t)) {
+
+ /* IPv4 */
+ sa_rsvr.s_in4.sin_family = AF_INET;
+ sa_rsvr.s_in4.sin_addr.s_addr =
+ rsvr_ip_addr.i_addr.in4.s_addr;
+ sa_rsvr.s_in4.sin_port = htons((ushort_t)rsvr_port);
+
+ err = idm_sosendto(socket, data, data_len,
+ (struct sockaddr *)&sa_rsvr.s_in4,
+ sizeof (struct sockaddr_in));
+ kmem_free(data, MAX_RAD_PACKET_LEN);
+ return (err);
+ } else if (rsvr_ip_addr.i_insize == sizeof (in6_addr_t)) {
+ /* IPv6 */
+ sa_rsvr.s_in6.sin6_family = AF_INET6;
+ bcopy(rsvr_ip_addr.i_addr.in6.s6_addr,
+ sa_rsvr.s_in6.sin6_addr.s6_addr, sizeof (struct in6_addr));
+ sa_rsvr.s_in6.sin6_port = htons((ushort_t)rsvr_port);
+
+ err = idm_sosendto(socket, data, data_len,
+ (struct sockaddr *)&sa_rsvr.s_in6,
+ sizeof (struct sockaddr_in6));
+ kmem_free(data, MAX_RAD_PACKET_LEN);
+ return (err);
+ } else {
+ /* Invalid IP address for RADIUS server. */
+ kmem_free(data, MAX_RAD_PACKET_LEN);
+ return (-1);
+ }
+}
+
+/*
+ * See radius_packet.h.
+ */
+int
+iscsit_rcv_radius_response(void *socket, uint8_t *shared_secret,
+ uint32_t shared_secret_len, uint8_t *req_authenticator,
+ radius_packet_data_t *resp_data)
+{
+ radius_packet_t *packet;
+ MD5_CTX context;
+ uint8_t *tmp_data;
+ uint8_t md5_digest[16]; /* MD5 Digest Length 16 */
+ uint16_t declared_len = 0;
+ size_t received_len = 0;
+
+ struct iovec iov[1];
+ struct nmsghdr msg;
+ struct sonode *so = (struct sonode *)socket;
+ int ret = 0;
+
+ tmp_data = kmem_zalloc(MAX_RAD_PACKET_LEN, KM_SLEEP);
+ iov[0].iov_base = (char *)tmp_data;
+ iov[0].iov_len = MAX_RAD_PACKET_LEN;
+
+ bzero(&msg, sizeof (msg));
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_control = NULL;
+ msg.msg_controllen = 0;
+ msg.msg_flags = MSG_WAITALL;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ (void) VOP_IOCTL(SOTOV(so), I_POP, 0, FKIOCTL, CRED(), &ret, NULL);
+ if (ret != 0) {
+ return (RAD_RSP_RCVD_NO_DATA);
+ }
+
+ received_len = iscsit_net_recvmsg(socket, &msg, RAD_RCV_TIMEOUT);
+
+ if (received_len <= (size_t)0) {
+ kmem_free(tmp_data, MAX_RAD_PACKET_LEN);
+ return (RAD_RSP_RCVD_NO_DATA);
+ }
+
+ /*
+ * Check if the received packet length is within allowable range.
+ * RFC 2865 section 3.
+ */
+ if (received_len < MIN_RAD_PACKET_LEN) {
+ kmem_free(tmp_data, MAX_RAD_PACKET_LEN);
+ return (RAD_RSP_RCVD_PROTOCOL_ERR);
+ } else if (received_len > MAX_RAD_PACKET_LEN) {
+ kmem_free(tmp_data, MAX_RAD_PACKET_LEN);
+ return (RAD_RSP_RCVD_PROTOCOL_ERR);
+ }
+
+ packet = (radius_packet_t *)tmp_data;
+ bcopy(packet->length, &declared_len, sizeof (ushort_t));
+ declared_len = ntohs(declared_len);
+
+ /*
+ * Discard packet with received length shorter than declared
+ * length. RFC 2865 section 3.
+ */
+ if (received_len < declared_len) {
+ kmem_free(tmp_data, MAX_RAD_PACKET_LEN);
+ return (RAD_RSP_RCVD_PROTOCOL_ERR);
+ }
+
+ /*
+ * Check if the declared packet length is within allowable range.
+ * RFC 2865 section 3.
+ */
+ if (declared_len < MIN_RAD_PACKET_LEN) {
+ kmem_free(tmp_data, MAX_RAD_PACKET_LEN);
+ return (RAD_RSP_RCVD_PROTOCOL_ERR);
+ } else if (declared_len > MAX_RAD_PACKET_LEN) {
+ kmem_free(tmp_data, MAX_RAD_PACKET_LEN);
+ return (RAD_RSP_RCVD_PROTOCOL_ERR);
+ }
+
+ /*
+ * Authenticate the incoming packet, using the following algorithm
+ * (RFC 2865 section 3):
+ *
+ * MD5(Code+ID+Length+RequestAuth+Attributes+Secret)
+ *
+ * Code = RADIUS packet code
+ * ID = RADIUS packet identifier
+ * Length = Declared length of the packet
+ * RequestAuth = The request authenticator
+ * Attributes = The response attributes
+ * Secret = The shared secret
+ */
+ MD5Init(&context);
+ bzero(&md5_digest, 16);
+ MD5Update(&context, &packet->code, 1);
+ MD5Update(&context, &packet->identifier, 1);
+ MD5Update(&context, packet->length, 2);
+ MD5Update(&context, req_authenticator, RAD_AUTHENTICATOR_LEN);
+
+ /*
+ * Include response attributes only if there is a payload
+ * If the received length is greater than the declared length,
+ * trust the declared length and shorten the packet (i.e., to
+ * treat the octets outside the range of the Length field as
+ * padding - RFC 2865 section 3).
+ */
+ if (declared_len > RAD_PACKET_HDR_LEN) {
+ /* Response Attributes */
+ MD5Update(&context, packet->data,
+ declared_len - RAD_PACKET_HDR_LEN);
+ }
+ MD5Update(&context, shared_secret, shared_secret_len);
+ MD5Final(md5_digest, &context);
+
+ if (bcmp(md5_digest, packet->authenticator, RAD_AUTHENTICATOR_LEN)
+ != 0) {
+ kmem_free(tmp_data, MAX_RAD_PACKET_LEN);
+ return (RAD_RSP_RCVD_AUTH_FAILED);
+ }
+
+ /*
+ * Annotate the RADIUS packet data with the data we received from
+ * the server.
+ */
+ resp_data->code = packet->code;
+ resp_data->identifier = packet->identifier;
+
+ kmem_free(tmp_data, MAX_RAD_PACKET_LEN);
+ return (RAD_RSP_RCVD_SUCCESS);
+}
+
+/*
+ * encode_chap_password -
+ *
+ * Encode a CHAP-Password attribute. This function basically prepends
+ * the identifier in front of chap_passwd and copy the results to
+ * *result.
+ */
+static void
+encode_chap_password(int identifier, int chap_passwd_len,
+ uint8_t *chap_passwd, uint8_t *result)
+{
+ result[0] = (uint8_t)identifier;
+ bcopy(chap_passwd, &result[1], chap_passwd_len);
+}
+/*
+ * iscsi_net_recvmsg - receive message on socket
+ */
+/* ARGSUSED */
+static size_t
+iscsit_net_recvmsg(void *socket, struct msghdr *msg, int timeout)
+{
+ int idx;
+ int total_len = 0;
+ struct uio uio;
+ uchar_t pri = 0;
+ int prflag = MSG_ANY;
+ rval_t rval;
+ struct sonode *sonode = (struct sonode *)socket;
+
+ /* Initialization of the uio structure. */
+ bzero(&uio, sizeof (uio));
+ uio.uio_iov = msg->msg_iov;
+ uio.uio_iovcnt = msg->msg_iovlen;
+ uio.uio_segflg = UIO_SYSSPACE;
+
+ for (idx = 0; idx < msg->msg_iovlen; idx++) {
+ total_len += (msg->msg_iov)[idx].iov_len;
+ }
+ uio.uio_resid = total_len;
+
+ /* If timeout requested on receive */
+ if (timeout > 0) {
+ boolean_t loopback = B_FALSE;
+ /* And this isn't a loopback connection */
+ if (sonode->so_laddr.soa_sa->sa_family == AF_INET) {
+ struct sockaddr_in *lin = (struct sockaddr_in *)
+ ((void *)sonode->so_laddr.soa_sa);
+ struct sockaddr_in *fin = (struct sockaddr_in *)
+ ((void *)sonode->so_faddr.soa_sa);
+
+ if ((lin->sin_family == fin->sin_family) &&
+ (bcmp(&lin->sin_addr, &fin->sin_addr,
+ sizeof (struct in_addr)) == 0)) {
+ loopback = B_TRUE;
+ }
+ } else {
+ struct sockaddr_in6 *lin6 = (struct sockaddr_in6 *)
+ ((void *)sonode->so_laddr.soa_sa);
+ struct sockaddr_in6 *fin6 = (struct sockaddr_in6 *)
+ ((void *)sonode->so_faddr.soa_sa);
+
+ if ((lin6->sin6_family == fin6->sin6_family) &&
+ (bcmp(&lin6->sin6_addr, &fin6->sin6_addr,
+ sizeof (struct in6_addr)) == 0)) {
+ loopback = B_TRUE;
+ }
+ }
+
+ if (loopback == B_FALSE) {
+ /*
+ * Then poll device for up to the timeout
+ * period or the requested data is received.
+ */
+ if (kstrgetmsg(SOTOV(sonode),
+ NULL, NULL, &pri, &prflag, timeout * 1000,
+ &rval) == ETIME) {
+ return (0);
+ }
+ }
+ }
+
+ /*
+ * Receive the requested data. Block until all
+ * data is received.
+ *
+ * resid occurs only when the connection is
+ * disconnected. In that case it will return
+ * the amount of data that was not received.
+ * In general this is the total amount we
+ * requested.
+ */
+ (void) sorecvmsg((struct sonode *)socket, msg, &uio);
+ return (total_len - uio.uio_resid);
+}
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_sess.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_sess.c
new file mode 100644
index 0000000000..0842c49df3
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_sess.c
@@ -0,0 +1,792 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/cpuvar.h>
+#include <sys/types.h>
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/modctl.h>
+#include <sys/sysmacros.h>
+
+#include <sys/socket.h>
+#include <sys/strsubr.h>
+#include <sys/note.h>
+#include <sys/sdt.h>
+
+#include <sys/stmf.h>
+#include <sys/stmf_ioctl.h>
+#include <sys/portif.h>
+#include <sys/idm/idm.h>
+
+#define ISCSIT_SESS_SM_STRINGS
+#include <iscsit.h>
+
+
+
+typedef struct {
+ list_node_t se_ctx_node;
+ iscsit_session_event_t se_ctx_event;
+ iscsit_conn_t *se_event_data;
+} sess_event_ctx_t;
+
+static void
+sess_sm_event_locked(iscsit_sess_t *ist, iscsit_session_event_t event,
+iscsit_conn_t *ict);
+
+static void
+sess_sm_event_dispatch(iscsit_sess_t *ist, sess_event_ctx_t *ctx);
+
+static void
+sess_sm_q1_free(iscsit_sess_t *ist, sess_event_ctx_t *ctx);
+
+static void
+sess_sm_q2_active(iscsit_sess_t *ist, sess_event_ctx_t *ctx);
+
+static void
+sess_sm_q3_logged_in(iscsit_sess_t *ist, sess_event_ctx_t *ctx);
+
+static void
+sess_sm_q4_failed(iscsit_sess_t *ist, sess_event_ctx_t *ctx);
+
+static void
+sess_sm_q5_continue(iscsit_sess_t *ist, sess_event_ctx_t *ctx);
+
+static void
+sess_sm_q6_done(iscsit_sess_t *ist, sess_event_ctx_t *ctx);
+
+static void
+sess_sm_q7_error(iscsit_sess_t *ist, sess_event_ctx_t *ctx);
+
+static void
+sess_sm_new_state(iscsit_sess_t *ist, sess_event_ctx_t *ctx,
+ iscsit_session_state_t new_state);
+
+
+static uint16_t
+iscsit_tsih_alloc(void)
+{
+ uintptr_t result;
+
+ result = (uintptr_t)vmem_alloc(iscsit_global.global_tsih_pool,
+ 1, VM_NOSLEEP | VM_NEXTFIT);
+
+ /* ISCSI_UNSPEC_TSIH (0) indicates failure */
+ if (result > ISCSI_MAX_TSIH) {
+ vmem_free(iscsit_global.global_tsih_pool, (void *)result, 1);
+ result = ISCSI_UNSPEC_TSIH;
+ }
+
+ return ((uint16_t)result);
+}
+
+static void
+iscsit_tsih_free(uint16_t tsih)
+{
+ vmem_free(iscsit_global.global_tsih_pool, (void *)(uintptr_t)tsih, 1);
+}
+
+
+iscsit_sess_t *
+iscsit_sess_create(iscsit_tgt_t *tgt, iscsit_conn_t *ict,
+ uint32_t cmdsn, uint8_t *isid, uint16_t tag,
+ char *initiator_name, char *target_name,
+ uint8_t *error_class, uint8_t *error_detail)
+{
+ iscsit_sess_t *result;
+
+
+ /*
+ * Even if this session create "fails" for some reason we still need
+ * to return a valid session pointer so that we can send the failed
+ * login response.
+ */
+ result = kmem_zalloc(sizeof (*result), KM_SLEEP);
+
+ /* Allocate TSIH */
+ if ((result->ist_tsih = iscsit_tsih_alloc()) == ISCSI_UNSPEC_TSIH) {
+ /* Out of TSIH's */
+ *error_class = ISCSI_STATUS_CLASS_TARGET_ERR;
+ *error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES;
+ /*
+ * Continue initializing this session so we can use it
+ * to complete the login process.
+ */
+ }
+
+ idm_sm_audit_init(&result->ist_state_audit);
+ rw_init(&result->ist_sn_rwlock, NULL, RW_DRIVER, NULL);
+ mutex_init(&result->ist_mutex, NULL, MUTEX_DEFAULT, NULL);
+ cv_init(&result->ist_cv, NULL, CV_DEFAULT, NULL);
+ list_create(&result->ist_events, sizeof (sess_event_ctx_t),
+ offsetof(sess_event_ctx_t, se_ctx_node));
+ list_create(&result->ist_conn_list, sizeof (iscsit_conn_t),
+ offsetof(iscsit_conn_t, ict_sess_ln));
+
+ result->ist_state = SS_Q1_FREE;
+ result->ist_last_state = SS_Q1_FREE;
+ bcopy(isid, result->ist_isid, ISCSI_ISID_LEN);
+ result->ist_tpgt_tag = tag;
+
+ result->ist_tgt = tgt;
+ result->ist_expcmdsn = cmdsn + 1;
+ result->ist_maxcmdsn = result->ist_expcmdsn + 1;
+
+ result->ist_initiator_name =
+ kmem_alloc(strlen(initiator_name) + 1, KM_SLEEP);
+ (void) strcpy(result->ist_initiator_name, initiator_name);
+ if (target_name) {
+ /* A discovery session might not have a target name */
+ result->ist_target_name =
+ kmem_alloc(strlen(target_name) + 1, KM_SLEEP);
+ (void) strcpy(result->ist_target_name, target_name);
+ }
+ idm_refcnt_init(&result->ist_refcnt, result);
+
+ /* Login code will fill in ist_stmf_sess if necessary */
+
+ /* Kick session state machine (also binds connection to session) */
+ iscsit_sess_sm_event(result, SE_CONN_IN_LOGIN, ict);
+
+ *error_class = ISCSI_STATUS_CLASS_SUCCESS;
+ /*
+ * As noted above we must return a session pointer even if something
+ * failed. The resources will get freed later.
+ */
+ return (result);
+}
+
+static void
+iscsit_sess_unref(void *ist_void)
+{
+ iscsit_sess_t *ist = ist_void;
+
+ /*
+ * State machine has run to completion, destroy session
+ *
+ * If we have an associated STMF session we should clean it
+ * up now.
+ *
+ * This session is no longer associated with a target at this
+ * point so don't touch the target.
+ */
+ mutex_enter(&ist->ist_mutex);
+ ASSERT(ist->ist_conn_count == 0);
+ if (ist->ist_stmf_sess != NULL) {
+ stmf_deregister_scsi_session(ist->ist_lport,
+ ist->ist_stmf_sess);
+ kmem_free(ist->ist_stmf_sess->ss_rport_id,
+ sizeof (scsi_devid_desc_t) +
+ strlen(ist->ist_initiator_name) + 1);
+ stmf_free(ist->ist_stmf_sess);
+ }
+ mutex_exit(&ist->ist_mutex);
+
+ iscsit_sess_destroy(ist);
+}
+
+void
+iscsit_sess_destroy(iscsit_sess_t *ist)
+{
+ idm_refcnt_destroy(&ist->ist_refcnt);
+ if (ist->ist_initiator_name)
+ kmem_free(ist->ist_initiator_name,
+ strlen(ist->ist_initiator_name) + 1);
+ if (ist->ist_initiator_alias)
+ kmem_free(ist->ist_initiator_alias,
+ strlen(ist->ist_initiator_alias) + 1);
+ if (ist->ist_target_name)
+ kmem_free(ist->ist_target_name,
+ strlen(ist->ist_target_name) + 1);
+ if (ist->ist_target_alias)
+ kmem_free(ist->ist_target_alias,
+ strlen(ist->ist_target_alias) + 1);
+ list_destroy(&ist->ist_conn_list);
+ list_destroy(&ist->ist_events);
+ cv_destroy(&ist->ist_cv);
+ mutex_destroy(&ist->ist_mutex);
+ rw_destroy(&ist->ist_sn_rwlock);
+ kmem_free(ist, sizeof (*ist));
+}
+
+void
+iscsit_sess_close(iscsit_sess_t *ist)
+{
+ iscsit_conn_t *ict;
+
+ mutex_enter(&ist->ist_mutex);
+ /*
+ * Note in the session state that we are forcing this session
+ * to close so that the session state machine can avoid
+ * pointless delays like transitions to SS_Q4_FAILED state.
+ */
+ ist->ist_admin_close = B_TRUE;
+ if (ist->ist_state == SS_Q3_LOGGED_IN) {
+ for (ict = list_head(&ist->ist_conn_list);
+ ict != NULL;
+ ict = list_next(&ist->ist_conn_list, ict)) {
+ iscsit_send_async_event(ict,
+ ISCSI_ASYNC_EVENT_REQUEST_LOGOUT);
+ }
+ }
+ mutex_exit(&ist->ist_mutex);
+}
+
+
+void
+iscsit_sess_bind_conn(iscsit_sess_t *ist, iscsit_conn_t *ict)
+{
+ iscsit_conn_hold(ict);
+ iscsit_sess_hold(ist);
+ ict->ict_sess = ist;
+ mutex_enter(&ist->ist_mutex);
+ ist->ist_conn_count++;
+ list_insert_tail(&ist->ist_conn_list, ict);
+ mutex_exit(&ist->ist_mutex);
+}
+
+void
+iscsit_sess_unbind_conn(iscsit_sess_t *ist, iscsit_conn_t *ict)
+{
+ mutex_enter(&ist->ist_mutex);
+ list_remove(&ist->ist_conn_list, ict);
+ ist->ist_conn_count--;
+ mutex_exit(&ist->ist_mutex);
+ iscsit_sess_rele(ist);
+ iscsit_conn_rele(ict);
+}
+
+void
+iscsit_sess_hold(iscsit_sess_t *ist)
+{
+ idm_refcnt_hold(&ist->ist_refcnt);
+}
+
+void
+iscsit_sess_rele(iscsit_sess_t *ist)
+{
+ idm_refcnt_rele(&ist->ist_refcnt);
+}
+
+iscsit_conn_t *
+iscsit_sess_lookup_conn(iscsit_sess_t *ist, uint16_t cid)
+{
+ iscsit_conn_t *result;
+
+ mutex_enter(&ist->ist_mutex);
+ for (result = list_head(&ist->ist_conn_list);
+ result != NULL;
+ result = list_next(&ist->ist_conn_list, result)) {
+ if (result->ict_cid == cid) {
+ iscsit_conn_hold(result);
+ mutex_exit(&ist->ist_mutex);
+ return (result);
+ }
+ }
+ mutex_exit(&ist->ist_mutex);
+
+ return (NULL);
+}
+
+iscsit_sess_t *
+iscsit_sess_reinstate(iscsit_tgt_t *tgt, iscsit_sess_t *ist, iscsit_conn_t *ict,
+ uint8_t *error_class, uint8_t *error_detail)
+{
+ iscsit_sess_t *new_sess;
+
+ mutex_enter(&ist->ist_mutex);
+
+ /*
+ * Session reinstatement replaces a current session with a new session.
+ * The new session will have the same ISID as the existing session.
+ */
+ new_sess = iscsit_sess_create(tgt, ict, 0,
+ ist->ist_isid, ist->ist_tpgt_tag,
+ ist->ist_initiator_name, ist->ist_target_name,
+ error_class, error_detail);
+ ASSERT(new_sess != NULL);
+
+ /* Copy additional fields from original session */
+ new_sess->ist_expcmdsn = ist->ist_expcmdsn;
+ new_sess->ist_maxcmdsn = ist->ist_expcmdsn + 1;
+
+ if (ist->ist_state != SS_Q6_DONE &&
+ ist->ist_state != SS_Q7_ERROR) {
+ /*
+ * Generate reinstate event
+ */
+ sess_sm_event_locked(ist, SE_SESSION_REINSTATE, NULL);
+ }
+ mutex_exit(&ist->ist_mutex);
+
+ return (new_sess);
+}
+
+int
+iscsit_sess_avl_compare(const void *void_sess1, const void *void_sess2)
+{
+ const iscsit_sess_t *sess1 = void_sess1;
+ const iscsit_sess_t *sess2 = void_sess2;
+ int result;
+
+ /*
+ * Sort by initiator name, then ISID then portal group tag
+ */
+ result = strcmp(sess1->ist_initiator_name, sess2->ist_initiator_name);
+ if (result < 0) {
+ return (-1);
+ } else if (result > 0) {
+ return (1);
+ }
+
+ /*
+ * Initiator names match, compare ISIDs
+ */
+ result = memcmp(sess1->ist_isid, sess2->ist_isid, ISCSI_ISID_LEN);
+ if (result < 0) {
+ return (-1);
+ } else if (result > 0) {
+ return (1);
+ }
+
+ /*
+ * ISIDs match, compare portal group tags
+ */
+ if (sess1->ist_tpgt_tag < sess2->ist_tpgt_tag) {
+ return (-1);
+ } else if (sess1->ist_tpgt_tag > sess2->ist_tpgt_tag) {
+ return (1);
+ }
+
+ /*
+ * Portal group tags match, compare TSIHs
+ */
+ if (sess1->ist_tsih < sess2->ist_tsih) {
+ return (-1);
+ } else if (sess1->ist_tsih > sess2->ist_tsih) {
+ return (1);
+ }
+
+ /*
+ * Sessions match
+ */
+ return (0);
+}
+
+
+/*
+ * State machine
+ */
+
+void
+iscsit_sess_sm_event(iscsit_sess_t *ist, iscsit_session_event_t event,
+ iscsit_conn_t *ict)
+{
+ mutex_enter(&ist->ist_mutex);
+ sess_sm_event_locked(ist, event, ict);
+ mutex_exit(&ist->ist_mutex);
+}
+
+static void
+sess_sm_event_locked(iscsit_sess_t *ist, iscsit_session_event_t event,
+ iscsit_conn_t *ict)
+{
+ sess_event_ctx_t *ctx;
+
+ iscsit_sess_hold(ist);
+
+ ctx = kmem_zalloc(sizeof (*ctx), KM_SLEEP);
+
+ ctx->se_ctx_event = event;
+ ctx->se_event_data = ict;
+
+ list_insert_tail(&ist->ist_events, ctx);
+ /*
+ * Use the icl_busy flag to keep the state machine single threaded.
+ * This also serves as recursion avoidance since this flag will
+ * always be set if we call login_sm_event from within the
+ * state machine code.
+ */
+ if (!ist->ist_sm_busy) {
+ ist->ist_sm_busy = B_TRUE;
+ while (!list_is_empty(&ist->ist_events)) {
+ ctx = list_head(&ist->ist_events);
+ list_remove(&ist->ist_events, ctx);
+ idm_sm_audit_event(&ist->ist_state_audit,
+ SAS_ISCSIT_SESS, (int)ist->ist_state,
+ (int)ctx->se_ctx_event, (uintptr_t)ict);
+ mutex_exit(&ist->ist_mutex);
+ sess_sm_event_dispatch(ist, ctx);
+ mutex_enter(&ist->ist_mutex);
+ }
+ ist->ist_sm_busy = B_FALSE;
+
+ }
+
+ iscsit_sess_rele(ist);
+}
+
+static void
+sess_sm_event_dispatch(iscsit_sess_t *ist, sess_event_ctx_t *ctx)
+{
+ iscsit_conn_t *ict;
+
+ DTRACE_PROBE2(session__event, iscsit_sess_t *, ist,
+ sess_event_ctx_t *, ctx);
+
+ IDM_SM_LOG(CE_NOTE, "sess_sm_event_dispatch: sess %p event %s(%d)",
+ (void *)ist, iscsit_se_name[ctx->se_ctx_event], ctx->se_ctx_event);
+
+ /* State independent actions */
+ switch (ctx->se_ctx_event) {
+ case SE_CONN_IN_LOGIN:
+ ict = ctx->se_event_data;
+ iscsit_sess_bind_conn(ist, ict);
+ break;
+ case SE_CONN_FAIL:
+ ict = ctx->se_event_data;
+ iscsit_sess_unbind_conn(ist, ict);
+ break;
+ }
+
+ /* State dependent actions */
+ switch (ist->ist_state) {
+ case SS_Q1_FREE:
+ sess_sm_q1_free(ist, ctx);
+ break;
+ case SS_Q2_ACTIVE:
+ sess_sm_q2_active(ist, ctx);
+ break;
+ case SS_Q3_LOGGED_IN:
+ sess_sm_q3_logged_in(ist, ctx);
+ break;
+ case SS_Q4_FAILED:
+ sess_sm_q4_failed(ist, ctx);
+ break;
+ case SS_Q5_CONTINUE:
+ sess_sm_q5_continue(ist, ctx);
+ break;
+ case SS_Q6_DONE:
+ sess_sm_q6_done(ist, ctx);
+ break;
+ case SS_Q7_ERROR:
+ sess_sm_q7_error(ist, ctx);
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+
+ kmem_free(ctx, sizeof (*ctx));
+}
+
+static void
+sess_sm_q1_free(iscsit_sess_t *ist, sess_event_ctx_t *ctx)
+{
+ switch (ctx->se_ctx_event) {
+ case SE_CONN_IN_LOGIN:
+ /* N1 */
+ sess_sm_new_state(ist, ctx, SS_Q2_ACTIVE);
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+}
+
+
+static void
+sess_sm_q2_active(iscsit_sess_t *ist, sess_event_ctx_t *ctx)
+{
+ switch (ctx->se_ctx_event) {
+ case SE_CONN_LOGGED_IN:
+ /* N2 track FFP connections */
+ ist->ist_ffp_conn_count++;
+ sess_sm_new_state(ist, ctx, SS_Q3_LOGGED_IN);
+ break;
+ case SE_CONN_IN_LOGIN:
+ /* N2.1, don't care stay in this state */
+ break;
+ case SE_CONN_FAIL:
+ /* N9 */
+ sess_sm_new_state(ist, ctx, SS_Q7_ERROR);
+ break;
+ case SE_SESSION_REINSTATE:
+ /* N11 */
+ sess_sm_new_state(ist, ctx, SS_Q6_DONE);
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+}
+
+static void
+sess_sm_q3_logged_in(iscsit_sess_t *ist, sess_event_ctx_t *ctx)
+{
+ iscsit_conn_t *ict;
+
+ switch (ctx->se_ctx_event) {
+ case SE_CONN_IN_LOGIN:
+ case SE_CONN_FAIL:
+ /* N2.2, don't care */
+ break;
+ case SE_CONN_LOGGED_IN:
+ /* N2.2, track FFP connections */
+ ist->ist_ffp_conn_count++;
+ break;
+ case SE_CONN_FFP_FAIL:
+ case SE_CONN_FFP_DISABLE:
+ /*
+ * Event data from event context is the associated connection
+ * which in this case happens to be the last FFP connection
+ * for the session. In certain cases we need to refer
+ * to this last valid connection (i.e. RFC3720 section 12.16)
+ * so we'll save off a pointer here for later use.
+ */
+ ASSERT(ist->ist_ffp_conn_count >= 1);
+ ist->ist_failed_conn = (iscsit_conn_t *)ctx->se_event_data;
+ ist->ist_ffp_conn_count--;
+ if (ist->ist_ffp_conn_count == 0) {
+ /*
+ * N5(fail) or N3(disable)
+ *
+ * If the event is SE_CONN_FFP_FAIL but we are
+ * in the midst of an administrative session close
+ * because of a service or target offline then
+ * there is no need to go to "failed" state.
+ */
+ sess_sm_new_state(ist, ctx,
+ ((ctx->se_ctx_event == SE_CONN_FFP_DISABLE) ||
+ (ist->ist_admin_close)) ?
+ SS_Q6_DONE : SS_Q4_FAILED);
+ }
+ break;
+ case SE_SESSION_CLOSE:
+ case SE_SESSION_REINSTATE:
+ /* N3 */
+ mutex_enter(&ist->ist_mutex);
+ if (ctx->se_ctx_event == SE_SESSION_CLOSE) {
+ ASSERT(ist->ist_ffp_conn_count >= 1);
+ ist->ist_ffp_conn_count--;
+ }
+ for (ict = list_head(&ist->ist_conn_list);
+ ict != NULL;
+ ict = list_next(&ist->ist_conn_list, ict)) {
+ if ((ctx->se_ctx_event == SE_SESSION_CLOSE) &&
+ ((iscsit_conn_t *)ctx->se_event_data == ict)) {
+ /*
+ * Skip this connection since it will
+ * see the logout response
+ */
+ continue;
+ }
+ idm_conn_event(ict->ict_ic, CE_LOGOUT_SESSION_SUCCESS,
+ NULL);
+ }
+ mutex_exit(&ist->ist_mutex);
+
+ sess_sm_new_state(ist, ctx, SS_Q6_DONE);
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+}
+
+static void
+sess_sm_timeout(void *arg)
+{
+ iscsit_sess_t *ist = arg;
+
+ iscsit_sess_sm_event(ist, SE_SESSION_TIMEOUT, NULL);
+}
+
+static void
+sess_sm_q4_failed(iscsit_sess_t *ist, sess_event_ctx_t *ctx)
+{
+ /* Session timer must not be running when we leave this event */
+ switch (ctx->se_ctx_event) {
+ case SE_CONN_IN_LOGIN:
+ /* N7 */
+ sess_sm_new_state(ist, ctx, SS_Q5_CONTINUE);
+ break;
+ case SE_SESSION_REINSTATE:
+ /* N6 */
+ (void) untimeout(ist->ist_state_timeout);
+ /*FALLTHROUGH*/
+ case SE_SESSION_TIMEOUT:
+ /* N6 */
+ sess_sm_new_state(ist, ctx, SS_Q6_DONE);
+ break;
+ case SE_CONN_FAIL:
+ /* Don't care */
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+}
+
+static void
+sess_sm_q5_continue(iscsit_sess_t *ist, sess_event_ctx_t *ctx)
+{
+ switch (ctx->se_ctx_event) {
+ case SE_CONN_FAIL:
+ /* N5 */
+ sess_sm_new_state(ist, ctx, SS_Q4_FAILED);
+ break;
+ case SE_CONN_LOGGED_IN:
+ /* N10 */
+ sess_sm_new_state(ist, ctx, SS_Q3_LOGGED_IN);
+ break;
+ case SE_SESSION_REINSTATE:
+ /* N11 */
+ sess_sm_new_state(ist, ctx, SS_Q6_DONE);
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+}
+
+static void
+sess_sm_q6_done(iscsit_sess_t *ist, sess_event_ctx_t *ctx)
+{
+ /* Terminal state */
+ switch (ctx->se_ctx_event) {
+ case SE_CONN_FFP_FAIL:
+ case SE_CONN_FFP_DISABLE:
+ ASSERT(ist->ist_ffp_conn_count >= 1);
+ ist->ist_ffp_conn_count--;
+ break;
+ case SE_CONN_FAIL:
+ if (ist->ist_conn_count == 0) {
+ idm_refcnt_async_wait_ref(&ist->ist_refcnt,
+ &iscsit_sess_unref);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+sess_sm_q7_error(iscsit_sess_t *ist, sess_event_ctx_t *ctx)
+{
+ /* Terminal state */
+ switch (ctx->se_ctx_event) {
+ case SE_CONN_FAIL:
+ if (ist->ist_conn_count == 0) {
+ idm_refcnt_async_wait_ref(&ist->ist_refcnt,
+ &iscsit_sess_unref);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static void
+sess_sm_new_state(iscsit_sess_t *ist, sess_event_ctx_t *ctx,
+ iscsit_session_state_t new_state)
+{
+ int t2r_secs;
+
+ /*
+ * Validate new state
+ */
+ ASSERT(new_state != SS_UNDEFINED);
+ ASSERT3U(new_state, <, SS_MAX_STATE);
+
+ new_state = (new_state < SS_MAX_STATE) ?
+ new_state : SS_UNDEFINED;
+
+ IDM_SM_LOG(CE_NOTE, "sess_sm_new_state: sess %p, evt %s(%d), "
+ "%s(%d) --> %s(%d)\n", (void *) ist,
+ iscsit_se_name[ctx->se_ctx_event], ctx->se_ctx_event,
+ iscsit_ss_name[ist->ist_state], ist->ist_state,
+ iscsit_ss_name[new_state], new_state);
+
+ DTRACE_PROBE3(sess__state__change,
+ iscsit_sess_t *, ist, sess_event_ctx_t *, ctx,
+ iscsit_session_state_t, new_state);
+
+ mutex_enter(&ist->ist_mutex);
+ idm_sm_audit_state_change(&ist->ist_state_audit, SAS_ISCSIT_SESS,
+ (int)ist->ist_state, (int)new_state);
+ ist->ist_last_state = ist->ist_state;
+ ist->ist_state = new_state;
+ mutex_exit(&ist->ist_mutex);
+
+ switch (ist->ist_state) {
+ case SS_Q1_FREE:
+ break;
+ case SS_Q2_ACTIVE:
+ iscsit_tgt_bind_sess(ist->ist_tgt, ist);
+ break;
+ case SS_Q3_LOGGED_IN:
+ break;
+ case SS_Q4_FAILED:
+ t2r_secs =
+ ist->ist_failed_conn->ict_op.op_default_time_2_retain;
+ ist->ist_state_timeout = timeout(sess_sm_timeout, ist,
+ drv_usectohz(t2r_secs*1000000));
+ break;
+ case SS_Q5_CONTINUE:
+ break;
+ case SS_Q6_DONE:
+ case SS_Q7_ERROR:
+ /*
+ * We won't need our TSIH anymore and it represents an
+ * implicit reference to the global TSIH pool. Get rid
+ * of it.
+ */
+ if (ist->ist_tsih != ISCSI_UNSPEC_TSIH) {
+ iscsit_tsih_free(ist->ist_tsih);
+ }
+
+ /*
+ * We don't want this session to show up anymore so unbind
+ * it now. After this call this session cannot have any
+ * references outside itself (implicit or explicit).
+ */
+ iscsit_tgt_unbind_sess(ist->ist_tgt, ist);
+
+ /*
+ * If we have more connections bound then more events
+ * are comming so don't wait for idle yet.
+ */
+ if (ist->ist_conn_count == 0) {
+ idm_refcnt_async_wait_ref(&ist->ist_refcnt,
+ &iscsit_sess_unref);
+ }
+ break;
+ default:
+ ASSERT(0);
+ /*NOTREACHED*/
+ }
+}
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_text.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_text.c
new file mode 100644
index 0000000000..48ef76cfb0
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_text.c
@@ -0,0 +1,324 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/cpuvar.h>
+#include <sys/types.h>
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/modctl.h>
+#include <sys/sysmacros.h>
+#include <sys/socket.h>
+#include <sys/strsubr.h>
+#include <inet/tcp.h>
+#include <sys/nvpair.h>
+
+#include <sys/stmf.h>
+#include <sys/stmf_ioctl.h>
+#include <sys/portif.h>
+#include <sys/idm/idm.h>
+#include <sys/idm/idm_conn_sm.h>
+#include <sys/idm/idm_text.h>
+#include <sys/idm/idm_so.h>
+#include <iscsit_isns.h>
+#include <iscsit.h>
+
+#define IPADDRSTRLEN INET6_ADDRSTRLEN /* space for ipaddr string */
+#define PORTALSTRLEN (IPADDRSTRLEN+16) /* add space for :port,tag */
+
+/*
+ * The kernel inet_ntop() function formats ipv4 address fields with
+ * leading zeros which the win2k initiator interprets as octal.
+ */
+
+static void iscsit_v4_ntop(struct in_addr *in, char a[], int size)
+{
+ unsigned char *p = (unsigned char *) in;
+
+ (void) snprintf(a, size, "%d.%d.%d.%d", *p, *(p+1), *(p+2), *(p+3));
+}
+
+static void
+iscsit_send_reject(idm_pdu_t *req_pdu, uint8_t reason_code)
+{
+ idm_pdu_t *reject_pdu;
+ iscsi_reject_rsp_hdr_t *rej_hdr;
+
+ reject_pdu = idm_pdu_alloc(sizeof (iscsi_hdr_t), req_pdu->isp_hdrlen);
+ if (reject_pdu == NULL) {
+ /* Just give up.. the initiator will timeout */
+ idm_pdu_complete(req_pdu, IDM_STATUS_SUCCESS);
+ return;
+ }
+
+ /* Payload contains the header from the bad PDU */
+ idm_pdu_init(reject_pdu, req_pdu->isp_ic, NULL, NULL);
+ bcopy(req_pdu->isp_hdr, reject_pdu->isp_data, req_pdu->isp_hdrlen);
+
+ rej_hdr = (iscsi_reject_rsp_hdr_t *)reject_pdu->isp_hdr;
+ bzero(rej_hdr, sizeof (*rej_hdr));
+ rej_hdr->opcode = ISCSI_OP_REJECT_MSG;
+ rej_hdr->flags = ISCSI_FLAG_FINAL;
+ rej_hdr->reason = reason_code;
+ hton24(rej_hdr->dlength, req_pdu->isp_hdrlen);
+ rej_hdr->must_be_ff[0] = 0xff;
+ rej_hdr->must_be_ff[1] = 0xff;
+ rej_hdr->must_be_ff[2] = 0xff;
+ rej_hdr->must_be_ff[3] = 0xff;
+
+ iscsit_pdu_tx(reject_pdu);
+ idm_pdu_complete(req_pdu, IDM_STATUS_SUCCESS);
+}
+
+static void
+iscsit_add_target_portals(nvlist_t *nv_resp, iscsit_tgt_t *target)
+{
+ iscsit_tpgt_t *tpg_list;
+ iscsit_tpg_t *tpg;
+ idm_addr_list_t *ipaddr_p;
+ idm_addr_t *tip;
+ iscsit_portal_t *portal;
+ int ipsize, i;
+ char *name = "TargetAddress";
+ char a[IPADDRSTRLEN];
+ char v[PORTALSTRLEN];
+ struct sockaddr_storage *ss;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ struct in_addr *in;
+ struct in6_addr *in6;
+ int type;
+
+
+ /*
+ * Look through the portal groups associated with this target.
+ */
+ mutex_enter(&target->target_mutex);
+ tpg_list = avl_first(&target->target_tpgt_list);
+ while (tpg_list != NULL) {
+ tpg = tpg_list->tpgt_tpg;
+ /*
+ * The default portal group will match any current interface.
+ * A target cannot listen on other portal groups if it
+ * listens on the default portal group.
+ */
+ if (tpg == iscsit_global.global_default_tpg) {
+ /*
+ * get the list of plumbed interfaces
+ */
+ ipsize = idm_get_ipaddr(&ipaddr_p);
+ if (ipsize == 0) {
+ mutex_exit(&target->target_mutex);
+ return;
+ }
+ tip = &ipaddr_p->al_addrs[0];
+ for (i = 0; i < ipaddr_p->al_out_cnt; i++, tip++) {
+ if (tip->a_addr.i_insize ==
+ sizeof (struct in_addr)) {
+ type = AF_INET;
+ in = &tip->a_addr.i_addr.in4;
+ iscsit_v4_ntop(in, a, sizeof (a));
+ (void) snprintf(v, sizeof (v),
+ "%s,1", a);
+ } else if (tip->a_addr.i_insize ==
+ sizeof (struct in6_addr)) {
+ type = AF_INET6;
+ in6 = &tip->a_addr.i_addr.in6;
+ (void) inet_ntop(type, in6, a,
+ sizeof (a));
+ (void) snprintf(v, sizeof (v),
+ "[%s],1", a);
+ } else {
+ break;
+ }
+ /*
+ * Add the TargetAddress=<addr> nvpair
+ */
+ (void) nvlist_add_string(nv_resp, name, v);
+ }
+ kmem_free(ipaddr_p, ipsize);
+ /*
+ * Cannot listen on other portal groups.
+ */
+ mutex_exit(&target->target_mutex);
+ return;
+ }
+ /*
+ * Found a defined portal group - add each portal address.
+ */
+ portal = avl_first(&tpg->tpg_portal_list);
+ while (portal != NULL) {
+ ss = &portal->portal_addr;
+ type = ss->ss_family;
+ switch (type) {
+ case AF_INET:
+ sin = (struct sockaddr_in *)ss;
+ in = &sin->sin_addr;
+ iscsit_v4_ntop(in, a, sizeof (a));
+ (void) snprintf(v, sizeof (v), "%s:%d,%d", a,
+ ntohs(sin->sin_port),
+ tpg_list->tpgt_tag);
+ (void) nvlist_add_string(nv_resp, name, v);
+ break;
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)ss;
+ in6 = &sin6->sin6_addr;
+ (void) inet_ntop(type, in6, a, sizeof (a));
+ (void) snprintf(v, sizeof (v), "[%s]:%d,%d", a,
+ sin6->sin6_port,
+ tpg_list->tpgt_tag);
+ (void) nvlist_add_string(nv_resp, name, v);
+ break;
+ default:
+ break;
+ }
+ portal = AVL_NEXT(&tpg->tpg_portal_list, portal);
+ }
+ tpg_list = AVL_NEXT(&target->target_tpgt_list, tpg_list);
+ }
+ mutex_exit(&target->target_mutex);
+}
+
+void
+iscsit_pdu_op_text_cmd(iscsit_conn_t *ict, idm_pdu_t *rx_pdu)
+{
+ iscsi_text_hdr_t *th_req = (iscsi_text_hdr_t *)rx_pdu->isp_hdr;
+ iscsi_text_rsp_hdr_t *th_resp;
+ nvlist_t *nv_resp;
+ char *textbuf;
+ char *kv_name, *kv_pair;
+ int flags;
+ int textbuflen;
+ int rc;
+ idm_pdu_t *resp;
+
+ flags = th_req->flags;
+ if ((flags & ISCSI_FLAG_FINAL) != ISCSI_FLAG_FINAL) {
+ /* Cannot handle multi-PDU messages now */
+ iscsit_send_reject(rx_pdu, ISCSI_REJECT_CMD_NOT_SUPPORTED);
+ return;
+ }
+ if (th_req->ttt != ISCSI_RSVD_TASK_TAG) {
+ /* Last of a multi-PDU message */
+ iscsit_send_reject(rx_pdu, ISCSI_REJECT_CMD_NOT_SUPPORTED);
+ return;
+ }
+
+ /*
+ * At this point we have a single PDU text command
+ */
+
+ textbuf = (char *)rx_pdu->isp_data;
+ textbuflen = rx_pdu->isp_datalen;
+ kv_name = "SendTargets=";
+ kv_pair = "SendTargets=All";
+ if (strncmp(kv_name, textbuf, strlen(kv_name)) != 0) {
+ /* Not a Sendtargets command */
+ iscsit_send_reject(rx_pdu, ISCSI_REJECT_CMD_NOT_SUPPORTED);
+ return;
+ }
+ if (strcmp(kv_pair, textbuf) == 0 &&
+ ict->ict_op.op_discovery_session == B_TRUE) {
+ iscsit_tgt_t *target;
+ int validlen;
+
+ /*
+ * Most common case of SendTargets=All during discovery.
+ */
+ /*
+ * Create an nvlist for response.
+ */
+ if (nvlist_alloc(&nv_resp, 0, KM_SLEEP) != 0) {
+ iscsit_send_reject(rx_pdu,
+ ISCSI_REJECT_CMD_NOT_SUPPORTED);
+ return;
+ }
+
+ ISCSIT_GLOBAL_LOCK(RW_READER);
+ target = avl_first(&iscsit_global.global_target_list);
+ while (target != NULL) {
+ char *name = "TargetName";
+ char *val = target->target_name;
+
+ (void) nvlist_add_string(nv_resp, name, val);
+ iscsit_add_target_portals(nv_resp, target);
+ target = AVL_NEXT(&iscsit_global.global_target_list,
+ target);
+ }
+ ISCSIT_GLOBAL_UNLOCK();
+
+ /*
+ * Convert the reponse nv list into text buffer.
+ */
+ textbuf = 0;
+ textbuflen = 0;
+ validlen = 0;
+ rc = idm_nvlist_to_textbuf(nv_resp, &textbuf,
+ &textbuflen, &validlen);
+ nvlist_free(nv_resp);
+ if (rc != 0) {
+ if (textbuf && textbuflen)
+ kmem_free(textbuf, textbuflen);
+ iscsit_send_reject(rx_pdu,
+ ISCSI_REJECT_CMD_NOT_SUPPORTED);
+ return;
+ }
+ /*
+ * Allocate a PDU and copy in text response buffer
+ */
+ resp = idm_pdu_alloc(sizeof (iscsi_hdr_t), validlen);
+ idm_pdu_init(resp, ict->ict_ic, NULL, NULL);
+ bcopy(textbuf, resp->isp_data, validlen);
+ kmem_free(textbuf, textbuflen);
+ /*
+ * Fill in the response header
+ */
+ th_resp = (iscsi_text_rsp_hdr_t *)resp->isp_hdr;
+ bzero(th_resp, sizeof (*th_resp));
+ th_resp->opcode = ISCSI_OP_TEXT_RSP;
+ th_resp->flags = ISCSI_FLAG_FINAL;
+ th_resp->ttt = ISCSI_RSVD_TASK_TAG;
+ th_resp->itt = th_req->itt;
+ hton24(th_resp->dlength, validlen);
+ } else {
+ /*
+ * Other cases to handle
+ * Discovery session:
+ * SendTargets=<target_name>
+ * Normal session
+ * SendTargets=<target_name> - should match session
+ * SendTargets=<NULL> - assume target name of session
+ * All others
+ * Error
+ */
+ iscsit_send_reject(rx_pdu, ISCSI_REJECT_CMD_NOT_SUPPORTED);
+ return;
+ }
+
+ /* Send the response on its way */
+ iscsit_pdu_tx(resp);
+ idm_pdu_complete(rx_pdu, IDM_STATUS_SUCCESS);
+}
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/iscsit_tgt.c b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_tgt.c
new file mode 100644
index 0000000000..8d42eccedb
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/iscsit_tgt.c
@@ -0,0 +1,2054 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/cpuvar.h>
+#include <sys/types.h>
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/modctl.h>
+#include <sys/sysmacros.h>
+#include <sys/sdt.h>
+
+#include <sys/socket.h>
+#include <sys/strsubr.h>
+
+#include <sys/stmf.h>
+#include <sys/stmf_ioctl.h>
+#include <sys/portif.h>
+#include <sys/idm/idm.h>
+
+#define ISCSIT_TGT_SM_STRINGS
+#include <iscsit.h>
+#include <iscsit_isns.h>
+
+typedef struct {
+ list_node_t te_ctx_node;
+ iscsit_tgt_event_t te_ctx_event;
+} tgt_event_ctx_t;
+
+static void
+tgt_sm_event_dispatch(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx);
+
+static void
+tgt_sm_created(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx);
+
+static void
+tgt_sm_onlining(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx);
+
+static void
+tgt_sm_online(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx);
+
+static void
+tgt_sm_stmf_online(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx);
+
+static void
+tgt_sm_deleting_need_offline(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx);
+
+static void
+tgt_sm_offlining(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx);
+
+static void
+tgt_sm_offline(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx);
+
+static void
+tgt_sm_stmf_offline(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx);
+
+static void
+tgt_sm_deleting_stmf_dereg(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx);
+
+static void
+tgt_sm_deleting_stmf_dereg_fail(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx);
+
+static void
+tgt_sm_deleting(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx);
+
+static void
+iscsit_tgt_dereg_retry(void *arg);
+
+static void
+iscsit_tgt_dereg_task(void *arg);
+
+static void
+tgt_sm_new_state(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx,
+ iscsit_tgt_state_t new_state);
+
+
+static iscsit_tgt_t *
+iscsit_tgt_create(it_tgt_t *cfg_tgt);
+
+static void
+iscsit_tgt_unref(void *tgt);
+
+static void
+iscsit_tgt_async_wait_ref(iscsit_tgt_t *tgt, idm_refcnt_cb_t *cb_func);
+
+static void
+iscsit_tgt_destroy(iscsit_tgt_t *tgt);
+
+static iscsit_tpgt_t *
+iscsit_tgt_lookup_tpgt_locked(iscsit_tgt_t *tgt, uint16_t tag);
+
+static iscsit_tpg_t *
+iscsit_tpg_lookup_locked(char *tpg_name);
+
+static iscsit_portal_t *
+iscsit_tpg_portal_lookup_locked(iscsit_tpg_t *tpg,
+ struct sockaddr_storage *sa);
+
+static idm_status_t
+iscsit_tgt_online(iscsit_tgt_t *tgt);
+
+static void
+iscsit_tgt_offline(iscsit_tgt_t *tgt);
+
+static idm_status_t
+iscsit_tgt_modify(iscsit_tgt_t *tgt, it_tgt_t *cfg_tgt);
+
+static idm_status_t
+iscsit_tgt_merge_tpgt(iscsit_tgt_t *tgt, it_tgt_t *cfg_tgt,
+ list_t *tpgt_del_list);
+
+static iscsit_tpgt_t *
+iscsit_tpgt_create(it_tpgt_t *cfg_tpgt);
+
+static iscsit_tpgt_t *
+iscsit_tpgt_create_default();
+
+static void
+iscsit_tpgt_destroy(iscsit_tpgt_t *tpgt);
+
+static iscsit_tpg_t *
+iscsit_tpg_create(it_tpg_t *tpg);
+
+static void
+iscsit_tpg_modify(iscsit_tpg_t *tpg, it_tpg_t *cfg_tpg);
+
+static void
+iscsit_tpg_destroy(iscsit_tpg_t *tpg);
+
+static iscsit_portal_t *
+iscsit_portal_create(iscsit_tpg_t *tpg, struct sockaddr_storage *sa);
+
+static void
+iscsit_portal_destroy(iscsit_portal_t *portal);
+
+static idm_status_t
+iscsit_portal_online(iscsit_portal_t *portal);
+
+static void
+iscsit_portal_offline(iscsit_portal_t *portal);
+
+
+
+/*
+ * Target state machine
+ */
+
+void
+iscsit_tgt_sm_event(iscsit_tgt_t *tgt, iscsit_tgt_event_t event)
+{
+ mutex_enter(&tgt->target_mutex);
+ tgt_sm_event_locked(tgt, event);
+ mutex_exit(&tgt->target_mutex);
+}
+
+void
+tgt_sm_event_locked(iscsit_tgt_t *tgt, iscsit_tgt_event_t event)
+{
+ tgt_event_ctx_t *ctx;
+
+ iscsit_tgt_hold(tgt);
+
+ ctx = kmem_zalloc(sizeof (*ctx), KM_SLEEP);
+
+ ctx->te_ctx_event = event;
+
+ list_insert_tail(&tgt->target_events, ctx);
+ /*
+ * Use the icl_busy flag to keep the state machine single threaded.
+ * This also serves as recursion avoidance since this flag will
+ * always be set if we call iscsit_tgt_sm_event from within the
+ * state machine code.
+ */
+ if (!tgt->target_sm_busy) {
+ tgt->target_sm_busy = B_TRUE;
+ while (!list_is_empty(&tgt->target_events)) {
+ ctx = list_head(&tgt->target_events);
+ list_remove(&tgt->target_events, ctx);
+ idm_sm_audit_event(&tgt->target_state_audit,
+ SAS_ISCSIT_TGT, (int)tgt->target_state,
+ (int)ctx->te_ctx_event, 0);
+ mutex_exit(&tgt->target_mutex);
+ tgt_sm_event_dispatch(tgt, ctx);
+ mutex_enter(&tgt->target_mutex);
+ }
+ tgt->target_sm_busy = B_FALSE;
+
+ }
+
+ iscsit_tgt_rele(tgt);
+}
+
+static void
+tgt_sm_event_dispatch(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx)
+{
+ DTRACE_PROBE2(tgt__event, iscsit_tgt_t *, tgt,
+ tgt_event_ctx_t *, ctx);
+
+ IDM_SM_LOG(CE_NOTE, "tgt_sm_event_dispatch: tgt %p event %s(%d)",
+ (void *)tgt, iscsit_te_name[ctx->te_ctx_event], ctx->te_ctx_event);
+
+ /* State independent actions */
+ switch (ctx->te_ctx_event) {
+ case TE_DELETE:
+ tgt->target_deleting = B_TRUE;
+ break;
+ }
+
+ /* State dependent actions */
+ switch (tgt->target_state) {
+ case TS_CREATED:
+ tgt_sm_created(tgt, ctx);
+ break;
+ case TS_ONLINING:
+ tgt_sm_onlining(tgt, ctx);
+ break;
+ case TS_ONLINE:
+ tgt_sm_online(tgt, ctx);
+ break;
+ case TS_STMF_ONLINE:
+ tgt_sm_stmf_online(tgt, ctx);
+ break;
+ case TS_DELETING_NEED_OFFLINE:
+ tgt_sm_deleting_need_offline(tgt, ctx);
+ break;
+ case TS_OFFLINING:
+ tgt_sm_offlining(tgt, ctx);
+ break;
+ case TS_OFFLINE:
+ tgt_sm_offline(tgt, ctx);
+ break;
+ case TS_STMF_OFFLINE:
+ tgt_sm_stmf_offline(tgt, ctx);
+ break;
+ case TS_DELETING_STMF_DEREG:
+ tgt_sm_deleting_stmf_dereg(tgt, ctx);
+ break;
+ case TS_DELETING_STMF_DEREG_FAIL:
+ tgt_sm_deleting_stmf_dereg_fail(tgt, ctx);
+ break;
+ case TS_DELETING:
+ tgt_sm_deleting(tgt, ctx);
+ break;
+ default:
+ ASSERT(0);
+ }
+
+ kmem_free(ctx, sizeof (*ctx));
+}
+
+static void
+tgt_sm_created(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx)
+{
+ stmf_change_status_t scs;
+
+ switch (ctx->te_ctx_event) {
+ case TE_STMF_ONLINE_REQ:
+ tgt_sm_new_state(tgt, ctx, TS_ONLINING);
+ break;
+ case TE_DELETE:
+ tgt_sm_new_state(tgt, ctx, TS_DELETING_STMF_DEREG);
+ break;
+ case TE_STMF_OFFLINE_REQ:
+ /*
+ * We're already offline but update to an equivelant
+ * state just to note that STMF talked to us.
+ */
+ scs.st_completion_status = STMF_SUCCESS;
+ scs.st_additional_info = NULL;
+ tgt_sm_new_state(tgt, ctx, TS_OFFLINE);
+ (void) stmf_ctl(STMF_CMD_LPORT_OFFLINE_COMPLETE,
+ tgt->target_stmf_lport, &scs);
+ break;
+ case TE_STMF_ONLINE_COMPLETE_ACK:
+ case TE_STMF_OFFLINE_COMPLETE_ACK:
+ /* Ignore */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+static void
+tgt_sm_onlining(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx)
+{
+ stmf_change_status_t scs;
+
+ switch (ctx->te_ctx_event) {
+ case TE_ONLINE_SUCCESS:
+ tgt_sm_new_state(tgt, ctx, TS_ONLINE);
+ break;
+ case TE_ONLINE_FAIL:
+ tgt_sm_new_state(tgt, ctx, TS_STMF_OFFLINE);
+ break;
+ case TE_DELETE:
+ /* TE_DELETE is handled in tgt_sm_event_dispatch() */
+ break;
+ case TE_STMF_ONLINE_REQ:
+ case TE_STMF_OFFLINE_REQ:
+ /*
+ * We can't complete STMF's request since we are busy going
+ * online.
+ */
+ scs.st_completion_status = STMF_INVALID_ARG;
+ scs.st_additional_info = NULL;
+ (void) stmf_ctl((ctx->te_ctx_event == TE_STMF_ONLINE_REQ) ?
+ STMF_CMD_LPORT_ONLINE_COMPLETE :
+ STMF_CMD_LPORT_OFFLINE_COMPLETE,
+ tgt->target_stmf_lport, &scs);
+ break;
+ case TE_STMF_ONLINE_COMPLETE_ACK:
+ case TE_STMF_OFFLINE_COMPLETE_ACK:
+ /* Ignore */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+static void
+tgt_sm_online(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx)
+{
+ stmf_change_status_t scs;
+
+ switch (ctx->te_ctx_event) {
+ case TE_STMF_ONLINE_COMPLETE_ACK:
+ if (tgt->target_deleting) {
+ tgt_sm_new_state(tgt, ctx, TS_DELETING_NEED_OFFLINE);
+ } else {
+ tgt_sm_new_state(tgt, ctx, TS_STMF_ONLINE);
+ }
+ break;
+ case TE_DELETE:
+ /* TE_DELETE is handled in tgt_sm_event_dispatch() */
+ break;
+ case TE_STMF_ONLINE_REQ:
+ case TE_STMF_OFFLINE_REQ:
+ /*
+ * We can't complete STMF's request since we are busy going
+ * online (waiting for acknowlegement from STMF)
+ */
+ scs.st_completion_status = STMF_INVALID_ARG;
+ scs.st_additional_info = NULL;
+ (void) stmf_ctl((ctx->te_ctx_event == TE_STMF_ONLINE_REQ) ?
+ STMF_CMD_LPORT_ONLINE_COMPLETE :
+ STMF_CMD_LPORT_OFFLINE_COMPLETE,
+ tgt->target_stmf_lport, &scs);
+ break;
+ case TE_STMF_OFFLINE_COMPLETE_ACK:
+ /* Ignore */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+
+static void
+tgt_sm_stmf_online(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx)
+{
+ stmf_change_status_t scs;
+
+ /* Deregister target with iSNS whenever we leave this state */
+
+ switch (ctx->te_ctx_event) {
+ case TE_DELETE:
+ (void) iscsit_isns_deregister(tgt);
+ tgt_sm_new_state(tgt, ctx, TS_DELETING_NEED_OFFLINE);
+ break;
+ case TE_STMF_OFFLINE_REQ:
+ (void) iscsit_isns_deregister(tgt);
+ tgt_sm_new_state(tgt, ctx, TS_OFFLINING);
+ break;
+ case TE_STMF_ONLINE_REQ:
+ /* Already online */
+ scs.st_completion_status = STMF_ALREADY;
+ scs.st_additional_info = NULL;
+ (void) stmf_ctl(STMF_CMD_LPORT_ONLINE_COMPLETE,
+ tgt->target_stmf_lport, &scs);
+ break;
+ case TE_STMF_ONLINE_COMPLETE_ACK:
+ case TE_STMF_OFFLINE_COMPLETE_ACK:
+ /* Ignore */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+
+static void
+tgt_sm_deleting_need_offline(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx)
+{
+ stmf_change_status_t scs;
+
+ switch (ctx->te_ctx_event) {
+ case TE_STMF_OFFLINE_REQ:
+ tgt_sm_new_state(tgt, ctx, TS_OFFLINING);
+ break;
+ case TE_DELETE:
+ /* TE_DELETE is handled in tgt_sm_event_dispatch() */
+ break;
+ case TE_STMF_ONLINE_REQ:
+ /*
+ * We can't complete STMF's request since we need to be offlined
+ */
+ scs.st_completion_status = STMF_INVALID_ARG;
+ scs.st_additional_info = NULL;
+ (void) stmf_ctl(STMF_CMD_LPORT_ONLINE_COMPLETE,
+ tgt->target_stmf_lport, &scs);
+ break;
+ case TE_STMF_ONLINE_COMPLETE_ACK:
+ case TE_STMF_OFFLINE_COMPLETE_ACK:
+ /* Ignore */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+
+static void
+tgt_sm_offlining(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx)
+{
+ stmf_change_status_t scs;
+
+ switch (ctx->te_ctx_event) {
+ case TE_OFFLINE_COMPLETE:
+ tgt_sm_new_state(tgt, ctx, TS_OFFLINE);
+ break;
+ case TE_DELETE:
+ /* TE_DELETE is handled in tgt_sm_event_dispatch() */
+ break;
+ case TE_STMF_ONLINE_REQ:
+ case TE_STMF_OFFLINE_REQ:
+ /*
+ * We can't complete STMF's request since we are busy going
+ * offline.
+ */
+ scs.st_completion_status = STMF_INVALID_ARG;
+ scs.st_additional_info = NULL;
+ (void) stmf_ctl((ctx->te_ctx_event == TE_STMF_ONLINE_REQ) ?
+ STMF_CMD_LPORT_ONLINE_COMPLETE :
+ STMF_CMD_LPORT_OFFLINE_COMPLETE,
+ tgt->target_stmf_lport, &scs);
+ break;
+ case TE_STMF_ONLINE_COMPLETE_ACK:
+ case TE_STMF_OFFLINE_COMPLETE_ACK:
+ /* Ignore */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+
+static void
+tgt_sm_offline(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx)
+{
+ stmf_change_status_t scs;
+
+ switch (ctx->te_ctx_event) {
+ case TE_STMF_OFFLINE_COMPLETE_ACK:
+ if (tgt->target_deleting) {
+ tgt_sm_new_state(tgt, ctx, TS_DELETING_STMF_DEREG);
+ } else {
+ tgt_sm_new_state(tgt, ctx, TS_STMF_OFFLINE);
+ }
+ break;
+ case TE_DELETE:
+ /* TE_DELETE is handled in tgt_sm_event_dispatch() */
+ break;
+ case TE_STMF_ONLINE_REQ:
+ case TE_STMF_OFFLINE_REQ:
+ /*
+ * We can't complete STMF's request since we are busy going
+ * offline.
+ */
+ scs.st_completion_status = STMF_INVALID_ARG;
+ scs.st_additional_info = NULL;
+ (void) stmf_ctl((ctx->te_ctx_event == TE_STMF_ONLINE_REQ) ?
+ STMF_CMD_LPORT_ONLINE_COMPLETE :
+ STMF_CMD_LPORT_OFFLINE_COMPLETE,
+ tgt->target_stmf_lport, &scs);
+ break;
+ case TE_STMF_ONLINE_COMPLETE_ACK:
+ /* Ignore */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+
+static void
+tgt_sm_stmf_offline(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx)
+{
+ stmf_change_status_t scs;
+
+ switch (ctx->te_ctx_event) {
+ case TE_STMF_ONLINE_REQ:
+ tgt_sm_new_state(tgt, ctx, TS_ONLINING);
+ break;
+ case TE_DELETE:
+ tgt_sm_new_state(tgt, ctx, TS_DELETING_STMF_DEREG);
+ break;
+ case TE_STMF_OFFLINE_REQ:
+ /* Already offline */
+ scs.st_completion_status = STMF_ALREADY;
+ scs.st_additional_info = NULL;
+ (void) stmf_ctl(STMF_CMD_LPORT_OFFLINE_COMPLETE,
+ tgt->target_stmf_lport, &scs);
+ break;
+ case TE_STMF_ONLINE_COMPLETE_ACK:
+ case TE_STMF_OFFLINE_COMPLETE_ACK:
+ /* Ignore */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+
+static void
+tgt_sm_deleting_stmf_dereg(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx)
+{
+ stmf_change_status_t scs;
+
+ /* Terminal state, no events */
+ switch (ctx->te_ctx_event) {
+ case TE_STMF_ONLINE_REQ:
+ case TE_STMF_OFFLINE_REQ:
+ /*
+ * We can't complete STMF's request since we are being deleted
+ */
+ scs.st_completion_status = STMF_INVALID_ARG;
+ scs.st_additional_info = NULL;
+ (void) stmf_ctl((ctx->te_ctx_event == TE_STMF_ONLINE_REQ) ?
+ STMF_CMD_LPORT_ONLINE_COMPLETE :
+ STMF_CMD_LPORT_OFFLINE_COMPLETE,
+ tgt->target_stmf_lport, &scs);
+ break;
+ case TE_STMF_ONLINE_COMPLETE_ACK:
+ case TE_STMF_OFFLINE_COMPLETE_ACK:
+ /* Ignore */
+ break;
+ case TE_STMF_DEREG_SUCCESS:
+ tgt_sm_new_state(tgt, ctx, TS_DELETING);
+ break;
+ case TE_STMF_DEREG_FAIL:
+ tgt_sm_new_state(tgt, ctx, TS_DELETING_STMF_DEREG_FAIL);
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+static void
+tgt_sm_deleting_stmf_dereg_fail(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx)
+{
+ stmf_change_status_t scs;
+
+ /* Terminal state, no events */
+ switch (ctx->te_ctx_event) {
+ case TE_STMF_ONLINE_REQ:
+ case TE_STMF_OFFLINE_REQ:
+ /*
+ * We can't complete STMF's request since we are being deleted
+ */
+ scs.st_completion_status = STMF_INVALID_ARG;
+ scs.st_additional_info = NULL;
+ (void) stmf_ctl((ctx->te_ctx_event == TE_STMF_ONLINE_REQ) ?
+ STMF_CMD_LPORT_ONLINE_COMPLETE :
+ STMF_CMD_LPORT_OFFLINE_COMPLETE,
+ tgt->target_stmf_lport, &scs);
+ break;
+ case TE_STMF_ONLINE_COMPLETE_ACK:
+ case TE_STMF_OFFLINE_COMPLETE_ACK:
+ /* Ignore */
+ break;
+ case TE_STMF_DEREG_RETRY:
+ tgt_sm_new_state(tgt, ctx, TS_DELETING_STMF_DEREG);
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+static void
+tgt_sm_deleting(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx)
+{
+ stmf_change_status_t scs;
+
+ /* Terminal state, no events */
+ switch (ctx->te_ctx_event) {
+ case TE_STMF_ONLINE_REQ:
+ case TE_STMF_OFFLINE_REQ:
+ /*
+ * We can't complete STMF's request since we are being deleted
+ */
+ scs.st_completion_status = STMF_INVALID_ARG;
+ scs.st_additional_info = NULL;
+ (void) stmf_ctl((ctx->te_ctx_event == TE_STMF_ONLINE_REQ) ?
+ STMF_CMD_LPORT_ONLINE_COMPLETE :
+ STMF_CMD_LPORT_OFFLINE_COMPLETE,
+ tgt->target_stmf_lport, &scs);
+ break;
+ case TE_STMF_ONLINE_COMPLETE_ACK:
+ case TE_STMF_OFFLINE_COMPLETE_ACK:
+ /* Ignore */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+
+static void
+iscsit_tgt_dereg_retry(void *arg)
+{
+ iscsit_tgt_t *tgt = arg;
+
+ /*
+ * Rather than guaranteeing the target state machine code will not
+ * block for long periods of time (tying up this callout thread)
+ * we will queue a task on the taskq to send the retry event.
+ * If it fails we'll setup another timeout and try again later.
+ */
+ if (taskq_dispatch(iscsit_global.global_dispatch_taskq,
+ iscsit_tgt_dereg_task, tgt, DDI_NOSLEEP) == NULL) {
+ /* Dispatch failed, try again later */
+ (void) timeout(iscsit_tgt_dereg_retry, tgt,
+ drv_usectohz(TGT_DEREG_RETRY_SECONDS * 1000000));
+ }
+}
+
+static void
+iscsit_tgt_dereg_task(void *arg)
+{
+ iscsit_tgt_t *tgt = arg;
+
+ iscsit_tgt_sm_event(tgt, TE_STMF_DEREG_RETRY);
+}
+
+static void
+tgt_sm_new_state(iscsit_tgt_t *tgt, tgt_event_ctx_t *ctx,
+ iscsit_tgt_state_t new_state)
+{
+ stmf_local_port_t *lport = tgt->target_stmf_lport;
+ stmf_change_status_t scs;
+ stmf_state_change_info_t sci;
+ idm_status_t idmrc;
+ stmf_status_t stmfrc;
+
+ scs.st_completion_status = STMF_SUCCESS;
+ scs.st_additional_info = NULL;
+
+ /*
+ * Validate new state
+ */
+ ASSERT(new_state != TS_UNDEFINED);
+ ASSERT3U(new_state, <, TS_MAX_STATE);
+
+ new_state = (new_state < TS_MAX_STATE) ?
+ new_state : TS_UNDEFINED;
+
+ IDM_SM_LOG(CE_NOTE, "tgt_sm_new_state: tgt %p, %s(%d) --> %s(%d)\n",
+ (void *) tgt, iscsit_ts_name[tgt->target_state], tgt->target_state,
+ iscsit_ts_name[new_state], new_state);
+ DTRACE_PROBE3(target__state__change,
+ iscsit_tgt_t *, tgt, tgt_event_ctx_t *, ctx,
+ iscsit_tgt_state_t, new_state);
+
+ mutex_enter(&tgt->target_mutex);
+ idm_sm_audit_state_change(&tgt->target_state_audit, SAS_ISCSIT_TGT,
+ (int)tgt->target_state, (int)new_state);
+ tgt->target_last_state = tgt->target_state;
+ tgt->target_state = new_state;
+ mutex_exit(&tgt->target_mutex);
+
+ switch (tgt->target_state) {
+ case TS_ONLINING:
+ idmrc = iscsit_tgt_online(tgt);
+ if (idmrc != IDM_STATUS_SUCCESS) {
+ scs.st_completion_status = STMF_TARGET_FAILURE;
+ iscsit_tgt_sm_event(tgt, TE_ONLINE_FAIL);
+ } else {
+ iscsit_tgt_sm_event(tgt, TE_ONLINE_SUCCESS);
+ }
+ /*
+ * Let STMF know the how the online operation completed.
+ * STMF will respond with an acknowlege later
+ */
+ (void) stmf_ctl(STMF_CMD_LPORT_ONLINE_COMPLETE, lport, &scs);
+ break;
+ case TS_ONLINE:
+ break;
+ case TS_STMF_ONLINE:
+ (void) iscsit_isns_register(tgt);
+ break;
+ case TS_DELETING_NEED_OFFLINE:
+ sci.st_rflags = STMF_RFLAG_STAY_OFFLINED;
+ sci.st_additional_info = "Offline for delete";
+ (void) stmf_ctl(STMF_CMD_LPORT_OFFLINE, lport, &sci);
+ break;
+ case TS_OFFLINING:
+ /* Async callback generates completion event */
+ iscsit_tgt_offline(tgt);
+ break;
+ case TS_OFFLINE:
+ break;
+ case TS_STMF_OFFLINE:
+ break;
+ case TS_DELETING_STMF_DEREG:
+ stmfrc = stmf_deregister_local_port(tgt->target_stmf_lport);
+ if (stmfrc == STMF_SUCCESS) {
+ iscsit_tgt_sm_event(tgt, TE_STMF_DEREG_SUCCESS);
+ } else {
+ iscsit_tgt_sm_event(tgt, TE_STMF_DEREG_FAIL);
+ }
+ break;
+ case TS_DELETING_STMF_DEREG_FAIL:
+ /* Retry dereg in 1 second */
+ (void) timeout(iscsit_tgt_dereg_retry, tgt,
+ drv_usectohz(TGT_DEREG_RETRY_SECONDS * 1000000));
+ break;
+ case TS_DELETING:
+ iscsit_tgt_async_wait_ref(tgt, iscsit_tgt_unref);
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+
+/*
+ * Target, TPGT, TPG utility functions
+ */
+
+it_cfg_status_t
+iscsit_config_merge_tgt(it_config_t *cfg)
+{
+ it_tgt_t *cfg_tgt;
+ iscsit_tgt_t *tgt, *next_tgt;
+
+
+ /*
+ * 1. >> Lock <<
+ * 2. Removing deleted objects
+ * 3. Add deleted targets to global delete list
+ * 4. "delete" event to target state machine
+ * 5. >> Unlock <<
+ * 6. Create new targets, update modified targets
+ */
+ for (tgt = avl_first(&iscsit_global.global_target_list);
+ tgt != NULL;
+ tgt = next_tgt) {
+ next_tgt = AVL_NEXT(&iscsit_global.global_target_list, tgt);
+
+ if (it_tgt_lookup(cfg, tgt->target_name) == NULL) {
+ avl_remove(&iscsit_global.global_target_list, tgt);
+ list_insert_tail(
+ &iscsit_global.global_deleted_target_list, tgt);
+ iscsit_tgt_sm_event(tgt, TE_DELETE);
+ }
+ }
+
+ /* Now walk through the list of configured targets */
+ for (cfg_tgt = cfg->config_tgt_list;
+ cfg_tgt != NULL;
+ cfg_tgt = cfg_tgt->tgt_next) {
+ /* See if we have an existing target */
+ tgt = iscsit_tgt_lookup_locked(cfg_tgt->tgt_name);
+
+ if (tgt == NULL) {
+ tgt = iscsit_tgt_create(cfg_tgt);
+ if (tgt == NULL)
+ return (ITCFG_TGT_CREATE_ERR);
+ avl_add(&iscsit_global.global_target_list, tgt);
+ } else {
+ (void) iscsit_tgt_modify(tgt, cfg_tgt);
+ iscsit_tgt_rele(tgt);
+ }
+ }
+
+ /*
+ * Targets on the iscsit_global.global_deleted_target_list will remove
+ * and destroy themselves when their associated state machines reach
+ * the TS_DELETED state and all references are released.
+ */
+ return (ITCFG_SUCCESS);
+}
+
+iscsit_tgt_t *
+iscsit_tgt_lookup(char *target_name)
+{
+ iscsit_tgt_t *result;
+
+ ISCSIT_GLOBAL_LOCK(RW_READER);
+ result = iscsit_tgt_lookup_locked(target_name);
+ ISCSIT_GLOBAL_UNLOCK();
+
+ return (result);
+}
+
+iscsit_tgt_t *
+iscsit_tgt_lookup_locked(char *target_name)
+{
+ iscsit_tgt_t tmp_tgt;
+ iscsit_tgt_t *result;
+
+ /*
+ * Use a dummy target for lookup, filling in all fields used in AVL
+ * comparison.
+ */
+ tmp_tgt.target_name = target_name;
+ if ((result = avl_find(&iscsit_global.global_target_list,
+ &tmp_tgt, NULL)) != NULL) {
+ iscsit_tgt_hold(result);
+ }
+
+ return (result);
+}
+
+iscsit_tgt_t *
+iscsit_tgt_create(it_tgt_t *cfg_tgt)
+{
+ iscsit_tgt_t *result;
+ stmf_local_port_t *lport;
+
+ /*
+ * Each target is an STMF local port.
+ */
+ lport = stmf_alloc(STMF_STRUCT_STMF_LOCAL_PORT,
+ sizeof (iscsit_tgt_t) + sizeof (scsi_devid_desc_t) +
+ strnlen(cfg_tgt->tgt_name, MAX_ISCSI_NODENAMELEN) + 1, 0);
+ if (lport == NULL) {
+ return (NULL);
+ }
+
+ result = lport->lport_port_private;
+ result->target_state = TS_CREATED;
+ result->target_stmf_lport_registered = 0;
+ /* Use pointer arithmetic to find scsi_devid_desc_t */
+ result->target_devid = (scsi_devid_desc_t *)(result + 1);
+ (void) strcpy((char *)result->target_devid->ident, cfg_tgt->tgt_name);
+ result->target_devid->ident_length =
+ strnlen(cfg_tgt->tgt_name, MAX_ISCSI_NODENAMELEN);
+ result->target_devid->protocol_id = PROTOCOL_iSCSI;
+ result->target_devid->piv = 1;
+ result->target_devid->code_set = CODE_SET_ASCII;
+ result->target_devid->association = ID_IS_TARGET_PORT;
+
+ /* Store a shortcut to the target name */
+ result->target_name = (char *)result->target_devid->ident;
+ idm_sm_audit_init(&result->target_state_audit);
+ mutex_init(&result->target_mutex, NULL, MUTEX_DEFAULT, NULL);
+ avl_create(&result->target_sess_list, iscsit_sess_avl_compare,
+ sizeof (iscsit_sess_t), offsetof(iscsit_sess_t, ist_tgt_ln));
+ avl_create(&result->target_tpgt_list, iscsit_tpgt_avl_compare,
+ sizeof (iscsit_tpgt_t), offsetof(iscsit_tpgt_t, tpgt_tgt_ln));
+ list_create(&result->target_events, sizeof (tgt_event_ctx_t),
+ offsetof(tgt_event_ctx_t, te_ctx_node));
+ idm_refcnt_init(&result->target_refcnt, result);
+ idm_refcnt_init(&result->target_sess_refcnt, result);
+
+ /* Finish initializing local port */
+ lport->lport_abort_timeout = 0xffffffff; /* seconds */
+ lport->lport_id = result->target_devid;
+ lport->lport_pp = iscsit_global.global_pp;
+ lport->lport_ds = iscsit_global.global_dbuf_store;
+ lport->lport_xfer_data = &iscsit_xfer_scsi_data;
+ lport->lport_send_status = &iscsit_send_scsi_status;
+ lport->lport_task_free = &iscsit_lport_task_free;
+ lport->lport_abort = &iscsit_abort;
+ lport->lport_ctl = &iscsit_ctl;
+ result->target_stmf_lport = lport;
+
+ /*
+ * Additional target modifications from config
+ */
+ if (iscsit_tgt_modify(result, cfg_tgt) != IDM_STATUS_SUCCESS) {
+ iscsit_tgt_destroy(result);
+ return (NULL);
+ }
+
+ /*
+ * Register the target with STMF but not until we have all the
+ * TPGT bindings and any other additional config setup. STMF
+ * may immediately ask us to go online.
+ */
+ if (stmf_register_local_port(lport) != STMF_SUCCESS) {
+ iscsit_tgt_destroy(result);
+ return (NULL);
+ }
+ result->target_stmf_lport_registered = 1;
+
+ iscsit_global_hold();
+
+ return (result);
+}
+
+static idm_status_t
+iscsit_tgt_modify(iscsit_tgt_t *tgt, it_tgt_t *cfg_tgt)
+{
+ idm_status_t idmrc = IDM_STATUS_SUCCESS;
+ list_t tpgt_del_list;
+
+ /* Merge TPGT */
+ list_create(&tpgt_del_list, sizeof (iscsit_tpgt_t),
+ offsetof(iscsit_tpgt_t, tpgt_delete_ln));
+
+ mutex_enter(&tgt->target_mutex);
+ if (tgt->target_props) {
+ nvlist_free(tgt->target_props);
+ tgt->target_props = NULL;
+ }
+ (void) nvlist_dup(cfg_tgt->tgt_properties, &tgt->target_props,
+ KM_SLEEP);
+
+ if ((idmrc = iscsit_tgt_merge_tpgt(tgt, cfg_tgt, &tpgt_del_list)) !=
+ IDM_STATUS_SUCCESS) {
+ /* This should never happen */
+ cmn_err(CE_WARN, "Fail to configure TPGTs for "
+ "target %s, the target modification could not be "
+ "completed.", tgt->target_name);
+ }
+
+ mutex_exit(&tgt->target_mutex);
+
+ iscsit_config_destroy_tpgts(&tpgt_del_list);
+
+ /*
+ * If the target is truly modified (not newly created),
+ * inform iSNS to update the target registration.
+ */
+ if ((tgt->target_generation > 0) &&
+ (cfg_tgt->tgt_generation > tgt->target_generation)) {
+ iscsit_isns_target_update(tgt);
+ }
+
+ tgt->target_generation = cfg_tgt->tgt_generation;
+
+ return (idmrc);
+}
+
+void
+iscsit_config_destroy_tpgts(list_t *tpgt_del_list)
+{
+ iscsit_tpgt_t *tpgt, *next_tpgt;
+
+ for (tpgt = list_head(tpgt_del_list);
+ tpgt != NULL;
+ tpgt = next_tpgt) {
+ next_tpgt = list_next(tpgt_del_list, tpgt);
+
+ list_remove(tpgt_del_list, tpgt);
+ idm_refcnt_wait_ref(&tpgt->tpgt_refcnt);
+ iscsit_tpgt_destroy(tpgt);
+ }
+}
+
+void
+iscsit_tgt_unref(void *tgt_void)
+{
+ iscsit_tgt_t *tgt = tgt_void;
+
+ ISCSIT_GLOBAL_LOCK(RW_WRITER);
+ list_remove(&iscsit_global.global_deleted_target_list, tgt);
+ ISCSIT_GLOBAL_UNLOCK();
+ iscsit_tgt_destroy(tgt);
+}
+
+void
+iscsit_tgt_async_wait_ref(iscsit_tgt_t *tgt, idm_refcnt_cb_t *cb_func)
+{
+ idm_refcnt_async_wait_ref(&tgt->target_refcnt, cb_func);
+}
+
+static void
+iscsit_tgt_destroy(iscsit_tgt_t *tgt)
+{
+ iscsit_tpgt_t *tpgt, *next_tpgt;
+
+ ASSERT(tgt->target_state == TS_DELETING);
+
+ /*
+ * Destroy all target portal group tags
+ */
+ mutex_enter(&tgt->target_mutex);
+ for (tpgt = avl_first(&tgt->target_tpgt_list);
+ tpgt != NULL;
+ tpgt = next_tpgt) {
+ next_tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt);
+ avl_remove(&tgt->target_tpgt_list, tpgt);
+ iscsit_tpgt_destroy(tpgt);
+ }
+
+ if (tgt->target_props) {
+ nvlist_free(tgt->target_props);
+ }
+ mutex_exit(&tgt->target_mutex);
+
+ /*
+ * Destroy target
+ */
+ idm_refcnt_destroy(&tgt->target_sess_refcnt);
+ idm_refcnt_destroy(&tgt->target_refcnt);
+ list_destroy(&tgt->target_events);
+ avl_destroy(&tgt->target_tpgt_list);
+ avl_destroy(&tgt->target_sess_list);
+ mutex_destroy(&tgt->target_mutex);
+ stmf_free(tgt->target_stmf_lport); /* Also frees "tgt' */
+ iscsit_global_rele();
+}
+
+void
+iscsit_tgt_hold(iscsit_tgt_t *tgt)
+{
+ idm_refcnt_hold(&tgt->target_refcnt);
+}
+
+void
+iscsit_tgt_rele(iscsit_tgt_t *tgt)
+{
+ idm_refcnt_rele(&tgt->target_refcnt);
+}
+
+int
+iscsit_tgt_avl_compare(const void *void_tgt1, const void *void_tgt2)
+{
+ const iscsit_tgt_t *tgt1 = void_tgt1;
+ const iscsit_tgt_t *tgt2 = void_tgt2;
+ int result;
+
+ /*
+ * Sort by ISID first then TSIH
+ */
+ result = strcmp(tgt1->target_name, tgt2->target_name);
+ if (result < 0) {
+ return (-1);
+ } else if (result > 0) {
+ return (1);
+ }
+
+ return (0);
+}
+
+
+iscsit_tpgt_t *
+iscsit_tgt_lookup_tpgt(iscsit_tgt_t *tgt, uint16_t tag)
+{
+ iscsit_tpgt_t *result;
+
+ mutex_enter(&tgt->target_mutex);
+ result = iscsit_tgt_lookup_tpgt_locked(tgt, tag);
+ mutex_exit(&tgt->target_mutex);
+
+ return (result);
+}
+
+static iscsit_tpgt_t *
+iscsit_tgt_lookup_tpgt_locked(iscsit_tgt_t *tgt, uint16_t tag)
+{
+ iscsit_tpgt_t tmp_tpgt;
+ iscsit_tpgt_t *result;
+
+ /* Caller holds tgt->target_mutex */
+ tmp_tpgt.tpgt_tag = tag;
+ if ((result = avl_find(&tgt->target_tpgt_list, &tmp_tpgt, NULL)) !=
+ NULL) {
+ iscsit_tpgt_hold(result);
+ }
+
+ return (result);
+}
+
+iscsit_portal_t *
+iscsit_tgt_lookup_portal(iscsit_tgt_t *tgt, struct sockaddr_storage *sa,
+ iscsit_tpgt_t **output_tpgt)
+{
+ iscsit_tpgt_t *tpgt;
+ iscsit_portal_t *portal;
+
+ /* Caller holds tgt->target_mutex */
+ ASSERT(mutex_owned(&tgt->target_mutex));
+ for (tpgt = avl_first(&tgt->target_tpgt_list);
+ tpgt != NULL;
+ tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) {
+ portal = iscsit_tpg_portal_lookup(tpgt->tpgt_tpg, sa);
+ if (portal) {
+ iscsit_tpgt_hold(tpgt);
+ *output_tpgt = tpgt;
+ return (portal);
+ }
+ }
+
+ return (NULL);
+}
+
+
+void
+iscsit_tgt_bind_sess(iscsit_tgt_t *tgt, iscsit_sess_t *sess)
+{
+ if (tgt) {
+ sess->ist_lport = tgt->target_stmf_lport;
+ iscsit_tgt_hold(tgt);
+ idm_refcnt_hold(&tgt->target_sess_refcnt);
+ mutex_enter(&tgt->target_mutex);
+ avl_add(&tgt->target_sess_list, sess);
+ mutex_exit(&tgt->target_mutex);
+ } else {
+ /* Discovery session */
+ sess->ist_lport = NULL;
+ iscsit_global_hold();
+ ISCSIT_GLOBAL_LOCK(RW_WRITER);
+ avl_add(&iscsit_global.global_discovery_sessions, sess);
+ ISCSIT_GLOBAL_UNLOCK();
+ }
+}
+
+void
+iscsit_tgt_unbind_sess(iscsit_tgt_t *tgt, iscsit_sess_t *sess)
+{
+ if (tgt) {
+ mutex_enter(&tgt->target_mutex);
+ avl_remove(&tgt->target_sess_list, sess);
+ mutex_exit(&tgt->target_mutex);
+ sess->ist_tgt = (iscsit_tgt_t *)SESS_UNBOUND_FROM_TGT;
+ idm_refcnt_rele(&tgt->target_sess_refcnt);
+ iscsit_tgt_rele(tgt);
+ } else {
+ /* Discovery session */
+ ISCSIT_GLOBAL_LOCK(RW_WRITER);
+ avl_remove(&iscsit_global.global_discovery_sessions, sess);
+ ISCSIT_GLOBAL_UNLOCK();
+ iscsit_global_rele();
+ }
+}
+
+#define LOCK_FOR_SESS_LOOKUP(lookup_tgt) { \
+ if ((lookup_tgt) == NULL) { \
+ ISCSIT_GLOBAL_LOCK(RW_READER); \
+ } else { \
+ mutex_enter(&(lookup_tgt)->target_mutex); \
+ } \
+}
+
+#define UNLOCK_FOR_SESS_LOOKUP(lookup_tgt) { \
+ if ((lookup_tgt) == NULL) { \
+ ISCSIT_GLOBAL_UNLOCK(); \
+ } else { \
+ mutex_exit(&(lookup_tgt)->target_mutex); \
+ } \
+}
+
+iscsit_sess_t *
+iscsit_tgt_lookup_sess(iscsit_tgt_t *tgt, char *initiator_name,
+ uint8_t *isid, uint16_t tsih, uint16_t tag)
+{
+ iscsit_sess_t tmp_sess;
+ avl_tree_t *sess_avl;
+ avl_index_t where;
+ iscsit_sess_t *result;
+
+ /*
+ * If tgt is NULL then we are looking for a discovery session
+ */
+ if (tgt == NULL) {
+ sess_avl = &iscsit_global.global_discovery_sessions;
+ } else {
+ sess_avl = &tgt->target_sess_list;
+ }
+
+ LOCK_FOR_SESS_LOOKUP(tgt);
+ if (avl_numnodes(sess_avl) == NULL) {
+ UNLOCK_FOR_SESS_LOOKUP(tgt);
+ return (NULL);
+ }
+
+ /*
+ * We'll try to find a session matching ISID + TSIH first. If we
+ * can't find one then we will return the closest match. If the
+ * caller needs an exact match it must compare the TSIH after
+ * the session is returned.
+ *
+ * The reason we do this "fuzzy matching" is to allow matching
+ * sessions with different TSIH values on the same AVL list. This
+ * makes session reinstatement much easier since the new session can
+ * live on the list at the same time as the old session is cleaning up.
+ */
+ bcopy(isid, tmp_sess.ist_isid, ISCSI_ISID_LEN);
+ tmp_sess.ist_initiator_name = initiator_name;
+ tmp_sess.ist_tsih = tsih;
+ tmp_sess.ist_tpgt_tag = tag;
+
+ result = avl_find(sess_avl, &tmp_sess, &where);
+ if (result != NULL) {
+ iscsit_sess_hold(result);
+ UNLOCK_FOR_SESS_LOOKUP(tgt);
+ return (result);
+ }
+
+ /*
+ * avl_find_nearest() may return a result with a different ISID so
+ * we should only return a result if the name and ISID match
+ */
+ result = avl_nearest(sess_avl, where, AVL_BEFORE);
+ if ((result != NULL) &&
+ (strcmp(result->ist_initiator_name, initiator_name) == 0) &&
+ (memcmp(result->ist_isid, isid, ISCSI_ISID_LEN) == 0) &&
+ (result->ist_tpgt_tag == tag)) {
+ iscsit_sess_hold(result);
+ UNLOCK_FOR_SESS_LOOKUP(tgt);
+ return (result);
+ }
+
+ result = avl_nearest(sess_avl, where, AVL_AFTER);
+ if ((result != NULL) &&
+ (strcmp(result->ist_initiator_name, initiator_name) == 0) &&
+ (memcmp(result->ist_isid, isid, ISCSI_ISID_LEN) == 0) &&
+ (result->ist_tpgt_tag == tag)) {
+ iscsit_sess_hold(result);
+ UNLOCK_FOR_SESS_LOOKUP(tgt);
+ return (result);
+ }
+
+ UNLOCK_FOR_SESS_LOOKUP(tgt);
+
+ return (NULL);
+}
+
+static idm_status_t
+iscsit_tgt_merge_tpgt(iscsit_tgt_t *tgt, it_tgt_t *cfg_tgt,
+ list_t *tpgt_del_list)
+{
+ iscsit_tpgt_t *tpgt, *next_tpgt;
+ it_tpgt_t *cfg_tpgt;
+
+ /*
+ * 1. >> Lock <<
+ * 2. Removing all objects and place on a temp list
+ * 3. Add new objects
+ * 4. >> Unlock <<
+ * 5. tpgt_del_list contains deleted objects
+ */
+ ASSERT(avl_is_empty(&tgt->target_tpgt_list) ||
+ (tpgt_del_list != NULL));
+
+ if (tpgt_del_list) {
+ for (tpgt = avl_first(&tgt->target_tpgt_list);
+ tpgt != NULL; tpgt = next_tpgt) {
+ next_tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt);
+ avl_remove(&tgt->target_tpgt_list, tpgt);
+ if (tgt->target_state == TS_STMF_ONLINE) {
+ tpgt->tpgt_needs_tpg_offline = B_TRUE;
+ }
+ list_insert_tail(tpgt_del_list, tpgt);
+ }
+ }
+
+ if (cfg_tgt->tgt_tpgt_list == NULL) {
+ tpgt = iscsit_tpgt_create_default();
+ if (tgt->target_state == TS_STMF_ONLINE) {
+ (void) iscsit_tpg_online(tpgt->tpgt_tpg);
+ }
+ avl_add(&tgt->target_tpgt_list, tpgt);
+ } else {
+ /* Add currently defined TPGTs */
+ for (cfg_tpgt = cfg_tgt->tgt_tpgt_list;
+ cfg_tpgt != NULL;
+ cfg_tpgt = cfg_tpgt->tpgt_next) {
+ tpgt = iscsit_tpgt_create(cfg_tpgt);
+ if (tpgt == NULL)
+ goto tpgt_merge_error;
+ if (tgt->target_state == TS_STMF_ONLINE) {
+ (void) iscsit_tpg_online(tpgt->tpgt_tpg);
+ }
+ avl_add(&tgt->target_tpgt_list, tpgt);
+ }
+ }
+
+ return (IDM_STATUS_SUCCESS);
+
+tpgt_merge_error:
+ /*
+ * This is a problem in configuration we received so it should never
+ * happen. That's good because we're probably no longer in a state
+ * requested by the user -- all the unbind operations have already
+ * taken place.
+ */
+ return (IDM_STATUS_FAIL);
+}
+
+static iscsit_tpgt_t *
+iscsit_tpgt_create(it_tpgt_t *cfg_tpgt)
+{
+ iscsit_tpg_t *tpg;
+ iscsit_tpgt_t *result;
+
+ /* This takes a reference on the TPG */
+ tpg = iscsit_tpg_lookup_locked(cfg_tpgt->tpgt_tpg_name);
+ if (tpg == NULL)
+ return (NULL);
+
+ result = kmem_zalloc(sizeof (*result), KM_SLEEP);
+
+ result->tpgt_tpg = tpg;
+ result->tpgt_tag = cfg_tpgt->tpgt_tag;
+
+ return (result);
+}
+
+iscsit_tpgt_t *
+iscsit_tpgt_create_default()
+{
+ iscsit_tpgt_t *result;
+
+ result = kmem_zalloc(sizeof (*result), KM_SLEEP);
+
+ result->tpgt_tpg = iscsit_global.global_default_tpg;
+ iscsit_tpg_hold(result->tpgt_tpg);
+ result->tpgt_tag = ISCSIT_DEFAULT_TPGT;
+
+ return (result);
+}
+
+void
+iscsit_tpgt_destroy(iscsit_tpgt_t *tpgt)
+{
+ if (tpgt->tpgt_needs_tpg_offline) {
+ iscsit_tpg_offline(tpgt->tpgt_tpg);
+ }
+ iscsit_tpg_rele(tpgt->tpgt_tpg);
+ kmem_free(tpgt, sizeof (*tpgt));
+}
+
+void
+iscsit_tpgt_hold(iscsit_tpgt_t *tpgt)
+{
+ idm_refcnt_hold(&tpgt->tpgt_refcnt);
+}
+
+void
+iscsit_tpgt_rele(iscsit_tpgt_t *tpgt)
+{
+ idm_refcnt_rele(&tpgt->tpgt_refcnt);
+}
+
+int
+iscsit_tpgt_avl_compare(const void *void_tpgt1, const void *void_tpgt2)
+{
+ const iscsit_tpgt_t *tpgt1 = void_tpgt1;
+ const iscsit_tpgt_t *tpgt2 = void_tpgt2;
+
+ if (tpgt1->tpgt_tag < tpgt2->tpgt_tag)
+ return (-1);
+ else if (tpgt1->tpgt_tag > tpgt2->tpgt_tag)
+ return (1);
+
+ return (0);
+}
+
+static idm_status_t
+iscsit_tgt_online(iscsit_tgt_t *tgt)
+{
+ iscsit_tpgt_t *tpgt, *tpgt_fail;
+ idm_status_t rc;
+
+ mutex_enter(&tgt->target_mutex);
+
+ ASSERT(tgt->target_sess_list.avl_numnodes == 0);
+ idm_refcnt_reset(&tgt->target_sess_refcnt);
+ for (tpgt = avl_first(&tgt->target_tpgt_list);
+ tpgt != NULL;
+ tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) {
+ rc = iscsit_tpg_online(tpgt->tpgt_tpg);
+ if (rc != IDM_STATUS_SUCCESS) {
+ tpgt_fail = tpgt;
+ goto tgt_online_fail;
+ }
+ }
+
+ mutex_exit(&tgt->target_mutex);
+
+ return (IDM_STATUS_SUCCESS);
+
+tgt_online_fail:
+ /* Offline all the tpgs we successfully onlined up to the failure */
+ for (tpgt = avl_first(&tgt->target_tpgt_list);
+ tpgt != tpgt_fail;
+ tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) {
+ iscsit_tpg_offline(tpgt->tpgt_tpg);
+ }
+ mutex_exit(&tgt->target_mutex);
+ return (rc);
+}
+
+static void
+iscsit_tgt_offline_cb(void *tgt_void)
+{
+ iscsit_tgt_t *tgt = tgt_void;
+ stmf_change_status_t scs;
+
+ iscsit_tgt_sm_event(tgt, TE_OFFLINE_COMPLETE);
+
+ scs.st_completion_status = STMF_SUCCESS;
+ scs.st_additional_info = NULL;
+ (void) stmf_ctl(STMF_CMD_LPORT_OFFLINE_COMPLETE,
+ tgt->target_stmf_lport, &scs);
+}
+
+static void
+iscsit_tgt_offline(iscsit_tgt_t *tgt)
+{
+ iscsit_tpgt_t *tpgt;
+ iscsit_sess_t *ist;
+
+ mutex_enter(&tgt->target_mutex);
+
+ /* Offline target portal groups */
+ for (tpgt = avl_first(&tgt->target_tpgt_list);
+ tpgt != NULL;
+ tpgt = AVL_NEXT(&tgt->target_tpgt_list, tpgt)) {
+ iscsit_tpg_offline(tpgt->tpgt_tpg);
+ }
+
+ /* Close any active sessions */
+ for (ist = avl_first(&tgt->target_sess_list);
+ ist != NULL;
+ ist = AVL_NEXT(&tgt->target_sess_list, ist)) {
+ /*
+ * This is not a synchronous operation but after all
+ * sessions have been cleaned up there will be no
+ * more session-related holds on the target.
+ */
+ iscsit_sess_close(ist);
+ }
+
+ mutex_exit(&tgt->target_mutex);
+
+ /*
+ * Wait for all the sessions to quiesce.
+ */
+ idm_refcnt_async_wait_ref(&tgt->target_sess_refcnt,
+ &iscsit_tgt_offline_cb);
+}
+
+it_cfg_status_t
+iscsit_config_merge_tpg(it_config_t *cfg, list_t *tpg_del_list)
+{
+ it_tpg_t *cfg_tpg;
+ iscsit_tpg_t *tpg, *next_tpg;
+
+ /*
+ * 1. >> Lock <<
+ * 2. Removing deleted objects and place on a temp list
+ * 3. Add new objects
+ * 4. >> Unlock <<
+ * 5. tpg_del_list contains objects to destroy
+ */
+ for (tpg = avl_first(&iscsit_global.global_tpg_list);
+ tpg != NULL;
+ tpg = next_tpg) {
+ next_tpg = AVL_NEXT(&iscsit_global.global_tpg_list, tpg);
+
+ if (it_tpg_lookup(cfg, tpg->tpg_name) == NULL) {
+ /*
+ * The policy around when to allow a target portal
+ * group to be deleted is implemented in libiscsit.
+ * By the time the request gets to the kernel module
+ * we expect that it conforms to policy so we will
+ * cleanup all references to TPG and destroy it if it
+ * is possible to do so.
+ *
+ */
+ avl_remove(&iscsit_global.global_tpg_list, tpg);
+ list_insert_tail(tpg_del_list, tpg);
+ }
+ }
+
+ /* Now walk through the list of configured target portal groups */
+ for (cfg_tpg = cfg->config_tpg_list;
+ cfg_tpg != NULL;
+ cfg_tpg = cfg_tpg->tpg_next) {
+ /* See if we have an existing target portal group */
+ tpg = iscsit_tpg_lookup_locked(cfg_tpg->tpg_name);
+
+ if (tpg == NULL) {
+ tpg = iscsit_tpg_create(cfg_tpg);
+ ASSERT(tpg != NULL);
+ avl_add(&iscsit_global.global_tpg_list, tpg);
+ } else {
+ mutex_enter(&tpg->tpg_mutex);
+ iscsit_tpg_modify(tpg, cfg_tpg);
+ mutex_exit(&tpg->tpg_mutex);
+ iscsit_tpg_rele(tpg);
+ }
+ }
+
+ return (ITCFG_SUCCESS);
+}
+
+
+void
+iscsit_config_destroy_tpgs(list_t *tpg_del_list)
+{
+ iscsit_tpg_t *tpg, *next_tpg;
+
+ /* Now finish destroying the target portal groups */
+ for (tpg = list_head(tpg_del_list);
+ tpg != NULL;
+ tpg = next_tpg) {
+ next_tpg = list_next(tpg_del_list, tpg);
+ list_remove(tpg_del_list, tpg);
+ idm_refcnt_wait_ref(&tpg->tpg_refcnt);
+
+ /* Kill it */
+ iscsit_tpg_destroy(tpg);
+ }
+}
+
+iscsit_tpg_t *
+iscsit_tpg_lookup(char *tpg_name)
+{
+ iscsit_tpg_t *result;
+
+ ISCSIT_GLOBAL_LOCK(RW_READER);
+ result = iscsit_tpg_lookup_locked(tpg_name);
+ ISCSIT_GLOBAL_UNLOCK();
+
+ return (result);
+}
+
+static iscsit_tpg_t *
+iscsit_tpg_lookup_locked(char *tpg_name)
+{
+ iscsit_tpg_t tmp_tpg;
+ iscsit_tpg_t *result;
+
+ (void) strlcpy(tmp_tpg.tpg_name, tpg_name, MAX_ISCSI_NODENAMELEN);
+ if ((result = avl_find(&iscsit_global.global_tpg_list,
+ &tmp_tpg, NULL)) != NULL) {
+ iscsit_tpg_hold(result);
+ }
+
+ return (result);
+}
+
+iscsit_tpg_t *
+iscsit_tpg_create(it_tpg_t *cfg_tpg)
+{
+ iscsit_tpg_t *tpg;
+
+ tpg = kmem_zalloc(sizeof (*tpg), KM_SLEEP);
+
+ mutex_init(&tpg->tpg_mutex, NULL, MUTEX_DEFAULT, NULL);
+ (void) strlcpy(tpg->tpg_name, cfg_tpg->tpg_name, MAX_TPG_NAMELEN);
+ avl_create(&tpg->tpg_portal_list, iscsit_portal_avl_compare,
+ sizeof (iscsit_portal_t), offsetof(iscsit_portal_t, portal_tpg_ln));
+ idm_refcnt_init(&tpg->tpg_refcnt, tpg);
+
+ mutex_enter(&tpg->tpg_mutex);
+ iscsit_tpg_modify(tpg, cfg_tpg);
+ mutex_exit(&tpg->tpg_mutex);
+ iscsit_global_hold();
+
+ return (tpg);
+}
+
+static void
+iscsit_tpg_modify(iscsit_tpg_t *tpg, it_tpg_t *cfg_tpg)
+{
+ iscsit_portal_t *portal, *next_portal;
+ it_portal_t *cfg_portal;
+
+ /* Update portals */
+ for (portal = avl_first(&tpg->tpg_portal_list);
+ portal != NULL;
+ portal = next_portal) {
+ next_portal = AVL_NEXT(&tpg->tpg_portal_list, portal);
+ if (it_portal_lookup(cfg_tpg, &portal->portal_addr) == NULL) {
+ avl_remove(&tpg->tpg_portal_list, portal);
+ iscsit_portal_destroy(portal);
+ }
+ }
+
+ for (cfg_portal = cfg_tpg->tpg_portal_list;
+ cfg_portal != NULL;
+ cfg_portal = cfg_portal->next) {
+ if ((portal = iscsit_tpg_portal_lookup_locked(tpg,
+ &cfg_portal->portal_addr)) == NULL) {
+ (void) iscsit_portal_create(tpg,
+ &cfg_portal->portal_addr);
+ } else {
+ iscsit_portal_rele(portal);
+ }
+ }
+}
+
+void
+iscsit_tpg_destroy(iscsit_tpg_t *tpg)
+{
+ iscsit_portal_t *portal, *next_portal;
+
+ for (portal = avl_first(&tpg->tpg_portal_list);
+ portal != NULL;
+ portal = next_portal) {
+ next_portal = AVL_NEXT(&tpg->tpg_portal_list, portal);
+ avl_remove(&tpg->tpg_portal_list, portal);
+ iscsit_portal_destroy(portal);
+ }
+
+ idm_refcnt_wait_ref(&tpg->tpg_refcnt);
+ idm_refcnt_destroy(&tpg->tpg_refcnt);
+ avl_destroy(&tpg->tpg_portal_list);
+ mutex_destroy(&tpg->tpg_mutex);
+ kmem_free(tpg, sizeof (*tpg));
+ iscsit_global_rele();
+}
+
+void
+iscsit_tpg_hold(iscsit_tpg_t *tpg)
+{
+ idm_refcnt_hold(&tpg->tpg_refcnt);
+}
+
+void
+iscsit_tpg_rele(iscsit_tpg_t *tpg)
+{
+ idm_refcnt_rele(&tpg->tpg_refcnt);
+}
+
+iscsit_tpg_t *
+iscsit_tpg_createdefault()
+{
+ iscsit_tpg_t *tpg;
+
+ tpg = kmem_zalloc(sizeof (*tpg), KM_SLEEP);
+
+ mutex_init(&tpg->tpg_mutex, NULL, MUTEX_DEFAULT, NULL);
+ (void) strlcpy(tpg->tpg_name, ISCSIT_DEFAULT_TPG, MAX_TPG_NAMELEN);
+ avl_create(&tpg->tpg_portal_list, iscsit_portal_avl_compare,
+ sizeof (iscsit_portal_t), offsetof(iscsit_portal_t, portal_tpg_ln));
+ idm_refcnt_init(&tpg->tpg_refcnt, tpg);
+
+ /* Now create default portal */
+ if (iscsit_portal_create(tpg, NULL) == NULL) {
+ iscsit_tpg_destroy(tpg);
+ return (NULL);
+ }
+
+ return (tpg);
+}
+
+void
+iscsit_tpg_destroydefault(iscsit_tpg_t *tpg)
+{
+ iscsit_portal_t *portal;
+
+ portal = avl_first(&tpg->tpg_portal_list);
+ ASSERT(portal != NULL);
+ avl_remove(&tpg->tpg_portal_list, portal);
+ iscsit_portal_destroy(portal);
+
+ idm_refcnt_wait_ref(&tpg->tpg_refcnt);
+ idm_refcnt_destroy(&tpg->tpg_refcnt);
+ avl_destroy(&tpg->tpg_portal_list);
+ mutex_destroy(&tpg->tpg_mutex);
+ kmem_free(tpg, sizeof (*tpg));
+}
+
+int
+iscsit_tpg_avl_compare(const void *void_tpg1, const void *void_tpg2)
+{
+ const iscsit_tpg_t *tpg1 = void_tpg1;
+ const iscsit_tpg_t *tpg2 = void_tpg2;
+ int result;
+
+ /*
+ * Sort by ISID first then TSIH
+ */
+ result = strcmp(tpg1->tpg_name, tpg2->tpg_name);
+ if (result < 0) {
+ return (-1);
+ } else if (result > 0) {
+ return (1);
+ }
+
+ return (0);
+}
+
+idm_status_t
+iscsit_tpg_online(iscsit_tpg_t *tpg)
+{
+ iscsit_portal_t *portal, *portal_fail;
+ idm_status_t rc;
+
+ mutex_enter(&tpg->tpg_mutex);
+ if (tpg->tpg_online == 0) {
+ for (portal = avl_first(&tpg->tpg_portal_list);
+ portal != NULL;
+ portal = AVL_NEXT(&tpg->tpg_portal_list, portal)) {
+ rc = iscsit_portal_online(portal);
+ if (rc != IDM_STATUS_SUCCESS) {
+ portal_fail = portal;
+ goto tpg_online_fail;
+ }
+ }
+ }
+ tpg->tpg_online++;
+
+ mutex_exit(&tpg->tpg_mutex);
+ return (IDM_STATUS_SUCCESS);
+
+tpg_online_fail:
+ /* Offline all the portals we successfully onlined up to the failure */
+ for (portal = avl_first(&tpg->tpg_portal_list);
+ portal != portal_fail;
+ portal = AVL_NEXT(&tpg->tpg_portal_list, portal)) {
+ iscsit_portal_offline(portal);
+ }
+ mutex_exit(&tpg->tpg_mutex);
+ return (rc);
+}
+
+void
+iscsit_tpg_offline(iscsit_tpg_t *tpg)
+{
+ iscsit_portal_t *portal;
+
+ mutex_enter(&tpg->tpg_mutex);
+ tpg->tpg_online--;
+ if (tpg->tpg_online == 0) {
+ for (portal = avl_first(&tpg->tpg_portal_list);
+ portal != NULL;
+ portal = AVL_NEXT(&tpg->tpg_portal_list, portal)) {
+ iscsit_portal_offline(portal);
+ }
+ }
+ mutex_exit(&tpg->tpg_mutex);
+}
+
+iscsit_portal_t *
+iscsit_tpg_portal_lookup(iscsit_tpg_t *tpg, struct sockaddr_storage *sa)
+{
+ iscsit_portal_t *result;
+
+ mutex_enter(&tpg->tpg_mutex);
+ result = iscsit_tpg_portal_lookup_locked(tpg, sa);
+ mutex_exit(&tpg->tpg_mutex);
+
+ return (result);
+}
+
+static iscsit_portal_t *
+iscsit_tpg_portal_lookup_locked(iscsit_tpg_t *tpg,
+ struct sockaddr_storage *sa)
+{
+ iscsit_portal_t tmp_portal;
+ iscsit_portal_t *result;
+
+ /* Caller holds tpg->tpg_mutex */
+ bcopy(sa, &tmp_portal.portal_addr, sizeof (*sa));
+ if ((result = avl_find(&tpg->tpg_portal_list, &tmp_portal, NULL)) !=
+ NULL) {
+ iscsit_portal_hold(result);
+ }
+
+ return (result);
+}
+
+iscsit_portal_t *
+iscsit_portal_create(iscsit_tpg_t *tpg, struct sockaddr_storage *sa)
+{
+ iscsit_portal_t *portal;
+
+ portal = kmem_zalloc(sizeof (*portal), KM_SLEEP);
+ /*
+ * If (sa == NULL) then we are being asked to create the default
+ * portal -- targets will use this portal when no portals are
+ * explicitly configured.
+ */
+ if (sa != NULL) {
+ bcopy(sa, &portal->portal_addr, sizeof (*sa));
+ }
+
+ idm_refcnt_init(&portal->portal_refcnt, portal);
+
+ /*
+ * Add this portal to the list
+ */
+ avl_add(&tpg->tpg_portal_list, portal);
+
+ return (portal);
+}
+
+void
+iscsit_portal_destroy(iscsit_portal_t *portal)
+{
+ ASSERT(portal->portal_svc == NULL);
+ idm_refcnt_destroy(&portal->portal_refcnt);
+ kmem_free(portal, sizeof (*portal));
+}
+
+void
+iscsit_portal_hold(iscsit_portal_t *portal)
+{
+ idm_refcnt_hold(&portal->portal_refcnt);
+}
+
+void
+iscsit_portal_rele(iscsit_portal_t *portal)
+{
+ idm_refcnt_rele(&portal->portal_refcnt);
+}
+
+int
+iscsit_portal_avl_compare(const void *void_portal1, const void *void_portal2)
+{
+ const iscsit_portal_t *portal1 = void_portal1;
+ const iscsit_portal_t *portal2 = void_portal2;
+ const struct sockaddr_storage *ss1, *ss2;
+ const struct in_addr *in1, *in2;
+ const struct in6_addr *in61, *in62;
+ int i;
+
+ /*
+ * Compare ports, then address family, then ip address
+ */
+ ss1 = &portal1->portal_addr;
+ ss2 = &portal2->portal_addr;
+ if (((struct sockaddr_in *)ss1)->sin_port !=
+ ((struct sockaddr_in *)ss2)->sin_port) {
+ if (((struct sockaddr_in *)ss1)->sin_port >
+ ((struct sockaddr_in *)ss2)->sin_port)
+ return (1);
+ else
+ return (-1);
+ }
+
+ /*
+ * ports are the same
+ */
+ if (ss1->ss_family != ss2->ss_family) {
+ if (ss1->ss_family == AF_INET)
+ return (1);
+ else
+ return (-1);
+ }
+ /*
+ * address families are the same
+ */
+ if (ss1->ss_family == AF_INET) {
+ in1 = &((struct sockaddr_in *)ss1)->sin_addr;
+ in2 = &((struct sockaddr_in *)ss2)->sin_addr;
+
+ if (in1->s_addr > in2->s_addr)
+ return (1);
+ else if (in1->s_addr < in2->s_addr)
+ return (-1);
+ else
+ return (0);
+ } else if (ss1->ss_family == AF_INET6) {
+ in61 = &((struct sockaddr_in6 *)ss1)->sin6_addr;
+ in62 = &((struct sockaddr_in6 *)ss2)->sin6_addr;
+
+ for (i = 0; i < 4; i++) {
+ if (in61->s6_addr32[i] > in62->s6_addr32[i])
+ return (1);
+ else if (in61->s6_addr32[i] < in62->s6_addr32[i])
+ return (-1);
+ }
+ return (0);
+ } else
+ cmn_err(CE_WARN,
+ "iscsit_portal_avl_compare: unknown ss_family %d",
+ ss1->ss_family);
+
+ return (1);
+}
+
+
+idm_status_t
+iscsit_portal_online(iscsit_portal_t *portal)
+{
+ idm_status_t rc;
+ idm_svc_t *svc;
+ idm_svc_req_t sr;
+ uint16_t port;
+ struct sockaddr_in *sin;
+
+ /* Caller holds parent TPG mutex */
+ if (portal->portal_online == 0) {
+ /*
+ * If there is no existing IDM service instance for this port,
+ * create one. If the service exists, then the lookup,
+ * creates a reference on the existing service.
+ */
+ sin = (struct sockaddr_in *)&portal->portal_addr;
+ port = ntohs(sin->sin_port);
+ if (port == 0)
+ port = ISCSI_LISTEN_PORT;
+ ASSERT(portal->portal_svc == NULL);
+ if ((svc = idm_tgt_svc_lookup(port)) == NULL) {
+ sr.sr_port = port;
+ sr.sr_li = iscsit_global.global_li;
+ sr.sr_conn_ops.icb_rx_scsi_cmd = &iscsit_op_scsi_cmd;
+ sr.sr_conn_ops.icb_rx_scsi_rsp = NULL;
+ sr.sr_conn_ops.icb_rx_misc = &iscsit_rx_pdu;
+ sr.sr_conn_ops.icb_rx_error = &iscsit_rx_pdu_error;
+ sr.sr_conn_ops.icb_task_aborted = &iscsit_task_aborted;
+ sr.sr_conn_ops.icb_client_notify =
+ &iscsit_client_notify;
+ sr.sr_conn_ops.icb_build_hdr = &iscsit_build_hdr;
+
+ if (idm_tgt_svc_create(&sr, &svc) !=
+ IDM_STATUS_SUCCESS) {
+ return (IDM_STATUS_FAIL);
+ }
+
+ /* Get reference on the service we just created */
+ idm_tgt_svc_hold(svc);
+ }
+ if ((rc = idm_tgt_svc_online(svc)) != IDM_STATUS_SUCCESS) {
+ idm_tgt_svc_rele_and_destroy(svc);
+ return (IDM_STATUS_FAIL);
+ }
+ portal->portal_svc = svc;
+
+ /*
+ * Only call iSNS for first online
+ */
+ iscsit_isns_portal_online(portal);
+ }
+
+ portal->portal_online++;
+
+ return (rc);
+}
+
+void
+iscsit_portal_offline(iscsit_portal_t *portal)
+{
+ portal->portal_online--;
+
+ if (portal->portal_online == 0) {
+ /*
+ * Only call iSNS for last offline
+ */
+ iscsit_isns_portal_offline(portal);
+ idm_tgt_svc_offline(portal->portal_svc);
+ /* If service is unreferenced, destroy it too */
+ idm_tgt_svc_rele_and_destroy(portal->portal_svc);
+ portal->portal_svc = NULL;
+ }
+
+}
+
+it_cfg_status_t
+iscsit_config_merge_ini(it_config_t *cfg)
+{
+ iscsit_ini_t *ini, *next_ini;
+ it_ini_t *cfg_ini;
+
+ /*
+ * Initiator objects are so simple we will just destroy all the current
+ * objects and build new ones. Nothing should ever reference an
+ * initator object.. instead just lookup the initiator object and
+ * grab the properties while holding the global config lock.
+ */
+ for (ini = avl_first(&iscsit_global.global_ini_list);
+ ini != NULL;
+ ini = next_ini) {
+ next_ini = AVL_NEXT(&iscsit_global.global_ini_list, ini);
+ avl_remove(&iscsit_global.global_ini_list, ini);
+ nvlist_free(ini->ini_props);
+ kmem_free(ini, sizeof (*ini));
+ iscsit_global_rele();
+ }
+
+ for (cfg_ini = cfg->config_ini_list;
+ cfg_ini != NULL;
+ cfg_ini = cfg_ini->ini_next) {
+ ini = kmem_zalloc(sizeof (iscsit_ini_t), KM_SLEEP);
+ (void) strlcpy(ini->ini_name, cfg_ini->ini_name,
+ MAX_ISCSI_NODENAMELEN);
+ (void) nvlist_dup(cfg_ini->ini_properties, &ini->ini_props,
+ KM_SLEEP);
+ avl_add(&iscsit_global.global_ini_list, ini);
+ iscsit_global_hold();
+ }
+
+ return (ITCFG_SUCCESS);
+}
+
+int
+iscsit_ini_avl_compare(const void *void_ini1, const void *void_ini2)
+{
+ const iscsit_ini_t *ini1 = void_ini1;
+ const iscsit_ini_t *ini2 = void_ini2;
+ int result;
+
+ /*
+ * Sort by ISID first then TSIH
+ */
+ result = strcmp(ini1->ini_name, ini2->ini_name);
+ if (result < 0) {
+ return (-1);
+ } else if (result > 0) {
+ return (1);
+ }
+
+ return (0);
+}
+
+iscsit_ini_t *
+iscsit_ini_lookup_locked(char *ini_name)
+{
+ iscsit_ini_t tmp_ini;
+ iscsit_ini_t *result;
+
+ /*
+ * Use a dummy target for lookup, filling in all fields used in AVL
+ * comparison.
+ */
+ (void) strlcpy(tmp_ini.ini_name, ini_name, MAX_ISCSI_NODENAMELEN);
+ result = avl_find(&iscsit_global.global_ini_list, &tmp_ini, NULL);
+
+ return (result);
+}
diff --git a/usr/src/uts/common/io/comstar/port/iscsit/radius_auth.h b/usr/src/uts/common/io/comstar/port/iscsit/radius_auth.h
new file mode 100644
index 0000000000..d77b2d23ca
--- /dev/null
+++ b/usr/src/uts/common/io/comstar/port/iscsit/radius_auth.h
@@ -0,0 +1,71 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+#ifndef _RADIUS_AUTH_H
+#define _RADIUS_AUTH_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <netinet/in.h>
+#include <sys/iscsit/chap.h>
+
+/*
+ * Function: iscsit_radius_chap_validate
+ *
+ * Description: To validate a target response given the
+ * associated challenge via the specified
+ * RADIUS server.
+ *
+ * Arguments:
+ * target_chap_name - The CHAP name of the target being authenticated.
+ * initiator_chap_name - The CHAP name of the authenticating initiator.
+ * challenge - The CHAP challenge to which the target responded.
+ * target_response - The target's CHAP response to be validated.
+ * identifier - The identifier associated with the CHAP challenge.
+ * radius_server_ip_address - The IP address of the RADIUS server.
+ * radius_server_port - The port number of the RADIUS server.
+ * radius_shared_secret - The shared secret for accessing the RADIUS server.
+ * radius_shared_secret_len - The length of the shared secret.
+ *
+ * Return: See chap_validation_status_type.
+ */
+chap_validation_status_type
+iscsit_radius_chap_validate(char *target_chap_name,
+ char *initiator_chap_name,
+ uint8_t *challenge,
+ uint32_t challenge_length,
+ uint8_t *target_response,
+ uint32_t response_length,
+ uint8_t identifier,
+ iscsi_ipaddr_t rad_svr_ip_addr,
+ uint32_t rad_svr_port,
+ uint8_t *rad_svr_shared_secret,
+ uint32_t rad_svr_shared_secret_len);
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RADIUS_AUTH_H */
diff --git a/usr/src/uts/common/io/idm/idm.c b/usr/src/uts/common/io/idm/idm.c
new file mode 100644
index 0000000000..bf95bf1481
--- /dev/null
+++ b/usr/src/uts/common/io/idm/idm.c
@@ -0,0 +1,2131 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/cpuvar.h>
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/modctl.h>
+
+#include <sys/socket.h>
+#include <sys/strsubr.h>
+#include <sys/sysmacros.h>
+
+#include <sys/socketvar.h>
+#include <netinet/in.h>
+
+#include <sys/idm/idm.h>
+#include <sys/idm/idm_so.h>
+
+#define IDM_NAME_VERSION "iSCSI Data Mover"
+
+extern struct mod_ops mod_miscops;
+extern struct mod_ops mod_miscops;
+
+static struct modlmisc modlmisc = {
+ &mod_miscops, /* Type of module */
+ IDM_NAME_VERSION
+};
+
+static struct modlinkage modlinkage = {
+ MODREV_1, (void *)&modlmisc, NULL
+};
+
+extern int idm_task_compare(const void *t1, const void *t2);
+extern void idm_wd_thread(void *arg);
+
+static int _idm_init(void);
+static int _idm_fini(void);
+static void idm_buf_bind_in_locked(idm_task_t *idt, idm_buf_t *buf);
+static void idm_buf_bind_out_locked(idm_task_t *idt, idm_buf_t *buf);
+static void idm_buf_unbind_in_locked(idm_task_t *idt, idm_buf_t *buf);
+static void idm_buf_unbind_out_locked(idm_task_t *idt, idm_buf_t *buf);
+static void idm_task_abort_one(idm_conn_t *ic, idm_task_t *idt,
+ idm_abort_type_t abort_type);
+static void idm_task_aborted(idm_task_t *idt, idm_status_t status);
+
+boolean_t idm_conn_logging = 0;
+boolean_t idm_svc_logging = 0;
+
+/*
+ * Potential tuneable for the maximum number of tasks. Default to
+ * IDM_TASKIDS_MAX
+ */
+
+uint32_t idm_max_taskids = IDM_TASKIDS_MAX;
+
+/*
+ * Global list of transport handles
+ * These are listed in preferential order, so we can simply take the
+ * first "it_conn_is_capable" hit. Note also that the order maps to
+ * the order of the idm_transport_type_t list.
+ */
+idm_transport_t idm_transport_list[] = {
+
+ /* iSER on InfiniBand transport handle */
+ {IDM_TRANSPORT_TYPE_ISER, /* type */
+ "/devices/ib/iser@0:iser", /* device path */
+ NULL, /* LDI handle */
+ NULL, /* transport ops */
+ NULL}, /* transport caps */
+
+ /* IDM native sockets transport handle */
+ {IDM_TRANSPORT_TYPE_SOCKETS, /* type */
+ NULL, /* device path */
+ NULL, /* LDI handle */
+ NULL, /* transport ops */
+ NULL} /* transport caps */
+
+};
+
+int
+_init(void)
+{
+ int rc;
+
+ if ((rc = _idm_init()) != 0) {
+ return (rc);
+ }
+
+ return (mod_install(&modlinkage));
+}
+
+int
+_fini(void)
+{
+ int rc;
+
+ if ((rc = _idm_fini()) != 0) {
+ return (rc);
+ }
+
+ if ((rc = mod_remove(&modlinkage)) != 0) {
+ return (rc);
+ }
+
+ return (rc);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
+
+/*
+ * idm_transport_register()
+ *
+ * Provides a mechanism for an IDM transport driver to register its
+ * transport ops and caps with the IDM kernel module. Invoked during
+ * a transport driver's attach routine.
+ */
+idm_status_t
+idm_transport_register(idm_transport_attr_t *attr)
+{
+ ASSERT(attr->it_ops != NULL);
+ ASSERT(attr->it_caps != NULL);
+
+ switch (attr->type) {
+ /* All known non-native transports here; for now, iSER */
+ case IDM_TRANSPORT_TYPE_ISER:
+ idm_transport_list[attr->type].it_ops = attr->it_ops;
+ idm_transport_list[attr->type].it_caps = attr->it_caps;
+ return (IDM_STATUS_SUCCESS);
+
+ default:
+ cmn_err(CE_NOTE, "idm: unknown transport type (0x%x) in "
+ "idm_transport_register", attr->type);
+ return (IDM_STATUS_SUCCESS);
+ }
+}
+
+/*
+ * idm_ini_conn_create
+ *
+ * This function is invoked by the iSCSI layer to create a connection context.
+ * This does not actually establish the socket connection.
+ *
+ * cr - Connection request parameters
+ * new_con - Output parameter that contains the new request if successful
+ *
+ */
+idm_status_t
+idm_ini_conn_create(idm_conn_req_t *cr, idm_conn_t **new_con)
+{
+ idm_transport_t *it;
+ idm_conn_t *ic;
+ int rc;
+
+ it = idm_transport_lookup(cr);
+
+retry:
+ ic = idm_conn_create_common(CONN_TYPE_INI, it->it_type,
+ &cr->icr_conn_ops);
+
+ bcopy(&cr->cr_ini_dst_addr, &ic->ic_ini_dst_addr,
+ sizeof (cr->cr_ini_dst_addr));
+
+ /* create the transport-specific connection components */
+ rc = it->it_ops->it_ini_conn_create(cr, ic);
+ if (rc != IDM_STATUS_SUCCESS) {
+ /* cleanup the failed connection */
+ idm_conn_destroy_common(ic);
+ kmem_free(ic, sizeof (idm_conn_t));
+
+ /*
+ * It is possible for an IB client to connect to
+ * an ethernet-only client via an IB-eth gateway.
+ * Therefore, if we are attempting to use iSER and
+ * fail, retry with sockets before ultimately
+ * failing the connection.
+ */
+ if (it->it_type == IDM_TRANSPORT_TYPE_ISER) {
+ it = &idm_transport_list[IDM_TRANSPORT_TYPE_SOCKETS];
+ goto retry;
+ }
+
+ return (IDM_STATUS_FAIL);
+ }
+
+ *new_con = ic;
+
+ mutex_enter(&idm.idm_global_mutex);
+ list_insert_tail(&idm.idm_ini_conn_list, ic);
+ mutex_exit(&idm.idm_global_mutex);
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+/*
+ * idm_ini_conn_destroy
+ *
+ * Releases any resources associated with the connection. This is the
+ * complement to idm_ini_conn_create.
+ * ic - idm_conn_t structure representing the relevant connection
+ *
+ */
+void
+idm_ini_conn_destroy(idm_conn_t *ic)
+{
+ mutex_enter(&idm.idm_global_mutex);
+ list_remove(&idm.idm_ini_conn_list, ic);
+ mutex_exit(&idm.idm_global_mutex);
+
+ ic->ic_transport_ops->it_ini_conn_destroy(ic);
+ idm_conn_destroy_common(ic);
+}
+
+/*
+ * idm_ini_conn_connect
+ *
+ * Establish connection to the remote system identified in idm_conn_t.
+ * The connection parameters including the remote IP address were established
+ * in the call to idm_ini_conn_create.
+ *
+ * ic - idm_conn_t structure representing the relevant connection
+ *
+ * Returns success if the connection was established, otherwise some kind
+ * of meaningful error code.
+ *
+ * Upon return the initiator can send a "login" request when it is ready.
+ */
+idm_status_t
+idm_ini_conn_connect(idm_conn_t *ic)
+{
+ idm_status_t rc;
+
+ rc = idm_conn_sm_init(ic);
+ if (rc != IDM_STATUS_SUCCESS) {
+ return (ic->ic_conn_sm_status);
+ }
+ /* Kick state machine */
+ idm_conn_event(ic, CE_CONNECT_REQ, NULL);
+
+ /* Wait for login flag */
+ mutex_enter(&ic->ic_state_mutex);
+ while (!(ic->ic_state_flags & CF_LOGIN_READY) &&
+ !(ic->ic_state_flags & CF_ERROR)) {
+ cv_wait(&ic->ic_state_cv, &ic->ic_state_mutex);
+ }
+ mutex_exit(&ic->ic_state_mutex);
+
+ if (ic->ic_state_flags & CF_ERROR) {
+ /* ic->ic_conn_sm_status will contains failure status */
+ return (ic->ic_conn_sm_status);
+ }
+
+ /* Ready to login */
+ ASSERT(ic->ic_state_flags & CF_LOGIN_READY);
+ (void) idm_notify_client(ic, CN_READY_FOR_LOGIN, NULL);
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+/*
+ * idm_ini_conn_sm_fini_task()
+ *
+ * Dispatch a thread on the global taskq to tear down an initiator connection's
+ * state machine. Note: We cannot do this from the disconnect thread as we will
+ * end up in a situation wherein the thread is running on a taskq that it then
+ * attempts to destroy.
+ */
+static void
+idm_ini_conn_sm_fini_task(void *ic_void)
+{
+ idm_conn_sm_fini((idm_conn_t *)ic_void);
+}
+
+/*
+ * idm_ini_conn_disconnect
+ *
+ * Forces a connection (previously established using idm_ini_conn_connect)
+ * to perform a controlled shutdown, cleaning up any outstanding requests.
+ *
+ * ic - idm_conn_t structure representing the relevant connection
+ *
+ * This is synchronous and it will return when the connection has been
+ * properly shutdown.
+ */
+/* ARGSUSED */
+void
+idm_ini_conn_disconnect(idm_conn_t *ic)
+{
+ mutex_enter(&ic->ic_state_mutex);
+
+ if (ic->ic_state_flags == 0) {
+ /* already disconnected */
+ mutex_exit(&ic->ic_state_mutex);
+ return;
+ }
+ ic->ic_state_flags = 0;
+ ic->ic_conn_sm_status = 0;
+ mutex_exit(&ic->ic_state_mutex);
+
+ /* invoke the transport-specific conn_destroy */
+ (void) ic->ic_transport_ops->it_ini_conn_disconnect(ic);
+
+ /* teardown the connection sm */
+ (void) taskq_dispatch(idm.idm_global_taskq, &idm_ini_conn_sm_fini_task,
+ (void *)ic, TQ_SLEEP);
+}
+
+/*
+ * idm_tgt_svc_create
+ *
+ * The target calls this service to obtain a service context for each available
+ * transport, starting a service of each type related to the IP address and port
+ * passed. The idm_svc_req_t contains the service parameters.
+ */
+idm_status_t
+idm_tgt_svc_create(idm_svc_req_t *sr, idm_svc_t **new_svc)
+{
+ idm_transport_type_t type;
+ idm_transport_t *it;
+ idm_svc_t *is;
+ int rc;
+
+ *new_svc = NULL;
+ is = kmem_zalloc(sizeof (idm_svc_t), KM_SLEEP);
+
+ /* Initialize transport-agnostic components of the service handle */
+ is->is_svc_req = *sr;
+ mutex_init(&is->is_mutex, NULL, MUTEX_DEFAULT, NULL);
+ cv_init(&is->is_cv, NULL, CV_DEFAULT, NULL);
+ mutex_init(&is->is_count_mutex, NULL, MUTEX_DEFAULT, NULL);
+ cv_init(&is->is_count_cv, NULL, CV_DEFAULT, NULL);
+ idm_refcnt_init(&is->is_refcnt, is);
+
+ /*
+ * Make sure all available transports are setup. We call this now
+ * instead of at initialization time in case IB has become available
+ * since we started (hotplug, etc).
+ */
+ idm_transport_setup(sr->sr_li);
+
+ /*
+ * Loop through the transports, configuring the transport-specific
+ * components of each one.
+ */
+ for (type = 0; type < IDM_TRANSPORT_NUM_TYPES; type++) {
+
+ it = &idm_transport_list[type];
+ /*
+ * If it_ops is NULL then the transport is unconfigured
+ * and we shouldn't try to start the service.
+ */
+ if (it->it_ops == NULL) {
+ continue;
+ }
+
+ rc = it->it_ops->it_tgt_svc_create(sr, is);
+ if (rc != IDM_STATUS_SUCCESS) {
+ /* Teardown any configured services */
+ while (type--) {
+ it = &idm_transport_list[type];
+ if (it->it_ops == NULL) {
+ continue;
+ }
+ it->it_ops->it_tgt_svc_destroy(is);
+ }
+ /* Free the svc context and return */
+ kmem_free(is, sizeof (idm_svc_t));
+ return (rc);
+ }
+ }
+
+ *new_svc = is;
+
+ mutex_enter(&idm.idm_global_mutex);
+ list_insert_tail(&idm.idm_tgt_svc_list, is);
+ mutex_exit(&idm.idm_global_mutex);
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+/*
+ * idm_tgt_svc_destroy
+ *
+ * is - idm_svc_t returned by the call to idm_tgt_svc_create
+ *
+ * Cleanup any resources associated with the idm_svc_t.
+ */
+void
+idm_tgt_svc_destroy(idm_svc_t *is)
+{
+ idm_transport_type_t type;
+ idm_transport_t *it;
+
+ mutex_enter(&idm.idm_global_mutex);
+ /* remove this service from the global list */
+ list_remove(&idm.idm_tgt_svc_list, is);
+ /* wakeup any waiters for service change */
+ cv_broadcast(&idm.idm_tgt_svc_cv);
+ mutex_exit(&idm.idm_global_mutex);
+
+ /* tear down the svc resources */
+ idm_refcnt_destroy(&is->is_refcnt);
+ cv_destroy(&is->is_count_cv);
+ mutex_destroy(&is->is_count_mutex);
+ cv_destroy(&is->is_cv);
+ mutex_destroy(&is->is_mutex);
+
+ /* teardown each transport-specific service */
+ for (type = 0; type < IDM_TRANSPORT_NUM_TYPES; type++) {
+ it = &idm_transport_list[type];
+ if (it->it_ops == NULL) {
+ continue;
+ }
+
+ it->it_ops->it_tgt_svc_destroy(is);
+ }
+
+ /* free the svc handle */
+ kmem_free(is, sizeof (idm_svc_t));
+}
+
+void
+idm_tgt_svc_hold(idm_svc_t *is)
+{
+ idm_refcnt_hold(&is->is_refcnt);
+}
+
+void
+idm_tgt_svc_rele_and_destroy(idm_svc_t *is)
+{
+ idm_refcnt_rele_and_destroy(&is->is_refcnt,
+ (idm_refcnt_cb_t *)&idm_tgt_svc_destroy);
+}
+
+/*
+ * idm_tgt_svc_online
+ *
+ * is - idm_svc_t returned by the call to idm_tgt_svc_create
+ *
+ * Online each transport service, as we want this target to be accessible
+ * via any configured transport.
+ *
+ * When the initiator establishes a new connection to the target, IDM will
+ * call the "new connect" callback defined in the idm_svc_req_t structure
+ * and it will pass an idm_conn_t structure representing that new connection.
+ */
+idm_status_t
+idm_tgt_svc_online(idm_svc_t *is)
+{
+
+ idm_transport_type_t type;
+ idm_transport_t *it;
+ int rc;
+ int svc_found;
+
+ mutex_enter(&is->is_mutex);
+ /* Walk through each of the transports and online them */
+ if (is->is_online == 0) {
+ svc_found = 0;
+ for (type = 0; type < IDM_TRANSPORT_NUM_TYPES; type++) {
+ it = &idm_transport_list[type];
+ if (it->it_ops == NULL) {
+ /* transport is not registered */
+ continue;
+ }
+
+ mutex_exit(&is->is_mutex);
+ rc = it->it_ops->it_tgt_svc_online(is);
+ mutex_enter(&is->is_mutex);
+ if (rc == IDM_STATUS_SUCCESS) {
+ /* We have at least one service running. */
+ svc_found = 1;
+ }
+ }
+ } else {
+ svc_found = 1;
+ }
+ if (svc_found)
+ is->is_online++;
+ mutex_exit(&is->is_mutex);
+
+ return (svc_found ? IDM_STATUS_SUCCESS : IDM_STATUS_FAIL);
+}
+
+/*
+ * idm_tgt_svc_offline
+ *
+ * is - idm_svc_t returned by the call to idm_tgt_svc_create
+ *
+ * Shutdown any online target services.
+ */
+void
+idm_tgt_svc_offline(idm_svc_t *is)
+{
+ idm_transport_type_t type;
+ idm_transport_t *it;
+
+ mutex_enter(&is->is_mutex);
+ is->is_online--;
+ if (is->is_online == 0) {
+ /* Walk through each of the transports and offline them */
+ for (type = 0; type < IDM_TRANSPORT_NUM_TYPES; type++) {
+ it = &idm_transport_list[type];
+ if (it->it_ops == NULL) {
+ /* transport is not registered */
+ continue;
+ }
+
+ mutex_exit(&is->is_mutex);
+ it->it_ops->it_tgt_svc_offline(is);
+ mutex_enter(&is->is_mutex);
+ }
+ }
+ mutex_exit(&is->is_mutex);
+}
+
+/*
+ * idm_tgt_svc_lookup
+ *
+ * Lookup a service instance listening on the specified port
+ */
+
+idm_svc_t *
+idm_tgt_svc_lookup(uint16_t port)
+{
+ idm_svc_t *result;
+
+retry:
+ mutex_enter(&idm.idm_global_mutex);
+ for (result = list_head(&idm.idm_tgt_svc_list);
+ result != NULL;
+ result = list_next(&idm.idm_tgt_svc_list, result)) {
+ if (result->is_svc_req.sr_port == port) {
+ if (result->is_online == 0) {
+ /*
+ * A service exists on this port, but it
+ * is going away, wait for it to cleanup.
+ */
+ cv_wait(&idm.idm_tgt_svc_cv,
+ &idm.idm_global_mutex);
+ mutex_exit(&idm.idm_global_mutex);
+ goto retry;
+ }
+ idm_tgt_svc_hold(result);
+ mutex_exit(&idm.idm_global_mutex);
+ return (result);
+ }
+ }
+ mutex_exit(&idm.idm_global_mutex);
+
+ return (NULL);
+}
+
+/*
+ * idm_negotiate_key_values()
+ * Give IDM level a chance to negotiate any login parameters it should own.
+ * -- leave unhandled parameters alone on request_nvl
+ * -- move all handled parameters to response_nvl with an appropriate response
+ * -- also add an entry to negotiated_nvl for any accepted parameters
+ */
+kv_status_t
+idm_negotiate_key_values(idm_conn_t *ic, nvlist_t *request_nvl,
+ nvlist_t *response_nvl, nvlist_t *negotiated_nvl)
+{
+ ASSERT(ic->ic_transport_ops != NULL);
+ return (ic->ic_transport_ops->it_negotiate_key_values(ic,
+ request_nvl, response_nvl, negotiated_nvl));
+}
+
+/*
+ * idm_notice_key_values()
+ * Activate at the IDM level any parameters that have been negotiated.
+ * Passes the set of key value pairs to the transport for activation.
+ * This will be invoked as the connection is entering full-feature mode.
+ */
+idm_status_t
+idm_notice_key_values(idm_conn_t *ic, nvlist_t *negotiated_nvl)
+{
+ ASSERT(ic->ic_transport_ops != NULL);
+ return (ic->ic_transport_ops->it_notice_key_values(ic,
+ negotiated_nvl));
+}
+
+/*
+ * idm_buf_tx_to_ini
+ *
+ * This is IDM's implementation of the 'Put_Data' operational primitive.
+ *
+ * This function is invoked by a target iSCSI layer to request its local
+ * Datamover layer to transmit the Data-In PDU to the peer iSCSI layer
+ * on the remote iSCSI node. The I/O buffer represented by 'idb' is
+ * transferred to the initiator associated with task 'idt'. The connection
+ * info, contents of the Data-In PDU header, the DataDescriptorIn, BHS,
+ * and the callback (idb->idb_buf_cb) at transfer completion are
+ * provided as input.
+ *
+ * This data transfer takes place transparently to the remote iSCSI layer,
+ * i.e. without its participation.
+ *
+ * Using sockets, IDM implements the data transfer by segmenting the data
+ * buffer into appropriately sized iSCSI PDUs and transmitting them to the
+ * initiator. iSER performs the transfer using RDMA write.
+ *
+ */
+idm_status_t
+idm_buf_tx_to_ini(idm_task_t *idt, idm_buf_t *idb,
+ uint32_t offset, uint32_t xfer_len,
+ idm_buf_cb_t idb_buf_cb, void *cb_arg)
+{
+ idm_status_t rc;
+
+ idb->idb_bufoffset = offset;
+ idb->idb_xfer_len = xfer_len;
+ idb->idb_buf_cb = idb_buf_cb;
+ idb->idb_cb_arg = cb_arg;
+
+ mutex_enter(&idt->idt_mutex);
+ switch (idt->idt_state) {
+ case TASK_ACTIVE:
+ idt->idt_tx_to_ini_start++;
+ idm_task_hold(idt);
+ idm_buf_bind_in_locked(idt, idb);
+ idb->idb_in_transport = B_TRUE;
+ rc = (*idt->idt_ic->ic_transport_ops->it_buf_tx_to_ini)
+ (idt, idb);
+ return (rc);
+
+ case TASK_SUSPENDING:
+ case TASK_SUSPENDED:
+ /*
+ * Bind buffer but don't start a transfer since the task
+ * is suspended
+ */
+ idm_buf_bind_in_locked(idt, idb);
+ mutex_exit(&idt->idt_mutex);
+ return (IDM_STATUS_SUCCESS);
+
+ case TASK_ABORTING:
+ case TASK_ABORTED:
+ /*
+ * Once the task is aborted, any buffers added to the
+ * idt_inbufv will never get cleaned up, so just return
+ * SUCCESS. The buffer should get cleaned up by the
+ * client or framework once task_aborted has completed.
+ */
+ mutex_exit(&idt->idt_mutex);
+ return (IDM_STATUS_SUCCESS);
+
+ default:
+ ASSERT(0);
+ break;
+ }
+ mutex_exit(&idt->idt_mutex);
+
+ return (IDM_STATUS_FAIL);
+}
+
+/*
+ * idm_buf_rx_from_ini
+ *
+ * This is IDM's implementation of the 'Get_Data' operational primitive.
+ *
+ * This function is invoked by a target iSCSI layer to request its local
+ * Datamover layer to retrieve certain data identified by the R2T PDU from the
+ * peer iSCSI layer on the remote node. The retrieved Data-Out PDU will be
+ * mapped to the respective buffer by the task tags (ITT & TTT).
+ * The connection information, contents of an R2T PDU, DataDescriptor, BHS, and
+ * the callback (idb->idb_buf_cb) notification for data transfer completion are
+ * are provided as input.
+ *
+ * When an iSCSI node sends an R2T PDU to its local Datamover layer, the local
+ * Datamover layer, the local and remote Datamover layers transparently bring
+ * about the data transfer requested by the R2T PDU, without the participation
+ * of the iSCSI layers.
+ *
+ * Using sockets, IDM transmits an R2T PDU for each buffer and the rx_data_out()
+ * assembles the Data-Out PDUs into the buffer. iSER uses RDMA read.
+ *
+ */
+idm_status_t
+idm_buf_rx_from_ini(idm_task_t *idt, idm_buf_t *idb,
+ uint32_t offset, uint32_t xfer_len,
+ idm_buf_cb_t idb_buf_cb, void *cb_arg)
+{
+ idm_status_t rc;
+
+ idb->idb_bufoffset = offset;
+ idb->idb_xfer_len = xfer_len;
+ idb->idb_buf_cb = idb_buf_cb;
+ idb->idb_cb_arg = cb_arg;
+
+ /*
+ * "In" buf list is for "Data In" PDU's, "Out" buf list is for
+ * "Data Out" PDU's
+ */
+ mutex_enter(&idt->idt_mutex);
+ switch (idt->idt_state) {
+ case TASK_ACTIVE:
+ idt->idt_rx_from_ini_start++;
+ idm_task_hold(idt);
+ idm_buf_bind_out_locked(idt, idb);
+ idb->idb_in_transport = B_TRUE;
+ rc = (*idt->idt_ic->ic_transport_ops->it_buf_rx_from_ini)
+ (idt, idb);
+ return (rc);
+ case TASK_SUSPENDING:
+ case TASK_SUSPENDED:
+ case TASK_ABORTING:
+ case TASK_ABORTED:
+ /*
+ * Bind buffer but don't start a transfer since the task
+ * is suspended
+ */
+ idm_buf_bind_out_locked(idt, idb);
+ mutex_exit(&idt->idt_mutex);
+ return (IDM_STATUS_SUCCESS);
+ default:
+ ASSERT(0);
+ break;
+ }
+ mutex_exit(&idt->idt_mutex);
+
+ return (IDM_STATUS_FAIL);
+}
+
+/*
+ * idm_buf_tx_to_ini_done
+ *
+ * The transport calls this after it has completed a transfer requested by
+ * a call to transport_buf_tx_to_ini
+ *
+ * Caller holds idt->idt_mutex, idt->idt_mutex is released before returning.
+ * idt may be freed after the call to idb->idb_buf_cb.
+ */
+void
+idm_buf_tx_to_ini_done(idm_task_t *idt, idm_buf_t *idb, idm_status_t status)
+{
+ ASSERT(mutex_owned(&idt->idt_mutex));
+ idb->idb_in_transport = B_FALSE;
+ idb->idb_tx_thread = B_FALSE;
+ idt->idt_tx_to_ini_done++;
+
+ /*
+ * idm_refcnt_rele may cause TASK_SUSPENDING --> TASK_SUSPENDED or
+ * TASK_ABORTING --> TASK_ABORTED transistion if the refcount goes
+ * to 0.
+ */
+ idm_task_rele(idt);
+ idb->idb_status = status;
+
+ switch (idt->idt_state) {
+ case TASK_ACTIVE:
+ idm_buf_unbind_in_locked(idt, idb);
+ mutex_exit(&idt->idt_mutex);
+ (*idb->idb_buf_cb)(idb, status);
+ return;
+ case TASK_SUSPENDING:
+ case TASK_SUSPENDED:
+ case TASK_ABORTING:
+ case TASK_ABORTED:
+ /*
+ * To keep things simple we will ignore the case where the
+ * transfer was successful and leave all buffers bound to the
+ * task. This allows us to also ignore the case where we've
+ * been asked to abort a task but the last transfer of the
+ * task has completed. IDM has no idea whether this was, in
+ * fact, the last transfer of the task so it would be difficult
+ * to handle this case. Everything should get sorted out again
+ * after task reassignment is complete.
+ *
+ * In the case of TASK_ABORTING we could conceivably call the
+ * buffer callback here but the timing of when the client's
+ * client_task_aborted callback is invoked vs. when the client's
+ * buffer callback gets invoked gets sticky. We don't want
+ * the client to here from us again after the call to
+ * client_task_aborted() but we don't want to give it a bunch
+ * of failed buffer transfers until we've called
+ * client_task_aborted(). Instead we'll just leave all the
+ * buffers bound and allow the client to cleanup.
+ */
+ break;
+ default:
+ ASSERT(0);
+ }
+ mutex_exit(&idt->idt_mutex);
+}
+
+/*
+ * idm_buf_rx_from_ini_done
+ *
+ * The transport calls this after it has completed a transfer requested by
+ * a call totransport_buf_tx_to_ini
+ *
+ * Caller holds idt->idt_mutex, idt->idt_mutex is released before returning.
+ * idt may be freed after the call to idb->idb_buf_cb.
+ */
+void
+idm_buf_rx_from_ini_done(idm_task_t *idt, idm_buf_t *idb, idm_status_t status)
+{
+ ASSERT(mutex_owned(&idt->idt_mutex));
+ idb->idb_in_transport = B_FALSE;
+ idt->idt_rx_from_ini_done++;
+
+ /*
+ * idm_refcnt_rele may cause TASK_SUSPENDING --> TASK_SUSPENDED or
+ * TASK_ABORTING --> TASK_ABORTED transistion if the refcount goes
+ * to 0.
+ */
+ idm_task_rele(idt);
+ idb->idb_status = status;
+
+ switch (idt->idt_state) {
+ case TASK_ACTIVE:
+ idm_buf_unbind_out_locked(idt, idb);
+ mutex_exit(&idt->idt_mutex);
+ (*idb->idb_buf_cb)(idb, status);
+ return;
+ case TASK_SUSPENDING:
+ case TASK_SUSPENDED:
+ case TASK_ABORTING:
+ case TASK_ABORTED:
+ /*
+ * To keep things simple we will ignore the case where the
+ * transfer was successful and leave all buffers bound to the
+ * task. This allows us to also ignore the case where we've
+ * been asked to abort a task but the last transfer of the
+ * task has completed. IDM has no idea whether this was, in
+ * fact, the last transfer of the task so it would be difficult
+ * to handle this case. Everything should get sorted out again
+ * after task reassignment is complete.
+ *
+ * In the case of TASK_ABORTING we could conceivably call the
+ * buffer callback here but the timing of when the client's
+ * client_task_aborted callback is invoked vs. when the client's
+ * buffer callback gets invoked gets sticky. We don't want
+ * the client to here from us again after the call to
+ * client_task_aborted() but we don't want to give it a bunch
+ * of failed buffer transfers until we've called
+ * client_task_aborted(). Instead we'll just leave all the
+ * buffers bound and allow the client to cleanup.
+ */
+ break;
+ default:
+ ASSERT(0);
+ }
+ mutex_exit(&idt->idt_mutex);
+}
+
+/*
+ * idm_buf_alloc
+ *
+ * Allocates a buffer handle and registers it for use with the transport
+ * layer. If a buffer is not passed on bufptr, the buffer will be allocated
+ * as well as the handle.
+ *
+ * ic - connection on which the buffer will be transferred
+ * bufptr - allocate memory for buffer if NULL, else assign to buffer
+ * buflen - length of buffer
+ *
+ * Returns idm_buf_t handle if successful, otherwise NULL
+ */
+idm_buf_t *
+idm_buf_alloc(idm_conn_t *ic, void *bufptr, uint64_t buflen)
+{
+ idm_buf_t *buf = NULL;
+ int rc;
+
+ ASSERT(ic != NULL);
+ ASSERT(idm.idm_buf_cache != NULL);
+ ASSERT(buflen > 0);
+
+ /* Don't allocate new buffers if we are not in FFP */
+ mutex_enter(&ic->ic_state_mutex);
+ if (!ic->ic_ffp) {
+ mutex_exit(&ic->ic_state_mutex);
+ return (NULL);
+ }
+
+
+ idm_conn_hold(ic);
+ mutex_exit(&ic->ic_state_mutex);
+
+ buf = kmem_cache_alloc(idm.idm_buf_cache, KM_NOSLEEP);
+ if (buf == NULL) {
+ idm_conn_rele(ic);
+ return (NULL);
+ }
+
+ buf->idb_ic = ic;
+ buf->idb_buflen = buflen;
+ buf->idb_exp_offset = 0;
+ buf->idb_bufoffset = 0;
+ buf->idb_xfer_len = 0;
+ buf->idb_magic = IDM_BUF_MAGIC;
+
+ /*
+ * If bufptr is NULL, we have an implicit request to allocate
+ * memory for this IDM buffer handle and register it for use
+ * with the transport. To simplify this, and to give more freedom
+ * to the transport layer for it's own buffer management, both of
+ * these actions will take place in the transport layer.
+ * If bufptr is set, then the caller has allocated memory (or more
+ * likely it's been passed from an upper layer), and we need only
+ * register the buffer for use with the transport layer.
+ */
+ if (bufptr == NULL) {
+ /*
+ * Allocate a buffer from the transport layer (which
+ * will also register the buffer for use).
+ */
+ rc = ic->ic_transport_ops->it_buf_alloc(buf, buflen);
+ if (rc != 0) {
+ idm_conn_rele(ic);
+ kmem_cache_free(idm.idm_buf_cache, buf);
+ return (NULL);
+ }
+ /* Set the bufalloc'd flag */
+ buf->idb_bufalloc = B_TRUE;
+ } else {
+ /*
+ * Set the passed bufptr into the buf handle, and
+ * register the handle with the transport layer.
+ */
+ buf->idb_buf = bufptr;
+
+ rc = ic->ic_transport_ops->it_buf_setup(buf);
+ if (rc != 0) {
+ idm_conn_rele(ic);
+ kmem_cache_free(idm.idm_buf_cache, buf);
+ return (NULL);
+ }
+ /* Ensure bufalloc'd flag is unset */
+ buf->idb_bufalloc = B_FALSE;
+ }
+
+ return (buf);
+
+}
+
+/*
+ * idm_buf_free
+ *
+ * Release a buffer handle along with the associated buffer that was allocated
+ * or assigned with idm_buf_alloc
+ */
+void
+idm_buf_free(idm_buf_t *buf)
+{
+ idm_conn_t *ic = buf->idb_ic;
+
+
+ buf->idb_task_binding = NULL;
+
+ if (buf->idb_bufalloc) {
+ ic->ic_transport_ops->it_buf_free(buf);
+ } else {
+ ic->ic_transport_ops->it_buf_teardown(buf);
+ }
+ kmem_cache_free(idm.idm_buf_cache, buf);
+ idm_conn_rele(ic);
+}
+
+/*
+ * idm_buf_bind_in
+ *
+ * This function associates a buffer with a task. This is only for use by the
+ * iSCSI initiator that will have only one buffer per transfer direction
+ *
+ */
+void
+idm_buf_bind_in(idm_task_t *idt, idm_buf_t *buf)
+{
+ mutex_enter(&idt->idt_mutex);
+ idm_buf_bind_in_locked(idt, buf);
+ mutex_exit(&idt->idt_mutex);
+}
+
+static void
+idm_buf_bind_in_locked(idm_task_t *idt, idm_buf_t *buf)
+{
+ buf->idb_task_binding = idt;
+ buf->idb_ic = idt->idt_ic;
+ idm_listbuf_insert(&idt->idt_inbufv, buf);
+}
+
+void
+idm_buf_bind_out(idm_task_t *idt, idm_buf_t *buf)
+{
+ mutex_enter(&idt->idt_mutex);
+ idm_buf_bind_out_locked(idt, buf);
+ mutex_exit(&idt->idt_mutex);
+}
+
+static void
+idm_buf_bind_out_locked(idm_task_t *idt, idm_buf_t *buf)
+{
+ buf->idb_task_binding = idt;
+ buf->idb_ic = idt->idt_ic;
+ idm_listbuf_insert(&idt->idt_outbufv, buf);
+}
+
+void
+idm_buf_unbind_in(idm_task_t *idt, idm_buf_t *buf)
+{
+ mutex_enter(&idt->idt_mutex);
+ idm_buf_unbind_in_locked(idt, buf);
+ mutex_exit(&idt->idt_mutex);
+}
+
+static void
+idm_buf_unbind_in_locked(idm_task_t *idt, idm_buf_t *buf)
+{
+ list_remove(&idt->idt_inbufv, buf);
+}
+
+void
+idm_buf_unbind_out(idm_task_t *idt, idm_buf_t *buf)
+{
+ mutex_enter(&idt->idt_mutex);
+ idm_buf_unbind_out_locked(idt, buf);
+ mutex_exit(&idt->idt_mutex);
+}
+
+static void
+idm_buf_unbind_out_locked(idm_task_t *idt, idm_buf_t *buf)
+{
+ list_remove(&idt->idt_outbufv, buf);
+}
+
+/*
+ * idm_buf_find() will lookup the idm_buf_t based on the relative offset in the
+ * iSCSI PDU
+ */
+idm_buf_t *
+idm_buf_find(void *lbuf, size_t data_offset)
+{
+ idm_buf_t *idb;
+ list_t *lst = (list_t *)lbuf;
+
+ /* iterate through the list to find the buffer */
+ for (idb = list_head(lst); idb != NULL; idb = list_next(lst, idb)) {
+
+ ASSERT((idb->idb_ic->ic_conn_type == CONN_TYPE_TGT) ||
+ (idb->idb_bufoffset == 0));
+
+ if ((data_offset >= idb->idb_bufoffset) &&
+ (data_offset < (idb->idb_bufoffset + idb->idb_buflen))) {
+
+ return (idb);
+ }
+ }
+
+ return (NULL);
+}
+
+/*
+ * idm_task_alloc
+ *
+ * This function will allocate a idm_task_t structure. A task tag is also
+ * generated and saved in idt_tt. The task is not active.
+ */
+idm_task_t *
+idm_task_alloc(idm_conn_t *ic)
+{
+ idm_task_t *idt;
+
+ ASSERT(ic != NULL);
+
+ /* Don't allocate new tasks if we are not in FFP */
+ mutex_enter(&ic->ic_state_mutex);
+ if (!ic->ic_ffp) {
+ mutex_exit(&ic->ic_state_mutex);
+ return (NULL);
+ }
+ idt = kmem_cache_alloc(idm.idm_task_cache, KM_NOSLEEP);
+ if (idt == NULL) {
+ mutex_exit(&ic->ic_state_mutex);
+ return (NULL);
+ }
+
+ ASSERT(list_is_empty(&idt->idt_inbufv));
+ ASSERT(list_is_empty(&idt->idt_outbufv));
+
+ idm_conn_hold(ic);
+ mutex_exit(&ic->ic_state_mutex);
+
+ idt->idt_state = TASK_IDLE;
+ idt->idt_ic = ic;
+ idt->idt_private = NULL;
+ idt->idt_exp_datasn = 0;
+ idt->idt_exp_rttsn = 0;
+
+ return (idt);
+}
+
+/*
+ * idm_task_start
+ *
+ * Add the task to an AVL tree to notify IDM about a new task. The caller
+ * sets up the idm_task_t structure with a prior call to idm_task_alloc().
+ * The task service does not function as a task/work engine, it is the
+ * responsibility of the initiator to start the data transfer and free the
+ * resources.
+ */
+void
+idm_task_start(idm_task_t *idt, uintptr_t handle)
+{
+ ASSERT(idt != NULL);
+
+ /* mark the task as ACTIVE */
+ idt->idt_state = TASK_ACTIVE;
+ idt->idt_client_handle = handle;
+ idt->idt_tx_to_ini_start = idt->idt_tx_to_ini_done =
+ idt->idt_rx_from_ini_start = idt->idt_rx_from_ini_done = 0;
+}
+
+/*
+ * idm_task_done
+ *
+ * This function will remove the task from the AVL tree indicating that the
+ * task is no longer active.
+ */
+void
+idm_task_done(idm_task_t *idt)
+{
+ ASSERT(idt != NULL);
+ ASSERT(idt->idt_refcnt.ir_refcnt == 0);
+
+ idt->idt_state = TASK_IDLE;
+ idm_refcnt_reset(&idt->idt_refcnt);
+}
+
+/*
+ * idm_task_free
+ *
+ * This function will free the Task Tag and the memory allocated for the task
+ * idm_task_done should be called prior to this call
+ */
+void
+idm_task_free(idm_task_t *idt)
+{
+ idm_conn_t *ic = idt->idt_ic;
+
+ ASSERT(idt != NULL);
+ ASSERT(idt->idt_state == TASK_IDLE);
+
+ /*
+ * It's possible for items to still be in the idt_inbufv list if
+ * they were added after idm_task_cleanup was called. We rely on
+ * STMF to free all buffers associated with the task however STMF
+ * doesn't know that we have this reference to the buffers.
+ * Use list_create so that we don't end up with stale references
+ * to these buffers.
+ */
+ list_create(&idt->idt_inbufv, sizeof (idm_buf_t),
+ offsetof(idm_buf_t, idb_buflink));
+ list_create(&idt->idt_outbufv, sizeof (idm_buf_t),
+ offsetof(idm_buf_t, idb_buflink));
+
+ kmem_cache_free(idm.idm_task_cache, idt);
+
+ idm_conn_rele(ic);
+}
+
+/*
+ * idm_task_find
+ *
+ * This function looks up a task by task tag
+ */
+/*ARGSUSED*/
+idm_task_t *
+idm_task_find(idm_conn_t *ic, uint32_t itt, uint32_t ttt)
+{
+ uint32_t tt, client_handle;
+ idm_task_t *idt;
+
+ /*
+ * Must match both itt and ttt. The table is indexed by itt
+ * for initiator connections and ttt for target connections.
+ */
+ if (IDM_CONN_ISTGT(ic)) {
+ tt = ttt;
+ client_handle = itt;
+ } else {
+ tt = itt;
+ client_handle = ttt;
+ }
+
+ rw_enter(&idm.idm_taskid_table_lock, RW_READER);
+ if (tt >= idm.idm_taskid_max) {
+ rw_exit(&idm.idm_taskid_table_lock);
+ return (NULL);
+ }
+
+ idt = idm.idm_taskid_table[tt];
+
+ if (idt != NULL) {
+ mutex_enter(&idt->idt_mutex);
+ if ((idt->idt_state != TASK_ACTIVE) ||
+ (IDM_CONN_ISTGT(ic) &&
+ (idt->idt_client_handle != client_handle))) {
+ /*
+ * Task is aborting, we don't want any more references.
+ */
+ mutex_exit(&idt->idt_mutex);
+ rw_exit(&idm.idm_taskid_table_lock);
+ return (NULL);
+ }
+ idm_task_hold(idt);
+ mutex_exit(&idt->idt_mutex);
+ }
+ rw_exit(&idm.idm_taskid_table_lock);
+
+ return (idt);
+}
+
+/*
+ * idm_task_find_by_handle
+ *
+ * This function looks up a task by the client-private idt_client_handle.
+ *
+ * This function should NEVER be called in the performance path. It is
+ * intended strictly for error recovery/task management.
+ */
+/*ARGSUSED*/
+void *
+idm_task_find_by_handle(idm_conn_t *ic, uintptr_t handle)
+{
+ idm_task_t *idt = NULL;
+ int idx = 0;
+
+ rw_enter(&idm.idm_taskid_table_lock, RW_READER);
+
+ for (idx = 0; idx < idm.idm_taskid_max; idx++) {
+ idt = idm.idm_taskid_table[idx];
+
+ if (idt == NULL)
+ continue;
+
+ mutex_enter(&idt->idt_mutex);
+
+ if (idt->idt_state != TASK_ACTIVE) {
+ /*
+ * Task is either in suspend, abort, or already
+ * complete.
+ */
+ mutex_exit(&idt->idt_mutex);
+ continue;
+ }
+
+ if (idt->idt_client_handle == handle) {
+ idm_task_hold(idt);
+ mutex_exit(&idt->idt_mutex);
+ break;
+ }
+
+ mutex_exit(&idt->idt_mutex);
+ }
+
+ rw_exit(&idm.idm_taskid_table_lock);
+
+ if ((idt == NULL) || (idx == idm.idm_taskid_max))
+ return (NULL);
+
+ return (idt->idt_private);
+}
+
+void
+idm_task_hold(idm_task_t *idt)
+{
+ idm_refcnt_hold(&idt->idt_refcnt);
+}
+
+void
+idm_task_rele(idm_task_t *idt)
+{
+ idm_refcnt_rele(&idt->idt_refcnt);
+}
+
+void
+idm_task_abort(idm_conn_t *ic, idm_task_t *idt, idm_abort_type_t abort_type)
+{
+ idm_task_t *task;
+ int idx;
+
+ /*
+ * Passing NULL as the task indicates that all tasks
+ * for this connection should be aborted.
+ */
+ if (idt == NULL) {
+ /*
+ * Only the connection state machine should ask for
+ * all tasks to abort and this should never happen in FFP.
+ */
+ ASSERT(!ic->ic_ffp);
+ rw_enter(&idm.idm_taskid_table_lock, RW_READER);
+ for (idx = 0; idx < idm.idm_taskid_max; idx++) {
+ task = idm.idm_taskid_table[idx];
+ if (task && (task->idt_state != TASK_IDLE) &&
+ (task->idt_ic == ic)) {
+ rw_exit(&idm.idm_taskid_table_lock);
+ idm_task_abort_one(ic, task, abort_type);
+ rw_enter(&idm.idm_taskid_table_lock, RW_READER);
+ }
+ }
+ rw_exit(&idm.idm_taskid_table_lock);
+ } else {
+ idm_task_abort_one(ic, idt, abort_type);
+ }
+}
+
+static void
+idm_task_abort_unref_cb(void *ref)
+{
+ idm_task_t *idt = ref;
+
+ mutex_enter(&idt->idt_mutex);
+ switch (idt->idt_state) {
+ case TASK_SUSPENDING:
+ idt->idt_state = TASK_SUSPENDED;
+ mutex_exit(&idt->idt_mutex);
+ idm_task_aborted(idt, IDM_STATUS_SUSPENDED);
+ return;
+ case TASK_ABORTING:
+ idt->idt_state = TASK_ABORTED;
+ mutex_exit(&idt->idt_mutex);
+ idm_task_aborted(idt, IDM_STATUS_ABORTED);
+ return;
+ default:
+ mutex_exit(&idt->idt_mutex);
+ ASSERT(0);
+ break;
+ }
+}
+
+static void
+idm_task_abort_one(idm_conn_t *ic, idm_task_t *idt, idm_abort_type_t abort_type)
+{
+ /* Caller must hold connection mutex */
+ mutex_enter(&idt->idt_mutex);
+ switch (idt->idt_state) {
+ case TASK_ACTIVE:
+ switch (abort_type) {
+ case AT_INTERNAL_SUSPEND:
+ /* Call transport to release any resources */
+ idt->idt_state = TASK_SUSPENDING;
+ mutex_exit(&idt->idt_mutex);
+ ic->ic_transport_ops->it_free_task_rsrc(idt);
+
+ /*
+ * Wait for outstanding references. When all
+ * references are released the callback will call
+ * idm_task_aborted().
+ */
+ idm_refcnt_async_wait_ref(&idt->idt_refcnt,
+ &idm_task_abort_unref_cb);
+ return;
+ case AT_INTERNAL_ABORT:
+ case AT_TASK_MGMT_ABORT:
+ idt->idt_state = TASK_ABORTING;
+ mutex_exit(&idt->idt_mutex);
+ ic->ic_transport_ops->it_free_task_rsrc(idt);
+
+ /*
+ * Wait for outstanding references. When all
+ * references are released the callback will call
+ * idm_task_aborted().
+ */
+ idm_refcnt_async_wait_ref(&idt->idt_refcnt,
+ &idm_task_abort_unref_cb);
+ return;
+ default:
+ ASSERT(0);
+ }
+ break;
+ case TASK_SUSPENDING:
+ /* Already called transport_free_task_rsrc(); */
+ switch (abort_type) {
+ case AT_INTERNAL_SUSPEND:
+ /* Already doing it */
+ break;
+ case AT_INTERNAL_ABORT:
+ case AT_TASK_MGMT_ABORT:
+ idt->idt_state = TASK_ABORTING;
+ break;
+ default:
+ ASSERT(0);
+ }
+ break;
+ case TASK_SUSPENDED:
+ /* Already called transport_free_task_rsrc(); */
+ switch (abort_type) {
+ case AT_INTERNAL_SUSPEND:
+ /* Already doing it */
+ break;
+ case AT_INTERNAL_ABORT:
+ case AT_TASK_MGMT_ABORT:
+ idt->idt_state = TASK_ABORTING;
+ mutex_exit(&idt->idt_mutex);
+
+ /*
+ * We could probably call idm_task_aborted directly
+ * here but we may be holding the conn lock. It's
+ * easier to just switch contexts. Even though
+ * we shouldn't really have any references we'll
+ * set the state to TASK_ABORTING instead of
+ * TASK_ABORTED so we can use the same code path.
+ */
+ idm_refcnt_async_wait_ref(&idt->idt_refcnt,
+ &idm_task_abort_unref_cb);
+ return;
+ default:
+ ASSERT(0);
+ }
+ break;
+ case TASK_ABORTING:
+ case TASK_ABORTED:
+ switch (abort_type) {
+ case AT_INTERNAL_SUSPEND:
+ /* We're already past this point... */
+ case AT_INTERNAL_ABORT:
+ case AT_TASK_MGMT_ABORT:
+ /* Already doing it */
+ break;
+ default:
+ ASSERT(0);
+ }
+ break;
+ case TASK_COMPLETE:
+ /*
+ * In this case, let it go. The status has already been
+ * sent (which may or may not get successfully transmitted)
+ * and we don't want to end up in a race between completing
+ * the status PDU and marking the task suspended.
+ */
+ break;
+ default:
+ ASSERT(0);
+ }
+ mutex_exit(&idt->idt_mutex);
+}
+
+static void
+idm_task_aborted(idm_task_t *idt, idm_status_t status)
+{
+ (*idt->idt_ic->ic_conn_ops.icb_task_aborted)(idt, status);
+}
+
+void
+idm_task_cleanup(idm_task_t *idt)
+{
+ idm_buf_t *idb, *next_idb;
+ list_t tmp_buflist;
+ ASSERT((idt->idt_state == TASK_SUSPENDED) ||
+ (idt->idt_state == TASK_ABORTED));
+
+ list_create(&tmp_buflist, sizeof (idm_buf_t),
+ offsetof(idm_buf_t, idb_buflink));
+
+ /*
+ * Remove all the buffers from the task and add them to a
+ * temporary local list -- we do this so that we can hold
+ * the task lock and prevent the task from going away if
+ * the client decides to call idm_task_done/idm_task_free.
+ * This could happen during abort in iscsit.
+ */
+ mutex_enter(&idt->idt_mutex);
+ for (idb = list_head(&idt->idt_inbufv);
+ idb != NULL;
+ idb = next_idb) {
+ next_idb = list_next(&idt->idt_inbufv, idb);
+ idm_buf_unbind_in_locked(idt, idb);
+ list_insert_tail(&tmp_buflist, idb);
+ }
+
+ for (idb = list_head(&idt->idt_outbufv);
+ idb != NULL;
+ idb = next_idb) {
+ next_idb = list_next(&idt->idt_outbufv, idb);
+ idm_buf_unbind_out_locked(idt, idb);
+ list_insert_tail(&tmp_buflist, idb);
+ }
+ mutex_exit(&idt->idt_mutex);
+
+ for (idb = list_head(&tmp_buflist); idb != NULL; idb = next_idb) {
+ next_idb = list_next(&tmp_buflist, idb);
+ list_remove(&tmp_buflist, idb);
+ (*idb->idb_buf_cb)(idb, IDM_STATUS_ABORTED);
+ }
+ list_destroy(&tmp_buflist);
+}
+
+
+/*
+ * idm_pdu_tx
+ *
+ * This is IDM's implementation of the 'Send_Control' operational primitive.
+ * This function is invoked by an initiator iSCSI layer requesting the transfer
+ * of a iSCSI command PDU or a target iSCSI layer requesting the transfer of a
+ * iSCSI response PDU. The PDU will be transmitted as-is by the local Datamover
+ * layer to the peer iSCSI layer in the remote iSCSI node. The connection info
+ * and iSCSI PDU-specific qualifiers namely BHS, AHS, DataDescriptor and Size
+ * are provided as input.
+ *
+ */
+void
+idm_pdu_tx(idm_pdu_t *pdu)
+{
+ idm_conn_t *ic = pdu->isp_ic;
+ iscsi_async_evt_hdr_t *async_evt;
+
+ /*
+ * If we are in full-featured mode then route SCSI-related
+ * commands to the appropriate function vector without checking
+ * the connection state. We will only be in full-feature mode
+ * when we are in an acceptable state for SCSI PDU's.
+ *
+ * We also need to ensure that there are no PDU events outstanding
+ * on the state machine. Any non-SCSI PDU's received in full-feature
+ * mode will result in PDU events and until these have been handled
+ * we need to route all PDU's through the state machine as PDU
+ * events to maintain ordering.
+ *
+ * Note that IDM cannot enter FFP mode until it processes in
+ * its state machine the last xmit of the login process.
+ * Hence, checking the IDM_PDU_LOGIN_TX flag here would be
+ * superfluous.
+ */
+ mutex_enter(&ic->ic_state_mutex);
+ if (ic->ic_ffp && (ic->ic_pdu_events == 0)) {
+ mutex_exit(&ic->ic_state_mutex);
+ switch (IDM_PDU_OPCODE(pdu)) {
+ case ISCSI_OP_SCSI_RSP:
+ /* Target only */
+ idm_pdu_tx_forward(ic, pdu);
+ return;
+ case ISCSI_OP_SCSI_TASK_MGT_RSP:
+ /* Target only */
+ idm_pdu_tx_forward(ic, pdu);
+ return;
+ case ISCSI_OP_SCSI_DATA_RSP:
+ /* Target only */
+ idm_pdu_tx_forward(ic, pdu);
+ return;
+ case ISCSI_OP_RTT_RSP:
+ /* Target only */
+ idm_pdu_tx_forward(ic, pdu);
+ return;
+ case ISCSI_OP_NOOP_IN:
+ /* Target only */
+ idm_pdu_tx_forward(ic, pdu);
+ return;
+ case ISCSI_OP_TEXT_RSP:
+ /* Target only */
+ idm_pdu_tx_forward(ic, pdu);
+ return;
+ case ISCSI_OP_TEXT_CMD:
+ case ISCSI_OP_NOOP_OUT:
+ case ISCSI_OP_SCSI_CMD:
+ case ISCSI_OP_SCSI_DATA:
+ case ISCSI_OP_SCSI_TASK_MGT_MSG:
+ /* Initiator only */
+ idm_pdu_tx_forward(ic, pdu);
+ return;
+ default:
+ break;
+ }
+
+ mutex_enter(&ic->ic_state_mutex);
+ }
+
+ /*
+ * Any PDU's processed outside of full-feature mode and non-SCSI
+ * PDU's in full-feature mode are handled by generating an
+ * event to the connection state machine. The state machine
+ * will validate the PDU against the current state and either
+ * transmit the PDU if the opcode is allowed or handle an
+ * error if the PDU is not allowed.
+ *
+ * This code-path will also generate any events that are implied
+ * by the PDU opcode. For example a "login response" with success
+ * status generates a CE_LOGOUT_SUCCESS_SND event.
+ */
+ switch (IDM_PDU_OPCODE(pdu)) {
+ case ISCSI_OP_LOGIN_CMD:
+ idm_conn_tx_pdu_event(ic, CE_LOGIN_SND, (uintptr_t)pdu);
+ break;
+ case ISCSI_OP_LOGIN_RSP:
+ idm_parse_login_rsp(ic, pdu, /* Is RX */ B_FALSE);
+ break;
+ case ISCSI_OP_LOGOUT_CMD:
+ idm_parse_logout_req(ic, pdu, /* Is RX */ B_FALSE);
+ break;
+ case ISCSI_OP_LOGOUT_RSP:
+ idm_parse_logout_rsp(ic, pdu, /* Is RX */ B_FALSE);
+ break;
+ case ISCSI_OP_ASYNC_EVENT:
+ async_evt = (iscsi_async_evt_hdr_t *)pdu->isp_hdr;
+ switch (async_evt->async_event) {
+ case ISCSI_ASYNC_EVENT_REQUEST_LOGOUT:
+ idm_conn_tx_pdu_event(ic, CE_ASYNC_LOGOUT_SND,
+ (uintptr_t)pdu);
+ break;
+ case ISCSI_ASYNC_EVENT_DROPPING_CONNECTION:
+ idm_conn_tx_pdu_event(ic, CE_ASYNC_DROP_CONN_SND,
+ (uintptr_t)pdu);
+ break;
+ case ISCSI_ASYNC_EVENT_DROPPING_ALL_CONNECTIONS:
+ idm_conn_tx_pdu_event(ic, CE_ASYNC_DROP_ALL_CONN_SND,
+ (uintptr_t)pdu);
+ break;
+ case ISCSI_ASYNC_EVENT_SCSI_EVENT:
+ case ISCSI_ASYNC_EVENT_PARAM_NEGOTIATION:
+ default:
+ idm_conn_tx_pdu_event(ic, CE_MISC_TX,
+ (uintptr_t)pdu);
+ break;
+ }
+ break;
+ case ISCSI_OP_SCSI_RSP:
+ /* Target only */
+ idm_conn_tx_pdu_event(ic, CE_MISC_TX, (uintptr_t)pdu);
+ break;
+ case ISCSI_OP_SCSI_TASK_MGT_RSP:
+ /* Target only */
+ idm_conn_tx_pdu_event(ic, CE_MISC_TX, (uintptr_t)pdu);
+ break;
+ case ISCSI_OP_SCSI_DATA_RSP:
+ /* Target only */
+ idm_conn_tx_pdu_event(ic, CE_MISC_TX, (uintptr_t)pdu);
+ break;
+ case ISCSI_OP_RTT_RSP:
+ /* Target only */
+ idm_conn_tx_pdu_event(ic, CE_MISC_TX, (uintptr_t)pdu);
+ break;
+ case ISCSI_OP_NOOP_IN:
+ /* Target only */
+ idm_conn_tx_pdu_event(ic, CE_MISC_TX, (uintptr_t)pdu);
+ break;
+ case ISCSI_OP_TEXT_RSP:
+ /* Target only */
+ idm_conn_tx_pdu_event(ic, CE_MISC_TX, (uintptr_t)pdu);
+ break;
+ /* Initiator only */
+ case ISCSI_OP_SCSI_CMD:
+ case ISCSI_OP_SCSI_TASK_MGT_MSG:
+ case ISCSI_OP_SCSI_DATA:
+ case ISCSI_OP_NOOP_OUT:
+ case ISCSI_OP_TEXT_CMD:
+ case ISCSI_OP_SNACK_CMD:
+ case ISCSI_OP_REJECT_MSG:
+ default:
+ /*
+ * Connection state machine will validate these PDU's against
+ * the current state. A PDU not allowed in the current
+ * state will cause a protocol error.
+ */
+ idm_conn_tx_pdu_event(ic, CE_MISC_TX, (uintptr_t)pdu);
+ break;
+ }
+ mutex_exit(&ic->ic_state_mutex);
+}
+
+/*
+ * Allocates a PDU along with memory for header and data.
+ */
+
+idm_pdu_t *
+idm_pdu_alloc(uint_t hdrlen, uint_t datalen)
+{
+ idm_pdu_t *result;
+
+ /*
+ * IDM clients should cache these structures for performance
+ * critical paths. We can't cache effectively in IDM because we
+ * don't know the correct header and data size.
+ *
+ * Valid header length is assumed to be hdrlen and valid data
+ * length is assumed to be datalen. isp_hdrlen and isp_datalen
+ * can be adjusted after the PDU is returned if necessary.
+ */
+ result = kmem_zalloc(sizeof (idm_pdu_t) + hdrlen + datalen, KM_SLEEP);
+ result->isp_flags |= IDM_PDU_ALLOC; /* For idm_pdu_free sanity check */
+ result->isp_hdr = (iscsi_hdr_t *)(result + 1); /* Ptr. Arithmetic */
+ result->isp_hdrlen = hdrlen;
+ result->isp_hdrbuflen = hdrlen;
+ result->isp_transport_hdrlen = 0;
+ result->isp_data = (uint8_t *)result->isp_hdr + hdrlen;
+ result->isp_datalen = datalen;
+ result->isp_databuflen = datalen;
+ result->isp_magic = IDM_PDU_MAGIC;
+
+ return (result);
+}
+
+/*
+ * Free a PDU previously allocated with idm_pdu_alloc() including any
+ * header and data space allocated as part of the original request.
+ * Additional memory regions referenced by subsequent modification of
+ * the isp_hdr and/or isp_data fields will not be freed.
+ */
+void
+idm_pdu_free(idm_pdu_t *pdu)
+{
+ /* Make sure the structure was allocated using idm_pdu_alloc() */
+ ASSERT(pdu->isp_flags & IDM_PDU_ALLOC);
+ kmem_free(pdu,
+ sizeof (idm_pdu_t) + pdu->isp_hdrbuflen + pdu->isp_databuflen);
+}
+
+/*
+ * Initialize the connection, private and callback fields in a PDU.
+ */
+void
+idm_pdu_init(idm_pdu_t *pdu, idm_conn_t *ic, void *private, idm_pdu_cb_t *cb)
+{
+ /*
+ * idm_pdu_complete() will call idm_pdu_free if the callback is
+ * NULL. This will only work if the PDU was originally allocated
+ * with idm_pdu_alloc().
+ */
+ ASSERT((pdu->isp_flags & IDM_PDU_ALLOC) ||
+ (cb != NULL));
+ pdu->isp_magic = IDM_PDU_MAGIC;
+ pdu->isp_ic = ic;
+ pdu->isp_private = private;
+ pdu->isp_callback = cb;
+}
+
+/*
+ * Initialize the header and header length field. This function should
+ * not be used to adjust the header length in a buffer allocated via
+ * pdu_pdu_alloc since it overwrites the existing header pointer.
+ */
+void
+idm_pdu_init_hdr(idm_pdu_t *pdu, uint8_t *hdr, uint_t hdrlen)
+{
+ pdu->isp_hdr = (iscsi_hdr_t *)((void *)hdr);
+ pdu->isp_hdrlen = hdrlen;
+}
+
+/*
+ * Initialize the data and data length fields. This function should
+ * not be used to adjust the data length of a buffer allocated via
+ * idm_pdu_alloc since it overwrites the existing data pointer.
+ */
+void
+idm_pdu_init_data(idm_pdu_t *pdu, uint8_t *data, uint_t datalen)
+{
+ pdu->isp_data = data;
+ pdu->isp_datalen = datalen;
+}
+
+void
+idm_pdu_complete(idm_pdu_t *pdu, idm_status_t status)
+{
+ if (pdu->isp_callback) {
+ pdu->isp_status = status;
+ (*pdu->isp_callback)(pdu, status);
+ } else {
+ idm_pdu_free(pdu);
+ }
+}
+
+/*
+ * State machine auditing
+ */
+
+void
+idm_sm_audit_init(sm_audit_buf_t *audit_buf)
+{
+ bzero(audit_buf, sizeof (sm_audit_buf_t));
+ audit_buf->sab_max_index = SM_AUDIT_BUF_MAX_REC - 1;
+}
+
+static
+sm_audit_record_t *
+idm_sm_audit_common(sm_audit_buf_t *audit_buf, sm_audit_record_type_t r_type,
+ sm_audit_sm_type_t sm_type,
+ int current_state)
+{
+ sm_audit_record_t *sar;
+
+ sar = audit_buf->sab_records;
+ sar += audit_buf->sab_index;
+ audit_buf->sab_index++;
+ audit_buf->sab_index &= audit_buf->sab_max_index;
+
+ sar->sar_type = r_type;
+ gethrestime(&sar->sar_timestamp);
+ sar->sar_sm_type = sm_type;
+ sar->sar_state = current_state;
+
+ return (sar);
+}
+
+void
+idm_sm_audit_event(sm_audit_buf_t *audit_buf,
+ sm_audit_sm_type_t sm_type, int current_state,
+ int event, uintptr_t event_info)
+{
+ sm_audit_record_t *sar;
+
+ sar = idm_sm_audit_common(audit_buf, SAR_STATE_EVENT,
+ sm_type, current_state);
+ sar->sar_event = event;
+ sar->sar_event_info = event_info;
+}
+
+void
+idm_sm_audit_state_change(sm_audit_buf_t *audit_buf,
+ sm_audit_sm_type_t sm_type, int current_state, int new_state)
+{
+ sm_audit_record_t *sar;
+
+ sar = idm_sm_audit_common(audit_buf, SAR_STATE_CHANGE,
+ sm_type, current_state);
+ sar->sar_new_state = new_state;
+}
+
+
+/*
+ * Object reference tracking
+ */
+
+void
+idm_refcnt_init(idm_refcnt_t *refcnt, void *referenced_obj)
+{
+ bzero(refcnt, sizeof (*refcnt));
+ idm_refcnt_reset(refcnt);
+ refcnt->ir_referenced_obj = referenced_obj;
+ bzero(&refcnt->ir_audit_buf, sizeof (refcnt_audit_buf_t));
+ refcnt->ir_audit_buf.anb_max_index = REFCNT_AUDIT_BUF_MAX_REC - 1;
+ mutex_init(&refcnt->ir_mutex, NULL, MUTEX_DEFAULT, NULL);
+ cv_init(&refcnt->ir_cv, NULL, CV_DEFAULT, NULL);
+}
+
+void
+idm_refcnt_destroy(idm_refcnt_t *refcnt)
+{
+ ASSERT(refcnt->ir_refcnt == 0);
+ cv_destroy(&refcnt->ir_cv);
+ mutex_destroy(&refcnt->ir_mutex);
+}
+
+void
+idm_refcnt_reset(idm_refcnt_t *refcnt)
+{
+ refcnt->ir_waiting = REF_NOWAIT;
+ refcnt->ir_refcnt = 0;
+}
+
+void
+idm_refcnt_hold(idm_refcnt_t *refcnt)
+{
+ /*
+ * Nothing should take a hold on an object after a call to
+ * idm_refcnt_wait_ref or idm_refcnd_async_wait_ref
+ */
+ ASSERT(refcnt->ir_waiting == REF_NOWAIT);
+
+ mutex_enter(&refcnt->ir_mutex);
+ refcnt->ir_refcnt++;
+ REFCNT_AUDIT(refcnt);
+ mutex_exit(&refcnt->ir_mutex);
+}
+
+static void
+idm_refcnt_unref_task(void *refcnt_void)
+{
+ idm_refcnt_t *refcnt = refcnt_void;
+
+ REFCNT_AUDIT(refcnt);
+ (*refcnt->ir_cb)(refcnt->ir_referenced_obj);
+}
+
+void
+idm_refcnt_rele(idm_refcnt_t *refcnt)
+{
+ mutex_enter(&refcnt->ir_mutex);
+ ASSERT(refcnt->ir_refcnt > 0);
+ refcnt->ir_refcnt--;
+ REFCNT_AUDIT(refcnt);
+ if (refcnt->ir_waiting == REF_NOWAIT) {
+ /* No one is waiting on this object */
+ mutex_exit(&refcnt->ir_mutex);
+ return;
+ }
+
+ /*
+ * Someone is waiting for this object to go idle so check if
+ * refcnt is 0. Waiting on an object then later grabbing another
+ * reference is not allowed so we don't need to handle that case.
+ */
+ if (refcnt->ir_refcnt == 0) {
+ if (refcnt->ir_waiting == REF_WAIT_ASYNC) {
+ if (taskq_dispatch(idm.idm_global_taskq,
+ &idm_refcnt_unref_task, refcnt, TQ_SLEEP) == NULL) {
+ cmn_err(CE_WARN,
+ "idm_refcnt_rele: Couldn't dispatch task");
+ }
+ } else if (refcnt->ir_waiting == REF_WAIT_SYNC) {
+ cv_signal(&refcnt->ir_cv);
+ }
+ }
+ mutex_exit(&refcnt->ir_mutex);
+}
+
+void
+idm_refcnt_rele_and_destroy(idm_refcnt_t *refcnt, idm_refcnt_cb_t *cb_func)
+{
+ mutex_enter(&refcnt->ir_mutex);
+ ASSERT(refcnt->ir_refcnt > 0);
+ refcnt->ir_refcnt--;
+ REFCNT_AUDIT(refcnt);
+
+ /*
+ * Someone is waiting for this object to go idle so check if
+ * refcnt is 0. Waiting on an object then later grabbing another
+ * reference is not allowed so we don't need to handle that case.
+ */
+ if (refcnt->ir_refcnt == 0) {
+ refcnt->ir_cb = cb_func;
+ refcnt->ir_waiting = REF_WAIT_ASYNC;
+ if (taskq_dispatch(idm.idm_global_taskq,
+ &idm_refcnt_unref_task, refcnt, TQ_SLEEP) == NULL) {
+ cmn_err(CE_WARN,
+ "idm_refcnt_rele: Couldn't dispatch task");
+ }
+ }
+ mutex_exit(&refcnt->ir_mutex);
+}
+
+void
+idm_refcnt_wait_ref(idm_refcnt_t *refcnt)
+{
+ mutex_enter(&refcnt->ir_mutex);
+ refcnt->ir_waiting = REF_WAIT_SYNC;
+ REFCNT_AUDIT(refcnt);
+ while (refcnt->ir_refcnt != 0)
+ cv_wait(&refcnt->ir_cv, &refcnt->ir_mutex);
+ mutex_exit(&refcnt->ir_mutex);
+}
+
+void
+idm_refcnt_async_wait_ref(idm_refcnt_t *refcnt, idm_refcnt_cb_t *cb_func)
+{
+ mutex_enter(&refcnt->ir_mutex);
+ refcnt->ir_waiting = REF_WAIT_ASYNC;
+ refcnt->ir_cb = cb_func;
+ REFCNT_AUDIT(refcnt);
+ /*
+ * It's possible we don't have any references. To make things easier
+ * on the caller use a taskq to call the callback instead of
+ * calling it synchronously
+ */
+ if (refcnt->ir_refcnt == 0) {
+ if (taskq_dispatch(idm.idm_global_taskq,
+ &idm_refcnt_unref_task, refcnt, TQ_SLEEP) == NULL) {
+ cmn_err(CE_WARN,
+ "idm_refcnt_async_wait_ref: "
+ "Couldn't dispatch task");
+ }
+ }
+ mutex_exit(&refcnt->ir_mutex);
+}
+
+void
+idm_refcnt_destroy_unref_obj(idm_refcnt_t *refcnt,
+ idm_refcnt_cb_t *cb_func)
+{
+ mutex_enter(&refcnt->ir_mutex);
+ if (refcnt->ir_refcnt == 0) {
+ mutex_exit(&refcnt->ir_mutex);
+ (*cb_func)(refcnt->ir_referenced_obj);
+ return;
+ }
+ mutex_exit(&refcnt->ir_mutex);
+}
+
+void
+idm_conn_hold(idm_conn_t *ic)
+{
+ idm_refcnt_hold(&ic->ic_refcnt);
+}
+
+void
+idm_conn_rele(idm_conn_t *ic)
+{
+ idm_refcnt_rele(&ic->ic_refcnt);
+}
+
+
+static int
+_idm_init(void)
+{
+ /* Initialize the rwlock for the taskid table */
+ rw_init(&idm.idm_taskid_table_lock, NULL, RW_DRIVER, NULL);
+
+ /* Initialize the global mutex and taskq */
+ mutex_init(&idm.idm_global_mutex, NULL, MUTEX_DEFAULT, NULL);
+
+ cv_init(&idm.idm_tgt_svc_cv, NULL, CV_DEFAULT, NULL);
+ cv_init(&idm.idm_wd_cv, NULL, CV_DEFAULT, NULL);
+
+ idm.idm_global_taskq = taskq_create("idm_global_taskq", 1, minclsyspri,
+ 4, 4, TASKQ_PREPOPULATE);
+ if (idm.idm_global_taskq == NULL) {
+ cv_destroy(&idm.idm_wd_cv);
+ cv_destroy(&idm.idm_tgt_svc_cv);
+ mutex_destroy(&idm.idm_global_mutex);
+ rw_destroy(&idm.idm_taskid_table_lock);
+ return (ENOMEM);
+ }
+
+ /* start watchdog thread */
+ idm.idm_wd_thread = thread_create(NULL, 0,
+ idm_wd_thread, NULL, 0, &p0, TS_RUN, minclsyspri);
+ if (idm.idm_wd_thread == NULL) {
+ /* Couldn't create the watchdog thread */
+ taskq_destroy(idm.idm_global_taskq);
+ cv_destroy(&idm.idm_wd_cv);
+ cv_destroy(&idm.idm_tgt_svc_cv);
+ mutex_destroy(&idm.idm_global_mutex);
+ rw_destroy(&idm.idm_taskid_table_lock);
+ return (ENOMEM);
+ }
+
+ mutex_enter(&idm.idm_global_mutex);
+ while (!idm.idm_wd_thread_running)
+ cv_wait(&idm.idm_wd_cv, &idm.idm_global_mutex);
+ mutex_exit(&idm.idm_global_mutex);
+
+ /*
+ * Allocate the task ID table and set "next" to 0.
+ */
+
+ idm.idm_taskid_max = idm_max_taskids;
+ idm.idm_taskid_table = (idm_task_t **)
+ kmem_zalloc(idm.idm_taskid_max * sizeof (idm_task_t *), KM_SLEEP);
+ idm.idm_taskid_next = 0;
+
+ /* Create the global buffer and task kmem caches */
+ idm.idm_buf_cache = kmem_cache_create("idm_buf_cache",
+ sizeof (idm_buf_t), 8, NULL, NULL, NULL, NULL, NULL, KM_SLEEP);
+
+ /*
+ * Note, we're explicitly allocating an additional iSER header-
+ * sized chunk for each of these elements. See idm_task_constructor().
+ */
+ idm.idm_task_cache = kmem_cache_create("idm_task_cache",
+ sizeof (idm_task_t) + IDM_TRANSPORT_HEADER_LENGTH, 8,
+ &idm_task_constructor, &idm_task_destructor,
+ NULL, NULL, NULL, KM_SLEEP);
+
+ /* Create the service and connection context lists */
+ list_create(&idm.idm_tgt_svc_list, sizeof (idm_svc_t),
+ offsetof(idm_svc_t, is_list_node));
+ list_create(&idm.idm_tgt_conn_list, sizeof (idm_conn_t),
+ offsetof(idm_conn_t, ic_list_node));
+ list_create(&idm.idm_ini_conn_list, sizeof (idm_conn_t),
+ offsetof(idm_conn_t, ic_list_node));
+
+ /* Initialize the native sockets transport */
+ idm_so_init(&idm_transport_list[IDM_TRANSPORT_TYPE_SOCKETS]);
+
+ /* Create connection ID pool */
+ (void) idm_idpool_create(&idm.idm_conn_id_pool);
+
+ return (DDI_SUCCESS);
+}
+
+static int
+_idm_fini(void)
+{
+ if (!list_is_empty(&idm.idm_ini_conn_list) ||
+ !list_is_empty(&idm.idm_tgt_conn_list) ||
+ !list_is_empty(&idm.idm_tgt_svc_list)) {
+ return (EBUSY);
+ }
+
+ mutex_enter(&idm.idm_global_mutex);
+ idm.idm_wd_thread_running = B_FALSE;
+ cv_signal(&idm.idm_wd_cv);
+ mutex_exit(&idm.idm_global_mutex);
+
+ thread_join(idm.idm_wd_thread_did);
+
+ idm_idpool_destroy(&idm.idm_conn_id_pool);
+ idm_so_fini();
+ list_destroy(&idm.idm_ini_conn_list);
+ list_destroy(&idm.idm_tgt_conn_list);
+ list_destroy(&idm.idm_tgt_svc_list);
+ kmem_cache_destroy(idm.idm_task_cache);
+ kmem_cache_destroy(idm.idm_buf_cache);
+ kmem_free(idm.idm_taskid_table,
+ idm.idm_taskid_max * sizeof (idm_task_t *));
+ mutex_destroy(&idm.idm_global_mutex);
+ cv_destroy(&idm.idm_wd_cv);
+ cv_destroy(&idm.idm_tgt_svc_cv);
+ rw_destroy(&idm.idm_taskid_table_lock);
+
+ return (0);
+}
diff --git a/usr/src/uts/common/io/idm/idm_conn_sm.c b/usr/src/uts/common/io/idm/idm_conn_sm.c
new file mode 100644
index 0000000000..a46491c038
--- /dev/null
+++ b/usr/src/uts/common/io/idm/idm_conn_sm.c
@@ -0,0 +1,1480 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/cpuvar.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/modctl.h>
+#include <sys/socket.h>
+#include <sys/strsubr.h>
+#include <sys/note.h>
+#include <sys/sdt.h>
+
+#define IDM_CONN_SM_STRINGS
+#include <sys/idm/idm.h>
+
+boolean_t idm_sm_logging = B_FALSE;
+
+extern idm_global_t idm; /* Global state */
+
+static void
+idm_conn_event_locked(idm_conn_t *ic, idm_conn_event_t event,
+ uintptr_t event_info, idm_pdu_event_type_t pdu_event_type);
+
+static void
+idm_conn_event_handler(void *event_ctx_opaque);
+
+static void
+idm_state_s1_free(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
+
+static void
+idm_state_s2_xpt_wait(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
+
+static void
+idm_state_s3_xpt_up(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
+
+static void
+idm_state_s4_in_login(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
+
+static void
+idm_state_s5_logged_in(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
+
+static void
+idm_state_s6_in_logout(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
+
+static void
+idm_logout_req_timeout(void *arg);
+
+static void
+idm_state_s7_logout_req(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
+
+static void
+idm_state_s8_cleanup(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
+
+static void
+idm_state_s9_init_error(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
+
+static void
+idm_state_s10_in_cleanup(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
+
+static void
+idm_state_s11_complete(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
+
+static void
+idm_state_s12_enable_dm(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
+
+static void
+idm_update_state(idm_conn_t *ic, idm_conn_state_t new_state,
+ idm_conn_event_ctx_t *event_ctx);
+
+static void
+idm_conn_unref(void *ic_void);
+
+static idm_pdu_event_action_t
+idm_conn_sm_validate_pdu(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx,
+ idm_pdu_t *pdu);
+
+static idm_status_t
+idm_ffp_enable(idm_conn_t *ic);
+
+static void
+idm_ffp_disable(idm_conn_t *ic, idm_ffp_disable_t disable_type);
+
+static void
+idm_initial_login_actions(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
+
+static void
+idm_login_success_actions(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx);
+
+idm_status_t
+idm_conn_sm_init(idm_conn_t *ic)
+{
+ char taskq_name[32];
+
+ /*
+ * Caller should have assigned a unique connection ID. Use this
+ * connection ID to create a unique connection name string
+ */
+ ASSERT(ic->ic_internal_cid != 0);
+ (void) snprintf(taskq_name, sizeof (taskq_name) - 1, "conn_sm%08x",
+ ic->ic_internal_cid);
+
+ ic->ic_state_taskq = taskq_create(taskq_name, 1, minclsyspri, 2, 2,
+ TASKQ_PREPOPULATE);
+ if (ic->ic_state_taskq == NULL) {
+ return (IDM_STATUS_FAIL);
+ }
+
+ idm_sm_audit_init(&ic->ic_state_audit);
+ mutex_init(&ic->ic_state_mutex, NULL, MUTEX_DEFAULT, NULL);
+ cv_init(&ic->ic_state_cv, NULL, CV_DEFAULT, NULL);
+
+ ic->ic_state = CS_S1_FREE;
+ ic->ic_last_state = CS_S1_FREE;
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+void
+idm_conn_sm_fini(idm_conn_t *ic)
+{
+ taskq_destroy(ic->ic_state_taskq);
+
+ cv_destroy(&ic->ic_state_cv);
+ /*
+ * The thread that generated the event that got us here may still
+ * hold the ic_state_mutex. Once it is released we can safely
+ * destroy it since there is no way to locate the object now.
+ */
+ mutex_enter(&ic->ic_state_mutex);
+ mutex_destroy(&ic->ic_state_mutex);
+}
+
+void
+idm_conn_event(idm_conn_t *ic, idm_conn_event_t event, uintptr_t event_info)
+{
+ mutex_enter(&ic->ic_state_mutex);
+ idm_conn_event_locked(ic, event, event_info, CT_NONE);
+ mutex_exit(&ic->ic_state_mutex);
+}
+
+idm_status_t
+idm_conn_reinstate_event(idm_conn_t *old_ic, idm_conn_t *new_ic)
+{
+ int result;
+
+ mutex_enter(&old_ic->ic_state_mutex);
+ if (((old_ic->ic_conn_type == CONN_TYPE_INI) &&
+ (old_ic->ic_state != CS_S8_CLEANUP)) ||
+ ((old_ic->ic_conn_type == CONN_TYPE_TGT) &&
+ (old_ic->ic_state < CS_S5_LOGGED_IN))) {
+ result = IDM_STATUS_FAIL;
+ } else {
+ result = IDM_STATUS_SUCCESS;
+ new_ic->ic_reinstate_conn = old_ic;
+ idm_conn_event_locked(new_ic->ic_reinstate_conn,
+ CE_CONN_REINSTATE, (uintptr_t)new_ic, CT_NONE);
+ }
+ mutex_exit(&old_ic->ic_state_mutex);
+
+ return (result);
+}
+
+void
+idm_conn_tx_pdu_event(idm_conn_t *ic, idm_conn_event_t event,
+ uintptr_t event_info)
+{
+ ASSERT(mutex_owned(&ic->ic_state_mutex));
+ ic->ic_pdu_events++;
+ idm_conn_event_locked(ic, event, event_info, CT_TX_PDU);
+}
+
+void
+idm_conn_rx_pdu_event(idm_conn_t *ic, idm_conn_event_t event,
+ uintptr_t event_info)
+{
+ ASSERT(mutex_owned(&ic->ic_state_mutex));
+ ic->ic_pdu_events++;
+ idm_conn_event_locked(ic, event, event_info, CT_RX_PDU);
+}
+
+static void
+idm_conn_event_locked(idm_conn_t *ic, idm_conn_event_t event,
+ uintptr_t event_info, idm_pdu_event_type_t pdu_event_type)
+{
+ idm_conn_event_ctx_t *event_ctx;
+
+ idm_sm_audit_event(&ic->ic_state_audit, SAS_IDM_CONN,
+ (int)ic->ic_state, (int)event, event_info);
+
+ /*
+ * It's very difficult to prevent a few straggling events
+ * at the end. For example idm_sorx_thread will generate
+ * a CE_TRANSPORT_FAIL event when it exits. Rather than
+ * push complicated restrictions all over the code to
+ * prevent this we will simply drop the events (and in
+ * the case of PDU events release them appropriately)
+ * since they are irrelevant once we are in a terminal state.
+ * Of course those threads need to have appropriate holds on
+ * the connection otherwise it might disappear.
+ */
+ if ((ic->ic_state == CS_S9_INIT_ERROR) ||
+ (ic->ic_state == CS_S11_COMPLETE)) {
+ if ((pdu_event_type == CT_TX_PDU) ||
+ (pdu_event_type == CT_RX_PDU)) {
+ ic->ic_pdu_events--;
+ idm_pdu_complete((idm_pdu_t *)event_info,
+ IDM_STATUS_SUCCESS);
+ }
+ IDM_SM_LOG(CE_NOTE, "*** Dropping event %s (%d) because of"
+ "state %s (%d)",
+ idm_ce_name[event], event,
+ idm_cs_name[ic->ic_state], ic->ic_state);
+ return;
+ }
+
+ /*
+ * Normal event handling
+ */
+ idm_conn_hold(ic);
+
+ event_ctx = kmem_zalloc(sizeof (*event_ctx), KM_SLEEP);
+ event_ctx->iec_ic = ic;
+ event_ctx->iec_event = event;
+ event_ctx->iec_info = event_info;
+ event_ctx->iec_pdu_event_type = pdu_event_type;
+
+ (void) taskq_dispatch(ic->ic_state_taskq, &idm_conn_event_handler,
+ event_ctx, TQ_SLEEP);
+}
+
+static void
+idm_conn_event_handler(void *event_ctx_opaque)
+{
+ idm_conn_event_ctx_t *event_ctx = event_ctx_opaque;
+ idm_conn_t *ic = event_ctx->iec_ic;
+ idm_pdu_t *pdu = (idm_pdu_t *)event_ctx->iec_info;
+ idm_pdu_event_action_t action;
+
+ IDM_SM_LOG(CE_NOTE, "idm_conn_event_handler: conn %p event %s(%d)",
+ (void *)ic, idm_ce_name[event_ctx->iec_event],
+ event_ctx->iec_event);
+ DTRACE_PROBE2(conn__event,
+ idm_conn_t *, ic, smb_event_ctx_t *, event_ctx);
+
+ /*
+ * Validate event
+ */
+ ASSERT(event_ctx->iec_event != CE_UNDEFINED);
+ ASSERT3U(event_ctx->iec_event, <, CE_MAX_EVENT);
+
+ /*
+ * Validate current state
+ */
+ ASSERT(ic->ic_state != CS_S0_UNDEFINED);
+ ASSERT3U(ic->ic_state, <, CS_MAX_STATE);
+
+ /*
+ * Validate PDU-related events against the current state. If a PDU
+ * is not allowed in the current state we change the event to a
+ * protocol error. This simplifies the state-specific event handlers.
+ * For example the CS_S2_XPT_WAIT state only needs to handle the
+ * CE_TX_PROTOCOL_ERROR and CE_RX_PROTOCOL_ERROR events since
+ * no PDU's can be transmitted or received in that state.
+ */
+ if (event_ctx->iec_pdu_event_type != CT_NONE) {
+ ASSERT(pdu != NULL);
+ action = idm_conn_sm_validate_pdu(ic, event_ctx, pdu);
+
+ switch (action) {
+ case CA_TX_PROTOCOL_ERROR:
+ /*
+ * Change event and forward the PDU
+ */
+ event_ctx->iec_event = CE_TX_PROTOCOL_ERROR;
+ break;
+ case CA_RX_PROTOCOL_ERROR:
+ /*
+ * Change event and forward the PDU.
+ */
+ event_ctx->iec_event = CE_RX_PROTOCOL_ERROR;
+ break;
+ case CA_FORWARD:
+ /*
+ * Let the state-specific event handlers take
+ * care of it.
+ */
+ break;
+ case CA_DROP:
+ /*
+ * It never even happened
+ */
+ IDM_SM_LOG(CE_NOTE, "*** drop PDU %p", (void *) pdu);
+ idm_pdu_complete(pdu, IDM_STATUS_FAIL);
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+ }
+
+ switch (ic->ic_state) {
+ case CS_S1_FREE:
+ idm_state_s1_free(ic, event_ctx);
+ break;
+ case CS_S2_XPT_WAIT:
+ idm_state_s2_xpt_wait(ic, event_ctx);
+ break;
+ case CS_S3_XPT_UP:
+ idm_state_s3_xpt_up(ic, event_ctx);
+ break;
+ case CS_S4_IN_LOGIN:
+ idm_state_s4_in_login(ic, event_ctx);
+ break;
+ case CS_S5_LOGGED_IN:
+ idm_state_s5_logged_in(ic, event_ctx);
+ break;
+ case CS_S6_IN_LOGOUT:
+ idm_state_s6_in_logout(ic, event_ctx);
+ break;
+ case CS_S7_LOGOUT_REQ:
+ idm_state_s7_logout_req(ic, event_ctx);
+ break;
+ case CS_S8_CLEANUP:
+ idm_state_s8_cleanup(ic, event_ctx);
+ break;
+ case CS_S9_INIT_ERROR:
+ idm_state_s9_init_error(ic, event_ctx);
+ break;
+ case CS_S10_IN_CLEANUP:
+ idm_state_s10_in_cleanup(ic, event_ctx);
+ break;
+ case CS_S11_COMPLETE:
+ idm_state_s11_complete(ic, event_ctx);
+ break;
+ case CS_S12_ENABLE_DM:
+ idm_state_s12_enable_dm(ic, event_ctx);
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+
+ /*
+ * Now that we've updated the state machine, if this was
+ * a PDU-related event take the appropriate action on the PDU
+ * (transmit it, forward it to the clients RX callback, drop
+ * it, etc).
+ */
+ if (event_ctx->iec_pdu_event_type != CT_NONE) {
+ switch (action) {
+ case CA_TX_PROTOCOL_ERROR:
+ idm_pdu_tx_protocol_error(ic, pdu);
+ break;
+ case CA_RX_PROTOCOL_ERROR:
+ idm_pdu_rx_protocol_error(ic, pdu);
+ break;
+ case CA_FORWARD:
+ if (event_ctx->iec_pdu_event_type == CT_RX_PDU) {
+ idm_pdu_rx_forward(ic, pdu);
+ } else {
+ idm_pdu_tx_forward(ic, pdu);
+ }
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+ }
+
+ /*
+ * Update outstanding PDU event count (see idm_pdu_tx for
+ * how this is used)
+ */
+ if ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ||
+ (event_ctx->iec_pdu_event_type == CT_RX_PDU)) {
+ mutex_enter(&ic->ic_state_mutex);
+ ic->ic_pdu_events--;
+ mutex_exit(&ic->ic_state_mutex);
+ }
+
+ idm_conn_rele(ic);
+ kmem_free(event_ctx, sizeof (*event_ctx));
+}
+
+static void
+idm_state_s1_free(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
+{
+ switch (event_ctx->iec_event) {
+ case CE_CONNECT_REQ:
+ /* T1 */
+ idm_update_state(ic, CS_S2_XPT_WAIT, event_ctx);
+ break;
+ case CE_CONNECT_ACCEPT:
+ /* T3 */
+ idm_update_state(ic, CS_S3_XPT_UP, event_ctx);
+ break;
+ case CE_TX_PROTOCOL_ERROR:
+ case CE_RX_PROTOCOL_ERROR:
+ /* This should never happen */
+ idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
+ break;
+ default:
+ ASSERT(0);
+ /*NOTREACHED*/
+ }
+}
+
+
+static void
+idm_state_s2_xpt_wait(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
+{
+ switch (event_ctx->iec_event) {
+ case CE_CONNECT_SUCCESS:
+ /* T4 */
+ idm_update_state(ic, CS_S4_IN_LOGIN, event_ctx);
+ break;
+ case CE_CONNECT_FAIL:
+ case CE_LOGOUT_OTHER_CONN_RCV:
+ case CE_TX_PROTOCOL_ERROR:
+ case CE_RX_PROTOCOL_ERROR:
+ /* T2 */
+ idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
+ break;
+ default:
+ ASSERT(0);
+ /*NOTREACHED*/
+ }
+}
+
+
+static void
+idm_login_timeout(void *arg)
+{
+ idm_conn_t *ic = arg;
+
+ idm_conn_event(ic, CE_LOGIN_TIMEOUT, NULL);
+}
+
+static void
+idm_state_s3_xpt_up(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
+{
+ switch (event_ctx->iec_event) {
+ case CE_LOGIN_RCV:
+ /* T4 */
+ idm_initial_login_actions(ic, event_ctx);
+ idm_update_state(ic, CS_S4_IN_LOGIN, event_ctx);
+ break;
+ case CE_LOGIN_TIMEOUT:
+ /*
+ * Don't need to cancel login timer since the timer is
+ * presumed to be the source of this event.
+ */
+ (void) idm_notify_client(ic, CN_LOGIN_FAIL, NULL);
+ idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
+ break;
+ case CE_CONNECT_REJECT:
+ case CE_CONNECT_FAIL:
+ case CE_TRANSPORT_FAIL:
+ case CE_LOGOUT_OTHER_CONN_SND:
+ /* T6 */
+ (void) untimeout(ic->ic_state_timeout);
+ (void) idm_notify_client(ic, CN_LOGIN_FAIL, NULL);
+ idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
+ break;
+ case CE_TX_PROTOCOL_ERROR:
+ case CE_RX_PROTOCOL_ERROR:
+ /* Don't care */
+ break;
+ default:
+ ASSERT(0);
+ /*NOTREACHED*/
+ }
+}
+
+static void
+idm_state_s4_in_login_fail_snd_done(idm_pdu_t *pdu, idm_status_t status)
+{
+ idm_conn_t *ic = pdu->isp_ic;
+
+ /*
+ * This pdu callback can be invoked by the tx thread,
+ * so run the disconnect code from another thread.
+ */
+ pdu->isp_status = status;
+ idm_conn_event(ic, CE_LOGIN_FAIL_SND_DONE, (uintptr_t)pdu);
+}
+
+static void
+idm_state_s4_in_login(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
+{
+ idm_pdu_t *pdu;
+
+ /*
+ * Login timer should no longer be active after leaving this
+ * state.
+ */
+ switch (event_ctx->iec_event) {
+ case CE_LOGIN_SUCCESS_RCV:
+ case CE_LOGIN_SUCCESS_SND:
+ (void) untimeout(ic->ic_state_timeout);
+ idm_login_success_actions(ic, event_ctx);
+ if (ic->ic_rdma_extensions) {
+ /* T19 */
+ idm_update_state(ic, CS_S12_ENABLE_DM, event_ctx);
+ } else {
+ /* T5 */
+ idm_update_state(ic, CS_S5_LOGGED_IN, event_ctx);
+ }
+ break;
+ case CE_LOGIN_TIMEOUT:
+ /* T7 */
+ (void) idm_notify_client(ic, CN_LOGIN_FAIL, NULL);
+ idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
+ break;
+ case CE_LOGIN_FAIL_SND_DONE:
+ (void) idm_notify_client(ic, CN_LOGIN_FAIL, NULL);
+ idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
+ break;
+ case CE_LOGIN_FAIL_SND:
+ /*
+ * Allow the logout response pdu to be sent and defer
+ * the state machine update until the completion callback.
+ * Only 1 level or callback interposition is allowed.
+ */
+ (void) untimeout(ic->ic_state_timeout);
+ pdu = (idm_pdu_t *)event_ctx->iec_info;
+ ASSERT(ic->ic_client_callback == NULL);
+ ic->ic_client_callback = pdu->isp_callback;
+ pdu->isp_callback =
+ idm_state_s4_in_login_fail_snd_done;
+ break;
+ case CE_LOGIN_FAIL_RCV:
+ case CE_TRANSPORT_FAIL:
+ case CE_LOGOUT_OTHER_CONN_SND:
+ case CE_LOGOUT_OTHER_CONN_RCV:
+ /* T7 */
+ (void) untimeout(ic->ic_state_timeout);
+ (void) idm_notify_client(ic, CN_LOGIN_FAIL, NULL);
+ idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
+ break;
+ case CE_LOGIN_SND:
+ /*
+ * Initiator connections will see initial login PDU
+ * in this state. Target connections see initial
+ * login PDU in "xpt up" state.
+ */
+ mutex_enter(&ic->ic_state_mutex);
+ if (!(ic->ic_state_flags & CF_INITIAL_LOGIN)) {
+ idm_initial_login_actions(ic, event_ctx);
+ }
+ mutex_exit(&ic->ic_state_mutex);
+ break;
+ case CE_MISC_TX:
+ case CE_MISC_RX:
+ case CE_LOGIN_RCV:
+ case CE_TX_PROTOCOL_ERROR:
+ case CE_RX_PROTOCOL_ERROR:
+ /* Don't care */
+ break;
+ default:
+ ASSERT(0);
+ /*NOTREACHED*/
+ }
+}
+
+
+static void
+idm_state_s5_logged_in(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
+{
+ switch (event_ctx->iec_event) {
+ case CE_LOGOUT_THIS_CONN_RCV:
+ case CE_LOGOUT_THIS_CONN_SND:
+ case CE_LOGOUT_OTHER_CONN_RCV:
+ case CE_LOGOUT_OTHER_CONN_SND:
+ /* T9 */
+ idm_ffp_disable(ic, FD_CONN_LOGOUT); /* Explicit logout */
+ idm_update_state(ic, CS_S6_IN_LOGOUT, event_ctx);
+ break;
+ case CE_LOGOUT_SESSION_RCV:
+ case CE_LOGOUT_SESSION_SND:
+ /* T9 */
+ idm_ffp_disable(ic, FD_SESS_LOGOUT); /* Explicit logout */
+ idm_update_state(ic, CS_S6_IN_LOGOUT, event_ctx);
+ break;
+ case CE_LOGOUT_SESSION_SUCCESS:
+ /* T8 */
+ idm_ffp_disable(ic, FD_SESS_LOGOUT); /* Explicit logout */
+
+ /* Close connection */
+ if (IDM_CONN_ISTGT(ic)) {
+ ic->ic_transport_ops->it_tgt_conn_disconnect(ic);
+ } else {
+ ic->ic_transport_ops->it_ini_conn_disconnect(ic);
+ }
+
+ idm_update_state(ic, CS_S11_COMPLETE, event_ctx);
+ break;
+ case CE_ASYNC_LOGOUT_RCV:
+ case CE_ASYNC_LOGOUT_SND:
+ /* T11 */
+ idm_update_state(ic, CS_S7_LOGOUT_REQ, event_ctx);
+ break;
+ case CE_TRANSPORT_FAIL:
+ case CE_ASYNC_DROP_CONN_RCV:
+ case CE_ASYNC_DROP_CONN_SND:
+ case CE_ASYNC_DROP_ALL_CONN_RCV:
+ case CE_ASYNC_DROP_ALL_CONN_SND:
+ /* T15 */
+ idm_ffp_disable(ic, FD_CONN_FAIL); /* Implicit logout */
+ idm_update_state(ic, CS_S8_CLEANUP, event_ctx);
+ break;
+ case CE_MISC_TX:
+ case CE_MISC_RX:
+ case CE_TX_PROTOCOL_ERROR:
+ case CE_RX_PROTOCOL_ERROR:
+ /* Don't care */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+static void
+idm_state_s6_in_logout_success_snd_done(idm_pdu_t *pdu, idm_status_t status)
+{
+ idm_conn_t *ic = pdu->isp_ic;
+
+ /*
+ * This pdu callback can be invoked by the tx thread,
+ * so run the disconnect code from another thread.
+ */
+ pdu->isp_status = status;
+ idm_conn_event(ic, CE_LOGOUT_SUCCESS_SND_DONE, (uintptr_t)pdu);
+}
+
+static void
+idm_state_s6_in_logout_fail_snd_done(idm_pdu_t *pdu, idm_status_t status)
+{
+ idm_conn_t *ic = pdu->isp_ic;
+
+ /*
+ * This pdu callback can be invoked by the tx thread,
+ * so run the disconnect code from another thread.
+ */
+ pdu->isp_status = status;
+ idm_conn_event(ic, CE_LOGOUT_FAIL_SND_DONE, (uintptr_t)pdu);
+}
+
+static void
+idm_state_s6_in_logout(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
+{
+ idm_pdu_t *pdu;
+
+ switch (event_ctx->iec_event) {
+ case CE_LOGOUT_SUCCESS_SND_DONE:
+ pdu = (idm_pdu_t *)event_ctx->iec_info;
+
+ /* Close connection (if it's not already closed) */
+ ASSERT(IDM_CONN_ISTGT(ic));
+ ic->ic_transport_ops->it_tgt_conn_disconnect(ic);
+
+ /* restore client callback */
+ pdu->isp_callback = ic->ic_client_callback;
+ ic->ic_client_callback = NULL;
+ idm_pdu_complete(pdu, pdu->isp_status);
+ idm_update_state(ic, CS_S11_COMPLETE, event_ctx);
+ break;
+ case CE_LOGOUT_FAIL_SND_DONE:
+ pdu = (idm_pdu_t *)event_ctx->iec_info;
+ /* restore client callback */
+ pdu->isp_callback = ic->ic_client_callback;
+ ic->ic_client_callback = NULL;
+ idm_pdu_complete(pdu, pdu->isp_status);
+ idm_update_state(ic, CS_S8_CLEANUP, event_ctx);
+ break;
+ case CE_LOGOUT_SUCCESS_SND:
+ case CE_LOGOUT_FAIL_SND:
+ /*
+ * Allow the logout response pdu to be sent and defer
+ * the state machine update until the completion callback.
+ * Only 1 level or callback interposition is allowed.
+ */
+ pdu = (idm_pdu_t *)event_ctx->iec_info;
+ ASSERT(ic->ic_client_callback == NULL);
+ ic->ic_client_callback = pdu->isp_callback;
+ if (event_ctx->iec_event == CE_LOGOUT_SUCCESS_SND) {
+ pdu->isp_callback =
+ idm_state_s6_in_logout_success_snd_done;
+ } else {
+ pdu->isp_callback =
+ idm_state_s6_in_logout_fail_snd_done;
+ }
+ break;
+ case CE_LOGOUT_SUCCESS_RCV:
+ case CE_LOGOUT_SESSION_SUCCESS:
+ /* T13 */
+
+ /* Close connection (if it's not already closed) */
+ if (IDM_CONN_ISTGT(ic)) {
+ ic->ic_transport_ops->it_tgt_conn_disconnect(ic);
+ } else {
+ ic->ic_transport_ops->it_ini_conn_disconnect(ic);
+ }
+
+ idm_update_state(ic, CS_S11_COMPLETE, event_ctx);
+ break;
+ case CE_ASYNC_LOGOUT_RCV:
+ /* T14 Do nothing */
+ break;
+ case CE_TRANSPORT_FAIL:
+ case CE_ASYNC_DROP_CONN_RCV:
+ case CE_ASYNC_DROP_CONN_SND:
+ case CE_ASYNC_DROP_ALL_CONN_RCV:
+ case CE_ASYNC_DROP_ALL_CONN_SND:
+ case CE_LOGOUT_FAIL_RCV:
+ idm_update_state(ic, CS_S8_CLEANUP, event_ctx);
+ break;
+ case CE_TX_PROTOCOL_ERROR:
+ case CE_RX_PROTOCOL_ERROR:
+ case CE_MISC_TX:
+ case CE_MISC_RX:
+ /* Don't care */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+
+static void
+idm_logout_req_timeout(void *arg)
+{
+ idm_conn_t *ic = arg;
+
+ idm_conn_event(ic, CE_LOGOUT_TIMEOUT, NULL);
+}
+
+static void
+idm_state_s7_logout_req(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
+{
+ /* Must cancel logout timer before leaving this state */
+ switch (event_ctx->iec_event) {
+ case CE_LOGOUT_THIS_CONN_RCV:
+ case CE_LOGOUT_THIS_CONN_SND:
+ case CE_LOGOUT_OTHER_CONN_RCV:
+ case CE_LOGOUT_OTHER_CONN_SND:
+ /* T10 */
+ if (IDM_CONN_ISTGT(ic)) {
+ (void) untimeout(ic->ic_state_timeout);
+ }
+ idm_ffp_disable(ic, FD_CONN_LOGOUT); /* Explicit logout */
+ idm_update_state(ic, CS_S6_IN_LOGOUT, event_ctx);
+ break;
+ case CE_LOGOUT_SESSION_RCV:
+ case CE_LOGOUT_SESSION_SND:
+ /* T10 */
+ if (IDM_CONN_ISTGT(ic)) {
+ (void) untimeout(ic->ic_state_timeout);
+ }
+ idm_ffp_disable(ic, FD_SESS_LOGOUT); /* Explicit logout */
+ idm_update_state(ic, CS_S6_IN_LOGOUT, event_ctx);
+ break;
+ case CE_ASYNC_LOGOUT_RCV:
+ case CE_ASYNC_LOGOUT_SND:
+ /* T12 Do nothing */
+ break;
+ case CE_TRANSPORT_FAIL:
+ case CE_ASYNC_DROP_CONN_RCV:
+ case CE_ASYNC_DROP_CONN_SND:
+ case CE_ASYNC_DROP_ALL_CONN_RCV:
+ case CE_ASYNC_DROP_ALL_CONN_SND:
+ /* T16 */
+ if (IDM_CONN_ISTGT(ic)) {
+ (void) untimeout(ic->ic_state_timeout);
+ }
+ /* FALLTHROUGH */
+ case CE_LOGOUT_TIMEOUT:
+ idm_ffp_disable(ic, FD_CONN_FAIL); /* Implicit logout */
+ idm_update_state(ic, CS_S8_CLEANUP, event_ctx);
+ break;
+ case CE_LOGOUT_SESSION_SUCCESS:
+ /* T18 */
+ if (IDM_CONN_ISTGT(ic)) {
+ (void) untimeout(ic->ic_state_timeout);
+ }
+ idm_ffp_disable(ic, FD_SESS_LOGOUT); /* Explicit logout */
+
+ /* Close connection (if it's not already closed) */
+ if (IDM_CONN_ISTGT(ic)) {
+ ic->ic_transport_ops->it_tgt_conn_disconnect(ic);
+ } else {
+ ic->ic_transport_ops->it_ini_conn_disconnect(ic);
+ }
+
+ idm_update_state(ic, CS_S11_COMPLETE, event_ctx);
+ break;
+ case CE_TX_PROTOCOL_ERROR:
+ case CE_RX_PROTOCOL_ERROR:
+ case CE_MISC_TX:
+ case CE_MISC_RX:
+ /* Don't care */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+
+static void
+idm_cleanup_timeout(void *arg)
+{
+ idm_conn_t *ic = arg;
+
+ idm_conn_event(ic, CE_CLEANUP_TIMEOUT, NULL);
+}
+
+static void
+idm_state_s8_cleanup(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
+{
+ idm_pdu_t *pdu;
+
+ /*
+ * Need to cancel the cleanup timeout before leaving this state
+ * if it hasn't already fired.
+ */
+ switch (event_ctx->iec_event) {
+ case CE_LOGOUT_SUCCESS_RCV:
+ case CE_LOGOUT_SUCCESS_SND:
+ case CE_LOGOUT_SESSION_SUCCESS:
+ (void) untimeout(ic->ic_state_timeout);
+ /*FALLTHROUGH*/
+ case CE_CLEANUP_TIMEOUT:
+ /* M1 */
+ idm_update_state(ic, CS_S11_COMPLETE, event_ctx);
+ break;
+ case CE_LOGOUT_OTHER_CONN_RCV:
+ case CE_LOGOUT_OTHER_CONN_SND:
+ /* M2 */
+ idm_update_state(ic, CS_S10_IN_CLEANUP, event_ctx);
+ break;
+ case CE_LOGOUT_SUCCESS_SND_DONE:
+ case CE_LOGOUT_FAIL_SND_DONE:
+ pdu = (idm_pdu_t *)event_ctx->iec_info;
+ /* restore client callback */
+ pdu->isp_callback = ic->ic_client_callback;
+ ic->ic_client_callback = NULL;
+ idm_pdu_complete(pdu, pdu->isp_status);
+ break;
+ case CE_LOGOUT_SESSION_RCV:
+ case CE_LOGOUT_SESSION_SND:
+ case CE_TX_PROTOCOL_ERROR:
+ case CE_RX_PROTOCOL_ERROR:
+ case CE_MISC_TX:
+ case CE_MISC_RX:
+ case CE_TRANSPORT_FAIL:
+ case CE_LOGOUT_TIMEOUT:
+ /* Don't care */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+/* ARGSUSED */
+static void
+idm_state_s9_init_error(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
+{
+ if (ic->ic_conn_type == CONN_TYPE_INI) {
+ mutex_enter(&ic->ic_state_mutex);
+ ic->ic_state_flags |= CF_ERROR;
+ ic->ic_conn_sm_status = IDM_STATUS_FAIL;
+ cv_signal(&ic->ic_state_cv);
+ mutex_exit(&ic->ic_state_mutex);
+ }
+}
+
+static void
+idm_state_s10_in_cleanup(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
+{
+ idm_pdu_t *pdu;
+
+ /*
+ * Need to cancel the cleanup timeout before leaving this state
+ * if it hasn't already fired.
+ */
+ switch (event_ctx->iec_event) {
+ case CE_LOGOUT_FAIL_RCV:
+ case CE_LOGOUT_FAIL_SND:
+ idm_update_state(ic, CS_S8_CLEANUP, event_ctx);
+ break;
+ case CE_LOGOUT_SUCCESS_SND:
+ case CE_LOGOUT_SUCCESS_RCV:
+ case CE_LOGOUT_SESSION_SUCCESS:
+ (void) untimeout(ic->ic_state_timeout);
+ /*FALLTHROUGH*/
+ case CE_CLEANUP_TIMEOUT:
+ idm_update_state(ic, CS_S11_COMPLETE, event_ctx);
+ break;
+ case CE_LOGOUT_SUCCESS_SND_DONE:
+ case CE_LOGOUT_FAIL_SND_DONE:
+ pdu = (idm_pdu_t *)event_ctx->iec_info;
+ /* restore client callback */
+ pdu->isp_callback = ic->ic_client_callback;
+ ic->ic_client_callback = NULL;
+ idm_pdu_complete(pdu, pdu->isp_status);
+ break;
+ case CE_TX_PROTOCOL_ERROR:
+ case CE_RX_PROTOCOL_ERROR:
+ case CE_MISC_TX:
+ case CE_MISC_RX:
+ case CE_LOGOUT_TIMEOUT:
+ /* Don't care */
+ break;
+ default:
+ ASSERT(0);
+ }
+}
+
+/* ARGSUSED */
+static void
+idm_state_s11_complete(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
+{
+ idm_pdu_t *pdu;
+
+ /*
+ * Cleanup logout success/fail completion if it's been delayed
+ * until now.
+ */
+ switch (event_ctx->iec_event) {
+ case CE_LOGOUT_SUCCESS_SND_DONE:
+ case CE_LOGOUT_FAIL_SND_DONE:
+ pdu = (idm_pdu_t *)event_ctx->iec_info;
+ /* restore client callback */
+ pdu->isp_callback = ic->ic_client_callback;
+ ic->ic_client_callback = NULL;
+ idm_pdu_complete(pdu, pdu->isp_status);
+ break;
+ }
+}
+
+static void
+idm_state_s12_enable_dm(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
+{
+ switch (event_ctx->iec_event) {
+ case CE_ENABLE_DM_SUCCESS:
+ /* T20 */
+ idm_update_state(ic, CS_S5_LOGGED_IN, event_ctx);
+ break;
+ case CE_ENABLE_DM_FAIL:
+ /* T21 */
+ idm_update_state(ic, CS_S9_INIT_ERROR, event_ctx);
+ break;
+ case CE_TRANSPORT_FAIL:
+ /*
+ * We expect to always hear back from the transport layer
+ * once we have an "enable data-mover" request outstanding.
+ * Therefore we'll ignore other events that may occur even
+ * when they clearly indicate a problem and wait for
+ * CE_ENABLE_DM_FAIL. On a related note this means the
+ * transport must ensure that it eventually completes the
+ * "enable data-mover" operation with either success or
+ * failure -- otherwise we'll be stuck here.
+ */
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+}
+
+static void
+idm_update_state(idm_conn_t *ic, idm_conn_state_t new_state,
+ idm_conn_event_ctx_t *event_ctx)
+{
+ int rc;
+ idm_status_t idm_status;
+
+ /*
+ * Validate new state
+ */
+ ASSERT(new_state != CS_S0_UNDEFINED);
+ ASSERT3U(new_state, <, CS_MAX_STATE);
+
+ /*
+ * Update state in context. We protect this with a mutex
+ * even though the state machine code is single threaded so that
+ * other threads can check the state value atomically.
+ */
+ new_state = (new_state < CS_MAX_STATE) ?
+ new_state : CS_S0_UNDEFINED;
+
+ IDM_SM_LOG(CE_NOTE, "idm_update_state: conn %p, evt %s(%d), "
+ "%s(%d) --> %s(%d)", (void *)ic,
+ idm_ce_name[event_ctx->iec_event], event_ctx->iec_event,
+ idm_cs_name[ic->ic_state], ic->ic_state,
+ idm_cs_name[new_state], new_state);
+
+ DTRACE_PROBE2(conn__state__change,
+ idm_conn_t *, ic, idm_conn_state_t, new_state);
+
+ mutex_enter(&ic->ic_state_mutex);
+ idm_sm_audit_state_change(&ic->ic_state_audit, SAS_IDM_CONN,
+ (int)ic->ic_state, (int)new_state);
+ ic->ic_last_state = ic->ic_state;
+ ic->ic_state = new_state;
+ cv_signal(&ic->ic_state_cv);
+ mutex_exit(&ic->ic_state_mutex);
+
+ switch (ic->ic_state) {
+ case CS_S1_FREE:
+ ASSERT(0); /* Initial state, can't return */
+ break;
+ case CS_S2_XPT_WAIT:
+ if ((rc = idm_ini_conn_finish(ic)) != 0) {
+ idm_conn_event(ic, CE_CONNECT_FAIL, NULL);
+ } else {
+ idm_conn_event(ic, CE_CONNECT_SUCCESS, NULL);
+ }
+ break;
+ case CS_S3_XPT_UP:
+ /*
+ * Finish any connection related setup including
+ * waking up the idm_tgt_conn_accept thread.
+ * and starting the login timer. If the function
+ * fails then we return to "free" state.
+ */
+ if ((rc = idm_tgt_conn_finish(ic)) != IDM_STATUS_SUCCESS) {
+ switch (rc) {
+ case IDM_STATUS_REJECT:
+ idm_conn_event(ic, CE_CONNECT_REJECT, NULL);
+ break;
+ default:
+ idm_conn_event(ic, CE_CONNECT_FAIL, NULL);
+ break;
+ }
+ }
+
+ /*
+ * First login received will cause a transition to
+ * CS_S4_IN_LOGIN. Start login timer.
+ */
+ ic->ic_state_timeout = timeout(idm_login_timeout, ic,
+ drv_usectohz(IDM_LOGIN_SECONDS*1000000));
+ break;
+ case CS_S4_IN_LOGIN:
+ if (ic->ic_conn_type == CONN_TYPE_INI) {
+ mutex_enter(&ic->ic_state_mutex);
+ ic->ic_state_flags |= CF_LOGIN_READY;
+ cv_signal(&ic->ic_state_cv);
+ mutex_exit(&ic->ic_state_mutex);
+ }
+ break;
+ case CS_S5_LOGGED_IN:
+ ASSERT(!ic->ic_ffp);
+ /*
+ * IDM can go to FFP before the initiator but it
+ * needs to go to FFP after the target (IDM target should
+ * go to FFP after notify_ack).
+ */
+ idm_status = idm_ffp_enable(ic);
+ if (idm_status != IDM_STATUS_SUCCESS) {
+ idm_conn_event(ic, CE_TRANSPORT_FAIL, NULL);
+ }
+
+ if (ic->ic_reinstate_conn) {
+ /* Connection reinstatement is complete */
+ idm_conn_event_locked(ic->ic_reinstate_conn,
+ CE_CONN_REINSTATE_SUCCESS, NULL, CT_NONE);
+ }
+ break;
+ case CS_S6_IN_LOGOUT:
+ break;
+ case CS_S7_LOGOUT_REQ:
+ /* Start logout timer for target connections */
+ if (IDM_CONN_ISTGT(ic)) {
+ ic->ic_state_timeout = timeout(idm_logout_req_timeout,
+ ic, drv_usectohz(IDM_LOGOUT_SECONDS*1000000));
+ }
+ break;
+ case CS_S8_CLEANUP:
+ /* Close connection (if it's not already closed) */
+ if (IDM_CONN_ISTGT(ic)) {
+ ic->ic_transport_ops->it_tgt_conn_disconnect(ic);
+ } else {
+ ic->ic_transport_ops->it_ini_conn_disconnect(ic);
+ }
+
+ /* Stop executing active tasks */
+ idm_task_abort(ic, NULL, AT_INTERNAL_SUSPEND);
+
+ /* Start logout timer */
+ ic->ic_state_timeout = timeout(idm_cleanup_timeout, ic,
+ drv_usectohz(IDM_CLEANUP_SECONDS*1000000));
+ break;
+ case CS_S10_IN_CLEANUP:
+ break;
+ case CS_S9_INIT_ERROR:
+ if (IDM_CONN_ISTGT(ic)) {
+ ic->ic_transport_ops->it_tgt_conn_disconnect(ic);
+ } else {
+ mutex_enter(&ic->ic_state_mutex);
+ ic->ic_state_flags |= CF_ERROR;
+ ic->ic_conn_sm_status = IDM_STATUS_FAIL;
+ cv_signal(&ic->ic_state_cv);
+ mutex_exit(&ic->ic_state_mutex);
+ ic->ic_transport_ops->it_ini_conn_disconnect(ic);
+ }
+ /*FALLTHROUGH*/
+ case CS_S11_COMPLETE:
+ /* No more traffic on this connection */
+ (void) idm_notify_client(ic, CN_CONNECT_LOST, NULL);
+
+ /* Abort all tasks */
+ idm_task_abort(ic, NULL, AT_INTERNAL_ABORT);
+
+ /*
+ * Handle terminal state actions on the global taskq so
+ * we can clean up all the connection resources from
+ * a separate thread context.
+ */
+ idm_refcnt_async_wait_ref(&ic->ic_refcnt, &idm_conn_unref);
+ break;
+ case CS_S12_ENABLE_DM:
+
+ /*
+ * The Enable DM state indicates the initiator to initiate
+ * the hello sequence and the target to get ready to accept
+ * the iSER Hello Message.
+ */
+ idm_status = (IDM_CONN_ISINI(ic)) ?
+ ic->ic_transport_ops->it_ini_enable_datamover(ic) :
+ ic->ic_transport_ops->it_tgt_enable_datamover(ic);
+
+ if (idm_status == IDM_STATUS_SUCCESS) {
+ idm_conn_event(ic, CE_ENABLE_DM_SUCCESS, NULL);
+ } else {
+ idm_conn_event(ic, CE_ENABLE_DM_FAIL, NULL);
+ }
+
+ break;
+ }
+}
+
+
+static void
+idm_conn_unref(void *ic_void)
+{
+ idm_conn_t *ic = ic_void;
+
+ /*
+ * Client should not be notified that the connection is destroyed
+ * until all references on the idm connection have been removed.
+ * Otherwise references on the associated client context would need
+ * to be tracked separately which seems like a waste (at least when
+ * there is a one for one correspondence with references on the
+ * IDM connection).
+ */
+ if (IDM_CONN_ISTGT(ic)) {
+ (void) idm_notify_client(ic, CN_CONNECT_DESTROY, NULL);
+ idm_svc_conn_destroy(ic);
+ } else {
+ /* Initiator may destroy connection during this call */
+ (void) idm_notify_client(ic, CN_CONNECT_DESTROY, NULL);
+ }
+}
+
+
+static idm_pdu_event_action_t
+idm_conn_sm_validate_pdu(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx,
+ idm_pdu_t *pdu)
+{
+ char *reason_string;
+ idm_pdu_event_action_t action;
+
+ ASSERT((event_ctx->iec_pdu_event_type == CT_RX_PDU) ||
+ (event_ctx->iec_pdu_event_type == CT_TX_PDU));
+
+ /*
+ * Let's check the simple stuff first. Make sure if this is a
+ * target connection that the PDU is appropriate for a target
+ * and if this is an initiator connection that the PDU is
+ * appropriate for an initiator. This code is not in the data
+ * path so organization is more important than performance.
+ */
+ switch (IDM_PDU_OPCODE(pdu)) {
+ case ISCSI_OP_NOOP_OUT:
+ case ISCSI_OP_SCSI_CMD:
+ case ISCSI_OP_SCSI_TASK_MGT_MSG:
+ case ISCSI_OP_LOGIN_CMD:
+ case ISCSI_OP_TEXT_CMD:
+ case ISCSI_OP_SCSI_DATA:
+ case ISCSI_OP_LOGOUT_CMD:
+ case ISCSI_OP_SNACK_CMD:
+ /*
+ * Only the initiator should send these PDU's and
+ * only the target should receive them.
+ */
+ if (IDM_CONN_ISINI(ic) &&
+ (event_ctx->iec_pdu_event_type == CT_RX_PDU)) {
+ reason_string = "Invalid RX PDU for initiator";
+ action = CA_RX_PROTOCOL_ERROR;
+ goto validate_pdu_done;
+ }
+
+ if (IDM_CONN_ISTGT(ic) &&
+ (event_ctx->iec_pdu_event_type == CT_TX_PDU)) {
+ reason_string = "Invalid TX PDU for target";
+ action = CA_TX_PROTOCOL_ERROR;
+ goto validate_pdu_done;
+ }
+ break;
+ case ISCSI_OP_NOOP_IN:
+ case ISCSI_OP_SCSI_RSP:
+ case ISCSI_OP_SCSI_TASK_MGT_RSP:
+ case ISCSI_OP_LOGIN_RSP:
+ case ISCSI_OP_TEXT_RSP:
+ case ISCSI_OP_SCSI_DATA_RSP:
+ case ISCSI_OP_LOGOUT_RSP:
+ case ISCSI_OP_RTT_RSP:
+ case ISCSI_OP_ASYNC_EVENT:
+ case ISCSI_OP_REJECT_MSG:
+ /*
+ * Only the target should send these PDU's and
+ * only the initiator should receive them.
+ */
+ if (IDM_CONN_ISTGT(ic) &&
+ (event_ctx->iec_pdu_event_type == CT_RX_PDU)) {
+ reason_string = "Invalid RX PDU for target";
+ action = CA_RX_PROTOCOL_ERROR;
+ goto validate_pdu_done;
+ }
+
+ if (IDM_CONN_ISINI(ic) &&
+ (event_ctx->iec_pdu_event_type == CT_TX_PDU)) {
+ reason_string = "Invalid TX PDU for initiator";
+ action = CA_TX_PROTOCOL_ERROR;
+ goto validate_pdu_done;
+ }
+ break;
+ default:
+ reason_string = "Unknown PDU Type";
+ action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ?
+ CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR);
+ goto validate_pdu_done;
+ }
+
+ /*
+ * Now validate the opcodes against the current state.
+ */
+ reason_string = "PDU not allowed in current state";
+ switch (IDM_PDU_OPCODE(pdu)) {
+ case ISCSI_OP_NOOP_OUT:
+ case ISCSI_OP_NOOP_IN:
+ /*
+ * Obviously S1-S3 are not allowed since login hasn't started.
+ * S8 is probably out as well since the connection has been
+ * dropped.
+ */
+ switch (ic->ic_state) {
+ case CS_S4_IN_LOGIN:
+ case CS_S5_LOGGED_IN:
+ case CS_S6_IN_LOGOUT:
+ case CS_S7_LOGOUT_REQ:
+ action = CA_FORWARD;
+ goto validate_pdu_done;
+ case CS_S8_CLEANUP:
+ case CS_S10_IN_CLEANUP:
+ action = CA_DROP;
+ break;
+ default:
+ action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ?
+ CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR);
+ goto validate_pdu_done;
+ }
+ /*NOTREACHED*/
+ case ISCSI_OP_SCSI_CMD:
+ case ISCSI_OP_SCSI_RSP:
+ case ISCSI_OP_SCSI_TASK_MGT_MSG:
+ case ISCSI_OP_SCSI_TASK_MGT_RSP:
+ case ISCSI_OP_SCSI_DATA:
+ case ISCSI_OP_SCSI_DATA_RSP:
+ case ISCSI_OP_RTT_RSP:
+ case ISCSI_OP_SNACK_CMD:
+ case ISCSI_OP_TEXT_CMD:
+ case ISCSI_OP_TEXT_RSP:
+ switch (ic->ic_state) {
+ case CS_S5_LOGGED_IN:
+ case CS_S6_IN_LOGOUT:
+ case CS_S7_LOGOUT_REQ:
+ action = CA_FORWARD;
+ goto validate_pdu_done;
+ case CS_S8_CLEANUP:
+ case CS_S10_IN_CLEANUP:
+ action = CA_DROP;
+ break;
+ default:
+ action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ?
+ CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR);
+ goto validate_pdu_done;
+ }
+ /*NOTREACHED*/
+ case ISCSI_OP_LOGOUT_CMD:
+ case ISCSI_OP_LOGOUT_RSP:
+ case ISCSI_OP_REJECT_MSG:
+ case ISCSI_OP_ASYNC_EVENT:
+ switch (ic->ic_state) {
+ case CS_S5_LOGGED_IN:
+ case CS_S6_IN_LOGOUT:
+ case CS_S7_LOGOUT_REQ:
+ action = CA_FORWARD;
+ goto validate_pdu_done;
+ case CS_S8_CLEANUP:
+ case CS_S10_IN_CLEANUP:
+ action = CA_DROP;
+ break;
+ default:
+ action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ?
+ CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR);
+ goto validate_pdu_done;
+ }
+ /*NOTREACHED*/
+ case ISCSI_OP_LOGIN_CMD:
+ case ISCSI_OP_LOGIN_RSP:
+ switch (ic->ic_state) {
+ case CS_S3_XPT_UP:
+ case CS_S4_IN_LOGIN:
+ action = CA_FORWARD;
+ goto validate_pdu_done;
+ default:
+ action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ?
+ CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR);
+ goto validate_pdu_done;
+ }
+ /*NOTREACHED*/
+ default:
+ /* This should never happen -- we already checked above */
+ ASSERT(0);
+ /*NOTREACHED*/
+ }
+
+ action = ((event_ctx->iec_pdu_event_type == CT_TX_PDU) ?
+ CA_TX_PROTOCOL_ERROR : CA_RX_PROTOCOL_ERROR);
+
+validate_pdu_done:
+ if (action != CA_FORWARD) {
+ DTRACE_PROBE2(idm__int__protocol__error,
+ idm_conn_event_ctx_t *, event_ctx,
+ char *, reason_string);
+ }
+
+ return (action);
+}
+
+/* ARGSUSED */
+void
+idm_pdu_tx_protocol_error(idm_conn_t *ic, idm_pdu_t *pdu)
+{
+ /*
+ * Return the PDU to the caller indicating it was a protocol error.
+ * Caller can take appropriate action.
+ */
+ idm_pdu_complete(pdu, IDM_STATUS_PROTOCOL_ERROR);
+}
+
+void
+idm_pdu_rx_protocol_error(idm_conn_t *ic, idm_pdu_t *pdu)
+{
+ /*
+ * Forward PDU to caller indicating it is a protocol error.
+ * Caller should take appropriate action.
+ */
+ (*ic->ic_conn_ops.icb_rx_error)(ic, pdu, IDM_STATUS_PROTOCOL_ERROR);
+}
+
+idm_status_t
+idm_notify_client(idm_conn_t *ic, idm_client_notify_t cn, uintptr_t data)
+{
+ /*
+ * We may want to make this more complicated at some point but
+ * for now lets just call the client's notify function and return
+ * the status.
+ */
+ return ((*ic->ic_conn_ops.icb_client_notify)(ic, cn, data));
+}
+
+static idm_status_t
+idm_ffp_enable(idm_conn_t *ic)
+{
+ idm_status_t rc;
+
+ /*
+ * On the initiator side the client will see this notification
+ * before the actual login succes PDU. This shouldn't be a big
+ * deal since the initiator drives the connection. It can simply
+ * wait for the login response then start sending SCSI commands.
+ * Kind ugly though compared with the way things work on target
+ * connections.
+ */
+ mutex_enter(&ic->ic_state_mutex);
+ ic->ic_ffp = B_TRUE;
+ mutex_exit(&ic->ic_state_mutex);
+
+ rc = idm_notify_client(ic, CN_FFP_ENABLED, NULL);
+ if (rc != IDM_STATUS_SUCCESS) {
+ mutex_enter(&ic->ic_state_mutex);
+ ic->ic_ffp = B_FALSE;
+ mutex_exit(&ic->ic_state_mutex);
+ }
+ return (rc);
+}
+
+static void
+idm_ffp_disable(idm_conn_t *ic, idm_ffp_disable_t disable_type)
+{
+ mutex_enter(&ic->ic_state_mutex);
+ ic->ic_ffp = B_FALSE;
+ mutex_exit(&ic->ic_state_mutex);
+
+ /* Client can't "fail" CN_FFP_DISABLED */
+ (void) idm_notify_client(ic, CN_FFP_DISABLED,
+ (uintptr_t)disable_type);
+}
+
+static void
+idm_initial_login_actions(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
+{
+ ASSERT((event_ctx->iec_event == CE_LOGIN_RCV) ||
+ (event_ctx->iec_event == CE_LOGIN_SND));
+
+ /*
+ * Currently it's not clear what we would do here -- since
+ * we went to the trouble of coding an "initial login" hook
+ * we'll leave it in for now. Remove before integration if
+ * it's not used for anything.
+ */
+ ic->ic_state_flags |= CF_INITIAL_LOGIN;
+}
+
+static void
+idm_login_success_actions(idm_conn_t *ic, idm_conn_event_ctx_t *event_ctx)
+{
+ idm_pdu_t *pdu = (idm_pdu_t *)event_ctx->iec_info;
+ iscsi_login_hdr_t *login_req =
+ (iscsi_login_hdr_t *)pdu->isp_hdr;
+
+ ASSERT((event_ctx->iec_event == CE_LOGIN_SUCCESS_RCV) ||
+ (event_ctx->iec_event == CE_LOGIN_SUCCESS_SND));
+
+ /*
+ * Save off CID
+ */
+ mutex_enter(&ic->ic_state_mutex);
+ ic->ic_login_cid = ntohs(login_req->cid);
+ ic->ic_login_info_valid = B_TRUE;
+
+ mutex_exit(&ic->ic_state_mutex);
+}
diff --git a/usr/src/uts/common/io/idm/idm_impl.c b/usr/src/uts/common/io/idm/idm_impl.c
new file mode 100644
index 0000000000..c7f2d47048
--- /dev/null
+++ b/usr/src/uts/common/io/idm/idm_impl.c
@@ -0,0 +1,1023 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/cpuvar.h>
+
+#include <sys/socket.h>
+#include <sys/strsubr.h>
+#include <sys/socketvar.h>
+#include <sys/sysmacros.h>
+
+#include <sys/idm/idm.h>
+#include <sys/idm/idm_so.h>
+
+extern idm_transport_t idm_transport_list[];
+
+void
+idm_pdu_rx(idm_conn_t *ic, idm_pdu_t *pdu)
+{
+ iscsi_async_evt_hdr_t *async_evt;
+
+ /*
+ * If we are in full-featured mode then route SCSI-related
+ * commands to the appropriate function vector
+ */
+ ic->ic_timestamp = ddi_get_lbolt();
+ mutex_enter(&ic->ic_state_mutex);
+ if (ic->ic_ffp && ic->ic_pdu_events == 0) {
+ mutex_exit(&ic->ic_state_mutex);
+
+ if (idm_pdu_rx_forward_ffp(ic, pdu) == B_TRUE) {
+ /* Forwarded SCSI-related commands */
+ return;
+ }
+ mutex_enter(&ic->ic_state_mutex);
+ }
+
+ /*
+ * If we get here with a SCSI-related PDU then we are not in
+ * full-feature mode and the PDU is a protocol error (SCSI command
+ * PDU's may sometimes be an exception, see below). All
+ * non-SCSI PDU's get treated them the same regardless of whether
+ * we are in full-feature mode.
+ *
+ * Look at the opcode and in some cases the PDU status and
+ * determine the appropriate event to send to the connection
+ * state machine. Generate the event, passing the PDU as data.
+ * If the current connection state allows reception of the event
+ * the PDU will be submitted to the IDM client for processing,
+ * otherwise the PDU will be dropped.
+ */
+ switch (IDM_PDU_OPCODE(pdu)) {
+ case ISCSI_OP_LOGIN_CMD:
+ idm_conn_rx_pdu_event(ic, CE_LOGIN_RCV, (uintptr_t)pdu);
+ break;
+ case ISCSI_OP_LOGIN_RSP:
+ idm_parse_login_rsp(ic, pdu, /* RX */ B_TRUE);
+ break;
+ case ISCSI_OP_LOGOUT_CMD:
+ idm_parse_logout_req(ic, pdu, /* RX */ B_TRUE);
+ break;
+ case ISCSI_OP_LOGOUT_RSP:
+ idm_parse_logout_rsp(ic, pdu, /* RX */ B_TRUE);
+ break;
+ case ISCSI_OP_ASYNC_EVENT:
+ async_evt = (iscsi_async_evt_hdr_t *)pdu->isp_hdr;
+ switch (async_evt->opcode) {
+ case ISCSI_ASYNC_EVENT_REQUEST_LOGOUT:
+ idm_conn_rx_pdu_event(ic, CE_ASYNC_LOGOUT_RCV,
+ (uintptr_t)pdu);
+ break;
+ case ISCSI_ASYNC_EVENT_DROPPING_CONNECTION:
+ idm_conn_rx_pdu_event(ic, CE_ASYNC_DROP_CONN_RCV,
+ (uintptr_t)pdu);
+ break;
+ case ISCSI_ASYNC_EVENT_DROPPING_ALL_CONNECTIONS:
+ idm_conn_rx_pdu_event(ic, CE_ASYNC_DROP_ALL_CONN_RCV,
+ (uintptr_t)pdu);
+ break;
+ case ISCSI_ASYNC_EVENT_SCSI_EVENT:
+ case ISCSI_ASYNC_EVENT_PARAM_NEGOTIATION:
+ default:
+ idm_conn_rx_pdu_event(ic, CE_MISC_RX,
+ (uintptr_t)pdu);
+ break;
+ }
+ break;
+ case ISCSI_OP_SCSI_CMD:
+ /*
+ * Consider this scenario: We are a target connection
+ * in "in login" state and a "login success sent" event has
+ * been generated but not yet handled. Since we've sent
+ * the login response but we haven't actually transitioned
+ * to FFP mode we might conceivably receive a SCSI command
+ * from the initiator before we are ready. We are actually
+ * in FFP we just don't know it yet -- to address this we
+ * can generate an event corresponding to the SCSI command.
+ * At the point when the event is handled by the state
+ * machine the login request will have been handled and we
+ * should be in FFP. If we are not in FFP by that time
+ * we can reject the SCSI command with a protocol error.
+ *
+ * This scenario only applies to the target.
+ */
+ case ISCSI_OP_SCSI_DATA:
+ case ISCSI_OP_SCSI_DATA_RSP:
+ case ISCSI_OP_RTT_RSP:
+ case ISCSI_OP_SNACK_CMD:
+ case ISCSI_OP_NOOP_IN:
+ case ISCSI_OP_NOOP_OUT:
+ case ISCSI_OP_TEXT_CMD:
+ case ISCSI_OP_TEXT_RSP:
+ case ISCSI_OP_REJECT_MSG:
+ case ISCSI_OP_SCSI_TASK_MGT_MSG:
+ case ISCSI_OP_SCSI_TASK_MGT_RSP:
+ /* Validate received PDU against current state */
+ idm_conn_rx_pdu_event(ic, CE_MISC_RX,
+ (uintptr_t)pdu);
+ break;
+ }
+ mutex_exit(&ic->ic_state_mutex);
+}
+
+void
+idm_pdu_tx_forward(idm_conn_t *ic, idm_pdu_t *pdu)
+{
+ (*ic->ic_transport_ops->it_tx_pdu)(ic, pdu);
+}
+
+boolean_t
+idm_pdu_rx_forward_ffp(idm_conn_t *ic, idm_pdu_t *pdu)
+{
+ /*
+ * If this is an FFP request, call the appropriate handler
+ * and return B_TRUE, otherwise return B_FALSE.
+ */
+ switch (IDM_PDU_OPCODE(pdu)) {
+ case ISCSI_OP_SCSI_CMD:
+ (*ic->ic_conn_ops.icb_rx_scsi_cmd)(ic, pdu);
+ return (B_TRUE);
+ case ISCSI_OP_SCSI_RSP:
+ (*ic->ic_conn_ops.icb_rx_scsi_rsp)(ic, pdu);
+ return (B_TRUE);
+ case ISCSI_OP_SCSI_DATA:
+ (*ic->ic_transport_ops->it_rx_dataout)(ic, pdu);
+ return (B_TRUE);
+ case ISCSI_OP_SCSI_DATA_RSP:
+ (*ic->ic_transport_ops->it_rx_datain)(ic, pdu);
+ return (B_TRUE);
+ case ISCSI_OP_RTT_RSP:
+ (*ic->ic_transport_ops->it_rx_rtt)(ic, pdu);
+ return (B_TRUE);
+ case ISCSI_OP_SCSI_TASK_MGT_MSG:
+ case ISCSI_OP_SCSI_TASK_MGT_RSP:
+ case ISCSI_OP_TEXT_CMD:
+ case ISCSI_OP_TEXT_RSP:
+ case ISCSI_OP_NOOP_OUT:
+ case ISCSI_OP_NOOP_IN:
+ (*ic->ic_conn_ops.icb_rx_misc)(ic, pdu);
+ return (B_TRUE);
+ default:
+ return (B_FALSE);
+ }
+ /*NOTREACHED*/
+}
+
+void
+idm_pdu_rx_forward(idm_conn_t *ic, idm_pdu_t *pdu)
+{
+ /*
+ * Some PDU's specific to FFP get special handling. This function
+ * will normally never be called in FFP with an FFP PDU since this
+ * is a slow path but in can happen on the target side during
+ * the transition to FFP. We primarily call
+ * idm_pdu_rx_forward_ffp here to avoid code duplication.
+ */
+ if (idm_pdu_rx_forward_ffp(ic, pdu) == B_FALSE) {
+ /*
+ * Non-FFP PDU, use generic RC handler
+ */
+ (*ic->ic_conn_ops.icb_rx_misc)(ic, pdu);
+ }
+}
+
+void
+idm_parse_login_rsp(idm_conn_t *ic, idm_pdu_t *login_rsp_pdu, boolean_t rx)
+{
+ iscsi_login_rsp_hdr_t *login_rsp =
+ (iscsi_login_rsp_hdr_t *)login_rsp_pdu->isp_hdr;
+ idm_conn_event_t new_event;
+
+ if (login_rsp->status_class == ISCSI_STATUS_CLASS_SUCCESS) {
+ if (!(login_rsp->flags & ISCSI_FLAG_LOGIN_CONTINUE) &&
+ (login_rsp->flags & ISCSI_FLAG_LOGIN_TRANSIT) &&
+ (ISCSI_LOGIN_NEXT_STAGE(login_rsp->flags) ==
+ ISCSI_FULL_FEATURE_PHASE)) {
+ new_event = (rx ? CE_LOGIN_SUCCESS_RCV :
+ CE_LOGIN_SUCCESS_SND);
+ } else {
+ new_event = (rx ? CE_MISC_RX : CE_MISC_TX);
+ }
+ } else {
+ new_event = (rx ? CE_LOGIN_FAIL_RCV : CE_LOGIN_FAIL_SND);
+ }
+
+ if (rx) {
+ idm_conn_rx_pdu_event(ic, new_event, (uintptr_t)login_rsp_pdu);
+ } else {
+ idm_conn_tx_pdu_event(ic, new_event, (uintptr_t)login_rsp_pdu);
+ }
+}
+
+
+void
+idm_parse_logout_req(idm_conn_t *ic, idm_pdu_t *logout_req_pdu, boolean_t rx)
+{
+ iscsi_logout_hdr_t *logout_req =
+ (iscsi_logout_hdr_t *)logout_req_pdu->isp_hdr;
+ idm_conn_event_t new_event;
+ uint8_t reason =
+ (logout_req->flags & ISCSI_FLAG_LOGOUT_REASON_MASK);
+
+ /*
+ * For a normal logout (close connection or close session) IDM
+ * will terminate processing of all tasks completing the tasks
+ * back to the client with a status indicating the connection
+ * was logged out. These tasks do not get completed.
+ *
+ * For a "close connection for recovery logout) IDM suspends
+ * processing of all tasks and completes them back to the client
+ * with a status indicating connection was logged out for
+ * recovery. Both initiator and target hang onto these tasks.
+ * When we add ERL2 support IDM will need to provide mechanisms
+ * to change the task and buffer associations to a new connection.
+ *
+ * This code doesn't address the possibility of MC/S. We'll
+ * need to decide how the separate connections get handled
+ * in that case. One simple option is to make the client
+ * generate the events for the other connections.
+ */
+ if (reason == ISCSI_LOGOUT_REASON_CLOSE_SESSION) {
+ new_event =
+ (rx ? CE_LOGOUT_SESSION_RCV : CE_LOGOUT_SESSION_SND);
+ } else if ((reason == ISCSI_LOGOUT_REASON_CLOSE_CONNECTION) ||
+ (reason == ISCSI_LOGOUT_REASON_RECOVERY)) {
+ /* Check logout CID against this connection's CID */
+ if (ntohs(logout_req->cid) == ic->ic_login_cid) {
+ /* Logout is for this connection */
+ new_event = (rx ? CE_LOGOUT_THIS_CONN_RCV :
+ CE_LOGOUT_THIS_CONN_SND);
+ } else {
+ /*
+ * Logout affects another connection. This is not
+ * a relevant event for this connection so we'll
+ * just treat it as a normal PDU event. Client
+ * will need to lookup the other connection and
+ * generate the event.
+ */
+ new_event = (rx ? CE_MISC_RX : CE_MISC_TX);
+ }
+ } else {
+ /* Invalid reason code */
+ new_event = (rx ? CE_RX_PROTOCOL_ERROR : CE_TX_PROTOCOL_ERROR);
+ }
+
+ if (rx) {
+ idm_conn_rx_pdu_event(ic, new_event, (uintptr_t)logout_req_pdu);
+ } else {
+ idm_conn_tx_pdu_event(ic, new_event, (uintptr_t)logout_req_pdu);
+ }
+}
+
+
+
+void
+idm_parse_logout_rsp(idm_conn_t *ic, idm_pdu_t *logout_rsp_pdu, boolean_t rx)
+{
+ idm_conn_event_t new_event;
+ iscsi_logout_rsp_hdr_t *logout_rsp =
+ (iscsi_logout_rsp_hdr_t *)logout_rsp_pdu->isp_hdr;
+
+ if (logout_rsp->response == ISCSI_STATUS_CLASS_SUCCESS) {
+ new_event = rx ? CE_LOGOUT_SUCCESS_RCV : CE_LOGOUT_SUCCESS_SND;
+ } else {
+ new_event = rx ? CE_LOGOUT_FAIL_RCV : CE_LOGOUT_FAIL_SND;
+ }
+
+ if (rx) {
+ idm_conn_rx_pdu_event(ic, new_event, (uintptr_t)logout_rsp_pdu);
+ } else {
+ idm_conn_tx_pdu_event(ic, new_event, (uintptr_t)logout_rsp_pdu);
+ }
+}
+
+/*
+ * idm_svc_conn_create()
+ * Transport-agnostic service connection creation, invoked from the transport
+ * layer.
+ */
+idm_status_t
+idm_svc_conn_create(idm_svc_t *is, idm_transport_type_t tt,
+ idm_conn_t **ic_result)
+{
+ idm_conn_t *ic;
+ idm_status_t rc;
+
+ ic = idm_conn_create_common(CONN_TYPE_TGT, tt,
+ &is->is_svc_req.sr_conn_ops);
+ ic->ic_svc_binding = is;
+
+ /*
+ * Prepare connection state machine
+ */
+ if ((rc = idm_conn_sm_init(ic)) != 0) {
+ idm_conn_destroy_common(ic);
+ return (rc);
+ }
+
+
+ *ic_result = ic;
+
+ mutex_enter(&idm.idm_global_mutex);
+ list_insert_tail(&idm.idm_tgt_conn_list, ic);
+ idm.idm_tgt_conn_count++;
+ mutex_exit(&idm.idm_global_mutex);
+
+ return (0);
+}
+
+void
+idm_svc_conn_destroy(idm_conn_t *ic)
+{
+ mutex_enter(&idm.idm_global_mutex);
+ list_remove(&idm.idm_tgt_conn_list, ic);
+ idm.idm_tgt_conn_count--;
+ mutex_exit(&idm.idm_global_mutex);
+
+ idm_conn_sm_fini(ic);
+
+ if (ic->ic_transport_private != NULL) {
+ ic->ic_transport_ops->it_tgt_conn_destroy(ic);
+ }
+ idm_conn_destroy_common(ic);
+}
+
+/*
+ * idm_conn_create_common()
+ *
+ * Allocate and initialize IDM connection context
+ */
+idm_conn_t *
+idm_conn_create_common(idm_conn_type_t conn_type, idm_transport_type_t tt,
+ idm_conn_ops_t *conn_ops)
+{
+ idm_conn_t *ic;
+ idm_transport_t *it;
+ idm_transport_type_t type;
+
+ for (type = 0; type < IDM_TRANSPORT_NUM_TYPES; type++) {
+ it = &idm_transport_list[type];
+
+ if ((it->it_ops != NULL) && (it->it_type == tt))
+ break;
+ }
+ ASSERT(it->it_type == tt);
+ if (it->it_type != tt)
+ return (NULL);
+
+ ic = kmem_zalloc(sizeof (idm_conn_t), KM_SLEEP);
+
+ /* Initialize data */
+ ic->ic_conn_type = conn_type;
+ ic->ic_conn_ops = *conn_ops;
+ ic->ic_transport_ops = it->it_ops;
+ ic->ic_transport_type = tt;
+ ic->ic_transport_private = NULL; /* Set by transport service */
+ ic->ic_internal_cid = idm_cid_alloc();
+ if (ic->ic_internal_cid == 0) {
+ kmem_free(ic, sizeof (idm_conn_t));
+ return (NULL);
+ }
+ mutex_init(&ic->ic_mutex, NULL, MUTEX_DEFAULT, NULL);
+ cv_init(&ic->ic_cv, NULL, CV_DEFAULT, NULL);
+ idm_refcnt_init(&ic->ic_refcnt, ic);
+
+ return (ic);
+}
+
+void
+idm_conn_destroy_common(idm_conn_t *ic)
+{
+ idm_refcnt_destroy(&ic->ic_refcnt);
+ cv_destroy(&ic->ic_cv);
+ mutex_destroy(&ic->ic_mutex);
+ idm_cid_free(ic->ic_internal_cid);
+
+ kmem_free(ic, sizeof (idm_conn_t));
+}
+
+/*
+ * Invoked from the SM as a result of client's invocation of
+ * idm_ini_conn_connect()
+ */
+idm_status_t
+idm_ini_conn_finish(idm_conn_t *ic)
+{
+ /* invoke transport-specific connection */
+ return (ic->ic_transport_ops->it_ini_conn_connect(ic));
+}
+
+idm_status_t
+idm_tgt_conn_finish(idm_conn_t *ic)
+{
+ idm_status_t rc;
+
+ rc = idm_notify_client(ic, CN_CONNECT_ACCEPT, NULL);
+ if (rc != IDM_STATUS_SUCCESS) {
+ return (IDM_STATUS_REJECT);
+ }
+
+ /* Target client is ready to receive a login, start connection */
+ return (ic->ic_transport_ops->it_tgt_conn_connect(ic));
+}
+
+idm_transport_t *
+idm_transport_lookup(idm_conn_req_t *cr)
+{
+ idm_transport_type_t type;
+ idm_transport_t *it;
+ idm_transport_caps_t caps;
+
+ /*
+ * Make sure all available transports are setup. We call this now
+ * instead of at initialization time in case IB has become available
+ * since we started (hotplug, etc).
+ */
+ idm_transport_setup(cr->cr_li);
+
+ /* Determine the transport for this connection */
+ for (type = 0; type < IDM_TRANSPORT_NUM_TYPES; type++) {
+ it = &idm_transport_list[type];
+
+ if (it->it_ops == NULL) {
+ /* transport is not registered */
+ continue;
+ }
+
+ if (it->it_ops->it_conn_is_capable(cr, &caps)) {
+ return (it);
+ }
+ }
+
+ ASSERT(0);
+ return (NULL); /* Make gcc happy */
+}
+
+void
+idm_transport_setup(ldi_ident_t li)
+{
+ idm_transport_type_t type;
+ idm_transport_t *it;
+ int rc;
+
+ for (type = 0; type < IDM_TRANSPORT_NUM_TYPES; type++) {
+ it = &idm_transport_list[type];
+ /*
+ * We may want to store the LDI handle in the idm_svc_t
+ * and then allow multiple calls to ldi_open_by_name. This
+ * would enable the LDI code to track who has the device open
+ * which could be useful in the case where we have multiple
+ * services and perhaps also have initiator and target opening
+ * the transport simultaneously. For now we stick with the
+ * plan.
+ */
+ if (it->it_ops == NULL) {
+ /* transport is not ready, try to initialize it */
+ if (it->it_type == IDM_TRANSPORT_TYPE_SOCKETS) {
+ idm_so_init(it);
+ } else {
+ rc = ldi_open_by_name(it->it_device_path,
+ FREAD | FWRITE, kcred, &it->it_ldi_hdl, li);
+ /*
+ * If the open is successful we will have
+ * filled in the LDI handle in the transport
+ * table and we expect that the transport
+ * registered itself.
+ */
+ if (rc != 0) {
+ it->it_ldi_hdl = NULL;
+ }
+ }
+ }
+ }
+}
+
+/*
+ * ID pool code. We use this to generate unique structure identifiers without
+ * searching the existing structures. This avoids the need to lock entire
+ * sets of structures at inopportune times. Adapted from the CIFS server code.
+ *
+ * A pool of IDs is a pool of 16 bit numbers. It is implemented as a bitmap.
+ * A bit set to '1' indicates that that particular value has been allocated.
+ * The allocation process is done shifting a bit through the whole bitmap.
+ * The current position of that index bit is kept in the idm_idpool_t
+ * structure and represented by a byte index (0 to buffer size minus 1) and
+ * a bit index (0 to 7).
+ *
+ * The pools start with a size of 8 bytes or 64 IDs. Each time the pool runs
+ * out of IDs its current size is doubled until it reaches its maximum size
+ * (8192 bytes or 65536 IDs). The IDs 0 and 65535 are never given out which
+ * means that a pool can have a maximum number of 65534 IDs available.
+ */
+
+static int
+idm_idpool_increment(
+ idm_idpool_t *pool)
+{
+ uint8_t *new_pool;
+ uint32_t new_size;
+
+ ASSERT(pool->id_magic == IDM_IDPOOL_MAGIC);
+
+ new_size = pool->id_size * 2;
+ if (new_size <= IDM_IDPOOL_MAX_SIZE) {
+ new_pool = kmem_alloc(new_size / 8, KM_NOSLEEP);
+ if (new_pool) {
+ bzero(new_pool, new_size / 8);
+ bcopy(pool->id_pool, new_pool, pool->id_size / 8);
+ kmem_free(pool->id_pool, pool->id_size / 8);
+ pool->id_pool = new_pool;
+ pool->id_free_counter += new_size - pool->id_size;
+ pool->id_max_free_counter += new_size - pool->id_size;
+ pool->id_size = new_size;
+ pool->id_idx_msk = (new_size / 8) - 1;
+ if (new_size >= IDM_IDPOOL_MAX_SIZE) {
+ /* id -1 made unavailable */
+ pool->id_pool[pool->id_idx_msk] = 0x80;
+ pool->id_free_counter--;
+ pool->id_max_free_counter--;
+ }
+ return (0);
+ }
+ }
+ return (-1);
+}
+
+/*
+ * idm_idpool_constructor
+ *
+ * This function initializes the pool structure provided.
+ */
+
+int
+idm_idpool_create(idm_idpool_t *pool)
+{
+
+ ASSERT(pool->id_magic != IDM_IDPOOL_MAGIC);
+
+ pool->id_size = IDM_IDPOOL_MIN_SIZE;
+ pool->id_idx_msk = (IDM_IDPOOL_MIN_SIZE / 8) - 1;
+ pool->id_free_counter = IDM_IDPOOL_MIN_SIZE - 1;
+ pool->id_max_free_counter = IDM_IDPOOL_MIN_SIZE - 1;
+ pool->id_bit = 0x02;
+ pool->id_bit_idx = 1;
+ pool->id_idx = 0;
+ pool->id_pool = (uint8_t *)kmem_alloc((IDM_IDPOOL_MIN_SIZE / 8),
+ KM_SLEEP);
+ bzero(pool->id_pool, (IDM_IDPOOL_MIN_SIZE / 8));
+ /* -1 id made unavailable */
+ pool->id_pool[0] = 0x01; /* id 0 made unavailable */
+ mutex_init(&pool->id_mutex, NULL, MUTEX_DEFAULT, NULL);
+ pool->id_magic = IDM_IDPOOL_MAGIC;
+ return (0);
+}
+
+/*
+ * idm_idpool_destructor
+ *
+ * This function tears down and frees the resources associated with the
+ * pool provided.
+ */
+
+void
+idm_idpool_destroy(idm_idpool_t *pool)
+{
+ ASSERT(pool->id_magic == IDM_IDPOOL_MAGIC);
+ ASSERT(pool->id_free_counter == pool->id_max_free_counter);
+ pool->id_magic = (uint32_t)~IDM_IDPOOL_MAGIC;
+ mutex_destroy(&pool->id_mutex);
+ kmem_free(pool->id_pool, (size_t)(pool->id_size / 8));
+}
+
+/*
+ * idm_idpool_alloc
+ *
+ * This function allocates an ID from the pool provided.
+ */
+int
+idm_idpool_alloc(idm_idpool_t *pool, uint16_t *id)
+{
+ uint32_t i;
+ uint8_t bit;
+ uint8_t bit_idx;
+ uint8_t byte;
+
+ ASSERT(pool->id_magic == IDM_IDPOOL_MAGIC);
+
+ mutex_enter(&pool->id_mutex);
+ if ((pool->id_free_counter == 0) && idm_idpool_increment(pool)) {
+ mutex_exit(&pool->id_mutex);
+ return (-1);
+ }
+
+ i = pool->id_size;
+ while (i) {
+ bit = pool->id_bit;
+ bit_idx = pool->id_bit_idx;
+ byte = pool->id_pool[pool->id_idx];
+ while (bit) {
+ if (byte & bit) {
+ bit = bit << 1;
+ bit_idx++;
+ continue;
+ }
+ pool->id_pool[pool->id_idx] |= bit;
+ *id = (uint16_t)(pool->id_idx * 8 + (uint32_t)bit_idx);
+ pool->id_free_counter--;
+ pool->id_bit = bit;
+ pool->id_bit_idx = bit_idx;
+ mutex_exit(&pool->id_mutex);
+ return (0);
+ }
+ pool->id_bit = 1;
+ pool->id_bit_idx = 0;
+ pool->id_idx++;
+ pool->id_idx &= pool->id_idx_msk;
+ --i;
+ }
+ /*
+ * This section of code shouldn't be reached. If there are IDs
+ * available and none could be found there's a problem.
+ */
+ ASSERT(0);
+ mutex_exit(&pool->id_mutex);
+ return (-1);
+}
+
+/*
+ * idm_idpool_free
+ *
+ * This function frees the ID provided.
+ */
+void
+idm_idpool_free(idm_idpool_t *pool, uint16_t id)
+{
+ ASSERT(pool->id_magic == IDM_IDPOOL_MAGIC);
+ ASSERT(id != 0);
+ ASSERT(id != 0xFFFF);
+
+ mutex_enter(&pool->id_mutex);
+ if (pool->id_pool[id >> 3] & (1 << (id & 7))) {
+ pool->id_pool[id >> 3] &= ~(1 << (id & 7));
+ pool->id_free_counter++;
+ ASSERT(pool->id_free_counter <= pool->id_max_free_counter);
+ mutex_exit(&pool->id_mutex);
+ return;
+ }
+ /* Freeing a free ID. */
+ ASSERT(0);
+ mutex_exit(&pool->id_mutex);
+}
+
+uint32_t
+idm_cid_alloc(void)
+{
+ /*
+ * ID pool works with 16-bit identifiers right now. That should
+ * be plenty since we will probably never have more than 2^16
+ * connections simultaneously.
+ */
+ uint16_t cid16;
+
+ if (idm_idpool_alloc(&idm.idm_conn_id_pool, &cid16) == -1) {
+ return (0); /* Fail */
+ }
+
+ return ((uint32_t)cid16);
+}
+
+void
+idm_cid_free(uint32_t cid)
+{
+ idm_idpool_free(&idm.idm_conn_id_pool, (uint16_t)cid);
+}
+
+
+/*
+ * Code for generating the header and data digests
+ *
+ * This is the CRC-32C table
+ * Generated with:
+ * width = 32 bits
+ * poly = 0x1EDC6F41
+ * reflect input bytes = true
+ * reflect output bytes = true
+ */
+
+uint32_t idm_crc32c_table[256] =
+{
+ 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4,
+ 0xC79A971F, 0x35F1141C, 0x26A1E7E8, 0xD4CA64EB,
+ 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B,
+ 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24,
+ 0x105EC76F, 0xE235446C, 0xF165B798, 0x030E349B,
+ 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384,
+ 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54,
+ 0x5D1D08BF, 0xAF768BBC, 0xBC267848, 0x4E4DFB4B,
+ 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A,
+ 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35,
+ 0xAA64D611, 0x580F5512, 0x4B5FA6E6, 0xB93425E5,
+ 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA,
+ 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45,
+ 0xF779DEAE, 0x05125DAD, 0x1642AE59, 0xE4292D5A,
+ 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A,
+ 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595,
+ 0x417B1DBC, 0xB3109EBF, 0xA0406D4B, 0x522BEE48,
+ 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957,
+ 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687,
+ 0x0C38D26C, 0xFE53516F, 0xED03A29B, 0x1F682198,
+ 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927,
+ 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38,
+ 0xDBFC821C, 0x2997011F, 0x3AC7F2EB, 0xC8AC71E8,
+ 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7,
+ 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096,
+ 0xA65C047D, 0x5437877E, 0x4767748A, 0xB50CF789,
+ 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859,
+ 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46,
+ 0x7198540D, 0x83F3D70E, 0x90A324FA, 0x62C8A7F9,
+ 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6,
+ 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36,
+ 0x3CDB9BDD, 0xCEB018DE, 0xDDE0EB2A, 0x2F8B6829,
+ 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C,
+ 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93,
+ 0x082F63B7, 0xFA44E0B4, 0xE9141340, 0x1B7F9043,
+ 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C,
+ 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3,
+ 0x55326B08, 0xA759E80B, 0xB4091BFF, 0x466298FC,
+ 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C,
+ 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033,
+ 0xA24BB5A6, 0x502036A5, 0x4370C551, 0xB11B4652,
+ 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D,
+ 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D,
+ 0xEF087A76, 0x1D63F975, 0x0E330A81, 0xFC588982,
+ 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D,
+ 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622,
+ 0x38CC2A06, 0xCAA7A905, 0xD9F75AF1, 0x2B9CD9F2,
+ 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED,
+ 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530,
+ 0x0417B1DB, 0xF67C32D8, 0xE52CC12C, 0x1747422F,
+ 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF,
+ 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0,
+ 0xD3D3E1AB, 0x21B862A8, 0x32E8915C, 0xC083125F,
+ 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540,
+ 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90,
+ 0x9E902E7B, 0x6CFBAD78, 0x7FAB5E8C, 0x8DC0DD8F,
+ 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE,
+ 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1,
+ 0x69E9F0D5, 0x9B8273D6, 0x88D28022, 0x7AB90321,
+ 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E,
+ 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81,
+ 0x34F4F86A, 0xC69F7B69, 0xD5CF889D, 0x27A40B9E,
+ 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E,
+ 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351
+};
+
+/*
+ * iscsi_crc32c - Steps through buffer one byte at at time, calculates
+ * reflected crc using table.
+ */
+uint32_t
+idm_crc32c(void *address, unsigned long length)
+{
+ uint8_t *buffer = address;
+ uint32_t crc = 0xffffffff, result;
+#ifdef _BIG_ENDIAN
+ uint8_t byte0, byte1, byte2, byte3;
+#endif
+
+ ASSERT(address != NULL);
+
+ while (length--) {
+ crc = idm_crc32c_table[(crc ^ *buffer++) & 0xFFL] ^
+ (crc >> 8);
+ }
+ result = crc ^ 0xffffffff;
+
+#ifdef _BIG_ENDIAN
+ byte0 = (uint8_t)(result & 0xFF);
+ byte1 = (uint8_t)((result >> 8) & 0xFF);
+ byte2 = (uint8_t)((result >> 16) & 0xFF);
+ byte3 = (uint8_t)((result >> 24) & 0xFF);
+ result = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3);
+#endif /* _BIG_ENDIAN */
+
+ return (result);
+}
+
+
+/*
+ * idm_crc32c_continued - Continues stepping through buffer one
+ * byte at at time, calculates reflected crc using table.
+ */
+uint32_t
+idm_crc32c_continued(void *address, unsigned long length, uint32_t crc)
+{
+ uint8_t *buffer = address;
+ uint32_t result;
+#ifdef _BIG_ENDIAN
+ uint8_t byte0, byte1, byte2, byte3;
+#endif
+
+ ASSERT(address != NULL);
+
+#ifdef _BIG_ENDIAN
+ byte0 = (uint8_t)((crc >> 24) & 0xFF);
+ byte1 = (uint8_t)((crc >> 16) & 0xFF);
+ byte2 = (uint8_t)((crc >> 8) & 0xFF);
+ byte3 = (uint8_t)(crc & 0xFF);
+ crc = ((byte3 << 24) | (byte2 << 16) | (byte1 << 8) | byte0);
+#endif
+
+ crc = crc ^ 0xffffffff;
+ while (length--) {
+ crc = idm_crc32c_table[(crc ^ *buffer++) & 0xFFL] ^
+ (crc >> 8);
+ }
+ result = crc ^ 0xffffffff;
+
+#ifdef _BIG_ENDIAN
+ byte0 = (uint8_t)(result & 0xFF);
+ byte1 = (uint8_t)((result >> 8) & 0xFF);
+ byte2 = (uint8_t)((result >> 16) & 0xFF);
+ byte3 = (uint8_t)((result >> 24) & 0xFF);
+ result = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3);
+#endif
+ return (result);
+}
+
+/* ARGSUSED */
+int
+idm_task_constructor(void *hdl, void *arg, int flags)
+{
+ idm_task_t *idt = (idm_task_t *)hdl;
+ uint32_t next_task;
+
+ mutex_init(&idt->idt_mutex, NULL, MUTEX_DEFAULT, NULL);
+
+ /* Find the next free task ID */
+ rw_enter(&idm.idm_taskid_table_lock, RW_WRITER);
+ next_task = idm.idm_taskid_next;
+ while (idm.idm_taskid_table[next_task]) {
+ next_task++;
+ if (next_task == idm.idm_taskid_max)
+ next_task = 0;
+ if (next_task == idm.idm_taskid_next) {
+ rw_exit(&idm.idm_taskid_table_lock);
+ return (-1);
+ }
+ }
+
+ idm.idm_taskid_table[next_task] = idt;
+ idm.idm_taskid_next = (next_task + 1) % idm.idm_taskid_max;
+ rw_exit(&idm.idm_taskid_table_lock);
+
+ idt->idt_tt = next_task;
+
+ list_create(&idt->idt_inbufv, sizeof (idm_buf_t),
+ offsetof(idm_buf_t, idb_buflink));
+ list_create(&idt->idt_outbufv, sizeof (idm_buf_t),
+ offsetof(idm_buf_t, idb_buflink));
+ idm_refcnt_init(&idt->idt_refcnt, idt);
+
+ /*
+ * Set the transport header pointer explicitly. This removes the
+ * need for per-transport header allocation, which simplifies cache
+ * init considerably. If at a later date we have an additional IDM
+ * transport that requires a different size, we'll revisit this.
+ */
+ idt->idt_transport_hdr = (void *)(idt + 1); /* pointer arithmetic */
+
+ return (0);
+}
+
+/* ARGSUSED */
+void
+idm_task_destructor(void *hdl, void *arg)
+{
+ idm_task_t *idt = (idm_task_t *)hdl;
+
+ /* Remove the task from the ID table */
+ rw_enter(&idm.idm_taskid_table_lock, RW_WRITER);
+ idm.idm_taskid_table[idt->idt_tt] = NULL;
+ rw_exit(&idm.idm_taskid_table_lock);
+
+ /* free the inbuf and outbuf */
+ idm_refcnt_destroy(&idt->idt_refcnt);
+ list_destroy(&idt->idt_inbufv);
+ list_destroy(&idt->idt_outbufv);
+
+ mutex_destroy(&idt->idt_mutex);
+}
+
+/*
+ * idm_listbuf_insert searches from the back of the list looking for the
+ * insertion point.
+ */
+void
+idm_listbuf_insert(list_t *lst, idm_buf_t *buf)
+{
+ idm_buf_t *idb;
+
+ /* iterate through the list to find the insertion point */
+ for (idb = list_tail(lst); idb != NULL; idb = list_prev(lst, idb)) {
+
+ if (idb->idb_bufoffset < buf->idb_bufoffset) {
+
+ list_insert_after(lst, idb, buf);
+ return;
+ }
+ }
+
+ /* add the buf to the head of the list */
+ list_insert_head(lst, buf);
+
+}
+
+/*ARGSUSED*/
+void
+idm_wd_thread(void *arg)
+{
+ idm_conn_t *ic;
+ clock_t wake_time;
+ clock_t idle_time;
+
+ mutex_enter(&idm.idm_global_mutex);
+ idm.idm_wd_thread_running = B_TRUE;
+ idm.idm_wd_thread_did = idm.idm_wd_thread->t_did;
+
+ cv_signal(&idm.idm_wd_cv);
+
+ while (idm.idm_wd_thread_running) {
+ for (ic = list_head(&idm.idm_tgt_conn_list);
+ ic != NULL;
+ ic = list_next(&idm.idm_tgt_conn_list, ic)) {
+ idle_time = ddi_get_lbolt() - ic->ic_timestamp;
+
+ /*
+ * If there hasn't been any activity on this
+ * connection for the specified period then
+ * drop the connection. We expect the initiator
+ * to keep the connection alive if it wants the
+ * connection to stay open.
+ *
+ * If it turns out to be desireable to take a
+ * more active role in maintaining the connect
+ * we could add a client callback to send
+ * a "keepalive" kind of message (no doubt a nop)
+ * and fire that on a shorter timer.
+ */
+ if (TICK_TO_SEC(idle_time) >
+ IDM_TRANSPORT_FAIL_IDLE_TIMEOUT) {
+ /*
+ * Only send the transport fail if we're in
+ * FFP. State machine timers should handle
+ * problems in non-ffp states.
+ */
+ if (ic->ic_ffp) {
+ mutex_exit(&idm.idm_global_mutex);
+ IDM_SM_LOG(CE_WARN, "idm_wd_thread: "
+ "conn %p idle for %d seconds, "
+ "sending CE_TRANSPORT_FAIL",
+ (void *)ic, (int)idle_time);
+ idm_conn_event(ic, CE_TRANSPORT_FAIL,
+ NULL);
+ mutex_enter(&idm.idm_global_mutex);
+ }
+ }
+ }
+
+ wake_time = lbolt + SEC_TO_TICK(IDM_WD_INTERVAL);
+ (void) cv_timedwait(&idm.idm_wd_cv, &idm.idm_global_mutex,
+ wake_time);
+ }
+ mutex_exit(&idm.idm_global_mutex);
+
+ thread_exit();
+}
diff --git a/usr/src/uts/common/io/idm/idm_so.c b/usr/src/uts/common/io/idm/idm_so.c
new file mode 100644
index 0000000000..b8c236d749
--- /dev/null
+++ b/usr/src/uts/common/io/idm/idm_so.c
@@ -0,0 +1,2525 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/conf.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/modctl.h>
+#include <sys/priv.h>
+#include <sys/cpuvar.h>
+#include <sys/socket.h>
+#include <sys/strsubr.h>
+#include <sys/sysmacros.h>
+#include <sys/sdt.h>
+#include <netinet/tcp.h>
+#include <inet/tcp.h>
+#include <sys/socketvar.h>
+#include <sys/pathname.h>
+#include <sys/fs/snode.h>
+#include <sys/fs/dv_node.h>
+#include <sys/vnode.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <sys/sockio.h>
+
+#include <sys/idm/idm.h>
+#include <sys/idm/idm_so.h>
+#include <sys/idm/idm_text.h>
+
+/*
+ * in6addr_any is currently all zeroes, but use the macro in case this
+ * ever changes.
+ */
+const struct in6_addr in6addr_any = IN6ADDR_ANY_INIT;
+
+static void idm_sorx_cache_pdu_cb(idm_pdu_t *pdu, idm_status_t status);
+static void idm_sorx_addl_pdu_cb(idm_pdu_t *pdu, idm_status_t status);
+static void idm_sotx_cache_pdu_cb(idm_pdu_t *pdu, idm_status_t status);
+
+static idm_status_t idm_so_conn_create_common(idm_conn_t *ic,
+ struct sonode *new_so);
+static void idm_so_conn_destroy_common(idm_conn_t *ic);
+static void idm_so_conn_connect_common(idm_conn_t *ic);
+
+static void idm_set_ini_preconnect_options(idm_so_conn_t *sc);
+static void idm_set_ini_postconnect_options(idm_so_conn_t *sc);
+static void idm_set_tgt_connect_options(struct sonode *sonode);
+static idm_status_t idm_i_so_tx(idm_pdu_t *pdu);
+
+static idm_status_t idm_sorecvdata(idm_conn_t *ic, idm_pdu_t *pdu);
+static idm_status_t idm_so_send_buf_region(idm_task_t *idt, uint8_t opcode,
+ idm_buf_t *idb, uint32_t buf_region_offset, uint32_t buf_region_length);
+
+static uint32_t idm_fill_iov(idm_pdu_t *pdu, idm_buf_t *idb,
+ uint32_t ro, uint32_t dlength);
+
+static idm_status_t idm_so_handle_digest(idm_conn_t *it,
+ nvpair_t *digest_choice, const idm_kv_xlate_t *ikvx);
+
+/*
+ * Transport ops prototypes
+ */
+static void idm_so_tx(idm_conn_t *ic, idm_pdu_t *pdu);
+static idm_status_t idm_so_buf_tx_to_ini(idm_task_t *idt, idm_buf_t *idb);
+static idm_status_t idm_so_buf_rx_from_ini(idm_task_t *idt, idm_buf_t *idb);
+static void idm_so_rx_datain(idm_conn_t *ic, idm_pdu_t *pdu);
+static void idm_so_rx_rtt(idm_conn_t *ic, idm_pdu_t *pdu);
+static void idm_so_rx_dataout(idm_conn_t *ic, idm_pdu_t *pdu);
+static idm_status_t idm_so_free_task_rsrc(idm_task_t *idt);
+static kv_status_t idm_so_negotiate_key_values(idm_conn_t *it,
+ nvlist_t *request_nvl, nvlist_t *response_nvl, nvlist_t *negotiated_nvl);
+static idm_status_t idm_so_notice_key_values(idm_conn_t *it,
+ nvlist_t *negotiated_nvl);
+static boolean_t idm_so_conn_is_capable(idm_conn_req_t *ic,
+ idm_transport_caps_t *caps);
+static idm_status_t idm_so_buf_alloc(idm_buf_t *idb, uint64_t buflen);
+static void idm_so_buf_free(idm_buf_t *idb);
+static idm_status_t idm_so_buf_setup(idm_buf_t *idb);
+static void idm_so_buf_teardown(idm_buf_t *idb);
+static idm_status_t idm_so_tgt_svc_create(idm_svc_req_t *sr, idm_svc_t *is);
+static void idm_so_tgt_svc_destroy(idm_svc_t *is);
+static idm_status_t idm_so_tgt_svc_online(idm_svc_t *is);
+static void idm_so_tgt_svc_offline(idm_svc_t *is);
+static void idm_so_tgt_conn_destroy(idm_conn_t *ic);
+static idm_status_t idm_so_tgt_conn_connect(idm_conn_t *ic);
+static void idm_so_conn_disconnect(idm_conn_t *ic);
+static idm_status_t idm_so_ini_conn_create(idm_conn_req_t *cr, idm_conn_t *ic);
+static void idm_so_ini_conn_destroy(idm_conn_t *ic);
+static idm_status_t idm_so_ini_conn_connect(idm_conn_t *ic);
+
+/*
+ * IDM Native Sockets transport operations
+ */
+static
+idm_transport_ops_t idm_so_transport_ops = {
+ idm_so_tx, /* it_tx_pdu */
+ idm_so_buf_tx_to_ini, /* it_buf_tx_to_ini */
+ idm_so_buf_rx_from_ini, /* it_buf_rx_from_ini */
+ idm_so_rx_datain, /* it_rx_datain */
+ idm_so_rx_rtt, /* it_rx_rtt */
+ idm_so_rx_dataout, /* it_rx_dataout */
+ NULL, /* it_alloc_conn_rsrc */
+ NULL, /* it_free_conn_rsrc */
+ NULL, /* it_tgt_enable_datamover */
+ NULL, /* it_ini_enable_datamover */
+ NULL, /* it_conn_terminate */
+ idm_so_free_task_rsrc, /* it_free_task_rsrc */
+ idm_so_negotiate_key_values, /* it_negotiate_key_values */
+ idm_so_notice_key_values, /* it_notice_key_values */
+ idm_so_conn_is_capable, /* it_conn_is_capable */
+ idm_so_buf_alloc, /* it_buf_alloc */
+ idm_so_buf_free, /* it_buf_free */
+ idm_so_buf_setup, /* it_buf_setup */
+ idm_so_buf_teardown, /* it_buf_teardown */
+ idm_so_tgt_svc_create, /* it_tgt_svc_create */
+ idm_so_tgt_svc_destroy, /* it_tgt_svc_destroy */
+ idm_so_tgt_svc_online, /* it_tgt_svc_online */
+ idm_so_tgt_svc_offline, /* it_tgt_svc_offline */
+ idm_so_tgt_conn_destroy, /* it_tgt_conn_destroy */
+ idm_so_tgt_conn_connect, /* it_tgt_conn_connect */
+ idm_so_conn_disconnect, /* it_tgt_conn_disconnect */
+ idm_so_ini_conn_create, /* it_ini_conn_create */
+ idm_so_ini_conn_destroy, /* it_ini_conn_destroy */
+ idm_so_ini_conn_connect, /* it_ini_conn_connect */
+ idm_so_conn_disconnect /* it_ini_conn_disconnect */
+};
+
+/*
+ * idm_so_init()
+ * Sockets transport initialization
+ */
+void
+idm_so_init(idm_transport_t *it)
+{
+ /* Cache for IDM Data and R2T Transmit PDU's */
+ idm.idm_sotx_pdu_cache = kmem_cache_create("idm_tx_pdu_cache",
+ sizeof (idm_pdu_t) + sizeof (iscsi_hdr_t), 8,
+ &idm_sotx_pdu_constructor, NULL, NULL, NULL, NULL, KM_SLEEP);
+
+ /* Cache for IDM Receive PDU's */
+ idm.idm_sorx_pdu_cache = kmem_cache_create("idm_rx_pdu_cache",
+ sizeof (idm_pdu_t) + IDM_SORX_CACHE_HDRLEN, 8,
+ &idm_sorx_pdu_constructor, NULL, NULL, NULL, NULL, KM_SLEEP);
+
+ /* Set the sockets transport ops */
+ it->it_ops = &idm_so_transport_ops;
+}
+
+/*
+ * idm_so_fini()
+ * Sockets transport teardown
+ */
+void
+idm_so_fini(void)
+{
+ kmem_cache_destroy(idm.idm_sotx_pdu_cache);
+ kmem_cache_destroy(idm.idm_sorx_pdu_cache);
+}
+
+struct sonode *
+idm_socreate(int domain, int type, int protocol)
+{
+ vnode_t *dvp;
+ vnode_t *vp;
+ struct snode *csp;
+ int err;
+ major_t maj;
+
+ if ((vp = solookup(domain, type, protocol, NULL, &err)) == NULL) {
+
+ /*
+ * solookup calls sogetvp if the vp is not found in the cache.
+ * Since the call to sogetvp is hardwired to use USERSPACE
+ * and declared static we'll do the work here instead.
+ */
+ err = lookupname(type == SOCK_STREAM ? "/dev/tcp" : "/dev/udp",
+ UIO_SYSSPACE, FOLLOW, NULLVPP, &vp);
+ if (err != 0)
+ return (NULL);
+
+ /* Check that it is the correct vnode */
+ if (vp->v_type != VCHR) {
+ VN_RELE(vp);
+ return (NULL);
+ }
+
+ csp = VTOS(VTOS(vp)->s_commonvp);
+ if (!(csp->s_flag & SDIPSET)) {
+ char *pathname = kmem_alloc(MAXPATHLEN, KM_SLEEP);
+
+ err = ddi_dev_pathname(vp->v_rdev, S_IFCHR,
+ pathname);
+ if (err == 0) {
+ err = devfs_lookupname(pathname, NULLVPP,
+ &dvp);
+ }
+ VN_RELE(vp);
+ kmem_free(pathname, MAXPATHLEN);
+ if (err != 0) {
+ return (NULL);
+ }
+ vp = dvp;
+ }
+
+ maj = getmajor(vp->v_rdev);
+ if (!STREAMSTAB(maj)) {
+ VN_RELE(vp);
+ return (NULL);
+ }
+ }
+ return (socreate(vp, domain, type, protocol, SOV_DEFAULT, NULL, &err));
+}
+
+/*
+ * idm_soshutdown will disconnect the socket and prevent subsequent PDU
+ * reception and transmission. The sonode still exists but its state
+ * gets modified to indicate it is no longer connected. Calls to
+ * idm_sorecv/idm_iov_sorecv will return so idm_soshutdown can be used
+ * regain control of a thread stuck in idm_sorecv.
+ */
+void
+idm_soshutdown(struct sonode *so)
+{
+ (void) soshutdown(so, SHUT_RDWR);
+}
+
+/*
+ * idm_sodestroy releases all resources associated with a socket previously
+ * created with idm_socreate. The socket must be shutdown using
+ * idm_soshutdown before the socket is destroyed with idm_sodestroy,
+ * otherwise undefined behavior will result.
+ */
+void
+idm_sodestroy(struct sonode *so)
+{
+ vnode_t *vp = SOTOV(so);
+
+ (void) VOP_CLOSE(vp, 0, 1, 0, kcred, NULL);
+
+ VN_RELE(vp);
+}
+
+/*
+ * IP address filter functions to flag addresses that should not
+ * go out to initiators through discovery.
+ */
+static boolean_t
+idm_v4_addr_okay(struct in_addr *in_addr)
+{
+ in_addr_t addr = ntohl(in_addr->s_addr);
+
+ if ((INADDR_NONE == addr) ||
+ (IN_MULTICAST(addr)) ||
+ ((addr >> IN_CLASSA_NSHIFT) == 0) ||
+ ((addr >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)) {
+ return (B_FALSE);
+ }
+ return (B_TRUE);
+}
+
+static boolean_t
+idm_v6_addr_okay(struct in6_addr *addr6)
+{
+
+ if ((IN6_IS_ADDR_UNSPECIFIED(addr6)) ||
+ (IN6_IS_ADDR_LOOPBACK(addr6)) ||
+ (IN6_IS_ADDR_MULTICAST(addr6)) ||
+ (IN6_IS_ADDR_V4MAPPED(addr6)) ||
+ (IN6_IS_ADDR_V4COMPAT(addr6)) ||
+ (IN6_IS_ADDR_LINKLOCAL(addr6))) {
+ return (B_FALSE);
+ }
+ return (B_TRUE);
+}
+
+/*
+ * idm_get_ipaddr will retrieve a list of IP Addresses which the host is
+ * configured with by sending down a sequence of kernel ioctl to IP STREAMS.
+ */
+int
+idm_get_ipaddr(idm_addr_list_t **ipaddr_p)
+{
+ struct sonode *so4, *so6;
+ vnode_t *vp, *vp4, *vp6;
+ struct lifnum lifn;
+ struct lifconf lifc;
+ struct lifreq *lp;
+ int rval;
+ int numifs;
+ int bufsize;
+ void *buf;
+ int i, j, n, rc;
+ struct sockaddr_storage ss;
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ idm_addr_t *ip;
+ idm_addr_list_t *ipaddr;
+ int size_ipaddr;
+
+ *ipaddr_p = NULL;
+ size_ipaddr = 0;
+ buf = NULL;
+
+ /* create an ipv4 and ipv6 UDP socket */
+ if ((so6 = idm_socreate(PF_INET6, SOCK_DGRAM, 0)) == NULL)
+ return (0);
+ if ((so4 = idm_socreate(PF_INET, SOCK_DGRAM, 0)) == NULL) {
+ idm_sodestroy(so6);
+ return (0);
+ }
+
+ /* setup the vp's for each socket type */
+ vp6 = SOTOV(so6);
+ vp4 = SOTOV(so4);
+ /* use vp6 for ioctls with unspecified families by default */
+ vp = vp6;
+
+retry_count:
+ /* snapshot the current number of interfaces */
+ lifn.lifn_family = PF_UNSPEC;
+ lifn.lifn_flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES;
+ lifn.lifn_count = 0;
+ if (VOP_IOCTL(vp, SIOCGLIFNUM, (intptr_t)&lifn, FKIOCTL, kcred,
+ &rval, NULL) != 0) {
+ goto cleanup;
+ }
+
+ numifs = lifn.lifn_count;
+ if (numifs <= 0) {
+ goto cleanup;
+ }
+
+ /* allocate extra room in case more interfaces appear */
+ numifs += 10;
+
+ /* get the interface names and ip addresses */
+ bufsize = numifs * sizeof (struct lifreq);
+ buf = kmem_alloc(bufsize, KM_SLEEP);
+
+ lifc.lifc_family = AF_UNSPEC;
+ lifc.lifc_flags = LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES;
+ lifc.lifc_len = bufsize;
+ lifc.lifc_buf = buf;
+ rc = VOP_IOCTL(vp, SIOCGLIFCONF, (intptr_t)&lifc, FKIOCTL, kcred,
+ &rval, NULL);
+ if (rc != 0) {
+ goto cleanup;
+ }
+ /* if our extra room is used up, try again */
+ if (bufsize <= lifc.lifc_len) {
+ kmem_free(buf, bufsize);
+ buf = NULL;
+ goto retry_count;
+ }
+ /* calc actual number of ifconfs */
+ n = lifc.lifc_len / sizeof (struct lifreq);
+
+ /* get ip address */
+ if (n > 0) {
+ size_ipaddr = sizeof (idm_addr_list_t) +
+ (n - 1) * sizeof (idm_addr_t);
+ ipaddr = kmem_zalloc(size_ipaddr, KM_SLEEP);
+ } else {
+ goto cleanup;
+ }
+
+ /*
+ * Examine the array of interfaces and filter uninteresting ones
+ */
+ for (i = 0, j = 0, lp = lifc.lifc_req; i < n; i++, lp++) {
+
+ /*
+ * Copy the address as the SIOCGLIFFLAGS ioctl is destructive
+ */
+ ss = lp->lifr_addr;
+ /*
+ * fetch the flags using the socket of the correct family
+ */
+ switch (ss.ss_family) {
+ case AF_INET:
+ vp = vp4;
+ break;
+ case AF_INET6:
+ vp = vp6;
+ break;
+ default:
+ continue;
+ }
+ rc = VOP_IOCTL(vp, SIOCGLIFFLAGS, (intptr_t)lp, FKIOCTL, kcred,
+ &rval, NULL);
+ if (rc == 0) {
+ /*
+ * If we got the flags, skip uninteresting
+ * interfaces based on flags
+ */
+ if ((lp->lifr_flags & IFF_UP) != IFF_UP)
+ continue;
+ if (lp->lifr_flags &
+ (IFF_ANYCAST|IFF_NOLOCAL|IFF_DEPRECATED))
+ continue;
+ }
+
+ /* save ip address */
+ ip = &ipaddr->al_addrs[j];
+ switch (ss.ss_family) {
+ case AF_INET:
+ sin = (struct sockaddr_in *)&ss;
+ if (!idm_v4_addr_okay(&sin->sin_addr))
+ continue;
+ ip->a_addr.i_addr.in4 = sin->sin_addr;
+ ip->a_addr.i_insize = sizeof (struct in_addr);
+ break;
+ case AF_INET6:
+ sin6 = (struct sockaddr_in6 *)&ss;
+ if (!idm_v6_addr_okay(&sin6->sin6_addr))
+ continue;
+ ip->a_addr.i_addr.in6 = sin6->sin6_addr;
+ ip->a_addr.i_insize = sizeof (struct in6_addr);
+ break;
+ default:
+ continue;
+ }
+ j++;
+ }
+
+ if (j == 0) {
+ /* no valid ifaddr */
+ kmem_free(ipaddr, size_ipaddr);
+ size_ipaddr = 0;
+ ipaddr = NULL;
+ } else {
+ ipaddr->al_out_cnt = j;
+ }
+
+
+cleanup:
+ idm_sodestroy(so6);
+ idm_sodestroy(so4);
+
+ if (buf != NULL)
+ kmem_free(buf, bufsize);
+
+ *ipaddr_p = ipaddr;
+ return (size_ipaddr);
+}
+
+int
+idm_sorecv(struct sonode *so, void *msg, size_t len)
+{
+ iovec_t iov;
+
+ ASSERT(so != NULL);
+ ASSERT(len != 0);
+
+ /*
+ * Fill in iovec and receive data
+ */
+ iov.iov_base = msg;
+ iov.iov_len = len;
+
+ return (idm_iov_sorecv(so, &iov, 1, len));
+}
+
+/*
+ * idm_sosendto - Sends a buffered data on a non-connected socket.
+ *
+ * This function puts the data provided on the wire by calling sosendmsg.
+ * It will return only when all the data has been sent or if an error
+ * occurs.
+ *
+ * Returns 0 for success, the socket errno value if sosendmsg fails, and
+ * -1 if sosendmsg returns success but uio_resid != 0
+ */
+int
+idm_sosendto(struct sonode *so, void *buff, size_t len,
+ struct sockaddr *name, socklen_t namelen)
+{
+ struct msghdr msg;
+ struct uio uio;
+ struct iovec iov[1];
+ int error;
+
+ iov[0].iov_base = buff;
+ iov[0].iov_len = len;
+
+ /* Initialization of the message header. */
+ bzero(&msg, sizeof (msg));
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+
+ /* Initialization of the uio structure. */
+ uio.uio_iov = iov;
+ uio.uio_iovcnt = 1;
+ uio.uio_segflg = UIO_SYSSPACE;
+ uio.uio_resid = len;
+
+ msg.msg_name = name;
+ msg.msg_namelen = namelen;
+
+ if ((error = sosendmsg(so, &msg, &uio)) == 0) {
+ /* Data sent */
+ if (uio.uio_resid == 0) {
+ /* All data sent. Success. */
+ return (0);
+ } else {
+ /* Not all data was sent. Failure */
+ return (-1);
+ }
+ }
+
+ /* Send failed */
+ return (error);
+}
+
+/*
+ * idm_iov_sosend - Sends an iovec on a connection.
+ *
+ * This function puts the data provided on the wire by calling sosendmsg.
+ * It will return only when all the data has been sent or if an error
+ * occurs.
+ *
+ * Returns 0 for success, the socket errno value if sosendmsg fails, and
+ * -1 if sosendmsg returns success but uio_resid != 0
+ */
+int
+idm_iov_sosend(struct sonode *so, iovec_t *iop, int iovlen, size_t total_len)
+{
+ struct msghdr msg;
+ struct uio uio;
+ int error;
+
+ ASSERT(iop != NULL);
+
+ /* Initialization of the message header. */
+ bzero(&msg, sizeof (msg));
+ msg.msg_iov = iop;
+ msg.msg_iovlen = iovlen;
+
+ /* Initialization of the uio structure. */
+ bzero(&uio, sizeof (uio));
+ uio.uio_iov = iop;
+ uio.uio_iovcnt = iovlen;
+ uio.uio_segflg = UIO_SYSSPACE;
+ uio.uio_resid = total_len;
+
+ if ((error = sosendmsg(so, &msg, &uio)) == 0) {
+ /* Data sent */
+ if (uio.uio_resid == 0) {
+ /* All data sent. Success. */
+ return (0);
+ } else {
+ /* Not all data was sent. Failure */
+ return (-1);
+ }
+ }
+
+ /* Send failed */
+ return (error);
+}
+
+/*
+ * idm_iov_sorecv - Receives an iovec from a connection
+ *
+ * This function gets the data asked for from the socket. It will return
+ * only when all the requested data has been retrieved or if an error
+ * occurs.
+ *
+ * Returns 0 for success, the socket errno value if sorecvmsg fails, and
+ * -1 if sorecvmsg returns success but uio_resid != 0
+ */
+int
+idm_iov_sorecv(struct sonode *so, iovec_t *iop, int iovlen, size_t total_len)
+{
+ struct msghdr msg;
+ struct uio uio;
+ int error;
+
+ ASSERT(iop != NULL);
+
+ /* Initialization of the message header. */
+ bzero(&msg, sizeof (msg));
+ msg.msg_iov = iop;
+ msg.msg_flags = MSG_WAITALL;
+ msg.msg_iovlen = iovlen;
+
+ /* Initialization of the uio structure. */
+ bzero(&uio, sizeof (uio));
+ uio.uio_iov = iop;
+ uio.uio_iovcnt = iovlen;
+ uio.uio_segflg = UIO_SYSSPACE;
+ uio.uio_resid = total_len;
+
+ if ((error = sorecvmsg(so, &msg, &uio)) == 0) {
+ /* Received data */
+ if (uio.uio_resid == 0) {
+ /* All requested data received. Success */
+ return (0);
+ } else {
+ /*
+ * Not all data was received. The connection has
+ * probably failed.
+ */
+ return (-1);
+ }
+ }
+
+ /* Receive failed */
+ return (error);
+}
+
+static void
+idm_set_ini_preconnect_options(idm_so_conn_t *sc)
+{
+ int conn_abort = 10000;
+ int conn_notify = 2000;
+ int abort = 30000;
+
+ /* Pre-connect socket options */
+ (void) sosetsockopt(sc->ic_so, IPPROTO_TCP, TCP_CONN_NOTIFY_THRESHOLD,
+ (char *)&conn_notify, sizeof (int));
+ (void) sosetsockopt(sc->ic_so, IPPROTO_TCP, TCP_CONN_ABORT_THRESHOLD,
+ (char *)&conn_abort, sizeof (int));
+ (void) sosetsockopt(sc->ic_so, IPPROTO_TCP, TCP_ABORT_THRESHOLD,
+ (char *)&abort, sizeof (int));
+}
+
+static void
+idm_set_ini_postconnect_options(idm_so_conn_t *sc)
+{
+ int32_t rcvbuf = IDM_RCVBUF_SIZE;
+ int32_t sndbuf = IDM_SNDBUF_SIZE;
+ const int on = 1;
+
+ /* Set postconnect options */
+ (void) sosetsockopt(sc->ic_so, IPPROTO_TCP, TCP_NODELAY,
+ (char *)&on, sizeof (int));
+ (void) sosetsockopt(sc->ic_so, SOL_SOCKET, SO_RCVBUF,
+ (char *)&rcvbuf, sizeof (int));
+ (void) sosetsockopt(sc->ic_so, SOL_SOCKET, SO_SNDBUF,
+ (char *)&sndbuf, sizeof (int));
+}
+
+static void
+idm_set_tgt_connect_options(struct sonode *sonode)
+{
+ int32_t rcvbuf = IDM_RCVBUF_SIZE;
+ int32_t sndbuf = IDM_SNDBUF_SIZE;
+ const int on = 1;
+
+ /* Set connect options */
+ (void) sosetsockopt(sonode, SOL_SOCKET, SO_RCVBUF,
+ (char *)&rcvbuf, sizeof (int));
+ (void) sosetsockopt(sonode, SOL_SOCKET, SO_SNDBUF,
+ (char *)&sndbuf, sizeof (int));
+ (void) sosetsockopt(sonode, IPPROTO_TCP, TCP_NODELAY,
+ (char *)&on, sizeof (on));
+}
+
+static uint32_t
+n2h24(const uchar_t *ptr)
+{
+ return ((ptr[0] << 16) | (ptr[1] << 8) | ptr[2]);
+}
+
+
+static idm_status_t
+idm_sorecvhdr(idm_conn_t *ic, idm_pdu_t *pdu)
+{
+ iscsi_hdr_t *bhs;
+ uint32_t hdr_digest_crc;
+ uint32_t crc_calculated;
+ void *new_hdr;
+ int ahslen = 0;
+ int total_len = 0;
+ int iovlen = 0;
+ struct iovec iov[2];
+ idm_so_conn_t *so_conn;
+ int rc;
+
+ so_conn = ic->ic_transport_private;
+
+ /*
+ * Read BHS
+ */
+ bhs = pdu->isp_hdr;
+ rc = idm_sorecv(so_conn->ic_so, pdu->isp_hdr, sizeof (iscsi_hdr_t));
+ if (rc != IDM_STATUS_SUCCESS) {
+ return (IDM_STATUS_FAIL);
+ }
+
+ /*
+ * Check actual AHS length against the amount available in the buffer
+ */
+ pdu->isp_hdrlen = sizeof (iscsi_hdr_t) +
+ (bhs->hlength * sizeof (uint32_t));
+ pdu->isp_datalen = n2h24(bhs->dlength);
+ if (bhs->hlength > IDM_SORX_CACHE_AHSLEN) {
+ /* Allocate a new header segment and change the callback */
+ new_hdr = kmem_alloc(pdu->isp_hdrlen, KM_SLEEP);
+ bcopy(pdu->isp_hdr, new_hdr, sizeof (iscsi_hdr_t));
+ pdu->isp_hdr = new_hdr;
+ pdu->isp_flags |= IDM_PDU_ADDL_HDR;
+
+ /*
+ * This callback will restore the expected values after
+ * the RX PDU has been processed.
+ */
+ pdu->isp_callback = idm_sorx_addl_pdu_cb;
+ }
+
+ /*
+ * Setup receipt of additional header and header digest (if enabled).
+ */
+ if (bhs->hlength > 0) {
+ iov[iovlen].iov_base = (caddr_t)(pdu->isp_hdr + 1);
+ ahslen = pdu->isp_hdrlen - sizeof (iscsi_hdr_t);
+ iov[iovlen].iov_len = ahslen;
+ total_len += iov[iovlen].iov_len;
+ iovlen++;
+ }
+
+ if (ic->ic_conn_flags & IDM_CONN_HEADER_DIGEST) {
+ iov[iovlen].iov_base = (caddr_t)&hdr_digest_crc;
+ iov[iovlen].iov_len = sizeof (hdr_digest_crc);
+ total_len += iov[iovlen].iov_len;
+ iovlen++;
+ }
+
+ if ((iovlen != 0) &&
+ (idm_iov_sorecv(so_conn->ic_so, &iov[0], iovlen,
+ total_len) != 0)) {
+ return (IDM_STATUS_FAIL);
+ }
+
+ /*
+ * Validate header digest if enabled
+ */
+ if (ic->ic_conn_flags & IDM_CONN_HEADER_DIGEST) {
+ crc_calculated = idm_crc32c(pdu->isp_hdr,
+ sizeof (iscsi_hdr_t) + ahslen);
+ if (crc_calculated != hdr_digest_crc) {
+ /* Invalid Header Digest */
+ return (IDM_STATUS_HEADER_DIGEST);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * idm_so_ini_conn_create()
+ * Allocate the sockets transport connection resources.
+ */
+static idm_status_t
+idm_so_ini_conn_create(idm_conn_req_t *cr, idm_conn_t *ic)
+{
+ struct sonode *so;
+ idm_so_conn_t *so_conn;
+ idm_status_t idmrc;
+
+ so = idm_socreate(cr->cr_domain, cr->cr_type,
+ cr->cr_protocol);
+ if (so == NULL) {
+ return (IDM_STATUS_FAIL);
+ }
+
+ /* Bind the socket if configured to do so */
+ if (cr->cr_bound) {
+ if (sobind(so, &cr->cr_bound_addr.sin,
+ SIZEOF_SOCKADDR(&cr->cr_bound_addr.sin), 0, 0) != 0) {
+ idm_sodestroy(so);
+ return (IDM_STATUS_FAIL);
+ }
+ }
+
+ idmrc = idm_so_conn_create_common(ic, so);
+ if (idmrc != IDM_STATUS_SUCCESS) {
+ idm_soshutdown(so);
+ idm_sodestroy(so);
+ return (IDM_STATUS_FAIL);
+ }
+
+ so_conn = ic->ic_transport_private;
+ /* Set up socket options */
+ idm_set_ini_preconnect_options(so_conn);
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+/*
+ * idm_so_ini_conn_destroy()
+ * Tear down the sockets transport connection resources.
+ */
+static void
+idm_so_ini_conn_destroy(idm_conn_t *ic)
+{
+ idm_so_conn_destroy_common(ic);
+}
+
+/*
+ * idm_so_ini_conn_connect()
+ * Establish the connection referred to by the handle previously allocated via
+ * idm_so_ini_conn_create().
+ */
+static idm_status_t
+idm_so_ini_conn_connect(idm_conn_t *ic)
+{
+ idm_so_conn_t *so_conn;
+
+ so_conn = ic->ic_transport_private;
+
+ if (soconnect(so_conn->ic_so, &ic->ic_ini_dst_addr.sin,
+ (SIZEOF_SOCKADDR(&ic->ic_ini_dst_addr.sin)), 0, 0) != 0) {
+ idm_soshutdown(so_conn->ic_so);
+ return (IDM_STATUS_FAIL);
+ }
+
+ idm_so_conn_connect_common(ic);
+
+ idm_set_ini_postconnect_options(so_conn);
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+idm_status_t
+idm_so_tgt_conn_create(idm_conn_t *ic, struct sonode *new_so)
+{
+ idm_status_t idmrc;
+
+ idmrc = idm_so_conn_create_common(ic, new_so);
+
+ return (idmrc);
+}
+
+static void
+idm_so_tgt_conn_destroy(idm_conn_t *ic)
+{
+ idm_so_conn_destroy_common(ic);
+}
+
+/*
+ * idm_so_tgt_conn_connect()
+ * Establish the connection in ic, passed from idm_tgt_conn_finish(), which
+ * is invoked from the SM as a result of an inbound connection request.
+ */
+static idm_status_t
+idm_so_tgt_conn_connect(idm_conn_t *ic)
+{
+ idm_so_conn_connect_common(ic);
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+static idm_status_t
+idm_so_conn_create_common(idm_conn_t *ic, struct sonode *new_so)
+{
+ idm_so_conn_t *so_conn;
+
+ so_conn = kmem_zalloc(sizeof (idm_so_conn_t), KM_SLEEP);
+ so_conn->ic_so = new_so;
+
+ ic->ic_transport_private = so_conn;
+ ic->ic_transport_hdrlen = 0;
+
+ /* Set the scoreboarding flag on this connection */
+ ic->ic_conn_flags |= IDM_CONN_USE_SCOREBOARD;
+
+ /*
+ * Initialize tx thread mutex and list
+ */
+ mutex_init(&so_conn->ic_tx_mutex, NULL, MUTEX_DEFAULT, NULL);
+ cv_init(&so_conn->ic_tx_cv, NULL, CV_DEFAULT, NULL);
+ list_create(&so_conn->ic_tx_list, sizeof (idm_pdu_t),
+ offsetof(idm_pdu_t, idm_tx_link));
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+static void
+idm_so_conn_destroy_common(idm_conn_t *ic)
+{
+ idm_so_conn_t *so_conn = ic->ic_transport_private;
+
+ ic->ic_transport_private = NULL;
+ idm_sodestroy(so_conn->ic_so);
+ list_destroy(&so_conn->ic_tx_list);
+ mutex_destroy(&so_conn->ic_tx_mutex);
+ cv_destroy(&so_conn->ic_tx_cv);
+
+ kmem_free(so_conn, sizeof (idm_so_conn_t));
+}
+
+static void
+idm_so_conn_connect_common(idm_conn_t *ic)
+{
+ idm_so_conn_t *so_conn;
+
+ so_conn = ic->ic_transport_private;
+
+ SOP_GETSOCKNAME(so_conn->ic_so);
+
+ /* Set the local and remote addresses in the idm conn handle */
+ mutex_enter(&so_conn->ic_so->so_lock);
+ bcopy(so_conn->ic_so->so_laddr_sa, &ic->ic_laddr,
+ so_conn->ic_so->so_laddr_len);
+ bcopy(so_conn->ic_so->so_faddr_sa, &ic->ic_raddr,
+ so_conn->ic_so->so_faddr_len);
+ mutex_exit(&so_conn->ic_so->so_lock);
+
+ mutex_enter(&ic->ic_mutex);
+ so_conn->ic_tx_thread = thread_create(NULL, 0, idm_sotx_thread, ic, 0,
+ &p0, TS_RUN, minclsyspri);
+ so_conn->ic_rx_thread = thread_create(NULL, 0, idm_sorx_thread, ic, 0,
+ &p0, TS_RUN, minclsyspri);
+
+ while (!so_conn->ic_rx_thread_running || !so_conn->ic_tx_thread_running)
+ cv_wait(&ic->ic_cv, &ic->ic_mutex);
+ mutex_exit(&ic->ic_mutex);
+}
+
+/*
+ * idm_so_conn_disconnect()
+ * Shutdown the socket connection and stop the thread
+ */
+static void
+idm_so_conn_disconnect(idm_conn_t *ic)
+{
+ idm_so_conn_t *so_conn;
+
+ so_conn = ic->ic_transport_private;
+
+ mutex_enter(&ic->ic_mutex);
+ so_conn->ic_rx_thread_running = B_FALSE;
+ so_conn->ic_tx_thread_running = B_FALSE;
+ /* We need to wakeup the TX thread */
+ mutex_enter(&so_conn->ic_tx_mutex);
+ cv_signal(&so_conn->ic_tx_cv);
+ mutex_exit(&so_conn->ic_tx_mutex);
+ mutex_exit(&ic->ic_mutex);
+
+ /* This should wakeup the RX thread if it is sleeping */
+ idm_soshutdown(so_conn->ic_so);
+
+ thread_join(so_conn->ic_tx_thread_did);
+ thread_join(so_conn->ic_rx_thread_did);
+}
+
+/*
+ * idm_so_tgt_svc_create()
+ * Establish a service on an IP address and port. idm_svc_req_t contains
+ * the service parameters.
+ */
+/*ARGSUSED*/
+static idm_status_t
+idm_so_tgt_svc_create(idm_svc_req_t *sr, idm_svc_t *is)
+{
+ idm_so_svc_t *so_svc;
+
+ so_svc = kmem_zalloc(sizeof (idm_so_svc_t), KM_SLEEP);
+
+ /* Set the new sockets service in svc handle */
+ is->is_so_svc = (void *)so_svc;
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+/*
+ * idm_so_tgt_svc_destroy()
+ * Teardown sockets resources allocated in idm_so_tgt_svc_create()
+ */
+static void
+idm_so_tgt_svc_destroy(idm_svc_t *is)
+{
+ /* the socket will have been torn down; free the service */
+ kmem_free(is->is_so_svc, sizeof (idm_so_svc_t));
+}
+
+/*
+ * idm_so_tgt_svc_online()
+ * Launch a watch thread on the svc allocated in idm_so_tgt_svc_create()
+ */
+
+static idm_status_t
+idm_so_tgt_svc_online(idm_svc_t *is)
+{
+ idm_so_svc_t *so_svc;
+ idm_svc_req_t *sr = &is->is_svc_req;
+ struct sockaddr_in6 sin6_ip;
+ const uint32_t on = 1;
+ const uint32_t off = 0;
+
+ mutex_enter(&is->is_mutex);
+ so_svc = (idm_so_svc_t *)is->is_so_svc;
+
+ /*
+ * Try creating an IPv6 socket first
+ */
+ if ((so_svc->is_so = idm_socreate(PF_INET6, SOCK_STREAM, 0)) == NULL) {
+ mutex_exit(&is->is_mutex);
+ return (IDM_STATUS_FAIL);
+ } else {
+ bzero(&sin6_ip, sizeof (sin6_ip));
+ sin6_ip.sin6_family = AF_INET6;
+ sin6_ip.sin6_port = htons(sr->sr_port);
+ sin6_ip.sin6_addr = in6addr_any;
+
+ (void) sosetsockopt(so_svc->is_so, SOL_SOCKET, SO_REUSEADDR,
+ (char *)&on, sizeof (on));
+ /*
+ * Turn off SO_MAC_EXEMPT so future sobinds succeed
+ */
+ (void) sosetsockopt(so_svc->is_so, SOL_SOCKET, SO_MAC_EXEMPT,
+ (char *)&off, sizeof (off));
+
+ if (sobind(so_svc->is_so, (struct sockaddr *)&sin6_ip,
+ sizeof (sin6_ip), 0, 0) != 0) {
+ mutex_exit(&is->is_mutex);
+ idm_sodestroy(so_svc->is_so);
+ return (IDM_STATUS_FAIL);
+ }
+ }
+
+ idm_set_tgt_connect_options(so_svc->is_so);
+
+ if (solisten(so_svc->is_so, 5) != 0) {
+ mutex_exit(&is->is_mutex);
+ idm_soshutdown(so_svc->is_so);
+ idm_sodestroy(so_svc->is_so);
+ return (IDM_STATUS_FAIL);
+ }
+
+ /* Launch a watch thread */
+ so_svc->is_thread = thread_create(NULL, 0, idm_so_svc_port_watcher,
+ is, 0, &p0, TS_RUN, minclsyspri);
+
+ if (so_svc->is_thread == NULL) {
+ /* Failure to launch; teardown the socket */
+ mutex_exit(&is->is_mutex);
+ idm_soshutdown(so_svc->is_so);
+ idm_sodestroy(so_svc->is_so);
+ return (IDM_STATUS_FAIL);
+ }
+
+ /* Wait for the port watcher thread to start */
+ while (!so_svc->is_thread_running)
+ cv_wait(&is->is_cv, &is->is_mutex);
+ mutex_exit(&is->is_mutex);
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+/*
+ * idm_so_tgt_svc_offline
+ *
+ * Stop listening on the IP address and port identified by idm_svc_t.
+ */
+static void
+idm_so_tgt_svc_offline(idm_svc_t *is)
+{
+ idm_so_svc_t *so_svc;
+
+ mutex_enter(&is->is_mutex);
+ so_svc = (idm_so_svc_t *)is->is_so_svc;
+ so_svc->is_thread_running = B_FALSE;
+ mutex_exit(&is->is_mutex);
+
+ /*
+ * When called from the kernel, soaccept blocks and cannot be woken
+ * up via the sockfs API. soclose does not work like you would
+ * hope. When the Volo project is available we can switch to that
+ * API which should address this issue. For now, we will poke at
+ * the socket to wake it up.
+ */
+ mutex_enter(&so_svc->is_so->so_lock);
+ so_svc->is_so->so_error = EINTR;
+ cv_signal(&so_svc->is_so->so_connind_cv);
+ mutex_exit(&so_svc->is_so->so_lock);
+
+ /*
+ * Now we expect the port watcher thread to terminate
+ */
+ thread_join(so_svc->is_thread_did);
+
+ /*
+ * Teardown socket
+ */
+ idm_sodestroy(so_svc->is_so);
+}
+
+/*
+ * Watch thread for target service connection establishment.
+ */
+void
+idm_so_svc_port_watcher(void *arg)
+{
+ idm_svc_t *svc = arg;
+ struct sonode *new_so;
+ idm_conn_t *ic;
+ idm_status_t idmrc;
+ idm_so_svc_t *so_svc;
+ int rc;
+ const uint32_t off = 0;
+
+ mutex_enter(&svc->is_mutex);
+
+ so_svc = svc->is_so_svc;
+ so_svc->is_thread_running = B_TRUE;
+ so_svc->is_thread_did = so_svc->is_thread->t_did;
+
+ cv_signal(&svc->is_cv);
+
+ IDM_SVC_LOG(CE_NOTE, "iSCSI service (%p/%d) online", (void *)svc,
+ svc->is_svc_req.sr_port);
+
+ while (so_svc->is_thread_running) {
+ mutex_exit(&svc->is_mutex);
+
+ if ((rc = soaccept(so_svc->is_so, 0, &new_so)) != 0) {
+ mutex_enter(&svc->is_mutex);
+ if (rc == ECONNABORTED)
+ continue;
+ /* Connection problem */
+ break;
+ }
+ /*
+ * Turn off SO_MAC_EXEMPT so future sobinds succeed
+ */
+ (void) sosetsockopt(new_so, SOL_SOCKET, SO_MAC_EXEMPT,
+ (char *)&off, sizeof (off));
+
+ idmrc = idm_svc_conn_create(svc, IDM_TRANSPORT_TYPE_SOCKETS,
+ &ic);
+ if (idmrc != IDM_STATUS_SUCCESS) {
+ /* Drop connection */
+ idm_soshutdown(new_so);
+ idm_sodestroy(new_so);
+ mutex_enter(&svc->is_mutex);
+ continue;
+ }
+
+ idmrc = idm_so_tgt_conn_create(ic, new_so);
+ if (idmrc != IDM_STATUS_SUCCESS) {
+ idm_svc_conn_destroy(ic);
+ idm_soshutdown(new_so);
+ idm_sodestroy(new_so);
+ mutex_enter(&svc->is_mutex);
+ continue;
+ }
+
+ /*
+ * Kick the state machine. At CS_S3_XPT_UP the state machine
+ * will notify the client (target) about the new connection.
+ */
+ idm_conn_event(ic, CE_CONNECT_ACCEPT, NULL);
+
+ mutex_enter(&svc->is_mutex);
+ }
+
+ so_svc->is_thread_running = B_FALSE;
+ mutex_exit(&svc->is_mutex);
+
+ IDM_SVC_LOG(CE_NOTE, "iSCSI service (%p/%d) offline", (void *)svc,
+ svc->is_svc_req.sr_port);
+
+ thread_exit();
+}
+
+/*
+ * idm_so_free_task_rsrc() stops any ongoing processing of the task and
+ * frees resources associated with the task.
+ *
+ * It's not clear that this should return idm_status_t. What do we do
+ * if it fails?
+ */
+static idm_status_t
+idm_so_free_task_rsrc(idm_task_t *idt)
+{
+ idm_buf_t *idb;
+
+ /*
+ * If this is a target connection, call idm_buf_rx_from_ini_done for
+ * any buffer on the "outbufv" list with idb->idb_in_transport==B_TRUE.
+ *
+ * In addition, remove any buffers associated with this task from
+ * the ic_tx_list. We'll do this by walking the idt_inbufv list, but
+ * items don't actually get removed from that list (and completion
+ * routines called) until idm_task_cleanup.
+ */
+ mutex_enter(&idt->idt_mutex);
+
+ for (idb = list_head(&idt->idt_outbufv); idb != NULL;
+ idb = list_next(&idt->idt_outbufv, idb)) {
+ if (idb->idb_in_transport) {
+ /*
+ * idm_buf_rx_from_ini_done releases idt->idt_mutex
+ */
+ idm_buf_rx_from_ini_done(idt, idb, IDM_STATUS_ABORTED);
+ mutex_enter(&idt->idt_mutex);
+ }
+ }
+
+ for (idb = list_head(&idt->idt_inbufv); idb != NULL;
+ idb = list_next(&idt->idt_inbufv, idb)) {
+ /*
+ * We want to remove these items from the tx_list as well,
+ * but knowing it's in the idt_inbufv list is not a guarantee
+ * that it's in the tx_list. If it's on the tx list then
+ * let idm_sotx_thread() clean it up.
+ */
+ if (idb->idb_in_transport && !idb->idb_tx_thread) {
+ /*
+ * idm_buf_tx_to_ini_done releases idt->idt_mutex
+ */
+ idm_buf_tx_to_ini_done(idt, idb, IDM_STATUS_ABORTED);
+ mutex_enter(&idt->idt_mutex);
+ }
+ }
+
+ mutex_exit(&idt->idt_mutex);
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+/*
+ * idm_so_negotiate_key_values() validates the key values for this connection
+ */
+/* ARGSUSED */
+static kv_status_t
+idm_so_negotiate_key_values(idm_conn_t *it, nvlist_t *request_nvl,
+ nvlist_t *response_nvl, nvlist_t *negotiated_nvl)
+{
+ /* All parameters are negotiated at the iscsit level */
+ return (KV_HANDLED);
+}
+
+/*
+ * idm_so_notice_key_values() activates the negotiated key values for
+ * this connection.
+ */
+static idm_status_t
+idm_so_notice_key_values(idm_conn_t *it, nvlist_t *negotiated_nvl)
+{
+ char *nvp_name;
+ nvpair_t *nvp;
+ nvpair_t *next_nvp;
+ int nvrc;
+ idm_status_t idm_status;
+ const idm_kv_xlate_t *ikvx;
+
+ for (nvp = nvlist_next_nvpair(negotiated_nvl, NULL);
+ nvp != NULL; nvp = next_nvp) {
+ next_nvp = nvlist_next_nvpair(negotiated_nvl, nvp);
+ nvp_name = nvpair_name(nvp);
+
+ ikvx = idm_lookup_kv_xlate(nvp_name, strlen(nvp_name));
+ switch (ikvx->ik_key_id) {
+ case KI_HEADER_DIGEST:
+ case KI_DATA_DIGEST:
+ idm_status = idm_so_handle_digest(it, nvp, ikvx);
+ ASSERT(idm_status == 0);
+
+ /* Remove processed item from negotiated_nvl list */
+ nvrc = nvlist_remove_all(
+ negotiated_nvl, ikvx->ik_key_name);
+ ASSERT(nvrc == 0);
+ break;
+ default:
+ break;
+ }
+ }
+ return (IDM_STATUS_SUCCESS);
+}
+
+
+static idm_status_t
+idm_so_handle_digest(idm_conn_t *it, nvpair_t *digest_choice,
+ const idm_kv_xlate_t *ikvx)
+{
+ int nvrc;
+ char *digest_choice_string;
+
+ nvrc = nvpair_value_string(digest_choice,
+ &digest_choice_string);
+ ASSERT(nvrc == 0);
+ if (strcasecmp(digest_choice_string, "crc32c") == 0) {
+ switch (ikvx->ik_key_id) {
+ case KI_HEADER_DIGEST:
+ it->ic_conn_flags |= IDM_CONN_HEADER_DIGEST;
+ break;
+ case KI_DATA_DIGEST:
+ it->ic_conn_flags |= IDM_CONN_DATA_DIGEST;
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+ } else if (strcasecmp(digest_choice_string, "none") == 0) {
+ switch (ikvx->ik_key_id) {
+ case KI_HEADER_DIGEST:
+ it->ic_conn_flags &= ~IDM_CONN_HEADER_DIGEST;
+ break;
+ case KI_DATA_DIGEST:
+ it->ic_conn_flags &= ~IDM_CONN_DATA_DIGEST;
+ break;
+ default:
+ ASSERT(0);
+ break;
+ }
+ } else {
+ ASSERT(0);
+ }
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+
+/*
+ * idm_so_conn_is_capable() verifies that the passed connection is provided
+ * for by the sockets interface.
+ */
+/* ARGSUSED */
+static boolean_t
+idm_so_conn_is_capable(idm_conn_req_t *ic, idm_transport_caps_t *caps)
+{
+ return (B_TRUE);
+}
+
+/*
+ * idm_so_rx_datain() validates the Data Sequence number of the PDU. The
+ * idm_sorecv_scsidata() function invoked earlier actually reads the data
+ * off the socket into the appropriate buffers.
+ */
+static void
+idm_so_rx_datain(idm_conn_t *ic, idm_pdu_t *pdu)
+{
+ iscsi_data_hdr_t *bhs;
+ idm_task_t *idt;
+ idm_buf_t *idb;
+ uint32_t datasn;
+ size_t offset;
+ iscsi_hdr_t *ihp = (iscsi_hdr_t *)pdu->isp_hdr;
+ iscsi_data_rsp_hdr_t *idrhp = (iscsi_data_rsp_hdr_t *)ihp;
+
+ ASSERT(ic != NULL);
+ ASSERT(pdu != NULL);
+
+ bhs = (iscsi_data_hdr_t *)pdu->isp_hdr;
+ datasn = ntohl(bhs->datasn);
+ offset = ntohl(bhs->offset);
+
+ ASSERT(bhs->opcode == ISCSI_OP_SCSI_DATA_RSP);
+
+ /*
+ * Look up the task corresponding to the initiator task tag
+ * to get the buffers affiliated with the task.
+ */
+ idt = idm_task_find(ic, bhs->itt, bhs->ttt);
+ if (idt == NULL) {
+ IDM_CONN_LOG(CE_WARN, "idm_so_rx_datain: failed to find task");
+ idm_pdu_rx_protocol_error(ic, pdu);
+ return;
+ }
+
+ idb = pdu->isp_sorx_buf;
+ if (idb == NULL) {
+ IDM_CONN_LOG(CE_WARN,
+ "idm_so_rx_datain: failed to find buffer");
+ idm_task_rele(idt);
+ idm_pdu_rx_protocol_error(ic, pdu);
+ return;
+ }
+
+ /*
+ * DataSN values should be sequential and should not have any gaps or
+ * repetitions. Check the DataSN with the one stored in the task.
+ */
+ if (datasn == idt->idt_exp_datasn) {
+ idt->idt_exp_datasn++; /* keep track of DataSN received */
+ } else {
+ IDM_CONN_LOG(CE_WARN, "idm_so_rx_datain: datasn out of order");
+ idm_task_rele(idt);
+ idm_pdu_rx_protocol_error(ic, pdu);
+ return;
+ }
+ idm_task_rele(idt);
+
+ /*
+ * PDUs in a sequence should be in continuously increasing
+ * address offset
+ */
+ if (offset != idb->idb_exp_offset) {
+ IDM_CONN_LOG(CE_WARN, "idm_so_rx_datain: unexpected offset");
+ idm_pdu_rx_protocol_error(ic, pdu);
+ return;
+ }
+ /* Expected next relative buffer offset */
+ idb->idb_exp_offset += n2h24(bhs->dlength);
+
+ /*
+ * For now call scsi_rsp which will process the data rsp
+ * Revisit, need to provide an explicit client entry point for
+ * phase collapse completions.
+ */
+ if (((ihp->opcode & ISCSI_OPCODE_MASK) == ISCSI_OP_SCSI_DATA_RSP) &&
+ (idrhp->flags & ISCSI_FLAG_DATA_STATUS)) {
+ (*ic->ic_conn_ops.icb_rx_scsi_rsp)(ic, pdu);
+ }
+
+ idm_pdu_complete(pdu, IDM_STATUS_SUCCESS);
+}
+
+/*
+ * The idm_so_rx_dataout() function is used by the iSCSI target to read
+ * data from the Data-Out PDU sent by the iSCSI initiator.
+ *
+ * This function gets the Initiator Task Tag from the PDU BHS and looks up the
+ * task to get the buffers associated with the PDU. A PDU might span buffers.
+ * The data is then read into the respective buffer.
+ */
+static void
+idm_so_rx_dataout(idm_conn_t *ic, idm_pdu_t *pdu)
+{
+
+ iscsi_data_hdr_t *bhs;
+ idm_task_t *idt;
+ idm_buf_t *idb;
+ size_t offset;
+
+ ASSERT(ic != NULL);
+ ASSERT(pdu != NULL);
+
+ bhs = (iscsi_data_hdr_t *)pdu->isp_hdr;
+ offset = ntohl(bhs->offset);
+ ASSERT(bhs->opcode == ISCSI_OP_SCSI_DATA);
+
+ /*
+ * Look up the task corresponding to the initiator task tag
+ * to get the buffers affiliated with the task.
+ */
+ idt = idm_task_find(ic, bhs->itt, bhs->ttt);
+ if (idt == NULL) {
+ IDM_CONN_LOG(CE_WARN,
+ "idm_so_rx_dataout: failed to find task");
+ idm_pdu_rx_protocol_error(ic, pdu);
+ return;
+ }
+
+ idb = pdu->isp_sorx_buf;
+ if (idb == NULL) {
+ IDM_CONN_LOG(CE_WARN,
+ "idm_so_rx_dataout: failed to find buffer");
+ idm_task_rele(idt);
+ idm_pdu_rx_protocol_error(ic, pdu);
+ return;
+ }
+
+ /* Keep track of data transferred - check data offsets */
+ if (offset != idb->idb_exp_offset) {
+ IDM_CONN_LOG(CE_NOTE, "idm_so_rx_dataout: offset out of seq: "
+ "%ld, %d", offset, idb->idb_exp_offset);
+ idm_task_rele(idt);
+ idm_pdu_rx_protocol_error(ic, pdu);
+ return;
+ }
+ /* Expected next relative offset */
+ idb->idb_exp_offset += ntoh24(bhs->dlength);
+
+ /*
+ * Call the buffer callback when the transfer is complete
+ *
+ * The connection state machine should only abort tasks after
+ * shutting down the connection so we are assured that there
+ * won't be a simultaneous attempt to abort this task at the
+ * same time as we are processing this PDU (due to a connection
+ * state change).
+ */
+ if (bhs->flags & ISCSI_FLAG_FINAL) {
+ /*
+ * We only want to call idm_buf_rx_from_ini_done once
+ * per transfer. It's possible that this task has
+ * already been aborted in which case
+ * idm_so_free_task_rsrc will call idm_buf_rx_from_ini_done
+ * for each buffer with idb_in_transport==B_TRUE. To
+ * close this window and ensure that this doesn't happen,
+ * we'll clear idb->idb_in_transport now while holding
+ * the task mutex. This is only really an issue for
+ * SCSI task abort -- if tasks were being aborted because
+ * of a connection state change the state machine would
+ * have already stopped the receive thread.
+ */
+ mutex_enter(&idt->idt_mutex);
+
+ /*
+ * Release the task hold here (obtained in idm_task_find)
+ * because the task may complete synchronously during
+ * idm_buf_rx_from_ini_done. Since we still have an active
+ * buffer we know there is at least one additional hold on idt.
+ */
+ idm_task_rele(idt);
+
+ /*
+ * idm_buf_rx_from_ini_done releases idt->idt_mutex
+ */
+ idm_buf_rx_from_ini_done(idt, idb, IDM_STATUS_SUCCESS);
+ idm_pdu_complete(pdu, IDM_STATUS_SUCCESS);
+ return;
+ }
+
+ idm_task_rele(idt);
+ idm_pdu_complete(pdu, IDM_STATUS_SUCCESS);
+}
+
+/*
+ * The idm_so_rx_rtt() function is used by the iSCSI initiator to handle
+ * the R2T PDU sent by the iSCSI target indicating that it is ready to
+ * accept data. This gets the Initiator Task Tag (itt) from the PDU BHS
+ * and looks up the task in the task tree using the itt to get the output
+ * buffers associated the task. The R2T PDU contains the offset of the
+ * requested data and the data length. This function then constructs a
+ * sequence of iSCSI PDUs and outputs the requested data. Each Data-Out
+ * PDU is associated with the R2T by the Target Transfer Tag (ttt).
+ */
+static void
+idm_so_rx_rtt(idm_conn_t *ic, idm_pdu_t *pdu)
+{
+ idm_task_t *idt;
+ idm_buf_t *idb;
+ iscsi_rtt_hdr_t *rtt_hdr;
+ uint32_t data_offset;
+
+ ASSERT(ic != NULL);
+ ASSERT(pdu != NULL);
+
+ rtt_hdr = (iscsi_rtt_hdr_t *)pdu->isp_hdr;
+ data_offset = ntohl(rtt_hdr->data_offset);
+
+ idt = idm_task_find(ic, rtt_hdr->itt, rtt_hdr->ttt);
+
+ if (idt == NULL) {
+ IDM_CONN_LOG(CE_WARN, "idm_so_rx_rtt: could not find task");
+ idm_pdu_rx_protocol_error(ic, pdu);
+ return;
+ }
+
+ /* Find the buffer bound to the task by the iSCSI initiator */
+ mutex_enter(&idt->idt_mutex);
+ idb = idm_buf_find(&idt->idt_outbufv, data_offset);
+ idt->idt_r2t_ttt = rtt_hdr->ttt;
+ /* reset to zero */
+ idt->idt_exp_datasn = 0;
+ if (idb == NULL) {
+ mutex_exit(&idt->idt_mutex);
+ idm_task_rele(idt);
+ IDM_CONN_LOG(CE_WARN, "idm_so_rx_rtt: could not find buffer");
+ idm_pdu_rx_protocol_error(ic, pdu);
+ return;
+ }
+
+ (void) idm_so_send_buf_region(idt, ISCSI_OP_SCSI_DATA, idb,
+ data_offset, ntohl(rtt_hdr->data_length));
+ mutex_exit(&idt->idt_mutex);
+
+ idm_pdu_complete(pdu, IDM_STATUS_SUCCESS);
+ idm_task_rele(idt);
+
+}
+
+idm_status_t
+idm_sorecvdata(idm_conn_t *ic, idm_pdu_t *pdu)
+{
+ uint8_t pad[ISCSI_PAD_WORD_LEN];
+ int pad_len;
+ uint32_t data_digest_crc;
+ uint32_t crc_calculated;
+ int total_len;
+ idm_so_conn_t *so_conn;
+
+ so_conn = ic->ic_transport_private;
+
+ pad_len = ((ISCSI_PAD_WORD_LEN -
+ (pdu->isp_datalen & (ISCSI_PAD_WORD_LEN - 1))) &
+ (ISCSI_PAD_WORD_LEN - 1));
+
+ ASSERT(pdu->isp_iovlen < (PDU_MAX_IOVLEN - 2)); /* pad + data digest */
+
+ total_len = pdu->isp_datalen;
+
+ if (pad_len) {
+ pdu->isp_iov[pdu->isp_iovlen].iov_base = (char *)&pad;
+ pdu->isp_iov[pdu->isp_iovlen].iov_len = pad_len;
+ total_len += pad_len;
+ pdu->isp_iovlen++;
+ }
+
+ /* setup data digest */
+ if ((ic->ic_conn_flags & IDM_CONN_DATA_DIGEST) != 0) {
+ pdu->isp_iov[pdu->isp_iovlen].iov_base =
+ (char *)&data_digest_crc;
+ pdu->isp_iov[pdu->isp_iovlen].iov_len =
+ sizeof (data_digest_crc);
+ total_len += sizeof (data_digest_crc);
+ pdu->isp_iovlen++;
+ }
+
+ if (idm_iov_sorecv(so_conn->ic_so, &pdu->isp_iov[0],
+ pdu->isp_iovlen, total_len) != 0) {
+ return (IDM_STATUS_IO);
+ }
+
+ if ((ic->ic_conn_flags & IDM_CONN_DATA_DIGEST) != 0) {
+ crc_calculated = idm_crc32c(pdu->isp_data,
+ pdu->isp_datalen);
+ if (pad_len) {
+ crc_calculated = idm_crc32c_continued((char *)&pad,
+ pad_len, crc_calculated);
+ }
+ if (crc_calculated != data_digest_crc) {
+ IDM_CONN_LOG(CE_WARN,
+ "idm_sorecvdata: "
+ "CRC error: actual 0x%x, calc 0x%x",
+ data_digest_crc, crc_calculated);
+
+ /* Invalid Data Digest */
+ return (IDM_STATUS_DATA_DIGEST);
+ }
+ }
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+/*
+ * idm_sorecv_scsidata() is used to receive scsi data from the socket. The
+ * Data-type PDU header must be read into the idm_pdu_t structure prior to
+ * calling this function.
+ */
+idm_status_t
+idm_sorecv_scsidata(idm_conn_t *ic, idm_pdu_t *pdu)
+{
+ iscsi_data_hdr_t *bhs;
+ idm_task_t *task;
+ uint32_t offset;
+ uint8_t opcode;
+ uint32_t dlength;
+ list_t *buflst;
+ uint32_t xfer_bytes;
+ idm_status_t status;
+
+ ASSERT(ic != NULL);
+ ASSERT(pdu != NULL);
+
+ bhs = (iscsi_data_hdr_t *)pdu->isp_hdr;
+
+ offset = ntohl(bhs->offset);
+ opcode = bhs->opcode;
+ dlength = n2h24(bhs->dlength);
+
+ ASSERT((opcode == ISCSI_OP_SCSI_DATA_RSP) ||
+ (opcode == ISCSI_OP_SCSI_DATA));
+
+ /*
+ * Successful lookup implicitly gets a "hold" on the task. This
+ * hold must be released before leaving this function. At one
+ * point we were caching this task context and retaining the hold
+ * but it turned out to be very difficult to release the hold properly.
+ * The task can be aborted and the connection shutdown between this
+ * call and the subsequent expected call to idm_so_rx_datain/
+ * idm_so_rx_dataout (in which case those functions are not called).
+ * Releasing the hold in the PDU callback doesn't work well either
+ * because the whole task may be completed by then at which point
+ * it is too late to release the hold -- for better or worse this
+ * code doesn't wait on the refcnts during normal operation.
+ * idm_task_find() is very fast and it is not a huge burden if we
+ * have to do it twice.
+ */
+ task = idm_task_find(ic, bhs->itt, bhs->ttt);
+ if (task == NULL) {
+ IDM_CONN_LOG(CE_WARN,
+ "idm_sorecv_scsidata: could not find task");
+ return (IDM_STATUS_FAIL);
+ }
+
+ mutex_enter(&task->idt_mutex);
+ buflst = (opcode == ISCSI_OP_SCSI_DATA_RSP) ?
+ &task->idt_inbufv : &task->idt_outbufv;
+ pdu->isp_sorx_buf = idm_buf_find(buflst, offset);
+ mutex_exit(&task->idt_mutex);
+
+ if (pdu->isp_sorx_buf == NULL) {
+ idm_task_rele(task);
+ IDM_CONN_LOG(CE_WARN, "idm_sorecv_scsidata: could not find "
+ "buffer for offset %x opcode=%x",
+ offset, opcode);
+ return (IDM_STATUS_FAIL);
+ }
+
+ xfer_bytes = idm_fill_iov(pdu, pdu->isp_sorx_buf, offset, dlength);
+ ASSERT(xfer_bytes != 0);
+ if (xfer_bytes != dlength) {
+ idm_task_rele(task);
+ /*
+ * Buffer overflow, connection error. The PDU data is still
+ * sitting in the socket so we can't use the connection
+ * again until that data is drained.
+ */
+ return (IDM_STATUS_FAIL);
+ }
+
+ status = idm_sorecvdata(ic, pdu);
+
+ idm_task_rele(task);
+
+ return (status);
+}
+
+static uint32_t
+idm_fill_iov(idm_pdu_t *pdu, idm_buf_t *idb, uint32_t ro, uint32_t dlength)
+{
+ uint32_t buf_ro = ro - idb->idb_bufoffset;
+ uint32_t xfer_len = min(dlength, idb->idb_buflen - buf_ro);
+
+ ASSERT(ro >= idb->idb_bufoffset);
+
+ pdu->isp_iov[pdu->isp_iovlen].iov_base =
+ (caddr_t)idb->idb_buf + buf_ro;
+ pdu->isp_iov[pdu->isp_iovlen].iov_len = xfer_len;
+ pdu->isp_iovlen++;
+
+ return (xfer_len);
+}
+
+int
+idm_sorecv_nonscsidata(idm_conn_t *ic, idm_pdu_t *pdu)
+{
+ pdu->isp_data = kmem_alloc(pdu->isp_datalen, KM_SLEEP);
+ ASSERT(pdu->isp_data != NULL);
+
+ pdu->isp_databuflen = pdu->isp_datalen;
+ pdu->isp_iov[0].iov_base = (caddr_t)pdu->isp_data;
+ pdu->isp_iov[0].iov_len = pdu->isp_datalen;
+ pdu->isp_iovlen = 1;
+ /*
+ * Since we are associating a new data buffer with this received
+ * PDU we need to set a specific callback to free the data
+ * after the PDU is processed.
+ */
+ pdu->isp_flags |= IDM_PDU_ADDL_DATA;
+ pdu->isp_callback = idm_sorx_addl_pdu_cb;
+
+ return (idm_sorecvdata(ic, pdu));
+}
+
+void
+idm_sorx_thread(void *arg)
+{
+ boolean_t conn_failure = B_FALSE;
+ idm_conn_t *ic = (idm_conn_t *)arg;
+ idm_so_conn_t *so_conn;
+ idm_pdu_t *pdu;
+ idm_status_t rc;
+
+ idm_conn_hold(ic);
+
+ mutex_enter(&ic->ic_mutex);
+
+ so_conn = ic->ic_transport_private;
+ so_conn->ic_rx_thread_running = B_TRUE;
+ so_conn->ic_rx_thread_did = so_conn->ic_rx_thread->t_did;
+ cv_signal(&ic->ic_cv);
+
+ while (so_conn->ic_rx_thread_running) {
+ mutex_exit(&ic->ic_mutex);
+
+ /*
+ * Get PDU with default header size (large enough for
+ * BHS plus any anticipated AHS). PDU from
+ * the cache will have all values set correctly
+ * for sockets RX including callback.
+ */
+ pdu = kmem_cache_alloc(idm.idm_sorx_pdu_cache, KM_SLEEP);
+ pdu->isp_ic = ic;
+ pdu->isp_flags = 0;
+ pdu->isp_transport_hdrlen = 0;
+
+ if ((rc = idm_sorecvhdr(ic, pdu)) != 0) {
+ /*
+ * Call idm_pdu_complete so that we call the callback
+ * and ensure any memory allocated in idm_sorecvhdr
+ * gets freed up.
+ */
+ idm_pdu_complete(pdu, IDM_STATUS_FAIL);
+
+ /*
+ * If ic_rx_thread_running is still set then
+ * this is some kind of connection problem
+ * on the socket. In this case we want to
+ * generate an event. Otherwise some other
+ * thread closed the socket due to another
+ * issue in which case we don't need to
+ * generate an event.
+ */
+ mutex_enter(&ic->ic_mutex);
+ if (so_conn->ic_rx_thread_running) {
+ conn_failure = B_TRUE;
+ so_conn->ic_rx_thread_running = B_FALSE;
+ }
+
+ continue;
+ }
+
+ /*
+ * Header has been read and validated. Now we need
+ * to read the PDU data payload (if present). SCSI data
+ * need to be transferred from the socket directly into
+ * the associated transfer buffer for the SCSI task.
+ */
+ if (pdu->isp_datalen != 0) {
+ if ((IDM_PDU_OPCODE(pdu) == ISCSI_OP_SCSI_DATA) ||
+ (IDM_PDU_OPCODE(pdu) == ISCSI_OP_SCSI_DATA_RSP)) {
+ rc = idm_sorecv_scsidata(ic, pdu);
+ /*
+ * All SCSI errors are fatal to the
+ * connection right now since we have no
+ * place to put the data. What we need
+ * is some kind of sink to dispose of unwanted
+ * SCSI data. For example an invalid task tag
+ * should not kill the connection (although
+ * we may want to drop the connection).
+ */
+ } else {
+ /*
+ * Not data PDUs so allocate a buffer for the
+ * data segment and read the remaining data.
+ */
+ rc = idm_sorecv_nonscsidata(ic, pdu);
+ }
+ if (rc != 0) {
+ /*
+ * Call idm_pdu_complete so that we call the
+ * callback and ensure any memory allocated
+ * in idm_sorecvhdr gets freed up.
+ */
+ idm_pdu_complete(pdu, IDM_STATUS_FAIL);
+
+ /*
+ * If ic_rx_thread_running is still set then
+ * this is some kind of connection problem
+ * on the socket. In this case we want to
+ * generate an event. Otherwise some other
+ * thread closed the socket due to another
+ * issue in which case we don't need to
+ * generate an event.
+ */
+ mutex_enter(&ic->ic_mutex);
+ if (so_conn->ic_rx_thread_running) {
+ conn_failure = B_TRUE;
+ so_conn->ic_rx_thread_running = B_FALSE;
+ }
+ continue;
+ }
+ }
+
+ /*
+ * Process RX PDU
+ */
+ idm_pdu_rx(ic, pdu);
+
+ mutex_enter(&ic->ic_mutex);
+ }
+
+ mutex_exit(&ic->ic_mutex);
+
+ /*
+ * If we dropped out of the RX processing loop because of
+ * a socket problem or other connection failure (including
+ * digest errors) then we need to generate a state machine
+ * event to shut the connection down.
+ * If the state machine is already in, for example, INIT_ERROR, this
+ * event will get dropped, and the TX thread will never be notified
+ * to shut down. To be safe, we'll just notify it here.
+ */
+ if (conn_failure) {
+ if (so_conn->ic_tx_thread_running) {
+ so_conn->ic_tx_thread_running = B_FALSE;
+ mutex_enter(&so_conn->ic_tx_mutex);
+ cv_signal(&so_conn->ic_tx_cv);
+ mutex_exit(&so_conn->ic_tx_mutex);
+ }
+
+ idm_conn_event(ic, CE_TRANSPORT_FAIL, rc);
+ }
+
+ idm_conn_rele(ic);
+
+ thread_exit();
+}
+
+/*
+ * idm_so_tx
+ *
+ * This is the implementation of idm_transport_ops_t's it_tx_pdu entry
+ * point. By definition, it is supposed to be fast. So, simply queue
+ * the entry and return. The real work is done by idm_i_so_tx() via
+ * idm_sotx_thread().
+ */
+
+static void
+idm_so_tx(idm_conn_t *ic, idm_pdu_t *pdu)
+{
+ idm_so_conn_t *so_conn = ic->ic_transport_private;
+
+ ASSERT(pdu->isp_ic == ic);
+ mutex_enter(&so_conn->ic_tx_mutex);
+
+ if (!so_conn->ic_tx_thread_running) {
+ mutex_exit(&so_conn->ic_tx_mutex);
+ idm_pdu_complete(pdu, IDM_STATUS_ABORTED);
+ return;
+ }
+
+ list_insert_tail(&so_conn->ic_tx_list, (void *)pdu);
+ cv_signal(&so_conn->ic_tx_cv);
+ mutex_exit(&so_conn->ic_tx_mutex);
+}
+
+static idm_status_t
+idm_i_so_tx(idm_pdu_t *pdu)
+{
+ idm_conn_t *ic = pdu->isp_ic;
+ idm_status_t status = IDM_STATUS_SUCCESS;
+ uint8_t pad[ISCSI_PAD_WORD_LEN];
+ int pad_len;
+ uint32_t hdr_digest_crc;
+ uint32_t data_digest_crc = 0;
+ int total_len = 0;
+ int iovlen = 0;
+ struct iovec iov[6];
+ idm_so_conn_t *so_conn;
+
+ so_conn = ic->ic_transport_private;
+
+ /* Setup BHS */
+ iov[iovlen].iov_base = (caddr_t)pdu->isp_hdr;
+ iov[iovlen].iov_len = pdu->isp_hdrlen;
+ total_len += iov[iovlen].iov_len;
+ iovlen++;
+
+ /* Setup header digest */
+ if (((pdu->isp_flags & IDM_PDU_LOGIN_TX) == 0) &&
+ (ic->ic_conn_flags & IDM_CONN_HEADER_DIGEST)) {
+ hdr_digest_crc = idm_crc32c(pdu->isp_hdr, pdu->isp_hdrlen);
+
+ iov[iovlen].iov_base = (caddr_t)&hdr_digest_crc;
+ iov[iovlen].iov_len = sizeof (hdr_digest_crc);
+ total_len += iov[iovlen].iov_len;
+ iovlen++;
+ }
+
+ /* Setup the data */
+ if (pdu->isp_datalen) {
+ idm_task_t *idt;
+ idm_buf_t *idb;
+ iscsi_data_hdr_t *ihp;
+ ihp = (iscsi_data_hdr_t *)pdu->isp_hdr;
+ /* Write of immediate data */
+ if (ic->ic_ffp &&
+ (ihp->opcode == ISCSI_OP_SCSI_CMD ||
+ ihp->opcode == ISCSI_OP_SCSI_DATA)) {
+ idt = idm_task_find(ic, ihp->itt, ihp->ttt);
+ if (idt) {
+ mutex_enter(&idt->idt_mutex);
+ idb = idm_buf_find(&idt->idt_outbufv, 0);
+ mutex_exit(&idt->idt_mutex);
+ idb->idb_xfer_len += pdu->isp_datalen;
+ }
+ }
+
+ iov[iovlen].iov_base = (caddr_t)pdu->isp_data;
+ iov[iovlen].iov_len = pdu->isp_datalen;
+ total_len += iov[iovlen].iov_len;
+ iovlen++;
+ }
+
+ /* Setup the data pad if necessary */
+ pad_len = ((ISCSI_PAD_WORD_LEN -
+ (pdu->isp_datalen & (ISCSI_PAD_WORD_LEN - 1))) &
+ (ISCSI_PAD_WORD_LEN - 1));
+
+ if (pad_len) {
+ bzero(pad, sizeof (pad));
+ iov[iovlen].iov_base = (void *)&pad;
+ iov[iovlen].iov_len = pad_len;
+ total_len += iov[iovlen].iov_len;
+ iovlen++;
+ }
+
+ /*
+ * Setup the data digest if enabled. Data-digest is not sent
+ * for login-phase PDUs.
+ */
+ if ((ic->ic_conn_flags & IDM_CONN_DATA_DIGEST) &&
+ ((pdu->isp_flags & IDM_PDU_LOGIN_TX) == 0) &&
+ (pdu->isp_datalen || pad_len)) {
+ /*
+ * RFC3720/10.2.3: A zero-length Data Segment also
+ * implies a zero-length data digest.
+ */
+ if (pdu->isp_datalen) {
+ data_digest_crc = idm_crc32c(pdu->isp_data,
+ pdu->isp_datalen);
+ }
+ if (pad_len) {
+ data_digest_crc = idm_crc32c_continued(&pad,
+ pad_len, data_digest_crc);
+ }
+
+ iov[iovlen].iov_base = (caddr_t)&data_digest_crc;
+ iov[iovlen].iov_len = sizeof (data_digest_crc);
+ total_len += iov[iovlen].iov_len;
+ iovlen++;
+ }
+
+ /* Transmit the PDU */
+ if (idm_iov_sosend(so_conn->ic_so, &iov[0], iovlen,
+ total_len) != 0) {
+ /* Set error status */
+ IDM_CONN_LOG(CE_WARN,
+ "idm_so_tx: failed to transmit the PDU, so: %p ic: %p "
+ "data: %p", (void *) so_conn->ic_so, (void *) ic,
+ (void *) pdu->isp_data);
+ status = IDM_STATUS_IO;
+ }
+
+ /*
+ * Success does not mean that the PDU actually reached the
+ * remote node since it could get dropped along the way.
+ */
+ idm_pdu_complete(pdu, status);
+
+ return (status);
+}
+
+/*
+ * The idm_so_buf_tx_to_ini() is used by the target iSCSI layer to transmit the
+ * Data-In PDUs using sockets. Based on the negotiated MaxRecvDataSegmentLength,
+ * the buffer is segmented into a sequence of Data-In PDUs, ordered by DataSN.
+ * A target can invoke this function multiple times for a single read command
+ * (identified by the same ITT) to split the input into several sequences.
+ *
+ * DataSN starts with 0 for the first data PDU of an input command and advances
+ * by 1 for each subsequent data PDU. Each sequence will have its own F bit,
+ * which is set to 1 for the last data PDU of a sequence.
+ *
+ * Scope for Prototype build:
+ * The data PDUs within a sequence will be sent in order with the buffer offset
+ * in increasing order. i.e. initiator and target must have negotiated the
+ * "DataPDUInOrder" to "Yes". The order between sequences is not enforced.
+ *
+ * Caller holds idt->idt_mutex
+ */
+static idm_status_t
+idm_so_buf_tx_to_ini(idm_task_t *idt, idm_buf_t *idb)
+{
+ idm_so_conn_t *so_conn = idb->idb_ic->ic_transport_private;
+ idm_pdu_t tmppdu;
+
+ ASSERT(mutex_owned(&idt->idt_mutex));
+
+ /*
+ * Put the idm_buf_t on the tx queue. It will be transmitted by
+ * idm_sotx_thread.
+ */
+ mutex_enter(&so_conn->ic_tx_mutex);
+
+ if (!so_conn->ic_tx_thread_running) {
+ mutex_exit(&so_conn->ic_tx_mutex);
+ /*
+ * Don't release idt->idt_mutex since we're supposed to hold
+ * in when calling idm_buf_tx_to_ini_done
+ */
+ idm_buf_tx_to_ini_done(idt, idb, IDM_STATUS_ABORTED);
+ return (IDM_STATUS_FAIL);
+ }
+
+ /*
+ * Build a template for the data PDU headers we will use so that
+ * the SN values will stay consistent with other PDU's we are
+ * transmitting like R2T and SCSI status.
+ */
+ bzero(&idb->idb_data_hdr_tmpl, sizeof (iscsi_hdr_t));
+ tmppdu.isp_hdr = &idb->idb_data_hdr_tmpl;
+ (*idt->idt_ic->ic_conn_ops.icb_build_hdr)(idt, &tmppdu,
+ ISCSI_OP_SCSI_DATA_RSP);
+ idb->idb_tx_thread = B_TRUE;
+ list_insert_tail(&so_conn->ic_tx_list, (void *)idb);
+ cv_signal(&so_conn->ic_tx_cv);
+ mutex_exit(&so_conn->ic_tx_mutex);
+ mutex_exit(&idt->idt_mutex);
+
+ /*
+ * Returning success here indicates the transfer was successfully
+ * dispatched -- it does not mean that the transfer completed
+ * successfully.
+ */
+ return (IDM_STATUS_SUCCESS);
+}
+
+/*
+ * The idm_so_buf_rx_from_ini() is used by the target iSCSI layer to specify the
+ * data blocks it is ready to receive from the initiator in response to a WRITE
+ * SCSI command. The target iSCSI layer passes the information about the desired
+ * data blocks to the initiator in one R2T PDU. The receiving buffer, the buffer
+ * offset and datalen are passed via the 'idb' argument.
+ *
+ * Scope for Prototype build:
+ * R2Ts are required for any Data-Out PDU, i.e. initiator and target must have
+ * negotiated the "InitialR2T" to "Yes".
+ *
+ * Caller holds idt->idt_mutex
+ */
+static idm_status_t
+idm_so_buf_rx_from_ini(idm_task_t *idt, idm_buf_t *idb)
+{
+ idm_pdu_t *pdu;
+ iscsi_rtt_hdr_t *rtt;
+
+ ASSERT(mutex_owned(&idt->idt_mutex));
+
+ pdu = kmem_cache_alloc(idm.idm_sotx_pdu_cache, KM_SLEEP);
+ pdu->isp_ic = idt->idt_ic;
+ bzero(pdu->isp_hdr, sizeof (iscsi_rtt_hdr_t));
+
+ /* iSCSI layer fills the TTT, ITT, StatSN, ExpCmdSN, MaxCmdSN */
+ (*idt->idt_ic->ic_conn_ops.icb_build_hdr)(idt, pdu, ISCSI_OP_RTT_RSP);
+
+ /* set the rttsn, rtt.flags, rtt.data_offset and rtt.data_length */
+ rtt = (iscsi_rtt_hdr_t *)(pdu->isp_hdr);
+
+ rtt->opcode = ISCSI_OP_RTT_RSP;
+ rtt->flags = ISCSI_FLAG_FINAL;
+ rtt->data_offset = htonl(idb->idb_bufoffset);
+ rtt->data_length = htonl(idb->idb_xfer_len);
+ rtt->rttsn = htonl(idt->idt_exp_rttsn++);
+
+ /* Keep track of buffer offsets */
+ idb->idb_exp_offset = idb->idb_bufoffset;
+ mutex_exit(&idt->idt_mutex);
+
+ /*
+ * Transmit the PDU. Call the internal routine directly as there
+ * is already implicit ordering of the PDU.
+ */
+ (void) idm_i_so_tx(pdu);
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+static idm_status_t
+idm_so_buf_alloc(idm_buf_t *idb, uint64_t buflen)
+{
+ idb->idb_buf = kmem_alloc(buflen, KM_NOSLEEP);
+ if (idb->idb_buf == NULL) {
+ IDM_CONN_LOG(CE_NOTE,
+ "idm_so_buf_alloc: failed buffer allocation");
+ return (IDM_STATUS_FAIL);
+ }
+ return (IDM_STATUS_SUCCESS);
+}
+
+/* ARGSUSED */
+static idm_status_t
+idm_so_buf_setup(idm_buf_t *idb)
+{
+ /* nothing to do here */
+ return (IDM_STATUS_SUCCESS);
+}
+
+/* ARGSUSED */
+static void
+idm_so_buf_teardown(idm_buf_t *idb)
+{
+ /* nothing to do here */
+}
+
+static void
+idm_so_buf_free(idm_buf_t *idb)
+{
+ kmem_free(idb->idb_buf, idb->idb_buflen);
+}
+
+idm_status_t
+idm_so_send_buf_region(idm_task_t *idt, uint8_t opcode, idm_buf_t *idb,
+ uint32_t buf_region_offset, uint32_t buf_region_length)
+{
+ idm_conn_t *ic;
+ uint32_t max_dataseglen;
+ size_t remainder, chunk;
+ uint32_t data_offset = buf_region_offset;
+ iscsi_data_hdr_t *bhs;
+ idm_pdu_t *pdu;
+
+ ASSERT(mutex_owned(&idt->idt_mutex));
+
+ ic = idt->idt_ic;
+
+ max_dataseglen = 8192; /* Need value from login negotiation */
+ remainder = buf_region_length;
+
+ while (remainder) {
+ if (idt->idt_state != TASK_ACTIVE) {
+ ASSERT((idt->idt_state != TASK_IDLE) &&
+ (idt->idt_state != TASK_COMPLETE));
+ return (IDM_STATUS_ABORTED);
+ }
+
+ /* check to see if we need to chunk the data */
+ if (remainder > max_dataseglen) {
+ chunk = max_dataseglen;
+ } else {
+ chunk = remainder;
+ }
+
+ /* Data PDU headers will always be sizeof (iscsi_hdr_t) */
+ pdu = kmem_cache_alloc(idm.idm_sotx_pdu_cache, KM_SLEEP);
+ pdu->isp_ic = ic;
+
+ /*
+ * For target we've already built a build a header template
+ * to use during the transfer. Use this template so that
+ * the SN values stay consistent with any unrelated PDU's
+ * being transmitted.
+ */
+ if (opcode == ISCSI_OP_SCSI_DATA_RSP) {
+ bcopy(&idb->idb_data_hdr_tmpl, pdu->isp_hdr,
+ sizeof (iscsi_hdr_t));
+ } else {
+ /*
+ * OK for now, but we should remove this bzero and
+ * make sure the build_hdr function is initializing the
+ * header properly
+ */
+ bzero(pdu->isp_hdr, sizeof (iscsi_hdr_t));
+
+ /*
+ * setup iscsi data hdr
+ * callback to the iSCSI layer to fill in the BHS
+ * CmdSN, StatSN, ExpCmdSN, MaxCmdSN, TTT, ITT and
+ * opcode
+ */
+ (*ic->ic_conn_ops.icb_build_hdr)(idt, pdu, opcode);
+ }
+
+ /*
+ * Set DataSN, data offset, and flags in BHS
+ * For the prototype build, A = 0, S = 0, U = 0
+ */
+ bhs = (iscsi_data_hdr_t *)(pdu->isp_hdr);
+
+ bhs->datasn = htonl(idt->idt_exp_datasn++);
+
+ hton24(bhs->dlength, chunk);
+ bhs->offset = htonl(idb->idb_bufoffset + data_offset);
+
+ if (chunk == remainder) {
+ bhs->flags = ISCSI_FLAG_FINAL; /* F bit set to 1 */
+ }
+
+ /* setup data */
+ pdu->isp_data = (uint8_t *)idb->idb_buf + data_offset;
+ pdu->isp_datalen = (uint_t)chunk;
+ remainder -= chunk;
+ data_offset += chunk;
+
+ /*
+ * Now that we're done working with idt_exp_datasn,
+ * idt->idt_state and idb->idb_bufoffset we can release
+ * the task lock -- don't want to hold it across the
+ * call to idm_i_so_tx since we could block.
+ */
+ mutex_exit(&idt->idt_mutex);
+
+ /*
+ * Transmit the PDU. Call the internal routine directly
+ * as there is already implicit ordering.
+ */
+ (void) idm_i_so_tx(pdu);
+
+ mutex_enter(&idt->idt_mutex);
+ }
+
+ return (IDM_STATUS_SUCCESS);
+}
+
+/*
+ * TX PDU cache
+ */
+/* ARGSUSED */
+int
+idm_sotx_pdu_constructor(void *hdl, void *arg, int flags)
+{
+ idm_pdu_t *pdu = hdl;
+
+ bzero(pdu, sizeof (idm_pdu_t));
+ pdu->isp_hdr = (iscsi_hdr_t *)(pdu + 1); /* Ptr arithmetic */
+ pdu->isp_hdrlen = sizeof (iscsi_hdr_t);
+ pdu->isp_callback = idm_sotx_cache_pdu_cb;
+ pdu->isp_magic = IDM_PDU_MAGIC;
+ bzero(pdu->isp_hdr, sizeof (iscsi_hdr_t));
+
+ return (0);
+}
+
+/* ARGSUSED */
+void
+idm_sotx_cache_pdu_cb(idm_pdu_t *pdu, idm_status_t status)
+{
+ /* reset values between use */
+ pdu->isp_datalen = 0;
+
+ kmem_cache_free(idm.idm_sotx_pdu_cache, pdu);
+}
+
+/*
+ * RX PDU cache
+ */
+/* ARGSUSED */
+int
+idm_sorx_pdu_constructor(void *hdl, void *arg, int flags)
+{
+ idm_pdu_t *pdu = hdl;
+
+ bzero(pdu, sizeof (idm_pdu_t));
+ pdu->isp_magic = IDM_PDU_MAGIC;
+ pdu->isp_hdr = (iscsi_hdr_t *)(pdu + 1); /* Ptr arithmetic */
+ pdu->isp_callback = idm_sorx_cache_pdu_cb;
+
+ return (0);
+}
+
+/* ARGSUSED */
+static void
+idm_sorx_cache_pdu_cb(idm_pdu_t *pdu, idm_status_t status)
+{
+ pdu->isp_iovlen = 0;
+ pdu->isp_sorx_buf = 0;
+ kmem_cache_free(idm.idm_sorx_pdu_cache, pdu);
+}
+
+static void
+idm_sorx_addl_pdu_cb(idm_pdu_t *pdu, idm_status_t status)
+{
+ /*
+ * We had to modify our cached RX PDU with a longer header buffer
+ * and/or a longer data buffer. Release the new buffers and fix
+ * the fields back to what we would expect for a cached RX PDU.
+ */
+ if (pdu->isp_flags & IDM_PDU_ADDL_HDR) {
+ kmem_free(pdu->isp_hdr, pdu->isp_hdrlen);
+ }
+ if (pdu->isp_flags & IDM_PDU_ADDL_DATA) {
+ kmem_free(pdu->isp_data, pdu->isp_datalen);
+ }
+ pdu->isp_hdr = (iscsi_hdr_t *)(pdu + 1);
+ pdu->isp_hdrlen = sizeof (iscsi_hdr_t);
+ pdu->isp_data = NULL;
+ pdu->isp_datalen = 0;
+ pdu->isp_sorx_buf = 0;
+ pdu->isp_callback = idm_sorx_cache_pdu_cb;
+ idm_sorx_cache_pdu_cb(pdu, status);
+}
+
+/*
+ * This thread is only active when I/O is queued for transmit
+ * because the socket is busy.
+ */
+void
+idm_sotx_thread(void *arg)
+{
+ idm_conn_t *ic = arg;
+ idm_tx_obj_t *object, *next;
+ idm_so_conn_t *so_conn;
+ idm_status_t status = IDM_STATUS_SUCCESS;
+
+ idm_conn_hold(ic);
+
+ mutex_enter(&ic->ic_mutex);
+ so_conn = ic->ic_transport_private;
+ so_conn->ic_tx_thread_running = B_TRUE;
+ so_conn->ic_tx_thread_did = so_conn->ic_tx_thread->t_did;
+ cv_signal(&ic->ic_cv);
+ mutex_exit(&ic->ic_mutex);
+
+ mutex_enter(&so_conn->ic_tx_mutex);
+
+ while (so_conn->ic_tx_thread_running) {
+ while (list_is_empty(&so_conn->ic_tx_list)) {
+ DTRACE_PROBE1(soconn__tx__sleep, idm_conn_t *, ic);
+ cv_wait(&so_conn->ic_tx_cv, &so_conn->ic_tx_mutex);
+ DTRACE_PROBE1(soconn__tx__wakeup, idm_conn_t *, ic);
+
+ if (!so_conn->ic_tx_thread_running) {
+ goto tx_bail;
+ }
+ }
+
+ object = (idm_tx_obj_t *)list_head(&so_conn->ic_tx_list);
+ list_remove(&so_conn->ic_tx_list, object);
+ mutex_exit(&so_conn->ic_tx_mutex);
+
+ switch (object->idm_tx_obj_magic) {
+ case IDM_PDU_MAGIC:
+ DTRACE_PROBE2(soconn__tx__pdu, idm_conn_t *, ic,
+ idm_pdu_t *, (idm_pdu_t *)object);
+
+ status = idm_i_so_tx((idm_pdu_t *)object);
+ break;
+
+ case IDM_BUF_MAGIC: {
+ idm_buf_t *idb = (idm_buf_t *)object;
+ idm_task_t *idt = idb->idb_task_binding;
+
+ DTRACE_PROBE2(soconn__tx__buf, idm_conn_t *, ic,
+ idm_buf_t *, idb);
+
+ mutex_enter(&idt->idt_mutex);
+ status = idm_so_send_buf_region(idt,
+ ISCSI_OP_SCSI_DATA_RSP, idb, 0, idb->idb_xfer_len);
+
+ /*
+ * TX thread owns the buffer so we expect it to
+ * be "in transport"
+ */
+ ASSERT(idb->idb_in_transport);
+ /*
+ * idm_buf_tx_to_ini_done releases idt->idt_mutex
+ */
+ idm_buf_tx_to_ini_done(idt, idb, status);
+ break;
+ }
+
+ default:
+ IDM_CONN_LOG(CE_WARN, "idm_sotx_thread: Unknown magic "
+ "(0x%08x)", object->idm_tx_obj_magic);
+ status = IDM_STATUS_FAIL;
+ }
+
+ mutex_enter(&so_conn->ic_tx_mutex);
+
+ if (status != IDM_STATUS_SUCCESS) {
+ so_conn->ic_tx_thread_running = B_FALSE;
+ idm_conn_event(ic, CE_TRANSPORT_FAIL, status);
+ }
+ }
+
+ /*
+ * Before we leave, we need to abort every item remaining in the
+ * TX list.
+ */
+
+tx_bail:
+ object = (idm_tx_obj_t *)list_head(&so_conn->ic_tx_list);
+
+ while (object != NULL) {
+ next = list_next(&so_conn->ic_tx_list, object);
+
+ list_remove(&so_conn->ic_tx_list, object);
+ switch (object->idm_tx_obj_magic) {
+ case IDM_PDU_MAGIC:
+ idm_pdu_complete((idm_pdu_t *)object,
+ IDM_STATUS_ABORTED);
+ break;
+
+ case IDM_BUF_MAGIC: {
+ idm_buf_t *idb = (idm_buf_t *)object;
+ idm_task_t *idt = idb->idb_task_binding;
+ mutex_exit(&so_conn->ic_tx_mutex);
+ mutex_enter(&idt->idt_mutex);
+ /*
+ * TX thread owns the buffer so we expect it to
+ * be "in transport"
+ */
+ ASSERT(idb->idb_in_transport);
+ /*
+ * idm_buf_tx_to_ini_done releases idt->idt_mutex
+ */
+ idm_buf_tx_to_ini_done(idt, idb, IDM_STATUS_ABORTED);
+ mutex_enter(&so_conn->ic_tx_mutex);
+ break;
+ }
+ default:
+ IDM_CONN_LOG(CE_WARN,
+ "idm_sotx_thread: Unexpected magic "
+ "(0x%08x)", object->idm_tx_obj_magic);
+ }
+
+ object = next;
+ }
+
+ mutex_exit(&so_conn->ic_tx_mutex);
+ idm_conn_rele(ic);
+ thread_exit();
+ /*NOTREACHED*/
+}
diff --git a/usr/src/uts/common/io/idm/idm_text.c b/usr/src/uts/common/io/idm/idm_text.c
new file mode 100644
index 0000000000..ee34dd115d
--- /dev/null
+++ b/usr/src/uts/common/io/idm/idm_text.c
@@ -0,0 +1,1709 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#include <sys/types.h>
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/sysmacros.h>
+#include <sys/socket.h>
+
+#include <sys/iscsi_protocol.h>
+#include <sys/idm/idm.h>
+#include <sys/idm/idm_text.h>
+
+
+extern int
+iscsi_base64_str_to_binary(char *hstr, int hstr_len,
+ uint8_t *binary, int binary_buf_len, int *out_len);
+
+
+static const char idm_hex_to_ascii[] = "0123456789abcdefABCDEF";
+
+static const idm_kv_xlate_t idm_kvpair_xlate[] = {
+ /*
+ * iSCSI Security Text Keys and Authentication Methods
+ */
+
+ { KI_AUTH_METHOD, "AuthMethod", KT_LIST_OF_VALUES, B_FALSE },
+ /*
+ * For values with RFC comments we need to read the RFC to see
+ * what type is appropriate. For now just treat the value as
+ * text.
+ */
+
+ /* Kerberos */
+ { KI_KRB_AP_REQ, "KRB_AP_REQ", KT_TEXT /* RFC1510 */, B_TRUE},
+ { KI_KRB_AP_REP, "KRB_AP_REP", KT_TEXT /* RFC1510 */, B_TRUE},
+
+ /* SPKM */
+ { KI_SPKM_REQ, "SPKM_REQ", KT_TEXT /* RFC2025 */, B_TRUE},
+ { KI_SPKM_ERROR, "SPKM_ERROR", KT_TEXT /* RFC2025 */, B_TRUE},
+ { KI_SPKM_REP_TI, "SPKM_REP_TI", KT_TEXT /* RFC2025 */, B_TRUE},
+ { KI_SPKM_REP_IT, "SPKM_REP_IT", KT_TEXT /* RFC2025 */, B_TRUE},
+
+ /*
+ * SRP
+ * U, s, A, B, M, and H(A | M | K) are defined in [RFC2945]
+ */
+ { KI_SRP_U, "SRP_U", KT_TEXT /* <U> */, B_TRUE},
+ { KI_TARGET_AUTH, "TargetAuth", KT_BOOLEAN, B_TRUE},
+ { KI_SRP_GROUP, "SRP_GROUP", KT_LIST_OF_VALUES /* <G1,..> */, B_FALSE},
+ { KI_SRP_A, "SRP_A", KT_TEXT /* <A> */, B_TRUE},
+ { KI_SRP_B, "SRP_B", KT_TEXT /* <B> */, B_TRUE},
+ { KI_SRP_M, "SRP_M", KT_TEXT /* <M> */, B_TRUE},
+ { KI_SRM_HM, "SRP_HM", KT_TEXT /* <H(A | M | K)> */, B_TRUE},
+
+ /*
+ * CHAP
+ */
+ { KI_CHAP_A, "CHAP_A", KT_LIST_OF_VALUES /* <A1,A2,..> */, B_FALSE },
+ { KI_CHAP_I, "CHAP_I", KT_NUMERICAL /* <I> */, B_TRUE },
+ { KI_CHAP_C, "CHAP_C", KT_BINARY /* <C> */, B_TRUE },
+ { KI_CHAP_N, "CHAP_N", KT_TEXT /* <N> */, B_TRUE },
+ { KI_CHAP_R, "CHAP_R", KT_BINARY /* <N> */, B_TRUE },
+
+
+ /*
+ * ISCSI Operational Parameter Keys
+ */
+ { KI_HEADER_DIGEST, "HeaderDigest", KT_LIST_OF_VALUES, B_FALSE },
+ { KI_DATA_DIGEST, "DataDigest", KT_LIST_OF_VALUES, B_FALSE },
+ { KI_MAX_CONNECTIONS, "MaxConnections", KT_NUMERICAL, B_FALSE },
+ { KI_SEND_TARGETS, "SendTargets", KT_TEXT, B_FALSE },
+ { KI_TARGET_NAME, "TargetName", KT_ISCSI_NAME, B_TRUE},
+ { KI_INITIATOR_NAME, "InitiatorName", KT_ISCSI_NAME, B_TRUE},
+ { KI_TARGET_ALIAS, "TargetAlias", KT_ISCSI_LOCAL_NAME, B_TRUE},
+ { KI_INITIATOR_ALIAS, "InitiatorAlias", KT_ISCSI_LOCAL_NAME, B_TRUE},
+ { KI_TARGET_ADDRESS, "TargetAddress", KT_TEXT, B_TRUE},
+ { KI_TARGET_PORTAL_GROUP_TAG, "TargetPortalGroupTag",
+ KT_NUMERICAL, B_TRUE },
+ { KI_INITIAL_R2T, "InitialR2T", KT_BOOLEAN, B_FALSE },
+ { KI_IMMEDIATE_DATA, "ImmediateData", KT_BOOLEAN, B_FALSE },
+ { KI_MAX_RECV_DATA_SEGMENT_LENGTH, "MaxRecvDataSegmentLength",
+ KT_NUMERICAL /* 512 to 2^24 - 1 */, B_TRUE },
+ { KI_MAX_BURST_LENGTH, "MaxBurstLength",
+ KT_NUMERICAL /* 512 to 2^24 - 1 */, B_FALSE },
+ { KI_FIRST_BURST_LENGTH, "FirstBurstLength",
+ KT_NUMERICAL /* 512 to 2^24 - 1 */, B_FALSE },
+ { KI_DEFAULT_TIME_2_WAIT, "DefaultTime2Wait",
+ KT_NUMERICAL /* 0 to 2600 */, B_FALSE },
+ { KI_DEFAULT_TIME_2_RETAIN, "DefaultTime2Retain",
+ KT_NUMERICAL /* 0 to 2600 */, B_FALSE },
+ { KI_MAX_OUTSTANDING_R2T, "MaxOutstandingR2T",
+ KT_NUMERICAL /* 1 to 65535 */, B_FALSE },
+ { KI_DATA_PDU_IN_ORDER, "DataPDUInOrder", KT_BOOLEAN, B_FALSE },
+ { KI_DATA_SEQUENCE_IN_ORDER, "DataSequenceInOrder",
+ KT_BOOLEAN, B_FALSE },
+ { KI_ERROR_RECOVERY_LEVEL, "ErrorRecoveryLevel",
+ KT_NUMERICAL /* 0 to 2 */, B_FALSE },
+ { KI_SESSION_TYPE, "SessionType", KT_TEXT, B_TRUE },
+ { KI_OFMARKER, "OFMarker", KT_BOOLEAN, B_FALSE },
+ { KI_OFMARKERINT, "OFMarkerInt", KT_NUMERIC_RANGE, B_FALSE },
+ { KI_IFMARKER, "IFMarker", KT_BOOLEAN, B_FALSE },
+ { KI_IFMARKERINT, "IFMarkerInt", KT_NUMERIC_RANGE, B_FALSE },
+
+ /*
+ * iSER-specific keys
+ */
+ { KI_RDMA_EXTENSIONS, "RDMAExtensions", KT_BOOLEAN, B_FALSE },
+ { KI_TARGET_RECV_DATA_SEGMENT_LENGTH, "TargetRecvDataSegmentLength",
+ KT_NUMERICAL /* 512 to 2^24 - 1 */, B_FALSE },
+ { KI_INITIATOR_RECV_DATA_SEGMENT_LENGTH,
+ "InitiatorRecvDataSegmentLength",
+ KT_NUMERICAL /* 512 to 2^24 - 1 */, B_FALSE },
+ { KI_MAX_OUTSTANDING_UNEXPECTED_PDUS, "MaxOutstandingUnexpectedPDUs",
+ KT_NUMERICAL /* 2 to 2^32 - 1 | 0 */, B_TRUE },
+
+ /*
+ * Table terminator. The type KT_TEXT will allow the response
+ * value of "NotUnderstood".
+ */
+ { KI_MAX_KEY, NULL, KT_TEXT, B_TRUE } /* Terminator */
+};
+
+
+#define TEXTBUF_CHUNKSIZE 8192
+
+typedef struct {
+ char *itb_mem;
+ int itb_offset;
+ int itb_mem_len;
+} idm_textbuf_t;
+
+/*
+ * Ignore all but the following keys during security negotiation
+ *
+ * SessionType
+ * InitiatorName
+ * TargetName
+ * TargetAddress
+ * InitiatorAlias
+ * TargetAlias
+ * TargetPortalGroupTag
+ * AuthMethod and associated auth keys
+ */
+
+static int idm_keyvalue_get_next(char **tb_scan, int *tb_len,
+ char **key, int *keylen, char **value);
+
+static int idm_nvlist_add_kv(nvlist_t *nvl, const idm_kv_xlate_t *ikvx,
+ char *value);
+
+static int idm_nvlist_add_string(nvlist_t *nvl,
+ const idm_kv_xlate_t *ikvx, char *value);
+
+static int idm_nvlist_add_boolean(nvlist_t *nvl,
+ const idm_kv_xlate_t *ikvx, char *value);
+
+static int idm_nvlist_add_binary(nvlist_t *nvl,
+ const idm_kv_xlate_t *ikvx, char *value);
+
+static int idm_nvlist_add_large_numerical(nvlist_t *nvl,
+ const idm_kv_xlate_t *ikvx, char *value);
+
+static int idm_nvlist_add_numerical(nvlist_t *nvl,
+ const idm_kv_xlate_t *ikvx, char *value);
+
+static int idm_nvlist_add_numeric_range(nvlist_t *nvl,
+ const idm_kv_xlate_t *ikvx, char *value);
+
+static int idm_nvlist_add_list_of_values(nvlist_t *nvl,
+ const idm_kv_xlate_t *ikvx, char *value);
+
+static int idm_itextbuf_add_nvpair(nvpair_t *nvp, idm_textbuf_t *itb);
+
+static int idm_itextbuf_add_string(nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb);
+
+static int idm_itextbuf_add_boolean(nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb);
+
+static int idm_itextbuf_add_binary(nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb);
+
+static int idm_itextbuf_add_large_numerical(nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb);
+
+static int idm_itextbuf_add_numerical(nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb);
+
+static int idm_itextbuf_add_numeric_range(nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb);
+
+static int idm_itextbuf_add_list_of_values(nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb);
+
+static void textbuf_memcpy(idm_textbuf_t *itb, void *mem, int mem_len);
+
+static void textbuf_strcpy(idm_textbuf_t *itb, char *str);
+
+static void textbuf_append_char(idm_textbuf_t *itb, char c);
+
+static void textbuf_terminate_kvpair(idm_textbuf_t *itb);
+
+static int idm_ascii_to_hex(char *enc_hex_byte, uint8_t *bin_val);
+
+static int idm_base16_str_to_binary(char *hstr, int hstr_len,
+ uint8_t *binary, int binary_length);
+
+static size_t idm_strcspn(const char *string, const char *charset);
+
+static size_t idm_strnlen(const char *str, size_t maxlen);
+
+/*
+ * Processes all whole iSCSI name-value pairs in a text buffer and adds
+ * a corresponding Solaris nvpair_t to the provided nvlist. If the last
+ * iSCSI name-value pair in textbuf is truncated (which can occur when
+ * the request spans multiple PDU's) then upon return textbuf will
+ * point to the truncated iSCSI name-value pair in the buffer and
+ * textbuflen will contain the remaining bytes in the buffer. The
+ * caller can save off this fragment of the iSCSI name-value pair for
+ * use when the next PDU in the request arrives.
+ *
+ * textbuflen includes the trailing 0x00!
+ */
+
+int
+idm_textbuf_to_nvlist(nvlist_t *nvl, char **textbuf, int *textbuflen)
+{
+ int rc = 0;
+ char *tbscan, *key, *value;
+ int tblen, keylen;
+
+ tbscan = *textbuf;
+ tblen = *textbuflen;
+
+ for (;;) {
+ if ((rc = idm_keyvalue_get_next(&tbscan, &tblen,
+ &key, &keylen, &value)) != 0) {
+ /* There was a problem reading the key/value pair */
+ break;
+ }
+
+ if ((rc = idm_nvlist_add_keyvalue(nvl,
+ key, keylen, value)) != 0) {
+ /* Something was wrong with either the key or value */
+ break;
+ }
+
+ if (tblen == 0) {
+ /* End of text buffer */
+ break;
+ }
+ }
+
+ *textbuf = tbscan;
+ *textbuflen = tblen;
+
+ return (rc);
+}
+
+/*
+ * If a test buffer starts with an ISCSI name-value pair fragment (a
+ * continuation from a previous buffer) return the length of the fragment
+ * contained in this buffer. We do not handle name-value pairs that span
+ * more than two buffers so if this buffer does not contain the remainder
+ * of the name value pair the function will return 0. If the first
+ * name-value pair in the buffer is complete the functionw will return 0.
+ */
+int
+idm_textbuf_to_firstfraglen(void *textbuf, int textbuflen)
+{
+ return (idm_strnlen(textbuf, textbuflen));
+}
+
+static int
+idm_keyvalue_get_next(char **tb_scan, int *tb_len,
+ char **key, int *keylen, char **value)
+{
+ /*
+ * Caller doesn't need "valuelen" returned since "value" will
+ * always be a NULL-terminated string.
+ */
+ size_t total_len, valuelen;
+
+ /*
+ * How many bytes to the first '\0'? This represents the total
+ * length of our iSCSI key/value pair.
+ */
+ total_len = idm_strnlen(*tb_scan, *tb_len);
+ if (total_len == *tb_len) {
+ /*
+ * No '\0', perhaps this key/value pair is continued in
+ * another buffer
+ */
+ return (E2BIG);
+ }
+
+ /*
+ * Found NULL, so this is a possible key-value pair. At
+ * the same time we've validated that there is actually a
+ * NULL in this string so it's safe to use regular
+ * string functions (i.e. strcpy instead of strncpy)
+ */
+ *key = *tb_scan;
+ *keylen = idm_strcspn(*tb_scan, "=");
+
+ if (*keylen == total_len) {
+ /* No '=', bad format */
+ return (EINVAL);
+ }
+
+ *tb_scan += *keylen + 1; /* Skip the '=' */
+ *tb_len -= *keylen + 1;
+
+ /*
+ * The remaining text after the '=' is the value
+ */
+ *value = *tb_scan;
+ valuelen = total_len - (*keylen + 1);
+
+ *tb_scan += valuelen + 1; /* Skip the '\0' */
+ *tb_len -= valuelen + 1;
+
+ return (0);
+}
+
+const idm_kv_xlate_t *
+idm_lookup_kv_xlate(const char *key, int keylen)
+{
+ const idm_kv_xlate_t *ikvx = &idm_kvpair_xlate[0];
+
+ /*
+ * Look for a matching key value in the key/value pair table.
+ * The matching entry in the table will tell us how to encode
+ * the key and value in the nvlist. If we don't recognize
+ * the key then we will simply encode it in string format.
+ * The login or text request code can generate the appropriate
+ * "not understood" resposne.
+ */
+ while (ikvx->ik_key_id != KI_MAX_KEY) {
+ /*
+ * Compare strings. "key" is not NULL-terminated so
+ * use strncmp. Since we are using strncmp we
+ * need to check that the lengths match, otherwise
+ * we might unintentionally lookup "TargetAddress"
+ * with a key of "Target" (or something similar).
+ *
+ * "value" is NULL-terminated so we can use it as
+ * a regular string.
+ */
+ if ((strncmp(ikvx->ik_key_name, key, keylen) == 0) &&
+ (strlen(ikvx->ik_key_name) == keylen)) {
+ /* Exit the loop since we found a match */
+ break;
+ }
+
+ /* No match, look at the next entry */
+ ikvx++;
+ }
+
+ return (ikvx);
+}
+
+static int
+idm_nvlist_add_kv(nvlist_t *nvl, const idm_kv_xlate_t *ikvx, char *value)
+{
+ int rc;
+
+ switch (ikvx->ik_idm_type) {
+ case KT_TEXT:
+ case KT_SIMPLE:
+ case KT_ISCSI_NAME:
+ case KT_ISCSI_LOCAL_NAME:
+ rc = idm_nvlist_add_string(nvl, ikvx, value);
+ break;
+ case KT_BOOLEAN:
+ rc = idm_nvlist_add_boolean(nvl, ikvx, value);
+ break;
+ case KT_REGULAR_BINARY:
+ case KT_LARGE_BINARY:
+ case KT_BINARY:
+ rc = idm_nvlist_add_binary(nvl, ikvx, value);
+ break;
+ case KT_LARGE_NUMERICAL:
+ rc = idm_nvlist_add_large_numerical(nvl, ikvx,
+ value);
+ break;
+ case KT_NUMERICAL:
+ rc = idm_nvlist_add_numerical(nvl, ikvx,
+ value);
+ break;
+ case KT_NUMERIC_RANGE:
+ rc = idm_nvlist_add_numeric_range(nvl, ikvx,
+ value);
+ break;
+ case KT_LIST_OF_VALUES:
+ rc = idm_nvlist_add_list_of_values(nvl, ikvx,
+ value);
+ break;
+ default:
+ ASSERT(0); /* This should never happen */
+ break;
+ }
+ if (rc != 0) {
+ /* could be one of the text constants */
+ rc = idm_nvlist_add_string(nvl, ikvx, value);
+ }
+
+ return (rc);
+}
+
+static int
+idm_nvlist_add_string(nvlist_t *nvl,
+ const idm_kv_xlate_t *ikvx, char *value)
+{
+ return (nvlist_add_string(nvl, ikvx->ik_key_name, value));
+}
+
+static int
+idm_nvlist_add_boolean(nvlist_t *nvl,
+ const idm_kv_xlate_t *ikvx, char *value)
+{
+ int rc;
+ boolean_t bool_val;
+
+ if (strcasecmp(value, "Yes") == 0) {
+ bool_val = B_TRUE;
+ } else if (strcasecmp(value, "No") == 0) {
+ bool_val = B_FALSE;
+ } else {
+ return (EINVAL);
+ }
+
+ rc = nvlist_add_boolean_value(nvl, ikvx->ik_key_name, bool_val);
+
+ return (rc);
+}
+
+static boolean_t
+kv_is_hex(char *value)
+{
+ return ((strncmp(value, "0x", strlen("0x")) == 0) ||
+ (strncmp(value, "0X", strlen("0X")) == 0));
+}
+
+static boolean_t
+kv_is_base64(char *value)
+{
+ return ((strncmp(value, "0b", strlen("0b")) == 0) ||
+ (strncmp(value, "0B", strlen("0B")) == 0));
+}
+
+
+static int
+idm_nvlist_add_binary(nvlist_t *nvl,
+ const idm_kv_xlate_t *ikvx, char *value)
+{
+ int rc;
+ int value_length;
+ uint64_t uint64_value;
+ int binary_length;
+ uchar_t *binary_array;
+
+ /*
+ * A binary value can be either decimal, hex or base64. If it's
+ * decimal then the encoded string must be less than 64 bits in
+ * length (8 characters). In all cases we will convert the
+ * included value to a byte array starting with the MSB. The
+ * assumption is that values meant to be treated as integers will
+ * use the "numerical" and "large numerical" types.
+ */
+ if (kv_is_hex(value)) {
+ value += strlen("0x");
+ value_length = strlen(value);
+ binary_length = (value_length + 1) / 2;
+ binary_array = kmem_alloc(binary_length, KM_SLEEP);
+
+ if (idm_base16_str_to_binary(value, value_length,
+ binary_array, binary_length) != 0) {
+ kmem_free(binary_array, binary_length);
+ return (EINVAL);
+ }
+
+ rc = nvlist_add_byte_array(nvl, ikvx->ik_key_name,
+ binary_array, binary_length);
+
+ kmem_free(binary_array, binary_length);
+
+ return (rc);
+
+ } else if (kv_is_base64(value)) {
+ value += strlen("0b");
+ value_length = strlen(value);
+ binary_array = kmem_alloc(value_length, KM_NOSLEEP);
+ if (binary_array == NULL) {
+ return (ENOMEM);
+ }
+
+ if (iscsi_base64_str_to_binary(value, value_length,
+ binary_array, value_length, &binary_length) != 0) {
+ kmem_free(binary_array, value_length);
+ return (EINVAL);
+ }
+
+ rc = nvlist_add_byte_array(nvl, ikvx->ik_key_name,
+ binary_array, binary_length);
+
+ kmem_free(binary_array, value_length);
+
+ return (rc);
+ } else {
+ /*
+ * Decimal value (not permitted for "large-binary_value" so
+ * it must be smaller than 64 bits. It's not really
+ * clear from the RFC what a decimal-binary-value might
+ * represent but presumably it should be treated the same
+ * as a hex or base64 value. Therefore we'll convert it
+ * to an array of bytes.
+ */
+ if ((rc = idm_strtoull(value, NULL, 0,
+ (u_longlong_t *)&uint64_value)) != 0)
+ return (rc);
+
+ rc = nvlist_add_byte_array(nvl, ikvx->ik_key_name,
+ (uint8_t *)&uint64_value, sizeof (uint64_value));
+
+ return (rc);
+ }
+
+ /* NOTREACHED */
+}
+
+
+static int
+idm_nvlist_add_large_numerical(nvlist_t *nvl,
+ const idm_kv_xlate_t *ikvx, char *value)
+{
+ /*
+ * A "large numerical" value can be larger than 64-bits. Since
+ * there is no upper bound on the size of the value, we will
+ * punt and store it in string form. We could also potentially
+ * treat the value as binary data.
+ */
+ return (nvlist_add_string(nvl, ikvx->ik_key_name, value));
+}
+
+
+static int
+idm_nvlist_add_numerical(nvlist_t *nvl,
+ const idm_kv_xlate_t *ikvx, char *value)
+{
+ int rc;
+ uint64_t uint64_value;
+
+ /*
+ * "Numerical" values in the iSCSI standard are up to 64-bits wide.
+ * On a 32-bit system we could see an overflow here during conversion.
+ * This shouldn't happen with real-world values for the current
+ * iSCSI parameters of "numerical" type.
+ */
+ rc = idm_strtoull(value, NULL, 0, (u_longlong_t *)&uint64_value);
+ if (rc == 0) {
+ rc = nvlist_add_uint64(nvl, ikvx->ik_key_name, uint64_value);
+ }
+
+ return (rc);
+}
+
+
+static int
+idm_nvlist_add_numeric_range(nvlist_t *nvl,
+ const idm_kv_xlate_t *ikvx, char *range)
+{
+ nvlist_t *range_nvl;
+ char *val_scan = range;
+ uint64_t start_val, end_val;
+ int val_len, range_len;
+ int rc;
+
+ /* We'll store the range an an nvlist with two values */
+ rc = nvlist_alloc(&range_nvl, NV_UNIQUE_NAME, KM_NOSLEEP);
+ if (rc != 0) {
+ return (rc);
+ }
+
+ /*
+ * We expect idm_keyvalue_get_next to ensure the string is
+ * terminated
+ */
+ range_len = strlen(range);
+
+ /*
+ * Find range separator
+ */
+ val_len = idm_strcspn(val_scan, "~");
+
+ if (val_len == range_len) {
+ /* invalid range */
+ nvlist_free(range_nvl);
+ return (EINVAL);
+ }
+
+ /*
+ * Start value
+ */
+ *(val_scan + val_len + 1) = '\0';
+ rc = idm_strtoull(val_scan, NULL, 0, (u_longlong_t *)&start_val);
+ if (rc == 0) {
+ rc = nvlist_add_uint64(range_nvl, "start", start_val);
+ }
+ if (rc != 0) {
+ nvlist_free(range_nvl);
+ return (rc);
+ }
+
+ /*
+ * End value
+ */
+ val_scan += val_len + 1;
+ rc = idm_strtoull(val_scan, NULL, 0, (u_longlong_t *)&end_val);
+ if (rc == 0) {
+ rc = nvlist_add_uint64(range_nvl, "start", end_val);
+ }
+ if (rc != 0) {
+ nvlist_free(range_nvl);
+ return (rc);
+ }
+
+ /*
+ * Now add the "range" nvlist to the main nvlist
+ */
+ rc = nvlist_add_nvlist(nvl, ikvx->ik_key_name, range_nvl);
+ if (rc != 0) {
+ nvlist_free(range_nvl);
+ return (rc);
+ }
+
+ nvlist_free(range_nvl);
+ return (0);
+}
+
+
+static int
+idm_nvlist_add_list_of_values(nvlist_t *nvl,
+ const idm_kv_xlate_t *ikvx, char *value_list)
+{
+ char value_name[8];
+ nvlist_t *value_list_nvl;
+ char *val_scan = value_list;
+ int value_index = 0;
+ int val_len, val_list_len;
+ int rc;
+
+ rc = nvlist_alloc(&value_list_nvl, NV_UNIQUE_NAME, KM_NOSLEEP);
+ if (rc != 0) {
+ return (rc);
+ }
+
+ /*
+ * We expect idm_keyvalue_get_next to ensure the string is
+ * terminated
+ */
+ val_list_len = strlen(value_list);
+ if (val_list_len == 0) {
+ nvlist_free(value_list_nvl);
+ return (EINVAL);
+ }
+
+ for (;;) {
+ (void) snprintf(value_name, 8, "value%d", value_index);
+
+ val_len = idm_strcspn(val_scan, ",");
+
+ if (*(val_scan + val_len) != '\0') {
+ *(val_scan + val_len) = '\0';
+ }
+ rc = nvlist_add_string(value_list_nvl, value_name, val_scan);
+ if (rc != 0) {
+ nvlist_free(value_list_nvl);
+ return (rc);
+ }
+
+ /*
+ * Move to next value, see if we're at the end of the value
+ * list
+ */
+ val_scan += val_len + 1;
+ if (val_scan == value_list + val_list_len + 1) {
+ break;
+ }
+
+ value_index++;
+ }
+
+ rc = nvlist_add_nvlist(nvl, ikvx->ik_key_name, value_list_nvl);
+ if (rc != 0) {
+ nvlist_free(value_list_nvl);
+ return (rc);
+ }
+
+ nvlist_free(value_list_nvl);
+ return (0);
+}
+
+/*
+ * Convert an nvlist containing standard iSCSI key names and values into
+ * a text buffer with properly formatted iSCSI key-value pairs ready to
+ * transmit on the wire. *textbuf should be NULL and will be set to point
+ * the resulting text buffer.
+ */
+
+int
+idm_nvlist_to_textbuf(nvlist_t *nvl, char **textbuf, int *textbuflen,
+ int *validlen)
+{
+ int rc = 0;
+ nvpair_t *nvp = NULL;
+ idm_textbuf_t itb;
+
+ bzero(&itb, sizeof (itb));
+
+ for (;;) {
+ nvp = nvlist_next_nvpair(nvl, nvp);
+
+ if (nvp == NULL) {
+ /* Last nvpair in nvlist, we're done */
+ break;
+ }
+
+ if ((rc = idm_itextbuf_add_nvpair(nvp, &itb)) != 0) {
+ /* There was a problem building the key/value pair */
+ break;
+ }
+ }
+
+ *textbuf = itb.itb_mem;
+ *textbuflen = itb.itb_mem_len;
+ *validlen = itb.itb_offset;
+
+ return (rc);
+}
+
+static int
+idm_itextbuf_add_nvpair(nvpair_t *nvp,
+ idm_textbuf_t *itb)
+{
+ int rc = 0;
+ char *key;
+ const idm_kv_xlate_t *ikvx;
+
+ key = nvpair_name(nvp);
+
+ ikvx = idm_lookup_kv_xlate(key, strlen(key));
+
+ /*
+ * Any key supplied by the initiator that is not in our table
+ * will be responded to with the string value "NotUnderstood".
+ * An example is a vendor specific key.
+ */
+ ASSERT((ikvx->ik_key_id != KI_MAX_KEY) ||
+ (nvpair_type(nvp) == DATA_TYPE_STRING));
+
+ /*
+ * Look for a matching key value in the key/value pair table.
+ * The matching entry in the table will tell us how to encode
+ * the key and value in the nvlist.
+ */
+ switch (ikvx->ik_idm_type) {
+ case KT_TEXT:
+ case KT_SIMPLE:
+ case KT_ISCSI_NAME:
+ case KT_ISCSI_LOCAL_NAME:
+ rc = idm_itextbuf_add_string(nvp, ikvx, itb);
+ break;
+ case KT_BOOLEAN:
+ rc = idm_itextbuf_add_boolean(nvp, ikvx, itb);
+ break;
+ case KT_REGULAR_BINARY:
+ case KT_LARGE_BINARY:
+ case KT_BINARY:
+ rc = idm_itextbuf_add_binary(nvp, ikvx, itb);
+ break;
+ case KT_LARGE_NUMERICAL:
+ rc = idm_itextbuf_add_large_numerical(nvp, ikvx, itb);
+ break;
+ case KT_NUMERICAL:
+ rc = idm_itextbuf_add_numerical(nvp, ikvx, itb);
+ break;
+ case KT_NUMERIC_RANGE:
+ rc = idm_itextbuf_add_numeric_range(nvp, ikvx, itb);
+ break;
+ case KT_LIST_OF_VALUES:
+ rc = idm_itextbuf_add_list_of_values(nvp, ikvx, itb);
+ break;
+ default:
+ ASSERT(0); /* This should never happen */
+ break;
+ }
+
+ return (rc);
+}
+
+/* ARGSUSED */
+static int
+idm_itextbuf_add_string(nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb)
+{
+ char *key_name;
+ char *value;
+ int rc;
+
+ /* Start with the key name */
+ key_name = nvpair_name(nvp);
+ textbuf_strcpy(itb, key_name);
+
+ /* Add separator */
+ textbuf_append_char(itb, '=');
+
+ /* Add value */
+ rc = nvpair_value_string(nvp, &value);
+ ASSERT(rc == 0);
+ textbuf_strcpy(itb, value);
+
+ /* Add trailing 0x00 */
+ textbuf_terminate_kvpair(itb);
+
+ return (0);
+}
+
+
+/* ARGSUSED */
+static int
+idm_itextbuf_add_boolean(nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb)
+{
+ char *key_name;
+ boolean_t value;
+ int rc;
+
+ /* Start with the key name */
+ key_name = nvpair_name(nvp);
+ textbuf_strcpy(itb, key_name);
+
+ /* Add separator */
+ textbuf_append_char(itb, '=');
+
+ /* Add value */
+ rc = nvpair_value_boolean_value(nvp, &value);
+ ASSERT(rc == 0);
+ textbuf_strcpy(itb, value ? "Yes" : "No");
+
+ /* Add trailing 0x00 */
+ textbuf_terminate_kvpair(itb);
+
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+idm_itextbuf_add_binary(nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb)
+{
+ char *key_name;
+ unsigned char *value;
+ unsigned int len;
+ unsigned long n;
+ int rc;
+
+ /* Start with the key name */
+ key_name = nvpair_name(nvp);
+ textbuf_strcpy(itb, key_name);
+
+ /* Add separator */
+ textbuf_append_char(itb, '=');
+
+ /* Add value */
+ rc = nvpair_value_byte_array(nvp, &value, &len);
+ ASSERT(rc == 0);
+
+ textbuf_strcpy(itb, "0x");
+
+ while (len > 0) {
+ n = *value++;
+ len--;
+
+ textbuf_append_char(itb, idm_hex_to_ascii[(n >> 4) & 0xf]);
+ textbuf_append_char(itb, idm_hex_to_ascii[n & 0xf]);
+ }
+
+ /* Add trailing 0x00 */
+ textbuf_terminate_kvpair(itb);
+
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+idm_itextbuf_add_large_numerical(nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb)
+{
+ ASSERT(0);
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+idm_itextbuf_add_numerical(nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb)
+{
+ char *key_name;
+ uint64_t value;
+ int rc;
+ char str[16];
+
+ /* Start with the key name */
+ key_name = nvpair_name(nvp);
+ textbuf_strcpy(itb, key_name);
+
+ /* Add separator */
+ textbuf_append_char(itb, '=');
+
+ /* Add value */
+ rc = nvpair_value_uint64(nvp, &value);
+ ASSERT(rc == 0);
+ (void) sprintf(str, "%llu", (u_longlong_t)value);
+ textbuf_strcpy(itb, str);
+
+ /* Add trailing 0x00 */
+ textbuf_terminate_kvpair(itb);
+
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+idm_itextbuf_add_numeric_range(nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb)
+{
+ ASSERT(0);
+ return (0);
+}
+
+/* ARGSUSED */
+static int
+idm_itextbuf_add_list_of_values(nvpair_t *nvp,
+ const idm_kv_xlate_t *ikvx, idm_textbuf_t *itb)
+{
+ char *key_name;
+ nvpair_t *vchoice = NULL;
+ char *vchoice_string = NULL;
+ int rc;
+
+ /* Start with the key name */
+ key_name = nvpair_name(nvp);
+ textbuf_strcpy(itb, key_name);
+
+ /* Add separator */
+ textbuf_append_char(itb, '=');
+
+ /* Add value choices */
+ vchoice = idm_get_next_listvalue(nvp, NULL);
+ while (vchoice != NULL) {
+ rc = nvpair_value_string(vchoice, &vchoice_string);
+ ASSERT(rc == 0);
+ textbuf_strcpy(itb, vchoice_string);
+ vchoice = idm_get_next_listvalue(nvp, vchoice);
+ if (vchoice != NULL) {
+ /* Add ',' between choices */
+ textbuf_append_char(itb, ',');
+ }
+ }
+
+ /* Add trailing 0x00 */
+ textbuf_terminate_kvpair(itb);
+
+ return (0);
+}
+
+
+static void
+textbuf_makeroom(idm_textbuf_t *itb, int size)
+{
+ char *new_mem;
+ int new_mem_len;
+
+ if (itb->itb_mem == NULL) {
+ itb->itb_mem_len = MAX(TEXTBUF_CHUNKSIZE, size);
+ itb->itb_mem = kmem_alloc(itb->itb_mem_len, KM_SLEEP);
+ } else if ((itb->itb_offset + size) > itb->itb_mem_len) {
+ new_mem_len = itb->itb_mem_len + MAX(TEXTBUF_CHUNKSIZE, size);
+ new_mem = kmem_alloc(new_mem_len, KM_SLEEP);
+ bcopy(itb->itb_mem, new_mem, itb->itb_mem_len);
+ kmem_free(itb->itb_mem, itb->itb_mem_len);
+ itb->itb_mem = new_mem;
+ itb->itb_mem_len = new_mem_len;
+ }
+}
+
+static void
+textbuf_memcpy(idm_textbuf_t *itb, void *mem, int mem_len)
+{
+ textbuf_makeroom(itb, mem_len);
+ (void) memcpy(itb->itb_mem + itb->itb_offset, mem, mem_len);
+ itb->itb_offset += mem_len;
+}
+
+static void
+textbuf_strcpy(idm_textbuf_t *itb, char *str)
+{
+ textbuf_memcpy(itb, str, strlen(str));
+}
+
+static void
+textbuf_append_char(idm_textbuf_t *itb, char c)
+{
+ textbuf_makeroom(itb, sizeof (char));
+ *(itb->itb_mem + itb->itb_offset) = c;
+ itb->itb_offset++;
+}
+
+static void
+textbuf_terminate_kvpair(idm_textbuf_t *itb)
+{
+ textbuf_append_char(itb, '\0');
+}
+
+static int
+idm_ascii_to_hex(char *enc_hex_byte, uint8_t *bin_val)
+{
+ uint8_t nibble1, nibble2;
+ char enc_char = *enc_hex_byte;
+
+ if (enc_char >= '0' && enc_char <= '9') {
+ nibble1 = (enc_char - '0');
+ } else if (enc_char >= 'A' && enc_char <= 'F') {
+ nibble1 = (0xA + (enc_char - 'A'));
+ } else if (enc_char >= 'a' && enc_char <= 'f') {
+ nibble1 = (0xA + (enc_char - 'a'));
+ } else {
+ return (EINVAL);
+ }
+
+ enc_hex_byte++;
+ enc_char = *enc_hex_byte;
+
+ if (enc_char >= '0' && enc_char <= '9') {
+ nibble2 = (enc_char - '0');
+ } else if (enc_char >= 'A' && enc_char <= 'F') {
+ nibble2 = (0xA + (enc_char - 'A'));
+ } else if (enc_char >= 'a' && enc_char <= 'f') {
+ nibble2 = (0xA + (enc_char - 'a'));
+ } else {
+ return (EINVAL);
+ }
+
+ *bin_val = (nibble1 << 4) | nibble2;
+
+ return (0);
+}
+
+
+static int idm_base16_str_to_binary(char *hstr, int hstr_len,
+ uint8_t *binary_array, int binary_length)
+{
+ char tmpstr[2];
+ uchar_t *binary_scan;
+
+ binary_scan = binary_array;
+
+ /*
+ * If the length of the encoded ascii hex value is a multiple
+ * of two then every two ascii characters correspond to a hex
+ * byte. If the length of the value is not a multiple of two
+ * then the first character is the first hex byte and then for
+ * the remaining of the string every two ascii characters
+ * correspond to a hex byte
+ */
+ if ((hstr_len % 2) != 0) {
+
+ tmpstr[0] = '0';
+ tmpstr[1] = *hstr;
+
+ if (idm_ascii_to_hex(tmpstr, binary_scan) != 0) {
+ return (EINVAL);
+ }
+
+ hstr++;
+ binary_scan++;
+ }
+
+ while (binary_scan != binary_array + binary_length) {
+ if (idm_ascii_to_hex(hstr, binary_scan) != 0) {
+ return (EINVAL);
+ }
+
+ hstr += 2;
+ binary_scan++;
+ }
+
+ return (0);
+}
+
+static size_t
+idm_strnlen(const char *str, size_t maxlen)
+{
+ const char *ptr;
+
+ ptr = memchr(str, 0, maxlen);
+ if (ptr == NULL)
+ return (maxlen);
+
+ return ((uintptr_t)ptr - (uintptr_t)str);
+}
+
+
+size_t
+idm_strcspn(const char *string, const char *charset)
+{
+ const char *p, *q;
+
+ for (q = string; *q != '\0'; ++q) {
+ for (p = charset; *p != '\0' && *p != *q; )
+ p++;
+ if (*p != '\0') {
+ break;
+ }
+ }
+ return ((uintptr_t)q - (uintptr_t)string);
+}
+
+/*
+ * We allow a list of choices to be represented as a single nvpair
+ * (list with one value choice), or as an nvlist with a single nvpair
+ * (also a list with on value choice), or as an nvlist with multiple
+ * nvpairs (a list with multiple value choices). This function implements
+ * the "get next" functionality regardless of the choice list structure.
+ *
+ * nvpair_t's that contain choices are always strings.
+ */
+nvpair_t *
+idm_get_next_listvalue(nvpair_t *value_list, nvpair_t *curr_nvp)
+{
+ nvpair_t *result;
+ nvlist_t *nvl;
+ int nvrc;
+ data_type_t nvp_type;
+
+ nvp_type = nvpair_type(value_list);
+
+ switch (nvp_type) {
+ case DATA_TYPE_NVLIST:
+ nvrc = nvpair_value_nvlist(value_list, &nvl);
+ ASSERT(nvrc == 0);
+ result = nvlist_next_nvpair(nvl, curr_nvp);
+ break;
+ case DATA_TYPE_STRING:
+ /* Single choice */
+ if (curr_nvp == NULL) {
+ result = value_list;
+ } else {
+ result = NULL;
+ }
+ break;
+ default:
+ ASSERT(0); /* Malformed choice list */
+ result = NULL;
+ break;
+ }
+
+ return (result);
+}
+
+kv_status_t
+idm_nvstat_to_kvstat(int nvrc)
+{
+ kv_status_t result;
+ switch (nvrc) {
+ case 0:
+ result = KV_HANDLED;
+ break;
+ case ENOMEM:
+ result = KV_NO_RESOURCES;
+ break;
+ case EINVAL:
+ result = KV_VALUE_ERROR;
+ break;
+ case EFAULT:
+ case ENOTSUP:
+ default:
+ result = KV_INTERNAL_ERROR;
+ break;
+ }
+
+ return (result);
+}
+
+void
+idm_kvstat_to_error(kv_status_t kvrc, uint8_t *class, uint8_t *detail)
+{
+ switch (kvrc) {
+ case KV_HANDLED:
+ case KV_HANDLED_NO_TRANSIT:
+ *class = ISCSI_STATUS_CLASS_SUCCESS;
+ *detail = ISCSI_LOGIN_STATUS_ACCEPT;
+ break;
+ case KV_UNHANDLED:
+ case KV_TARGET_ONLY:
+ /* protocol error */
+ *class = ISCSI_STATUS_CLASS_INITIATOR_ERR;
+ *detail = ISCSI_LOGIN_STATUS_INVALID_REQUEST;
+ break;
+ case KV_VALUE_ERROR:
+ /* invalid value */
+ *class = ISCSI_STATUS_CLASS_INITIATOR_ERR;
+ *detail = ISCSI_LOGIN_STATUS_INIT_ERR;
+ break;
+ case KV_NO_RESOURCES:
+ /* no memory */
+ *class = ISCSI_STATUS_CLASS_TARGET_ERR;
+ *detail = ISCSI_LOGIN_STATUS_NO_RESOURCES;
+ break;
+ case KV_MISSING_FIELDS:
+ /* key/value pair(s) missing */
+ *class = ISCSI_STATUS_CLASS_INITIATOR_ERR;
+ *detail = ISCSI_LOGIN_STATUS_MISSING_FIELDS;
+ break;
+ case KV_AUTH_FAILED:
+ /* authentication failed */
+ *class = ISCSI_STATUS_CLASS_INITIATOR_ERR;
+ *detail = ISCSI_LOGIN_STATUS_AUTH_FAILED;
+ break;
+ default:
+ /* target error */
+ *class = ISCSI_STATUS_CLASS_TARGET_ERR;
+ *detail = ISCSI_LOGIN_STATUS_TARGET_ERROR;
+ break;
+ }
+}
+
+int
+idm_nvlist_add_keyvalue(nvlist_t *nvl,
+ char *key, int keylen, char *value)
+{
+ const idm_kv_xlate_t *ikvx;
+
+ ikvx = idm_lookup_kv_xlate(key, keylen);
+
+ if (ikvx->ik_key_id == KI_MAX_KEY) {
+ return (nvlist_add_string(nvl, key, value));
+ }
+
+ return (idm_nvlist_add_kv(nvl, ikvx, value));
+}
+
+int
+idm_nvlist_add_id(nvlist_t *nvl, iscsikey_id_t kv_id, char *value)
+{
+ int i;
+ for (i = 0; i < KI_MAX_KEY; i++) {
+ if (idm_kvpair_xlate[i].ik_key_id == kv_id) {
+ return
+ (idm_nvlist_add_kv(nvl,
+ &idm_kvpair_xlate[i], value));
+ }
+ }
+ return (EFAULT);
+}
+
+char *
+idm_id_to_name(iscsikey_id_t kv_id)
+{
+ int i;
+ for (i = 0; i < KI_MAX_KEY; i++) {
+ if (idm_kvpair_xlate[i].ik_key_id == kv_id) {
+ return (idm_kvpair_xlate[i].ik_key_name);
+ }
+ }
+ return (NULL);
+}
+
+/*
+ * return the value in a buffer that must be freed by the caller
+ */
+char *
+idm_nvpair_value_to_textbuf(nvpair_t *nvp)
+{
+ int rv, len;
+ idm_textbuf_t itb;
+ char *str;
+
+ bzero(&itb, sizeof (itb));
+ rv = idm_itextbuf_add_nvpair(nvp, &itb);
+ if (rv != 0)
+ return (NULL);
+ str = kmem_alloc(itb.itb_mem_len, KM_SLEEP);
+ len = idm_strcspn(itb.itb_mem, "=");
+ if (len > strlen(itb.itb_mem)) {
+ kmem_free(itb.itb_mem, itb.itb_mem_len);
+ return (NULL);
+ }
+ (void) strcpy(str, &itb.itb_mem[len+1]);
+ /* free the allocation done in idm_textbuf_add_nvpair */
+ kmem_free(itb.itb_mem, itb.itb_mem_len);
+ return (str);
+}
+
+/*
+ * build an iscsi text buffer - the memory gets freed in
+ * idm_itextbuf_free
+ */
+void *
+idm_nvlist_to_itextbuf(nvlist_t *nvl)
+{
+ idm_textbuf_t *itb;
+ char *textbuf;
+ int validlen, textbuflen;
+
+ if (idm_nvlist_to_textbuf(nvl, &textbuf, &textbuflen,
+ &validlen) != IDM_STATUS_SUCCESS) {
+ return (NULL);
+ }
+ itb = kmem_zalloc(sizeof (idm_textbuf_t), KM_SLEEP);
+ ASSERT(itb != NULL);
+ itb->itb_mem = textbuf;
+ itb->itb_mem_len = textbuflen;
+ itb->itb_offset = validlen;
+ return ((void *)itb);
+}
+
+/*
+ * Update the pdu data up to min of max_xfer_len or data left.
+ * The first call to this routine should send
+ * a NULL bufptr. Subsequent calls send in the buffer returned.
+ * Call this routine until the string returned is NULL
+ */
+char *
+idm_pdu_init_text_data(idm_pdu_t *pdu, void *arg,
+ int max_xfer_len, char *bufptr, int *transit)
+{
+ char *start_ptr, *end_ptr, *ptr;
+ idm_textbuf_t *itb = arg;
+ iscsi_hdr_t *ihp = pdu->isp_hdr;
+ int send = 0;
+
+ ASSERT(itb != NULL);
+ ASSERT(pdu != NULL);
+ ASSERT(transit != NULL);
+ if (bufptr == NULL) {
+ /* first call - check the length */
+ if (itb->itb_offset <= max_xfer_len) {
+ idm_pdu_init_data(pdu, (uint8_t *)itb->itb_mem,
+ itb->itb_offset);
+ ihp->flags &= ~ISCSI_FLAG_TEXT_CONTINUE;
+ *transit = 1;
+ return (NULL);
+ }
+ /* we have more data than will fit in one pdu */
+ start_ptr = itb->itb_mem;
+ end_ptr = &itb->itb_mem[max_xfer_len - 1];
+
+ } else {
+ if ((uintptr_t)&itb->itb_mem[itb->itb_offset] -
+ (uintptr_t)bufptr <= max_xfer_len) {
+ idm_pdu_init_data(pdu, (uint8_t *)bufptr,
+ (uintptr_t)&itb->itb_mem[itb->itb_offset] -
+ (uintptr_t)bufptr);
+ ihp->flags &= ~ISCSI_FLAG_TEXT_CONTINUE;
+ *transit = 1;
+ return (NULL);
+ }
+ /* we have more data then will fit in one pdu */
+ start_ptr = bufptr;
+ end_ptr = &bufptr[max_xfer_len - 1];
+ }
+ /* break after key, after =, after the value or after '\0' */
+ ptr = end_ptr;
+ if (end_ptr + 1 <= &itb->itb_mem[itb->itb_offset]) {
+ /* if next char is an '=' or '\0' send it */
+ if (*(end_ptr + 1) == '=' || *(end_ptr + 1) == '\0') {
+ send = 1;
+ }
+ }
+ if (!send) {
+ while (*ptr != '\0' && *ptr != '=' && ptr != start_ptr) {
+ ptr--;
+ }
+ }
+ idm_pdu_init_data(pdu, (uint8_t *)start_ptr,
+ ((uintptr_t)ptr - (uintptr_t)start_ptr) + 1);
+ ihp->flags |= ISCSI_FLAG_TEXT_CONTINUE;
+ *transit = 0;
+ return (++ptr);
+}
+
+void
+idm_itextbuf_free(void *arg)
+{
+ idm_textbuf_t *itb = arg;
+ ASSERT(itb != NULL);
+ kmem_free(itb->itb_mem, itb->itb_mem_len);
+ kmem_free(itb, sizeof (idm_textbuf_t));
+}
+
+/*
+ * Allocate an nvlist and poputlate with key=value from the pdu list.
+ * NOTE: caller must free the list
+ */
+idm_status_t
+idm_pdu_list_to_nvlist(list_t *pdu_list, nvlist_t **nvlist,
+ uint8_t *error_detail)
+{
+ idm_pdu_t *pdu, *next_pdu;
+ boolean_t split_kv = B_FALSE;
+ char *textbuf, *leftover_textbuf = NULL;
+ int textbuflen, leftover_textbuflen = 0;
+ char *split_kvbuf;
+ int split_kvbuflen, cont_fraglen;
+ iscsi_login_hdr_t *lh;
+ int rc;
+ int ret = IDM_STATUS_SUCCESS;
+
+ *error_detail = ISCSI_LOGIN_STATUS_ACCEPT;
+ /* Allocate a new nvlist for request key/value pairs */
+ rc = nvlist_alloc(nvlist, NV_UNIQUE_NAME,
+ KM_NOSLEEP);
+ if (rc != 0) {
+ *error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES;
+ ret = IDM_STATUS_FAIL;
+ goto cleanup;
+ }
+
+ /*
+ * A login request can be split across multiple PDU's. The state
+ * machine has collected all the PDU's that make up this login request
+ * and assembled them on the "icl_pdu_list" queue. Process each PDU
+ * and convert the text keywords to nvlist form.
+ */
+ pdu = list_head(pdu_list);
+ while (pdu != NULL) {
+ next_pdu = list_next(pdu_list, pdu);
+
+ lh = (iscsi_login_hdr_t *)pdu->isp_hdr;
+
+ textbuf = (char *)pdu->isp_data;
+ textbuflen = pdu->isp_datalen;
+ if (textbuflen == 0) {
+ /* This shouldn't really happen but it could.. */
+ list_remove(pdu_list, pdu);
+ idm_pdu_complete(pdu, IDM_STATUS_SUCCESS);
+ pdu = next_pdu;
+ continue;
+ }
+
+ /*
+ * If we encountered a split key-value pair on the last
+ * PDU then handle it now by grabbing the remainder of the
+ * key-value pair from the next PDU and splicing them
+ * together. Obviously on the first PDU this will never
+ * happen.
+ */
+ if (split_kv) {
+ cont_fraglen = idm_textbuf_to_firstfraglen(textbuf,
+ textbuflen);
+ if (cont_fraglen == pdu->isp_datalen) {
+ /*
+ * This key-value pair spans more than two
+ * PDU's. We don't handle this.
+ */
+ *error_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR;
+ ret = IDM_STATUS_FAIL;
+ goto cleanup;
+ }
+
+ split_kvbuflen = leftover_textbuflen + cont_fraglen;
+ split_kvbuf = kmem_alloc(split_kvbuflen, KM_NOSLEEP);
+ if (split_kvbuf == NULL) {
+ *error_detail = ISCSI_LOGIN_STATUS_NO_RESOURCES;
+ ret = IDM_STATUS_FAIL;
+ goto cleanup;
+ }
+
+ bcopy(leftover_textbuf, split_kvbuf,
+ leftover_textbuflen);
+ bcopy(textbuf,
+ (uint8_t *)split_kvbuf + leftover_textbuflen,
+ cont_fraglen);
+
+
+ if (idm_textbuf_to_nvlist(*nvlist,
+ &split_kvbuf, &split_kvbuflen) != 0) {
+ /*
+ * Need to handle E2BIG case, indicating that
+ * a key-value pair is split across multiple
+ * PDU's.
+ */
+ kmem_free(split_kvbuf, split_kvbuflen);
+
+ *error_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR;
+ ret = IDM_STATUS_FAIL;
+ goto cleanup;
+ }
+
+ ASSERT(split_kvbuflen != NULL);
+ kmem_free(split_kvbuf, split_kvbuflen);
+
+ /* Now handle the remainder of the PDU as normal */
+ textbuf += (cont_fraglen + 1);
+ textbuflen -= (cont_fraglen + 1);
+ }
+
+ /*
+ * Convert each key-value pair in the text buffer to nvlist
+ * format. If the list has already been created the nvpair
+ * elements will be added on to the existing list. Otherwise
+ * a new nvlist will be created.
+ */
+ if (idm_textbuf_to_nvlist(*nvlist,
+ &textbuf, &textbuflen) != 0) {
+
+ *error_detail = ISCSI_LOGIN_STATUS_TARGET_ERROR;
+ ret = IDM_STATUS_FAIL;
+ goto cleanup;
+ }
+
+ ASSERT(
+ ((lh->flags & ISCSI_FLAG_LOGIN_CONTINUE) &&
+ (next_pdu != NULL)) ||
+ (!(lh->flags & ISCSI_FLAG_LOGIN_CONTINUE) &&
+ (next_pdu == NULL)));
+
+ if ((lh->flags & ISCSI_FLAG_LOGIN_CONTINUE) &
+ (textbuflen != 0)) {
+ /*
+ * Key-value pair is split over two PDU's. We
+ * assume it willl never be split over more than
+ * two PDU's.
+ */
+ split_kv = B_TRUE;
+ leftover_textbuf = textbuf;
+ leftover_textbuflen = textbuflen;
+ } else {
+ split_kv = B_FALSE;
+ if (textbuflen != 0) {
+ /*
+ * Incomplete keyword but no additional
+ * PDU's. This is a malformed login
+ * request.
+ */
+ *error_detail =
+ ISCSI_LOGIN_STATUS_INVALID_REQUEST;
+ ret = IDM_STATUS_FAIL;
+ goto cleanup;
+ }
+ }
+
+ list_remove(pdu_list, pdu);
+ idm_pdu_complete(pdu, IDM_STATUS_SUCCESS);
+ pdu = next_pdu;
+ }
+
+cleanup:
+
+ /*
+ * Free any remaining PDUs on the list. This will only
+ * happen if there were errors encountered during
+ * processing of the textbuf.
+ */
+ pdu = list_head(pdu_list);
+ while (pdu != NULL) {
+ next_pdu = list_next(pdu_list, pdu);
+ list_remove(pdu_list, pdu);
+ idm_pdu_complete(pdu, IDM_STATUS_SUCCESS);
+ pdu = next_pdu;
+ }
+
+ /*
+ * If there were no errors, we have a complete nvlist representing
+ * all the iSCSI key-value pairs in the login request PDU's
+ * that make up this request.
+ */
+ return (ret);
+}
+
+/*
+ * idm_strtoull
+ *
+ * Since the kernel only provides ddi_strtoul (not the ddi_strtoull that we
+ * would like to use) we need our own conversion function to convert
+ * string integer representations to 64-bit integers. ddi_strtoul doesn't
+ * work well for us on 32-bit systems. This code is shamelessly ripped
+ * from ddi_strtol.c. Eventually we should push this back into the DDI
+ * so that we don't need our own version anymore.
+ */
+
+#define isalnum(ch) (isalpha(ch) || isdigit(ch))
+#define isalpha(ch) (isupper(ch) || islower(ch))
+#define isdigit(ch) ((ch) >= '0' && (ch) <= '9')
+#define islower(ch) ((ch) >= 'a' && (ch) <= 'z')
+#define isspace(ch) (((ch) == ' ') || ((ch) == '\r') || ((ch) == '\n') || \
+ ((ch) == '\t') || ((ch) == '\f'))
+#define isupper(ch) ((ch) >= 'A' && (ch) <= 'Z')
+#define isxdigit(ch) (isdigit(ch) || ((ch) >= 'a' && (ch) <= 'f') || \
+ ((ch) >= 'A' && (ch) <= 'F'))
+
+#define DIGIT(x) \
+ (isdigit(x) ? (x) - '0' : islower(x) ? (x) + 10 - 'a' : (x) + 10 - 'A')
+
+#define MBASE ('z' - 'a' + 1 + 10)
+
+#define lisalnum(x) \
+ (isdigit(x) || ((x) >= 'a' && (x) <= 'z') || ((x) >= 'A' && (x) <= 'Z'))
+
+#ifndef ULLONG_MAX
+#define ULLONG_MAX 18446744073709551615ULL
+#endif
+
+int
+idm_strtoull(const char *str, char **nptr, int base,
+ unsigned long long *result)
+{
+ unsigned long long val;
+ int c;
+ int xx;
+ unsigned long long multmax;
+ int neg = 0;
+ const char **ptr = (const char **)nptr;
+ const unsigned char *ustr = (const unsigned char *)str;
+
+ if (ptr != (const char **)0)
+ *ptr = (char *)ustr; /* in case no number is formed */
+ if (base < 0 || base > MBASE || base == 1) {
+ /* base is invalid -- should be a fatal error */
+ return (EINVAL);
+ }
+ if (!isalnum(c = *ustr)) {
+ while (isspace(c))
+ c = *++ustr;
+ switch (c) {
+ case '-':
+ neg++;
+ /* FALLTHROUGH */
+ case '+':
+ c = *++ustr;
+ }
+ }
+ if (base == 0)
+ if (c != '0')
+ base = 10;
+ else if (ustr[1] == 'x' || ustr[1] == 'X')
+ base = 16;
+ else
+ base = 8;
+ /*
+ * for any base > 10, the digits incrementally following
+ * 9 are assumed to be "abc...z" or "ABC...Z"
+ */
+ if (!lisalnum(c) || (xx = DIGIT(c)) >= base)
+ return (EINVAL); /* no number formed */
+ if (base == 16 && c == '0' && (ustr[1] == 'x' || ustr[1] == 'X') &&
+ isxdigit(ustr[2]))
+ c = *(ustr += 2); /* skip over leading "0x" or "0X" */
+
+ multmax = ULLONG_MAX / (unsigned long long)base;
+ val = DIGIT(c);
+ for (c = *++ustr; lisalnum(c) && (xx = DIGIT(c)) < base; ) {
+ if (val > multmax)
+ goto overflow;
+ val *= base;
+ if (ULONG_MAX - val < xx)
+ goto overflow;
+ val += xx;
+ c = *++ustr;
+ }
+ if (ptr != (const char **)0)
+ *ptr = (char *)ustr;
+ *result = neg ? -val : val;
+ return (0);
+
+overflow:
+ for (c = *++ustr; lisalnum(c) && (xx = DIGIT(c)) < base; )
+ c = *++ustr;
+ if (ptr != (const char **)0)
+ *ptr = (char *)ustr;
+ return (ERANGE);
+}
diff --git a/usr/src/uts/common/sys/Makefile b/usr/src/uts/common/sys/Makefile
index 725a56854c..d0d808d459 100644
--- a/usr/src/uts/common/sys/Makefile
+++ b/usr/src/uts/common/sys/Makefile
@@ -717,6 +717,22 @@ IBMGTHDRS= \
IBDHDRS= \
ibd.h
+IDMHDRS= \
+ idm.h \
+ idm_impl.h \
+ idm_so.h \
+ idm_text.h \
+ idm_transport.h \
+ idm_conn_sm.h
+
+ISCSITHDRS= \
+ radius_packet.h \
+ radius_protocol.h \
+ chap.h \
+ isns_protocol.h \
+ iscsi_if.h \
+ iscsit_common.h
+
ISOHDRS= \
signal_iso.h
@@ -1087,6 +1103,9 @@ CHECKHDRS= \
$(IBMFHDRS:%.h=ib/mgt/ibmf/%.check) \
$(TAVORHDRS:%.h=ib/adapters/tavor/%.check) \
$(HERMONHDRS:%.h=ib/adapters/hermon/%.check) \
+ $(IDMHDRS:%.h=idm/%.check) \
+ $(ISCSIHDRS:%.h=iscsi/%.check) \
+ $(ISCSITHDRS:%.h=iscsit/%.check) \
$(ISOHDRS:%.h=iso/%.check) \
$(FMHDRS:%.h=fm/%.check) \
$(FMFSHDRS:%.h=fm/fs/%.check) \
@@ -1129,6 +1148,9 @@ CHECKHDRS= \
$(ROOTCRYPTOHDRS) \
$(ROOTDCAMHDRS) \
$(ROOTISOHDRS) \
+ $(ROOTIDMHDRS) \
+ $(ROOTISCSIHDRS) \
+ $(ROOTISCSITHDRS) \
$(ROOTFC4HDRS) \
$(ROOTFCHDRS) \
$(ROOTFCIMPLHDRS) \
@@ -1183,6 +1205,9 @@ install_h: \
$(ROOTCRYPTOHDRS) \
$(ROOTDCAMHDRS) \
$(ROOTISOHDRS) \
+ $(ROOTIDMHDRS) \
+ $(ROOTISCSIHDRS) \
+ $(ROOTISCSITHDRS) \
$(ROOTFC4HDRS) \
$(ROOTFCHDRS) \
$(ROOTFCIMPLHDRS) \
diff --git a/usr/src/uts/common/sys/Makefile.syshdrs b/usr/src/uts/common/sys/Makefile.syshdrs
index c29c951933..bc18a6e042 100644
--- a/usr/src/uts/common/sys/Makefile.syshdrs
+++ b/usr/src/uts/common/sys/Makefile.syshdrs
@@ -74,6 +74,12 @@ ib/adapters/tavor/%.check: ib/adapters/tavor/%.h
ib/adapters/hermon/%.check: ib/adapters/hermon/%.h
$(DOT_H_CHECK)
+idm/%.check: idm/%.h
+ $(DOT_H_CHECK)
+
+iscsit/%.check: iscsit/%.h
+ $(DOT_H_CHECK)
+
lvm/%.check: lvm/%.h
$(DOT_H_CHECK)
@@ -180,6 +186,8 @@ ROOTDIRS= \
$(ROOTDIR)/ib/mgt/ibmf \
$(ROOTDIR)/ib/ibnex \
$(ROOTDIR)/ib/clients/ibd \
+ $(ROOTDIR)/idm \
+ $(ROOTDIR)/iscsit \
$(ROOTDIR)/lvm \
$(ROOTDIR)/pcmcia \
$(ROOTDIR)/scsi \
@@ -227,6 +235,9 @@ ROOTIBMFHDRS= $(IBMFHDRS:%=$(ROOTDIR)/ib/mgt/ibmf/%)
ROOTTAVORHDRS= $(TAVORHDRS:%=$(ROOTDIR)/ib/adapters/tavor/%)
ROOTHERMONHDRS= $(HERMONHDRS:%=$(ROOTDIR)/ib/adapters/hermon/%)
+ROOTIDMHDRS= $(IDMHDRS:%=$(ROOTDIR)/idm/%)
+ROOTISCSITHDRS= $(ISCSITHDRS:%=$(ROOTDIR)/iscsit/%)
+
ROOTISOHDRS= $(ISOHDRS:%=$(ROOTDIR)/iso/%)
ROOTFMHDRS= $(FMHDRS:%=$(ROOTDIR)/fm/%)
diff --git a/usr/src/uts/common/sys/idm/idm.h b/usr/src/uts/common/sys/idm/idm.h
new file mode 100644
index 0000000000..7d97084c10
--- /dev/null
+++ b/usr/src/uts/common/sys/idm/idm.h
@@ -0,0 +1,445 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _IDM_H
+#define _IDM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ IDM_STATUS_SUCCESS = 0,
+ IDM_STATUS_FAIL,
+ IDM_STATUS_NORESOURCES,
+ IDM_STATUS_REJECT,
+ IDM_STATUS_IO,
+ IDM_STATUS_ABORTED,
+ IDM_STATUS_SUSPENDED,
+ IDM_STATUS_HEADER_DIGEST,
+ IDM_STATUS_DATA_DIGEST,
+ IDM_STATUS_PROTOCOL_ERROR
+} idm_status_t;
+
+typedef enum {
+ CN_CONNECT_ACCEPT = 1, /* Target only */
+ CN_LOGIN_FAIL, /* Target only */
+ CN_READY_FOR_LOGIN, /* Initiator only */
+ CN_FFP_ENABLED,
+ CN_FFP_DISABLED,
+ CN_CONNECT_LOST,
+ CN_CONNECT_DESTROY
+} idm_client_notify_t;
+
+typedef enum {
+ FD_CONN_FAIL,
+ FD_CONN_LOGOUT,
+ FD_SESS_LOGOUT
+} idm_ffp_disable_t;
+
+typedef enum {
+ AT_INTERNAL_SUSPEND,
+ AT_INTERNAL_ABORT,
+ AT_TASK_MGMT_ABORT
+} idm_abort_type_t;
+
+typedef enum {
+ TASK_IDLE,
+ TASK_ACTIVE,
+ TASK_SUSPENDING,
+ TASK_SUSPENDED,
+ TASK_ABORTING,
+ TASK_ABORTED,
+ TASK_COMPLETE
+} idm_task_state_t;
+
+typedef enum {
+ KV_HANDLED = 0,
+ KV_HANDLED_NO_TRANSIT,
+ KV_UNHANDLED,
+ KV_TARGET_ONLY,
+ KV_NO_RESOURCES,
+ KV_INTERNAL_ERROR,
+ KV_VALUE_ERROR,
+ KV_MISSING_FIELDS,
+ KV_AUTH_FAILED
+} kv_status_t;
+
+/*
+ * Request structures
+ */
+
+/* Defined in idm_impl.h */
+struct idm_conn_s;
+struct idm_svc_s;
+struct idm_buf_s;
+struct idm_pdu_s;
+struct idm_task_s;
+
+typedef idm_status_t (idm_client_notify_cb_t)(
+ struct idm_conn_s *ic, idm_client_notify_t cn, uintptr_t data);
+
+typedef void (idm_rx_pdu_cb_t)(struct idm_conn_s *ic, struct idm_pdu_s *pdu);
+
+typedef void (idm_rx_pdu_error_cb_t)(struct idm_conn_s *ic,
+ struct idm_pdu_s *pdu, idm_status_t status);
+
+typedef void (idm_buf_cb_t)(struct idm_buf_s *idb, idm_status_t status);
+
+typedef void (idm_pdu_cb_t)(struct idm_pdu_s *pdu, idm_status_t status);
+
+typedef void (idm_task_cb_t)(struct idm_task_s *task, idm_status_t status);
+
+typedef void (idm_build_hdr_cb_t)(
+ struct idm_task_s *task, struct idm_pdu_s *pdu, uint8_t opcode);
+
+typedef union idm_sockaddr {
+ struct sockaddr sin;
+ struct sockaddr_in sin4;
+ struct sockaddr_in6 sin6;
+} idm_sockaddr_t;
+
+#define SIZEOF_SOCKADDR(so) \
+ ((so)->sa_family == AF_INET ? \
+ sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6))
+
+typedef struct {
+ idm_rx_pdu_cb_t *icb_rx_scsi_cmd;
+ idm_rx_pdu_cb_t *icb_rx_scsi_rsp;
+ idm_rx_pdu_cb_t *icb_rx_misc;
+ idm_rx_pdu_error_cb_t *icb_rx_error;
+ idm_task_cb_t *icb_task_aborted;
+ idm_client_notify_cb_t *icb_client_notify;
+ idm_build_hdr_cb_t *icb_build_hdr;
+} idm_conn_ops_t;
+
+typedef struct {
+ int cr_domain;
+ int cr_type;
+ int cr_protocol;
+ boolean_t cr_bound;
+ idm_sockaddr_t cr_bound_addr;
+ idm_sockaddr_t cr_ini_dst_addr;
+ ldi_ident_t cr_li;
+ idm_conn_ops_t icr_conn_ops;
+} idm_conn_req_t;
+
+typedef struct {
+ uint16_t sr_port;
+ ldi_ident_t sr_li;
+ idm_conn_ops_t sr_conn_ops;
+} idm_svc_req_t;
+
+
+/* This is not how other networking code handles this */
+typedef struct {
+ union {
+ struct in_addr in4;
+ struct in6_addr in6;
+ } i_addr;
+ /* i_insize determines which is valid in the union above */
+ int i_insize;
+} idm_ipaddr_t;
+
+typedef struct {
+ idm_ipaddr_t a_addr;
+ uint32_t a_port,
+ a_oid;
+} idm_addr_t;
+
+typedef struct {
+ uint32_t al_vers, /* In */
+ al_oid; /* In */
+ uint32_t al_in_cnt; /* In */
+ uint32_t al_out_cnt; /* Out */
+ uint32_t al_tpgt; /* Out */
+ idm_addr_t al_addrs[1]; /* Out */
+} idm_addr_list_t;
+
+/*
+ * State machine auditing
+ */
+
+#define SM_AUDIT_BUF_MAX_REC 32
+
+typedef enum {
+ SAR_UNDEFINED = 0,
+ SAR_STATE_EVENT,
+ SAR_STATE_CHANGE
+} sm_audit_record_type_t;
+
+typedef enum {
+ SAS_UNDEFINED = 0,
+ SAS_IDM_CONN,
+ SAS_IDM_TASK,
+ SAS_ISCSIT_TGT,
+ SAS_ISCSIT_SESS,
+ SAS_ISCSIT_LOGIN
+} sm_audit_sm_type_t;
+
+typedef struct {
+ timespec_t sar_timestamp;
+ sm_audit_sm_type_t sar_sm_type;
+ sm_audit_record_type_t sar_type;
+ int sar_state;
+ int sar_new_state; /* Only for SAR_STATE_CHANGE */
+ int sar_event; /* Only for SAR_STATE_EVENT */
+ uintptr_t sar_event_info; /* Only for SAR_STATE_EVENT */
+} sm_audit_record_t;
+
+typedef struct {
+ int sab_index;
+ int sab_max_index;
+ sm_audit_record_t sab_records[SM_AUDIT_BUF_MAX_REC];
+} sm_audit_buf_t;
+
+extern boolean_t idm_sm_logging;
+extern boolean_t idm_conn_logging;
+extern boolean_t idm_svc_logging;
+
+#define IDM_SM_LOG if (idm_sm_logging) cmn_err
+#define IDM_CONN_LOG if (idm_conn_logging) cmn_err
+#define IDM_SVC_LOG if (idm_svc_logging) cmn_err
+
+void idm_sm_audit_init(sm_audit_buf_t *audit_buf);
+
+void idm_sm_audit_event(sm_audit_buf_t *audit_buf,
+ sm_audit_sm_type_t sm_type,
+ int state, int event, uintptr_t event_info);
+
+void idm_sm_audit_state_change(sm_audit_buf_t *audit_buf,
+ sm_audit_sm_type_t sm_type, int state, int new_state);
+
+
+#include <sys/iscsi_protocol.h>
+#include <sys/idm/idm_conn_sm.h>
+#include <sys/idm/idm_transport.h>
+#include <sys/idm/idm_impl.h>
+#include <sys/idm/idm_text.h>
+#include <sys/idm/idm_so.h>
+
+/*
+ * iSCSI Initiator Services
+ */
+
+idm_status_t
+idm_ini_conn_create(idm_conn_req_t *cr, idm_conn_t **new_con);
+
+idm_status_t
+idm_ini_conn_connect(idm_conn_t *ic);
+
+void
+idm_ini_conn_disconnect(idm_conn_t *ic);
+
+void
+idm_ini_conn_destroy(idm_conn_t *ic);
+
+/*
+ * iSCSI Target Services
+ */
+
+idm_status_t
+idm_tgt_svc_create(idm_svc_req_t *sr, idm_svc_t **new_svc);
+
+idm_status_t
+idm_tgt_svc_online(idm_svc_t *is);
+
+void
+idm_tgt_svc_offline(idm_svc_t *is);
+
+void
+idm_tgt_svc_destroy(idm_svc_t *is);
+
+void
+idm_tgt_svc_destroy_if_unref(idm_svc_t *is);
+
+idm_svc_t *
+idm_tgt_svc_lookup(uint16_t port);
+
+void
+idm_tgt_svc_hold(idm_svc_t *is);
+
+void
+idm_tgt_svc_rele_and_destroy(idm_svc_t *is);
+
+idm_status_t
+idm_tgt_conn_accept(idm_conn_t *ic);
+
+void
+idm_tgt_conn_reject(idm_conn_t *ic);
+
+void
+idm_conn_hold(idm_conn_t *ic);
+
+void
+idm_conn_rele(idm_conn_t *ic);
+
+/*
+ * Target data transfer services
+ */
+idm_status_t
+idm_buf_tx_to_ini(idm_task_t *idt, idm_buf_t *idb,
+ uint32_t offset, uint32_t xfer_length,
+ idm_buf_cb_t idb_buf_cb, void *cb_arg);
+
+idm_status_t
+idm_buf_rx_from_ini(idm_task_t *idt, idm_buf_t *idb,
+ uint32_t offset, uint32_t xfer_length,
+ idm_buf_cb_t idb_buf_cb, void *cb_arg);
+
+void
+idm_buf_tx_to_ini_done(idm_task_t *idt, idm_buf_t *idb, idm_status_t status);
+
+void
+idm_buf_rx_from_ini_done(idm_task_t *idt, idm_buf_t *idb, idm_status_t status);
+
+/*
+ * Shared Initiator/Target Services
+ */
+kv_status_t
+idm_negotiate_key_values(idm_conn_t *ic, nvlist_t *request_nvl,
+ nvlist_t *response_nvl, nvlist_t *negotiated_nvl);
+
+idm_status_t
+idm_notice_key_values(idm_conn_t *ic, nvlist_t *negotiated_nvl);
+
+/*
+ * Buffer services
+ */
+
+idm_buf_t *
+idm_buf_alloc(idm_conn_t *ic, void *bufptr, uint64_t buflen);
+
+void
+idm_buf_free(idm_buf_t *idb);
+
+void
+idm_buf_bind_in(idm_task_t *idt, idm_buf_t *buf);
+
+void
+idm_buf_bind_out(idm_task_t *idt, idm_buf_t *buf);
+
+void
+idm_buf_unbind_in(idm_task_t *idt, idm_buf_t *buf);
+
+void
+idm_buf_unbind_out(idm_task_t *idt, idm_buf_t *buf);
+
+idm_buf_t *
+idm_buf_find(void *lbuf, size_t data_offset);
+
+/*
+ * Task services
+ */
+idm_task_t *
+idm_task_alloc(idm_conn_t *ic);
+
+void
+idm_task_start(idm_task_t *idt, uintptr_t handle);
+
+void
+idm_task_abort(idm_conn_t *ic, idm_task_t *idt, idm_abort_type_t abort_type);
+
+void
+idm_task_cleanup(idm_task_t *idt);
+
+void
+idm_task_done(idm_task_t *idt);
+
+void
+idm_task_free(idm_task_t *idt);
+
+idm_task_t *
+idm_task_find(idm_conn_t *ic, uint32_t itt, uint32_t ttt);
+
+void *
+idm_task_find_by_handle(idm_conn_t *ic, uintptr_t handle);
+
+void
+idm_task_hold(idm_task_t *idt);
+
+void
+idm_task_rele(idm_task_t *idt);
+
+/*
+ * PDU Services
+ */
+
+idm_pdu_t *
+idm_pdu_alloc(uint_t hdrlen, uint_t datalen);
+
+void
+idm_pdu_free(idm_pdu_t *pdu);
+
+void
+idm_pdu_init(idm_pdu_t *pdu, idm_conn_t *ic, void *private, idm_pdu_cb_t *cb);
+
+void
+idm_pdu_init_hdr(idm_pdu_t *pdu, uint8_t *hdr, uint_t hdrlen);
+
+void
+idm_pdu_init_data(idm_pdu_t *pdu, uint8_t *data, uint_t datalen);
+
+void
+idm_pdu_complete(idm_pdu_t *pdu, idm_status_t status);
+
+void
+idm_pdu_tx(idm_pdu_t *pdu);
+
+/*
+ * Object reference tracking
+ */
+
+void
+idm_refcnt_init(idm_refcnt_t *refcnt, void *referenced_obj);
+
+void
+idm_refcnt_destroy(idm_refcnt_t *refcnt);
+
+void
+idm_refcnt_reset(idm_refcnt_t *refcnt);
+
+void
+idm_refcnt_hold(idm_refcnt_t *refcnt);
+
+void
+idm_refcnt_rele(idm_refcnt_t *refcnt);
+
+void
+idm_refcnt_rele_and_destroy(idm_refcnt_t *refcnt, idm_refcnt_cb_t *cb_func);
+
+void
+idm_refcnt_wait_ref(idm_refcnt_t *refcnt);
+
+void
+idm_refcnt_async_wait_ref(idm_refcnt_t *refcnt, idm_refcnt_cb_t *cb_func);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IDM_H */
diff --git a/usr/src/uts/common/sys/idm/idm_conn_sm.h b/usr/src/uts/common/sys/idm/idm_conn_sm.h
new file mode 100644
index 0000000000..8d7ee745ed
--- /dev/null
+++ b/usr/src/uts/common/sys/idm/idm_conn_sm.h
@@ -0,0 +1,269 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+#ifndef _IDM_CONN_SM_H_
+#define _IDM_CONN_SM_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/*
+ * IDM connection state machine events. Most events get generated internally
+ * either by the state machine or by the IDM TX/RX code. For example when IDM
+ * receives a login request for a target connectionit will generate a
+ * CE_LOGIN_RCV event. Similarly when the target sends a successful login
+ * response IDM generate a "CE_LOGIN_SUCCESS_SND" event. The following
+ * events are not detected on the TX/RX path and must be generated explicitly
+ * by the client when appropriate:
+ *
+ * CE_LOGOUT_OTHER_CONN_RCV
+ * CE_ASYNC_DROP_CONN_RCV (Only because the message may be received on
+ * a different connection from the connection being dropped)
+ * CE_ASYNC_DROP_ALL_CONN_RCV
+ * CE_LOGOUT_OTHER_CONN_SND
+ * CE_ASYNC_DROP_ALL_CONN_SND
+ *
+ * The following events might occur in any state since they are driven
+ * by the PDU's that IDM receives:
+ *
+ * CE_LOGIN_RCV
+ * CE_LOGIN_SUCCESS_RCV
+ * CE_LOGIN_FAIL_RCV
+ * CE_LOGOUT_SUCCESS_RCV
+ * CE_LOGOUT_FAIL_RCV
+ * CE_ASYNC_LOGOUT_RCV
+ * CE_MISC_RCV
+ * CE_RX_PROTOCOL_ERROR
+ */
+
+#define IDM_LOGIN_SECONDS 20
+#define IDM_LOGOUT_SECONDS 20
+#define IDM_CLEANUP_SECONDS 0
+
+/* Update idm_ce_name table whenever connection events are modified */
+typedef enum {
+ CE_UNDEFINED = 0,
+
+ /* Initiator events */
+ CE_CONNECT_REQ,
+ CE_CONNECT_FAIL,
+ CE_CONNECT_SUCCESS,
+ CE_LOGIN_SND,
+ CE_LOGIN_SUCCESS_RCV,
+ CE_LOGIN_FAIL_RCV,
+ CE_LOGOUT_THIS_CONN_SND,
+ CE_LOGOUT_OTHER_CONN_SND,
+ CE_LOGOUT_SESSION_SND,
+ CE_LOGOUT_SUCCESS_RCV,
+ CE_LOGOUT_FAIL_RCV,
+ CE_ASYNC_LOGOUT_RCV,
+ CE_ASYNC_DROP_CONN_RCV,
+ CE_ASYNC_DROP_ALL_CONN_RCV,
+
+ /* Target events */
+ CE_CONNECT_ACCEPT,
+ CE_CONNECT_REJECT,
+ CE_LOGIN_RCV,
+ CE_LOGIN_TIMEOUT,
+ CE_LOGIN_SUCCESS_SND,
+ CE_LOGIN_FAIL_SND,
+ CE_LOGIN_FAIL_SND_DONE,
+ CE_LOGOUT_THIS_CONN_RCV,
+ CE_LOGOUT_OTHER_CONN_RCV,
+ CE_LOGOUT_SESSION_RCV,
+ CE_LOGOUT_SUCCESS_SND,
+ CE_LOGOUT_SUCCESS_SND_DONE,
+ CE_LOGOUT_FAIL_SND,
+ CE_LOGOUT_FAIL_SND_DONE,
+ CE_CLEANUP_TIMEOUT,
+ CE_ASYNC_LOGOUT_SND,
+ CE_ASYNC_DROP_CONN_SND,
+ CE_ASYNC_DROP_ALL_CONN_SND,
+ CE_LOGOUT_TIMEOUT,
+
+ /* Common events */
+ CE_TRANSPORT_FAIL,
+ CE_MISC_TX,
+ CE_TX_PROTOCOL_ERROR,
+ CE_MISC_RX,
+ CE_RX_PROTOCOL_ERROR,
+ CE_LOGOUT_SESSION_SUCCESS,
+ CE_CONN_REINSTATE,
+ CE_CONN_REINSTATE_SUCCESS,
+ CE_CONN_REINSTATE_FAIL,
+ CE_ENABLE_DM_SUCCESS,
+ CE_ENABLE_DM_FAIL,
+
+ /* Add new events above CE_MAX_EVENT */
+ CE_MAX_EVENT
+} idm_conn_event_t;
+
+#ifdef IDM_CONN_SM_STRINGS
+/* An array of event text values, for use in logging events */
+static const char *idm_ce_name[CE_MAX_EVENT+1] = {
+ "CE_UNDEFINED",
+ "CE_CONNECT_REQ",
+ "CE_CONNECT_FAIL",
+ "CE_CONNECT_SUCCESS",
+ "CE_LOGIN_SND",
+ "CE_LOGIN_SUCCESS_RCV",
+ "CE_LOGIN_FAIL_RCV",
+ "CE_LOGOUT_THIS_CONN_SND",
+ "CE_LOGOUT_OTHER_CONN_SND",
+ "CE_LOGOUT_SESSION_SND",
+ "CE_LOGOUT_SUCCESS_RCV",
+ "CE_LOGOUT_FAIL_RCV",
+ "CE_ASYNC_LOGOUT_RCV",
+ "CE_ASYNC_DROP_CONN_RCV",
+ "CE_ASYNC_DROP_ALL_CONN_RCV",
+ "CE_CONNECT_ACCEPT",
+ "CE_CONNECT_REJECT",
+ "CE_LOGIN_RCV",
+ "CE_LOGIN_TIMEOUT",
+ "CE_LOGIN_SUCCESS_SND",
+ "CE_LOGIN_FAIL_SND",
+ "CE_LOGIN_FAIL_SND_DONE",
+ "CE_LOGOUT_THIS_CONN_RCV",
+ "CE_LOGOUT_OTHER_CONN_RCV",
+ "CE_LOGOUT_SESSION_RCV",
+ "CE_LOGOUT_SUCCESS_SND",
+ "CE_LOGOUT_SUCCESS_SND_DONE",
+ "CE_LOGOUT_FAIL_SND",
+ "CE_LOGOUT_FAIL_SND_DONE",
+ "CE_CLEANUP_TIMEOUT",
+ "CE_ASYNC_LOGOUT_SND",
+ "CE_ASYNC_DROP_CONN_SND",
+ "CE_ASYNC_DROP_ALL_CONN_SND",
+ "CE_LOGOUT_TIMEOUT",
+ "CE_TRANSPORT_FAIL",
+ "CE_MISC_TX",
+ "CE_TX_PROTOCOL_ERROR",
+ "CE_MISC_RX",
+ "CE_RX_PROTOCOL_ERROR",
+ "CE_LOGOUT_SESSION_SUCCESS",
+ "CE_CONN_REINSTATE",
+ "CE_CONN_REINSTATE_SUCCESS",
+ "CE_CONN_REINSTATE_FAIL",
+ "CE_ENABLE_DM_SUCCESS",
+ "CE_ENABLE_DM_FAIL",
+ "CE_MAX_EVENT"
+};
+#endif
+
+/* Update idm_cs_name table whenever connection states are modified */
+typedef enum {
+ CS_S0_UNDEFINED = 0,
+
+ CS_S1_FREE,
+ CS_S2_XPT_WAIT,
+ CS_S3_XPT_UP,
+ CS_S4_IN_LOGIN,
+ CS_S5_LOGGED_IN,
+ CS_S6_IN_LOGOUT,
+ CS_S7_LOGOUT_REQ,
+ CS_S8_CLEANUP,
+ CS_S9_INIT_ERROR,
+ CS_S10_IN_CLEANUP,
+ CS_S11_COMPLETE,
+ CS_S12_ENABLE_DM,
+
+ /* Add new connection states above CS_MAX_STATE */
+ CS_MAX_STATE
+} idm_conn_state_t;
+
+#ifdef IDM_CONN_SM_STRINGS
+/* An array of state text values, for use in logging state transitions */
+static const char *idm_cs_name[CS_MAX_STATE+1] = {
+ "CS_S0_UNDEFINED",
+ "CS_S1_FREE",
+ "CS_S2_XPT_WAIT",
+ "CS_S3_XPT_UP",
+ "CS_S4_IN_LOGIN",
+ "CS_S5_LOGGED_IN",
+ "CS_S6_IN_LOGOUT",
+ "CS_S7_LOGOUT_REQ",
+ "CS_S8_CLEANUP",
+ "CS_S9_INIT_ERROR",
+ "CS_S10_IN_CLEANUP",
+ "CS_S11_COMPLETE",
+ "CS_S12_ENABLE_DM",
+ "CS_MAX_STATE"
+};
+#endif
+
+typedef enum {
+ CT_NONE = 0,
+ CT_RX_PDU,
+ CT_TX_PDU
+} idm_pdu_event_type_t;
+
+typedef enum {
+ CA_TX_PROTOCOL_ERROR, /* Send "protocol error" to state machine */
+ CA_RX_PROTOCOL_ERROR, /* Send "protocol error" to state machine */
+ CA_FORWARD, /* State machine event and foward to client */
+ CA_DROP /* Drop PDU */
+} idm_pdu_event_action_t;
+
+typedef struct {
+ struct idm_conn_s *iec_ic;
+ idm_conn_event_t iec_event;
+ uintptr_t iec_info;
+ idm_pdu_event_type_t iec_pdu_event_type;
+} idm_conn_event_ctx_t;
+
+idm_status_t
+idm_conn_sm_init(struct idm_conn_s *ic);
+
+void
+idm_conn_sm_fini(struct idm_conn_s *ic);
+
+idm_status_t
+idm_notify_client(struct idm_conn_s *ic, idm_client_notify_t cn,
+ uintptr_t data);
+
+void
+idm_conn_event(struct idm_conn_s *ic, idm_conn_event_t event, uintptr_t data);
+
+idm_status_t
+idm_conn_reinstate_event(struct idm_conn_s *old_ic, struct idm_conn_s *new_ic);
+
+void
+idm_conn_tx_pdu_event(struct idm_conn_s *ic, idm_conn_event_t event,
+ uintptr_t data);
+
+void
+idm_conn_rx_pdu_event(struct idm_conn_s *ic, idm_conn_event_t event,
+ uintptr_t data);
+
+char *
+idm_conn_state_str(struct idm_conn_s *ic);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IDM_CONN_SM_H_ */
diff --git a/usr/src/uts/common/sys/idm/idm_impl.h b/usr/src/uts/common/sys/idm/idm_impl.h
new file mode 100644
index 0000000000..e18472a18d
--- /dev/null
+++ b/usr/src/uts/common/sys/idm/idm_impl.h
@@ -0,0 +1,479 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+#ifndef _IDM_IMPL_H_
+#define _IDM_IMPL_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/avl.h>
+#include <sys/socket_impl.h>
+
+/*
+ * IDM lock order:
+ *
+ * idm_taskid_table_lock, idm_task_t.idt_mutex
+ */
+
+#define CF_LOGIN_READY 0x00000001
+#define CF_INITIAL_LOGIN 0x00000002
+#define CF_ERROR 0x80000000
+
+typedef enum {
+ CONN_TYPE_INI = 1,
+ CONN_TYPE_TGT
+} idm_conn_type_t;
+
+/*
+ * Watchdog interval in seconds
+ */
+#define IDM_WD_INTERVAL 5
+
+/*
+ * Timeout period before a TRANSPORT_FAIL event is generated in seconds
+ * if the connection is idle.
+ */
+#define IDM_TRANSPORT_FAIL_IDLE_TIMEOUT 30
+
+/*
+ * IDM reference count structure. Audit code is shamelessly adapted
+ * from CIFS server.
+ */
+
+#define REFCNT_AUDIT_STACK_DEPTH 16
+#define REFCNT_AUDIT_BUF_MAX_REC 16
+
+typedef struct {
+ uint32_t anr_refcnt;
+ int anr_depth;
+ pc_t anr_stack[REFCNT_AUDIT_STACK_DEPTH];
+} refcnt_audit_record_t;
+
+typedef struct {
+ int anb_index;
+ int anb_max_index;
+ refcnt_audit_record_t anb_records[REFCNT_AUDIT_BUF_MAX_REC];
+} refcnt_audit_buf_t;
+
+#define REFCNT_AUDIT(_rf_) { \
+ refcnt_audit_record_t *anr; \
+ \
+ anr = (_rf_)->ir_audit_buf.anb_records; \
+ anr += (_rf_)->ir_audit_buf.anb_index; \
+ (_rf_)->ir_audit_buf.anb_index++; \
+ (_rf_)->ir_audit_buf.anb_index &= \
+ (_rf_)->ir_audit_buf.anb_max_index; \
+ anr->anr_refcnt = (_rf_)->ir_refcnt; \
+ anr->anr_depth = getpcstack(anr->anr_stack, \
+ REFCNT_AUDIT_STACK_DEPTH); \
+}
+
+struct idm_refcnt_s;
+
+typedef void (idm_refcnt_cb_t)(void *ref_obj);
+
+typedef enum {
+ REF_NOWAIT,
+ REF_WAIT_SYNC,
+ REF_WAIT_ASYNC
+} idm_refcnt_wait_t;
+
+typedef struct idm_refcnt_s {
+ int ir_refcnt;
+ void *ir_referenced_obj;
+ idm_refcnt_wait_t ir_waiting;
+ kmutex_t ir_mutex;
+ kcondvar_t ir_cv;
+ idm_refcnt_cb_t *ir_cb;
+ refcnt_audit_buf_t ir_audit_buf;
+} idm_refcnt_t;
+
+/*
+ * connection parameters - These parameters would be populated at
+ * connection create, or during key-value negotiation at login
+ */
+typedef struct idm_conn_params_s {
+ uint32_t max_dataseglen;
+} idm_conn_param_t;
+
+typedef struct idm_svc_s {
+ list_node_t is_list_node;
+ kmutex_t is_mutex;
+ kcondvar_t is_cv;
+ kmutex_t is_count_mutex;
+ kcondvar_t is_count_cv;
+ idm_refcnt_t is_refcnt;
+ int is_online;
+ /* transport-specific service components */
+ void *is_so_svc;
+ void *is_iser_svc;
+ idm_svc_req_t is_svc_req;
+} idm_svc_t;
+
+typedef struct idm_conn_s {
+ list_node_t ic_list_node;
+ void *ic_handle;
+ idm_refcnt_t ic_refcnt;
+ idm_svc_t *ic_svc_binding; /* Target conn. only */
+ idm_sockaddr_t ic_ini_dst_addr;
+ struct sockaddr_storage ic_laddr; /* conn local address */
+ struct sockaddr_storage ic_raddr; /* conn remote address */
+ idm_conn_state_t ic_state;
+ idm_conn_state_t ic_last_state;
+ sm_audit_buf_t ic_state_audit;
+ kmutex_t ic_state_mutex;
+ kcondvar_t ic_state_cv;
+ uint32_t ic_state_flags;
+ timeout_id_t ic_state_timeout;
+ struct idm_conn_s *ic_reinstate_conn; /* For conn reinst. */
+ struct idm_conn_s *ic_logout_conn; /* For other conn logout */
+ taskq_t *ic_state_taskq;
+ int ic_pdu_events;
+ boolean_t ic_login_info_valid;
+ boolean_t ic_rdma_extensions;
+ uint16_t ic_login_cid;
+
+ kmutex_t ic_mutex;
+ kcondvar_t ic_cv;
+ idm_status_t ic_conn_sm_status;
+
+ boolean_t ic_ffp;
+ uint32_t ic_internal_cid;
+
+ uint32_t ic_conn_flags;
+ idm_conn_type_t ic_conn_type;
+ idm_conn_ops_t ic_conn_ops;
+ idm_transport_ops_t *ic_transport_ops;
+ idm_transport_type_t ic_transport_type;
+ int ic_transport_hdrlen;
+ void *ic_transport_private;
+ idm_conn_param_t ic_conn_params;
+ /*
+ * Save client callback to interpose idm callback
+ */
+ idm_pdu_cb_t *ic_client_callback;
+ clock_t ic_timestamp;
+} idm_conn_t;
+
+#define IDM_CONN_HEADER_DIGEST 0x00000001
+#define IDM_CONN_DATA_DIGEST 0x00000002
+#define IDM_CONN_USE_SCOREBOARD 0x00000004
+
+#define IDM_CONN_ISINI(ICI_IC) ((ICI_IC)->ic_conn_type == CONN_TYPE_INI)
+#define IDM_CONN_ISTGT(ICI_IC) ((ICI_IC)->ic_conn_type == CONN_TYPE_TGT)
+
+/*
+ * An IDM target task can transfer data using multiple buffers. The task
+ * will maintain a list of buffers, and each buffer will contain the relative
+ * offset of the transfer and a pointer to the next buffer in the list.
+ *
+ * Note on client private data:
+ * idt_private is intended to be a pointer to some sort of client-
+ * specific state.
+ *
+ * idt_client_handle is a more generic client-private piece of data that can
+ * be used by the client for the express purpose of task lookup. The driving
+ * use case for this is for the client to store the initiator task tag for
+ * a given task so that it may be more easily retrieved for task management.
+ *
+ * The key take away here is that clients should never call
+ * idm_task_find_by_handle in the performance path.
+ *
+ * An initiator will require only one buffer per task, the offset will be 0.
+ */
+
+typedef struct idm_task_s {
+ idm_conn_t *idt_ic; /* Associated connection */
+ /* connection type is in idt_ic->ic_conn_type */
+ kmutex_t idt_mutex;
+ void *idt_private; /* Client private data */
+ uintptr_t idt_client_handle; /* Client private */
+ uint32_t idt_tt; /* Task tag */
+ uint32_t idt_r2t_ttt; /* R2T Target Task tag */
+ idm_task_state_t idt_state;
+ idm_refcnt_t idt_refcnt;
+
+ /*
+ * Statistics
+ */
+ int idt_tx_to_ini_start;
+ int idt_tx_to_ini_done;
+ int idt_rx_from_ini_start;
+ int idt_rx_from_ini_done;
+
+ uint32_t idt_exp_datasn; /* expected datasn */
+ uint32_t idt_exp_rttsn; /* expected rttsn */
+ list_t idt_inbufv; /* chunks of IN buffers */
+ list_t idt_outbufv; /* chunks of OUT buffers */
+
+ /*
+ * Transport header, which describes this tasks remote tagged buffer
+ */
+ int idt_transport_hdrlen;
+ void *idt_transport_hdr;
+} idm_task_t;
+
+int idm_task_constructor(void *task_void, void *arg, int flags);
+void idm_task_destructor(void *task_void, void *arg);
+
+#define IDM_TASKIDS_MAX 16384
+#define IDM_BUF_MAGIC 0x49425546 /* "IBUF" */
+
+/* Protect with task mutex */
+typedef struct idm_buf_s {
+ uint32_t idb_magic; /* "IBUF" */
+
+ /*
+ * Note: idm_tx_link *must* be the second element in the list for
+ * proper TX PDU ordering.
+ */
+ list_node_t idm_tx_link; /* link in a list of TX objects */
+
+ list_node_t idb_buflink; /* link in a multi-buffer data xfer */
+ idm_conn_t *idb_ic; /* Associated connection */
+ void *idb_buf; /* data */
+ uint64_t idb_buflen; /* length of buffer */
+ size_t idb_bufoffset; /* offset in a multi-buffer xfer */
+ boolean_t idb_bufalloc; /* true if alloc'd in idm_buf_alloc */
+ /*
+ * DataPDUInOrder=Yes, so to track that the PDUs in a sequence are sent
+ * in continuously increasing address order, check that offsets for a
+ * single buffer xfer are in order.
+ */
+ uint32_t idb_exp_offset;
+ size_t idb_xfer_len; /* Current requested xfer len */
+ void *idb_buf_private; /* transport-specific buf handle */
+ void *idb_reg_private; /* transport-specific reg handle */
+ idm_buf_cb_t *idb_buf_cb; /* Data Completion Notify, tgt only */
+ void *idb_cb_arg; /* Client private data */
+ idm_task_t *idb_task_binding;
+ boolean_t idb_in_transport;
+ boolean_t idb_tx_thread; /* Sockets only */
+ iscsi_hdr_t idb_data_hdr_tmpl; /* Sockets only */
+ idm_status_t idb_status;
+} idm_buf_t;
+
+#define PDU_MAX_IOVLEN 12
+#define IDM_PDU_MAGIC 0x49504455 /* "IPDU" */
+
+typedef struct idm_pdu_s {
+ uint32_t isp_magic; /* "IPDU" */
+
+ /*
+ * Internal - Order is vital. idm_tx_link *must* be the second
+ * element in this structure for proper TX PDU ordering.
+ */
+ list_node_t idm_tx_link;
+
+ list_node_t isp_client_lnd;
+
+ idm_conn_t *isp_ic; /* Must be set */
+ iscsi_hdr_t *isp_hdr;
+ uint_t isp_hdrlen;
+ uint8_t *isp_data;
+ uint_t isp_datalen;
+
+ /* Transport header */
+ void *isp_transport_hdr;
+ uint32_t isp_transport_hdrlen;
+ void *isp_transport_private;
+
+ /*
+ * isp_data is used for sending SCSI status, NOP, text, scsi and
+ * non-scsi data. Data is received using isp_iov and isp_iovlen
+ * to support data over multiple buffers.
+ */
+ void *isp_private;
+ idm_pdu_cb_t *isp_callback;
+ idm_status_t isp_status;
+
+ /*
+ * The following four elements are only used in
+ * idm_sorecv_scsidata() currently.
+ */
+ struct iovec isp_iov[PDU_MAX_IOVLEN];
+ int isp_iovlen;
+ idm_buf_t *isp_sorx_buf;
+
+ /* Implementation data for idm_pdu_alloc and sorx PDU cache */
+ uint32_t isp_flags;
+ uint_t isp_hdrbuflen;
+ uint_t isp_databuflen;
+} idm_pdu_t;
+
+/*
+ * This "generic" object is used when removing an item from the ic_tx_list
+ * in order to determine whether it's an idm_pdu_t or an idm_buf_t
+ */
+
+typedef struct {
+ uint32_t idm_tx_obj_magic;
+ /*
+ * idm_tx_link *must* be the second element in this structure.
+ */
+ list_node_t idm_tx_link;
+} idm_tx_obj_t;
+
+
+#define IDM_PDU_OPCODE(PDU) \
+ ((PDU)->isp_hdr->opcode & ISCSI_OPCODE_MASK)
+
+#define IDM_PDU_ALLOC 0x00000001
+#define IDM_PDU_ADDL_HDR 0x00000002
+#define IDM_PDU_ADDL_DATA 0x00000004
+#define IDM_PDU_LOGIN_TX 0x00000008
+
+#define OSD_EXT_CDB_AHSLEN (200 - 15)
+#define BIDI_AHS_LENGTH 5
+#define IDM_SORX_CACHE_AHSLEN \
+ (((OSD_EXT_CDB_AHSLEN + 3) + \
+ (BIDI_AHS_LENGTH + 3)) / sizeof (uint32_t))
+#define IDM_SORX_CACHE_HDRLEN (sizeof (iscsi_hdr_t) + IDM_SORX_CACHE_AHSLEN)
+
+/*
+ * ID pool
+ */
+
+#define IDM_IDPOOL_MAGIC 0x4944504C /* IDPL */
+#define IDM_IDPOOL_MIN_SIZE 64 /* Number of IDs to begin with */
+#define IDM_IDPOOL_MAX_SIZE 64 * 1024
+
+typedef struct idm_idpool {
+ uint32_t id_magic;
+ kmutex_t id_mutex;
+ uint8_t *id_pool;
+ uint32_t id_size;
+ uint8_t id_bit;
+ uint8_t id_bit_idx;
+ uint32_t id_idx;
+ uint32_t id_idx_msk;
+ uint32_t id_free_counter;
+ uint32_t id_max_free_counter;
+} idm_idpool_t;
+
+/*
+ * Global IDM state structure
+ */
+typedef struct {
+ kmutex_t idm_global_mutex;
+ taskq_t *idm_global_taskq;
+ kthread_t *idm_wd_thread;
+ kt_did_t idm_wd_thread_did;
+ boolean_t idm_wd_thread_running;
+ kcondvar_t idm_wd_cv;
+ list_t idm_tgt_svc_list;
+ kcondvar_t idm_tgt_svc_cv;
+ list_t idm_tgt_conn_list;
+ int idm_tgt_conn_count;
+ list_t idm_ini_conn_list;
+ kmem_cache_t *idm_buf_cache;
+ kmem_cache_t *idm_task_cache;
+ krwlock_t idm_taskid_table_lock;
+ idm_task_t **idm_taskid_table;
+ uint32_t idm_taskid_next;
+ uint32_t idm_taskid_max;
+ idm_idpool_t idm_conn_id_pool;
+ kmem_cache_t *idm_sotx_pdu_cache;
+ kmem_cache_t *idm_sorx_pdu_cache;
+} idm_global_t;
+
+idm_global_t idm; /* Global state */
+
+int
+idm_idpool_create(idm_idpool_t *pool);
+
+void
+idm_idpool_destroy(idm_idpool_t *pool);
+
+int
+idm_idpool_alloc(idm_idpool_t *pool, uint16_t *id);
+
+void
+idm_idpool_free(idm_idpool_t *pool, uint16_t id);
+
+void
+idm_pdu_rx(idm_conn_t *ic, idm_pdu_t *pdu);
+
+void
+idm_pdu_tx_forward(idm_conn_t *ic, idm_pdu_t *pdu);
+
+boolean_t
+idm_pdu_rx_forward_ffp(idm_conn_t *ic, idm_pdu_t *pdu);
+
+void
+idm_pdu_rx_forward(idm_conn_t *ic, idm_pdu_t *pdu);
+
+void
+idm_pdu_tx_protocol_error(idm_conn_t *ic, idm_pdu_t *pdu);
+
+void
+idm_pdu_rx_protocol_error(idm_conn_t *ic, idm_pdu_t *pdu);
+
+void idm_parse_login_rsp(idm_conn_t *ic, idm_pdu_t *logout_req_pdu,
+ boolean_t rx);
+
+void idm_parse_logout_req(idm_conn_t *ic, idm_pdu_t *logout_req_pdu,
+ boolean_t rx);
+
+void idm_parse_logout_rsp(idm_conn_t *ic, idm_pdu_t *login_rsp_pdu,
+ boolean_t rx);
+
+idm_status_t idm_svc_conn_create(idm_svc_t *is, idm_transport_type_t type,
+ idm_conn_t **ic_result);
+
+void idm_svc_conn_destroy(idm_conn_t *ic);
+
+idm_status_t idm_ini_conn_finish(idm_conn_t *ic);
+
+idm_status_t idm_tgt_conn_finish(idm_conn_t *ic);
+
+idm_conn_t *idm_conn_create_common(idm_conn_type_t conn_type,
+ idm_transport_type_t tt, idm_conn_ops_t *conn_ops);
+
+void idm_conn_destroy_common(idm_conn_t *ic);
+
+void idm_conn_close(idm_conn_t *ic);
+
+uint32_t idm_cid_alloc(void);
+
+void idm_cid_free(uint32_t cid);
+
+uint32_t idm_crc32c(void *address, unsigned long length);
+
+uint32_t idm_crc32c_continued(void *address, unsigned long length,
+ uint32_t crc);
+
+void idm_listbuf_insert(list_t *lst, idm_buf_t *buf);
+
+int idm_task_compare(const void *v1, const void *v2);
+
+idm_conn_t *idm_lookup_conn(uint8_t *isid, uint16_t tsih, uint16_t cid);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IDM_IMPL_H_ */
diff --git a/usr/src/uts/common/sys/idm/idm_so.h b/usr/src/uts/common/sys/idm/idm_so.h
new file mode 100644
index 0000000000..134896ed4f
--- /dev/null
+++ b/usr/src/uts/common/sys/idm/idm_so.h
@@ -0,0 +1,110 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _IDM_SO_H
+#define _IDM_SO_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/idm/idm_transport.h>
+
+/*
+ * Define TCP window size (send and receive buffer sizes)
+ */
+
+#define IDM_RCVBUF_SIZE (256 * 1024)
+#define IDM_SNDBUF_SIZE (256 * 1024)
+
+/* sockets-specific portion of idm_svc_t */
+typedef struct idm_so_svc_s {
+ struct sonode *is_so;
+ kthread_t *is_thread;
+ kt_did_t is_thread_did;
+ boolean_t is_thread_running;
+} idm_so_svc_t;
+
+/* sockets-specific portion of idm_conn_t */
+typedef struct idm_so_conn_s {
+ struct sonode *ic_so;
+
+ kthread_t *ic_tx_thread;
+ kt_did_t ic_tx_thread_did;
+ boolean_t ic_tx_thread_running;
+ kmutex_t ic_tx_mutex;
+ kcondvar_t ic_tx_cv;
+ list_t ic_tx_list; /* List of PDUs for transmit */
+
+ kthread_t *ic_rx_thread;
+ kt_did_t ic_rx_thread_did;
+ boolean_t ic_rx_thread_running;
+} idm_so_conn_t;
+
+void idm_so_init(idm_transport_t *it);
+void idm_so_fini();
+
+/* Socket functions */
+
+struct sonode *
+idm_socreate(int domain, int type, int protocol);
+
+void idm_soshutdown(struct sonode *so);
+
+void idm_sodestroy(struct sonode *so);
+
+int idm_get_ipaddr(idm_addr_list_t **);
+
+int idm_sorecv(struct sonode *so, void *msg, size_t len);
+
+int idm_sosendto(struct sonode *so, void *buff, size_t len,
+ struct sockaddr *name, socklen_t namelen);
+
+int idm_iov_sosend(struct sonode *so, iovec_t *iop, int iovlen,
+ size_t total_len);
+
+int idm_iov_sorecv(struct sonode *so, iovec_t *iop, int iovlen,
+ size_t total_len);
+
+void idm_sotx_thread(void *arg);
+void idm_sorx_thread(void *arg);
+
+
+int idm_sotx_pdu_constructor(void *hdl, void *arg, int flags);
+
+void idm_sotx_pdu_destructor(void *pdu_void, void *arg);
+
+int idm_sorx_pdu_constructor(void *hdl, void *arg, int flags);
+
+void idm_sorx_pdu_destructor(void *pdu_void, void *arg);
+
+void idm_so_svc_port_watcher(void *arg);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IDM_SO_H */
diff --git a/usr/src/uts/common/sys/idm/idm_text.h b/usr/src/uts/common/sys/idm/idm_text.h
new file mode 100644
index 0000000000..35649466a7
--- /dev/null
+++ b/usr/src/uts/common/sys/idm/idm_text.h
@@ -0,0 +1,198 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+#ifndef _IDM_TEXT_H_
+#define _IDM_TEXT_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/idm/idm_impl.h>
+
+/*
+ * Numerical identifiers for iSCSI name-value pair keys (just so that
+ * we can use case statements to handle a particular key-value pair
+ * after we find it in idm_kvpair_xlate).
+ *
+ * We want to use a bitmask to keep track of negotiated key-value pairs
+ * so keep this enum under 64 values -- or spend some time reworking the
+ * login code.
+ */
+typedef enum {
+ KI_AUTH_METHOD = 1,
+ KI_KRB_AP_REQ,
+ KI_KRB_AP_REP,
+
+ /* SPKM */
+ KI_SPKM_REQ,
+ KI_SPKM_ERROR,
+ KI_SPKM_REP_TI,
+ KI_SPKM_REP_IT,
+
+ /*
+ * SRP
+ */
+ KI_SRP_U,
+ KI_TARGET_AUTH,
+ KI_SRP_GROUP,
+ KI_SRP_A,
+ KI_SRP_B,
+ KI_SRP_M,
+ KI_SRM_HM,
+
+ /*
+ * CHAP
+ */
+ KI_CHAP_A,
+ KI_CHAP_I,
+ KI_CHAP_C,
+ KI_CHAP_N,
+ KI_CHAP_R,
+
+
+ /*
+ * ISCSI Operational Parameter Keys
+ */
+ KI_HEADER_DIGEST,
+ KI_DATA_DIGEST,
+ KI_MAX_CONNECTIONS,
+ KI_SEND_TARGETS,
+ KI_TARGET_NAME,
+ KI_INITIATOR_NAME,
+ KI_TARGET_ALIAS,
+ KI_INITIATOR_ALIAS,
+ KI_TARGET_ADDRESS,
+ KI_TARGET_PORTAL_GROUP_TAG,
+ KI_INITIAL_R2T,
+ KI_IMMEDIATE_DATA,
+ KI_MAX_RECV_DATA_SEGMENT_LENGTH,
+ KI_MAX_BURST_LENGTH,
+ KI_FIRST_BURST_LENGTH,
+ KI_DEFAULT_TIME_2_WAIT,
+ KI_DEFAULT_TIME_2_RETAIN,
+ KI_MAX_OUTSTANDING_R2T,
+ KI_DATA_PDU_IN_ORDER,
+ KI_DATA_SEQUENCE_IN_ORDER,
+ KI_ERROR_RECOVERY_LEVEL,
+ KI_SESSION_TYPE,
+ KI_OFMARKER,
+ KI_OFMARKERINT,
+ KI_IFMARKER,
+ KI_IFMARKERINT,
+
+ /*
+ * iSER-specific keys
+ */
+ KI_RDMA_EXTENSIONS,
+ KI_TARGET_RECV_DATA_SEGMENT_LENGTH,
+ KI_INITIATOR_RECV_DATA_SEGMENT_LENGTH,
+ KI_MAX_OUTSTANDING_UNEXPECTED_PDUS,
+
+ /*
+ * End of list marker, no keys below here.
+ */
+ KI_MAX_KEY
+} iscsikey_id_t;
+
+/* Numerical types for iSCSI name-value pair values */
+typedef enum {
+ KT_TEXT,
+ KT_ISCSI_NAME,
+ KT_ISCSI_LOCAL_NAME,
+ KT_BOOLEAN,
+ KT_NUMERICAL, /* Hex or decimal constant */
+ KT_LARGE_NUMERICAL, /* Hex, decimal or Base64 constant */
+ KT_NUMERIC_RANGE,
+ KT_REGULAR_BINARY, /* Hex, decimal, base64 not longer than 64 bits */
+ KT_LARGE_BINARY, /* Hex, decimal, base64 longer than 64 bites */
+ KT_BINARY, /* Regular binary or large binary */
+ KT_SIMPLE,
+ KT_LIST_OF_VALUES
+} idmkey_type_t;
+
+typedef struct {
+ iscsikey_id_t ik_key_id;
+ char *ik_key_name;
+ idmkey_type_t ik_idm_type; /* RFC type */
+ boolean_t ik_declarative;
+} idm_kv_xlate_t;
+
+const idm_kv_xlate_t *
+idm_lookup_kv_xlate(const char *key, int keylen);
+
+int
+idm_nvlist_add_keyvalue(nvlist_t *nvl, char *key, int keylen, char *value);
+
+int
+idm_textbuf_to_nvlist(nvlist_t *nvl, char **textbuf, int *textbuflen);
+
+int
+idm_textbuf_to_firstfraglen(void *textbuf, int textbuflen);
+
+int
+idm_nvlist_to_textbuf(nvlist_t *nvl, char **textbuf, int *textbuflen,
+ int *tblen_required);
+
+kv_status_t
+idm_nvstat_to_kvstat(int nvrc);
+
+void
+idm_kvstat_to_error(kv_status_t kvrc, uint8_t *class, uint8_t *detail);
+
+int
+idm_nvlist_add_id(nvlist_t *nvl, iscsikey_id_t kv_id, char *value);
+
+nvpair_t *
+idm_get_next_listvalue(nvpair_t *value_list, nvpair_t *curr_nvp);
+
+char *
+idm_id_to_name(iscsikey_id_t kv_id);
+
+char *
+idm_nvpair_value_to_textbuf(nvpair_t *nvp);
+
+idm_status_t
+idm_pdu_list_to_nvlist(list_t *pdu_list, nvlist_t **nvlist,
+ uint8_t *error_detail);
+
+void *
+idm_nvlist_to_itextbuf(nvlist_t *nvl);
+
+char *
+idm_pdu_init_text_data(idm_pdu_t *pdu, void *arg,
+ int max_xfer_len, char *bufptr, int *transit);
+
+void
+idm_itextbuf_free(void *arg);
+
+int
+idm_strtoull(const char *str, char **nptr, int base,
+ unsigned long long *result);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IDM_TEXT_H_ */
diff --git a/usr/src/uts/common/sys/idm/idm_transport.h b/usr/src/uts/common/sys/idm/idm_transport.h
new file mode 100644
index 0000000000..0f763f9636
--- /dev/null
+++ b/usr/src/uts/common/sys/idm/idm_transport.h
@@ -0,0 +1,221 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _IDM_TRANSPORT_H_
+#define _IDM_TRANSPORT_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/nvpair.h>
+#include <sys/sunldi.h>
+
+#define IDM_TRANSPORT_PATHLEN 0x40
+
+/* Note, this is tied to iSER currently */
+#define IDM_TRANSPORT_HEADER_LENGTH 0x20
+
+/*
+ * idm_transport_type_t
+ * An enumerated list of the transports available to iSER.
+ * Note that new transports should be added to the enum prior to NUM_TYPES.
+ */
+typedef enum {
+ IDM_TRANSPORT_TYPE_ISER = 0,
+ IDM_TRANSPORT_TYPE_SOCKETS,
+ IDM_TRANSPORT_NUM_TYPES,
+ IDM_TRANSPORT_TYPE_UNDEFINED
+} idm_transport_type_t;
+
+/*
+ * idm_transport_caps_t
+ * Encodes a set of attributes describing an IDM transport's capabilities.
+ * JB - do we need this?
+ */
+typedef struct idm_transport_caps_s {
+ uint32_t flags;
+} idm_transport_caps_t;
+
+/*
+ * Transport routine definitions for idm_transport_ops_t below
+ */
+
+/* Send_Control - transmit a Control-type PDU */
+typedef void (transport_tx_op_t)(struct idm_conn_s *ic, struct idm_pdu_s *pdu);
+
+/*
+ * Target transport data primitives, caller (IDM) holds idt->idt_mutex,
+ * and the transport should release the mutex before returning.
+ */
+typedef idm_status_t (transport_buf_tx_to_ini_op_t)(struct idm_task_s *idt,
+ struct idm_buf_s *idb);
+typedef idm_status_t (transport_buf_rx_from_ini_op_t)(struct idm_task_s *idt,
+ struct idm_buf_s *idb);
+
+/* Initiator transport data handlers */
+typedef void (transport_rx_datain_op_t)(struct idm_conn_s *ic,
+ struct idm_pdu_s *pdu);
+typedef void (transport_rx_rtt_op_t)(struct idm_conn_s *ic,
+ struct idm_pdu_s *pdu);
+
+/* Target transport Data-out handler */
+typedef void (transport_rx_dataout_op_t)(struct idm_conn_s *ic,
+ struct idm_pdu_s *pdu);
+
+/* Transport-specific resource allocation and free */
+typedef idm_status_t (transport_alloc_conn_rsrc_op_t)(struct idm_conn_s *ic);
+typedef idm_status_t (transport_free_conn_rsrc_op_t)(struct idm_conn_s *ic);
+
+/* Transport driver operations enable/disable */
+typedef idm_status_t (transport_tgt_enable_datamover_op_t)(struct
+ idm_conn_s *ic);
+typedef idm_status_t (transport_ini_enable_datamover_op_t)(struct
+ idm_conn_s *ic);
+typedef idm_status_t (transport_conn_terminate_op_t)(struct idm_conn_s *ic);
+
+/* Task resource cleanup */
+typedef idm_status_t (transport_free_task_rsrcs_op_t)(struct idm_task_s *it);
+
+/* Negotiate key value pairs */
+typedef kv_status_t (transport_negotiate_key_values_op_t)(struct
+ idm_conn_s *ic, nvlist_t *request_nvl, nvlist_t *response_nvl,
+ nvlist_t *negotiated_nvl);
+
+/* Activate the negotiated key value pairs */
+typedef idm_status_t (transport_notice_key_values_op_t)(struct idm_conn_s *ic,
+ nvlist_t *negotiated_nvl);
+
+/* Transport capability probe */
+typedef boolean_t (transport_conn_is_capable_op_t)(idm_conn_req_t *ic,
+ struct idm_transport_caps_s *caps);
+
+/* Transport buffer services */
+typedef idm_status_t (transport_buf_alloc_op_t)(struct idm_buf_s *idb,
+ uint64_t buflen);
+typedef idm_status_t (transport_buf_setup_op_t)(struct idm_buf_s *idb);
+typedef void (transport_buf_teardown_op_t)(struct idm_buf_s *idb);
+typedef void (transport_buf_free_op_t)(struct idm_buf_s *idb);
+
+/* Transport target context and service management services */
+typedef idm_status_t (transport_tgt_svc_create_op_t)(idm_svc_req_t *sr,
+ struct idm_svc_s *is);
+typedef void (transport_tgt_svc_destroy_op_t)(struct idm_svc_s *is);
+typedef idm_status_t (transport_tgt_svc_online_op_t)(struct idm_svc_s *is);
+typedef void (transport_tgt_svc_offline_op_t)(struct idm_svc_s *is);
+
+/* Transport target connection establishment */
+typedef void (transport_tgt_conn_destroy_op_t)(struct idm_conn_s *ic);
+typedef idm_status_t (transport_tgt_conn_connect_op_t)(struct idm_conn_s *ic);
+typedef void (transport_tgt_conn_disconnect_op_t)(struct idm_conn_s *ic);
+
+/* Transport initiator context and connection management services */
+typedef idm_status_t (transport_ini_conn_create_op_t)(idm_conn_req_t *cr,
+ struct idm_conn_s *ic);
+typedef void (transport_ini_conn_destroy_op_t)(struct idm_conn_s *ic);
+typedef idm_status_t (transport_ini_conn_connect_op_t)(struct idm_conn_s *ic);
+typedef void (transport_ini_conn_disconnect_op_t)(struct idm_conn_s *ic);
+
+
+/*
+ * idm_transport_ops_t
+ * Encodes a set of vectors into an IDM transport driver that implement the
+ * transport-specific Datamover operations for IDM usage. These routines are
+ * invoked by the IDM layer to execute the transport-specific implementations
+ * of the DataMover primitives and supporting routines.
+ */
+typedef struct idm_transport_ops_s {
+ transport_tx_op_t *it_tx_pdu;
+ transport_buf_tx_to_ini_op_t *it_buf_tx_to_ini;
+ transport_buf_rx_from_ini_op_t *it_buf_rx_from_ini;
+ transport_rx_datain_op_t *it_rx_datain;
+ transport_rx_rtt_op_t *it_rx_rtt;
+ transport_rx_dataout_op_t *it_rx_dataout;
+ transport_alloc_conn_rsrc_op_t *it_alloc_conn_rsrc;
+ transport_free_conn_rsrc_op_t *it_free_conn_rsrc;
+ transport_tgt_enable_datamover_op_t *it_tgt_enable_datamover;
+ transport_ini_enable_datamover_op_t *it_ini_enable_datamover;
+ transport_conn_terminate_op_t *it_conn_terminate;
+ transport_free_task_rsrcs_op_t *it_free_task_rsrc;
+ transport_negotiate_key_values_op_t *it_negotiate_key_values;
+ transport_notice_key_values_op_t *it_notice_key_values;
+ transport_conn_is_capable_op_t *it_conn_is_capable;
+ transport_buf_alloc_op_t *it_buf_alloc;
+ transport_buf_free_op_t *it_buf_free;
+ transport_buf_setup_op_t *it_buf_setup;
+ transport_buf_teardown_op_t *it_buf_teardown;
+ transport_tgt_svc_create_op_t *it_tgt_svc_create;
+ transport_tgt_svc_destroy_op_t *it_tgt_svc_destroy;
+ transport_tgt_svc_online_op_t *it_tgt_svc_online;
+ transport_tgt_svc_offline_op_t *it_tgt_svc_offline;
+ transport_tgt_conn_destroy_op_t *it_tgt_conn_destroy;
+ transport_tgt_conn_connect_op_t *it_tgt_conn_connect;
+ transport_tgt_conn_disconnect_op_t *it_tgt_conn_disconnect;
+ transport_ini_conn_create_op_t *it_ini_conn_create;
+ transport_ini_conn_destroy_op_t *it_ini_conn_destroy;
+ transport_ini_conn_connect_op_t *it_ini_conn_connect;
+ transport_ini_conn_disconnect_op_t *it_ini_conn_disconnect;
+} idm_transport_ops_t;
+
+/*
+ * idm_transport_t encodes all of the data related to an IDM transport
+ * type. In addition to type and capabilities, it also stores a pointer
+ * to the connection and transport operation implementations, and also
+ * it stores the LDI handle.
+ */
+typedef struct idm_transport_s {
+ idm_transport_type_t it_type;
+ char *it_device_path;
+ ldi_handle_t it_ldi_hdl;
+ idm_transport_ops_t *it_ops;
+ idm_transport_caps_t *it_caps;
+} idm_transport_t;
+
+/*
+ * idm_transport_attr_t encodes details of a transport driver seeking
+ * registration with the IDM kernel module.
+ */
+typedef struct idm_transport_attr_s {
+ idm_transport_type_t type;
+ idm_transport_ops_t *it_ops;
+ idm_transport_caps_t *it_caps;
+} idm_transport_attr_t;
+
+/* IDM transport API */
+idm_status_t
+idm_transport_register(idm_transport_attr_t *attr);
+
+idm_transport_t *
+idm_transport_lookup(idm_conn_req_t *cr);
+
+void
+idm_transport_setup(ldi_ident_t li);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IDM_TRANSPORT_H_ */
diff --git a/usr/src/uts/common/sys/iscsi_protocol.h b/usr/src/uts/common/sys/iscsi_protocol.h
index 1f529c4d84..0da0730290 100644
--- a/usr/src/uts/common/sys/iscsi_protocol.h
+++ b/usr/src/uts/common/sys/iscsi_protocol.h
@@ -19,15 +19,13 @@
* CDDL HEADER END
*/
/*
- * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#ifndef _ISCSI_PROTOCOL_H
#define _ISCSI_PROTOCOL_H
-#pragma ident "%Z%%M% %I% %E% SMI"
-
#ifdef __cplusplus
extern "C" {
#endif
@@ -85,6 +83,12 @@ uint32_t iscsi_crc32c_continued(void *address, unsigned long length,
/* text separtor between key value pairs exhanged in login */
#define ISCSI_TEXT_SEPARATOR '='
+/* reserved text constants for Text Mode Negotiation */
+#define ISCSI_TEXT_NONE "None"
+#define ISCSI_TEXT_REJECT "Reject"
+#define ISCSI_TEXT_IRRELEVANT "Irrelevant"
+#define ISCSI_TEXT_NOTUNDERSTOOD "NotUnderstood"
+
/* Sun's initiator session ID */
#define ISCSI_SUN_ISID_0 0x40 /* ISID - EN format */
#define ISCSI_SUN_ISID_1 0x00 /* Sec B */
@@ -357,10 +361,18 @@ typedef struct _iscsi_scsi_task_mgt_rsp_hdr {
#define SCSI_TCP_TM_RESP_NO_TASK 0x01
#define SCSI_TCP_TM_RESP_NO_LUN 0x02
#define SCSI_TCP_TM_RESP_TASK_ALLEGIANT 0x03
-#define SCSI_TCP_TM_RESP_NO_FAILOVER 0x04
-#define SCSI_TCP_TM_RESP_IN_PRGRESS 0x05
+#define SCSI_TCP_TM_RESP_NO_ALLG_REASSN 0x04
+#define SCSI_TCP_TM_RESP_FUNC_NOT_SUPP 0x05
+#define SCSI_TCP_TM_RESP_FUNC_AUTH_FAIL 0x06
#define SCSI_TCP_TM_RESP_REJECTED 0xff
+/*
+ * Maintained for backward compatibility.
+ */
+
+#define SCSI_TCP_TM_RESP_NO_FAILOVER SCSI_TCP_TM_RESP_NO_ALLG_REASSN
+#define SCSI_TCP_TM_RESP_IN_PRGRESS SCSI_TCP_TM_RESP_FUNC_NOT_SUPP
+
/* Ready To Transfer Header */
typedef struct _iscsi_rtt_hdr {
uint8_t opcode;
@@ -464,6 +476,8 @@ typedef struct _iscsi_text_rsp_hdr {
*/
} iscsi_text_rsp_hdr_t;
+#define ISCSI_ISID_LEN 6
+
/* Login Header */
typedef struct _iscsi_login_hdr {
uint8_t opcode;
@@ -472,7 +486,7 @@ typedef struct _iscsi_login_hdr {
uint8_t min_version; /* Min. version supported */
uint8_t hlength;
uint8_t dlength[3];
- uint8_t isid[6]; /* Initiator Session ID */
+ uint8_t isid[ISCSI_ISID_LEN]; /* Initiator Session ID */
uint16_t tsid; /* Target Session ID */
uint32_t itt; /* Initiator Task Tag */
uint16_t cid;
@@ -502,7 +516,7 @@ typedef struct _iscsi_login_rsp_hdr {
uint8_t active_version; /* Active version */
uint8_t hlength;
uint8_t dlength[3];
- uint8_t isid[6]; /* Initiator Session ID */
+ uint8_t isid[ISCSI_ISID_LEN]; /* Initiator Session ID */
uint16_t tsid; /* Target Session ID */
uint32_t itt; /* Initiator Task Tag */
uint32_t rsvd3;
@@ -631,7 +645,9 @@ typedef struct _iscsi_reject_rsp_hdr {
uint8_t rsvd2;
uint8_t rsvd3;
uint8_t dlength[3];
- uint8_t rsvd4[16];
+ uint8_t rsvd4[8];
+ uint8_t must_be_ff[4];
+ uint8_t rsvd4a[4];
uint32_t statsn;
uint32_t expcmdsn;
uint32_t maxcmdsn;
@@ -676,6 +692,19 @@ typedef struct _iscsi_reject_rsp_hdr {
#define ISCSI_DEFAULT_OFMARKER FALSE
/*
+ * Minimum values from the iSCSI specification
+ */
+
+#define ISCSI_MIN_TIME2RETAIN 0
+#define ISCSI_MIN_TIME2WAIT 0
+#define ISCSI_MIN_ERROR_RECOVERY_LEVEL 0
+#define ISCSI_MIN_RECV_DATA_SEGMENT_LENGTH 0x200
+#define ISCSI_MIN_FIRST_BURST_LENGTH 0x200
+#define ISCSI_MIN_MAX_BURST_LENGTH 0x200
+#define ISCSI_MIN_CONNECTIONS 1
+#define ISCSI_MIN_MAX_OUTSTANDING_R2T 1
+
+/*
* Maximum values from the iSCSI specification
*/
#define ISCSI_MAX_HEADER_DIGEST 3
diff --git a/usr/src/uts/common/sys/iscsit/chap.h b/usr/src/uts/common/sys/iscsit/chap.h
new file mode 100644
index 0000000000..c1fe54005b
--- /dev/null
+++ b/usr/src/uts/common/sys/iscsit/chap.h
@@ -0,0 +1,94 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _CHAP_H
+#define _CHAP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <netinet/in.h>
+#include <sys/int_types.h>
+
+#include <sys/iscsit/iscsi_if.h>
+#include <sys/iscsit/radius_protocol.h>
+
+typedef enum chap_validation_status_type {
+ CHAP_VALIDATION_PASSED, /* CHAP validation passed */
+ CHAP_VALIDATION_INVALID_RESPONSE, /* Invalid CHAP response */
+ CHAP_VALIDATION_DUP_SECRET, /* Same CHAP secret used */
+ /* for authentication in the */
+ /* other direction */
+ CHAP_VALIDATION_UNKNOWN_AUTH_METHOD, /* Unknown authentication */
+ /* method */
+ CHAP_VALIDATION_INTERNAL_ERROR, /* MISC internal error */
+ CHAP_VALIDATION_RADIUS_ACCESS_ERROR, /* Problem accessing RADIUS */
+ CHAP_VALIDATION_BAD_RADIUS_SECRET, /* Invalid RADIUS shared */
+ /* secret */
+ CHAP_VALIDATION_UNKNOWN_RADIUS_CODE /* Irrelevant or unknown */
+ /* RADIUS packet code */
+ /* returned */
+} chap_validation_status_type;
+
+typedef enum authentication_method_type {
+ RADIUS_AUTHENTICATION,
+ DIRECT_AUTHENTICATION
+} authentication_method_type;
+
+typedef struct radius_config {
+ iscsi_ipaddr_t rad_svr_addr; /* IPv6 enabled */
+ uint32_t rad_svr_port;
+ uint8_t rad_svr_shared_secret[MAX_RAD_SHARED_SECRET_LEN];
+ uint32_t rad_svr_shared_secret_len;
+} RADIUS_CONFIG;
+
+/*
+ * To validate a target CHAP response given the associated challenge.
+ *
+ * target_chap_name - The CHAP name of the target being authenticated.
+ * initiator_chap_name - The CHAP name of the authenticating initiator.
+ * challenge - The CHAP challenge to which the target responded.
+ * target_response - The target's CHAP response to be validated.
+ * identifier - The identifier associated with the CHAP challenge.
+ * auth_method - The authentication method to be used.
+ * auth_config_data - Any required configuration data to support the
+ * specified authentication method.
+ */
+chap_validation_status_type
+chap_validate(
+ char *target_chap_name,
+ char *initiator_chap_name,
+ uint8_t *challenge,
+ uint8_t *target_response,
+ uint8_t identifier,
+ authentication_method_type auth_method,
+ void *auth_config_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _CHAP_H */
diff --git a/usr/src/uts/common/sys/iscsit/iscsi_if.h b/usr/src/uts/common/sys/iscsit/iscsi_if.h
new file mode 100644
index 0000000000..c290254638
--- /dev/null
+++ b/usr/src/uts/common/sys/iscsit/iscsi_if.h
@@ -0,0 +1,651 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _ISCSI_IF_H
+#define _ISCSI_IF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _KERNEL
+#include <sys/types.h>
+#include <sys/strsubr.h> /* for prototype of kstrgetmsg */
+#include <sys/socket.h>
+#include <sys/socketvar.h> /* for struct sonode */
+#endif
+#include <netinet/in.h>
+#include <sys/scsi/impl/uscsi.h>
+#include <sys/iscsi_protocol.h>
+
+/*
+ * Each of the top level structures have a version field as
+ * the first member. That version value will be set by the
+ * caller. The consumer of the structure will check to see
+ * if the version is correct.
+ */
+#define ISCSI_INTERFACE_VERSION 3
+
+/*
+ * The maximum length of an iSCSI name is 223. 224 is used
+ * to provide space for a null character.
+ */
+#define ISCSI_MAX_NAME_LEN 224
+
+/*
+ * Login parameter values are used instead of ascii text
+ * between the IMA plug-in and kernel.
+ */
+#define ISCSI_LOGIN_PARAM_DATA_SEQUENCE_IN_ORDER 0x0000 /* bool */
+#define ISCSI_LOGIN_PARAM_IMMEDIATE_DATA 0x0001 /* bool */
+#define ISCSI_LOGIN_PARAM_INITIAL_R2T 0x0002 /* bool */
+#define ISCSI_LOGIN_PARAM_DATA_PDU_IN_ORDER 0x0003 /* bool */
+#define ISCSI_LOGIN_PARAM_HEADER_DIGEST 0x0004 /* int */
+#define ISCSI_LOGIN_PARAM_DATA_DIGEST 0x0005 /* int */
+#define ISCSI_LOGIN_PARAM_DEFAULT_TIME_2_RETAIN 0x0006 /* int */
+#define ISCSI_LOGIN_PARAM_DEFAULT_TIME_2_WAIT 0x0007 /* int */
+#define ISCSI_LOGIN_PARAM_MAX_RECV_DATA_SEGMENT_LENGTH 0x0008 /* int */
+#define ISCSI_LOGIN_PARAM_FIRST_BURST_LENGTH 0x0009 /* int */
+#define ISCSI_LOGIN_PARAM_MAX_BURST_LENGTH 0x000A /* int */
+#define ISCSI_LOGIN_PARAM_MAX_CONNECTIONS 0x000B /* int */
+#define ISCSI_LOGIN_PARAM_OUTSTANDING_R2T 0x000C /* int */
+#define ISCSI_LOGIN_PARAM_ERROR_RECOVERY_LEVEL 0x000D /* int */
+/*
+ * number of login parameters - needs to be updated when new parameter added
+ */
+#define ISCSI_NUM_LOGIN_PARAM 0x000E
+
+/*
+ * Used internally by the persistent store code. Currently a bitmap is kept of
+ * which params are currently set. This allows for quick a look up instead of
+ * cycling through the possible entries. Using an unsigned int as the bitmap we
+ * can have parameter numbers up through 31. Since the current only has 22
+ * we're okay.
+ */
+#define ISCSI_LOGIN_PARAM_DB_ENTRY 0x0020
+/*
+ * Special case. When this parameter value is set in iscsi_param_set_t
+ * the member s_value (type iscsi_param_set_t) is not used.
+ * The name field contains the InitiatorName for the system which
+ * should be used for all future sessions.
+ */
+#define ISCSI_LOGIN_PARAM_INITIATOR_NAME 0x0021
+#define ISCSI_LOGIN_PARAM_INITIATOR_ALIAS 0x0022
+
+#define ISCSI_DEVCTL "devctl"
+#define ISCSI_DRIVER_DEVCTL "/devices/iscsi:" ISCSI_DEVCTL
+
+/*
+ * ioctls supported by the driver.
+ */
+#define ISCSI_IOCTL (('i' << 24) | ('S' << 16) | ('C' << 8))
+#define ISCSI_CREATE_OID (ISCSI_IOCTL | 2)
+#define ISCSI_LOGIN (ISCSI_IOCTL | 3)
+#define ISCSI_LOGOUT (ISCSI_IOCTL | 4)
+#define ISCSI_PARAM_GET (ISCSI_IOCTL | 5)
+#define ISCSI_PARAM_SET (ISCSI_IOCTL | 6)
+#define ISCSI_TARGET_PARAM_CLEAR (ISCSI_IOCTL | 8)
+#define ISCSI_TARGET_OID_LIST_GET (ISCSI_IOCTL | 9)
+#define ISCSI_TARGET_PROPS_GET (ISCSI_IOCTL | 10)
+#define ISCSI_TARGET_PROPS_SET (ISCSI_IOCTL | 11)
+#define ISCSI_TARGET_ADDRESS_GET (ISCSI_IOCTL | 12)
+#define ISCSI_CHAP_SET (ISCSI_IOCTL | 13)
+#define ISCSI_CHAP_GET (ISCSI_IOCTL | 14)
+#define ISCSI_CHAP_CLEAR (ISCSI_IOCTL | 15)
+#define ISCSI_STATIC_GET (ISCSI_IOCTL | 16)
+#define ISCSI_STATIC_SET (ISCSI_IOCTL | 17)
+#define ISCSI_STATIC_CLEAR (ISCSI_IOCTL | 18)
+#define ISCSI_DISCOVERY_SET (ISCSI_IOCTL | 19)
+#define ISCSI_DISCOVERY_GET (ISCSI_IOCTL | 20)
+#define ISCSI_DISCOVERY_CLEAR (ISCSI_IOCTL | 21)
+#define ISCSI_DISCOVERY_PROPS (ISCSI_IOCTL | 22)
+#define ISCSI_DISCOVERY_ADDR_SET (ISCSI_IOCTL | 23)
+#define ISCSI_DISCOVERY_ADDR_LIST_GET (ISCSI_IOCTL | 24)
+#define ISCSI_DISCOVERY_ADDR_CLEAR (ISCSI_IOCTL | 25)
+#define ISCSI_RADIUS_SET (ISCSI_IOCTL | 26)
+#define ISCSI_RADIUS_GET (ISCSI_IOCTL | 27)
+#define ISCSI_DB_RELOAD (ISCSI_IOCTL | 28)
+#define ISCSI_LUN_OID_LIST_GET (ISCSI_IOCTL | 29)
+#define ISCSI_LUN_PROPS_GET (ISCSI_IOCTL | 30)
+#define ISCSI_CONN_OID_LIST_GET (ISCSI_IOCTL | 31)
+#define ISCSI_CONN_PROPS_GET (ISCSI_IOCTL | 32)
+#define ISCSI_USCSI (ISCSI_IOCTL | 33)
+#define ISCSI_DOOR_HANDLE_SET (ISCSI_IOCTL | 34)
+#define ISCSI_DISCOVERY_EVENTS (ISCSI_IOCTL | 35)
+#define ISCSI_AUTH_SET (ISCSI_IOCTL | 36)
+#define ISCSI_AUTH_GET (ISCSI_IOCTL | 37)
+#define ISCSI_AUTH_CLEAR (ISCSI_IOCTL | 38)
+#define ISCSI_SENDTGTS_GET (ISCSI_IOCTL | 39)
+#define ISCSI_ISNS_SERVER_ADDR_SET (ISCSI_IOCTL | 40)
+#define ISCSI_ISNS_SERVER_ADDR_LIST_GET (ISCSI_IOCTL | 41)
+#define ISCSI_ISNS_SERVER_ADDR_CLEAR (ISCSI_IOCTL | 42)
+#define ISCSI_ISNS_SERVER_GET (ISCSI_IOCTL | 43)
+#define ISCSI_GET_CONFIG_SESSIONS (ISCSI_IOCTL | 44)
+#define ISCSI_SET_CONFIG_SESSIONS (ISCSI_IOCTL | 45)
+#define ISCSI_INIT_NODE_NAME_SET (ISCSI_IOCTL | 46)
+#define ISCSI_DB_DUMP (ISCSI_IOCTL | 100) /* DBG */
+
+/*
+ * Misc. defines
+ */
+#define ISCSI_CHAP_NAME_LEN 512
+#define ISCSI_CHAP_SECRET_LEN 16
+#define ISCSI_TGT_OID_LIST 0x0001
+#define ISCSI_STATIC_TGT_OID_LIST 0x0002
+#define ISCSI_TGT_PARAM_OID_LIST 0x0004
+#define ISCSI_SESS_PARAM 0x0001
+#define ISCSI_CONN_PARAM 0x0002
+
+/* digest level defines */
+#define ISCSI_DIGEST_NONE 0
+#define ISCSI_DIGEST_CRC32C 1
+#define ISCSI_DIGEST_CRC32C_NONE 2 /* offer both, prefer CRC32C */
+#define ISCSI_DIGEST_NONE_CRC32C 3 /* offer both, prefer None */
+
+/*
+ * A last error associated with each target session is returned in the
+ * iscsi_target_t structure.
+ */
+typedef enum iscsi_error {
+ NoError, AuthenticationError, LoginParamError, ConnectionReset
+} iscsi_error_t;
+
+/*
+ * The values associated with each enum is based on the IMA specification.
+ */
+typedef enum iSCSIDiscoveryMethod {
+ iSCSIDiscoveryMethodUnknown = 0,
+ iSCSIDiscoveryMethodStatic = 1,
+ iSCSIDiscoveryMethodSLP = 2,
+ iSCSIDiscoveryMethodISNS = 4,
+ iSCSIDiscoveryMethodSendTargets = 8
+} iSCSIDiscoveryMethod_t;
+#define ISCSI_ALL_DISCOVERY_METHODS (iSCSIDiscoveryMethodStatic | \
+ iSCSIDiscoveryMethodSLP | \
+ iSCSIDiscoveryMethodISNS | \
+ iSCSIDiscoveryMethodSendTargets)
+
+/*
+ * Before anything can be done to a target it must have an OID.
+ */
+typedef struct iscsi_oid {
+ uint32_t o_vers; /* In */
+ uchar_t o_name[ISCSI_MAX_NAME_LEN]; /* In */
+ /*
+ * tpgt is only 16 bits per spec. use 32 in ioctl to reduce
+ * packing issue. Also -1 tpgt denotes default value. iSCSI
+ * stack will detemermine tpgt during login.
+ */
+ int o_tpgt; /* In */
+ uint32_t o_oid; /* Out */
+} iscsi_oid_t;
+#define ISCSI_OID_NOTSET 0
+#define ISCSI_INITIATOR_OID 1 /* Other OIDs follow > 1 */
+#define ISCSI_DEFAULT_TPGT -1
+
+/*
+ * iSCSI Login Parameters - Reference iscsi draft for
+ * definitions of the below login params.
+ */
+typedef struct iscsi_login_params {
+ boolean_t immediate_data;
+ boolean_t initial_r2t;
+ int first_burst_length; /* range: 512 - 2**24-1 */
+ int max_burst_length; /* range: 512 - 2**24-1 */
+ boolean_t data_pdu_in_order;
+ boolean_t data_sequence_in_order;
+ int default_time_to_wait;
+ int default_time_to_retain;
+ int header_digest;
+ int data_digest;
+ int max_recv_data_seg_len; /* range: 512 - 2**24-1 */
+ int max_xmit_data_seg_len; /* range: 512 - 2**24-1 */
+ int max_connections;
+ int max_outstanding_r2t;
+ int error_recovery_level;
+ boolean_t ifmarker;
+ boolean_t ofmarker;
+} iscsi_login_params_t;
+
+/*
+ * Once parameters have been set via ISCSI_SET_PARAM the login is initiated
+ * by sending an ISCSI_LOGIN ioctl with the following structure filled in.
+ */
+typedef struct entry {
+ int e_vers;
+ uint32_t e_oid;
+ union {
+ struct in_addr u_in4;
+ struct in6_addr u_in6;
+ } e_u;
+ /*
+ * e_insize indicates which of the previous structs is valid.
+ */
+ int e_insize;
+ int e_port;
+ int e_tpgt;
+} entry_t;
+
+/*
+ * Used when setting or gettnig the Initiator Name or Alias.
+ */
+typedef struct node_name {
+ unsigned char n_name[ISCSI_MAX_NAME_LEN];
+ int n_len;
+} node_name_t;
+
+typedef struct _iSCSIMinMaxValue {
+ uint32_t i_current,
+ i_default,
+ i_min,
+ i_max,
+ i_incr;
+} iscsi_int_info_t;
+
+typedef struct _iSCSIBoolValue {
+ boolean_t b_current,
+ b_default;
+} iscsi_bool_info_t;
+
+typedef struct _iSCSIParamValueGet {
+ boolean_t v_valid,
+ v_settable;
+ iscsi_int_info_t v_integer;
+ iscsi_bool_info_t v_bool;
+ uchar_t v_name[ISCSI_MAX_NAME_LEN];
+} iscsi_get_value_t;
+
+typedef struct _iSCSILoginParamGet {
+ uint32_t g_vers; /* In */
+ uint32_t g_oid; /* In */
+ uint32_t g_param; /* Out */
+ iscsi_get_value_t g_value; /* Out */
+ uint32_t g_conn_cid; /* In */
+
+ /*
+ * To indicate whether session or connection related param is
+ * being requested.
+ */
+ uint32_t g_param_type; /* In */
+} iscsi_param_get_t;
+
+typedef struct iscsi_set_value {
+ uint32_t v_integer;
+ boolean_t v_bool;
+ uchar_t v_name[ISCSI_MAX_NAME_LEN];
+} iscsi_set_value_t;
+
+/*
+ * All of the members of this structure are set by the user agent and
+ * consumed by the driver.
+ */
+typedef struct iSCSILoginParamSet {
+ uint32_t s_vers,
+ s_oid;
+ uint32_t s_param;
+ iscsi_set_value_t s_value;
+} iscsi_param_set_t;
+
+/*
+ * Data in this structure is set by the user agent and consumed by
+ * the driver.
+ */
+typedef struct chap_props {
+ uint32_t c_vers,
+ c_retries,
+ c_oid;
+ unsigned char c_user[128];
+ uint32_t c_user_len;
+ unsigned char c_secret[16];
+ uint32_t c_secret_len;
+} iscsi_chap_props_t;
+
+typedef enum authMethod {
+ authMethodNone = 0x00,
+ authMethodCHAP = 0x01,
+ authMethodSRP = 0x02,
+ authMethodKRB5 = 0x04,
+ authMethodSPKM1 = 0x08,
+ authMethodSPKM2 = 0x10
+} authMethod_t;
+
+/*
+ * Data in this structure is set by the user agent and consumed by
+ * the driver.
+ */
+typedef struct auth_props {
+ uint32_t a_vers;
+ uint32_t a_oid;
+ boolean_t a_bi_auth;
+ authMethod_t a_auth_method;
+} iscsi_auth_props_t;
+
+/*
+ * Data in this structure is set by the user agent and consumed by
+ * the driver.
+ */
+#define MAX_RAD_SHARED_SECRET_LEN 128
+typedef struct radius_props {
+ uint32_t r_vers;
+ uint32_t r_oid;
+ union {
+ struct in_addr u_in4;
+ struct in6_addr u_in6;
+ } r_addr;
+ /*
+ * r_insize indicates which of the previous structs is valid.
+ */
+ int r_insize;
+
+ uint32_t r_port;
+ uint8_t r_shared_secret[MAX_RAD_SHARED_SECRET_LEN];
+ boolean_t r_radius_access;
+ boolean_t r_radius_config_valid;
+ uint32_t r_shared_secret_len;
+} iscsi_radius_props_t;
+
+typedef struct _IPAddress {
+ union {
+ struct in_addr in4;
+ struct in6_addr in6;
+ } i_addr;
+ /* i_insize determines which is valid in the union above */
+ int i_insize;
+} iscsi_ipaddr_t;
+
+typedef struct _iSCSITargetAddressKey {
+ iscsi_ipaddr_t a_addr;
+ uint32_t a_port,
+ a_oid;
+} iscsi_addr_t;
+
+typedef struct _iSCSITargetAddressKeyProperties {
+ uint32_t al_vers, /* In */
+ al_oid; /* In */
+ uint32_t al_in_cnt; /* In */
+ uint32_t al_out_cnt; /* Out */
+ uint32_t al_tpgt; /* Out */
+ iscsi_addr_t al_addrs[1]; /* Out */
+} iscsi_addr_list_t;
+
+typedef struct _iSCSITargetProperties {
+ uint32_t p_vers, /* In */
+ p_oid; /* In */
+ uchar_t p_name[ISCSI_MAX_NAME_LEN]; /* Out */
+ uint_t p_name_len; /* Out */
+ uchar_t p_alias[ISCSI_MAX_NAME_LEN]; /* Out */
+ uint_t p_alias_len; /* Out */
+ iSCSIDiscoveryMethod_t p_discovery; /* Out */
+ boolean_t p_connected; /* Out */
+ uint32_t p_num_of_connections; /* Out */
+ /* ---- If connected == B_TRUE then lastErr has no meaning. ---- */
+ iscsi_error_t p_last_err; /* Out */
+ /*
+ * Target portal group tag = -1 value means default.
+ */
+ int p_tpgt_conf; /* Out */
+ int p_tpgt_nego; /* Out */
+ uchar_t p_isid[ISCSI_ISID_LEN]; /* Out */
+ uchar_t p_reserved[128];
+} iscsi_property_t;
+
+typedef struct _iSCSITargetDeviceList {
+ uint32_t tl_vers, /* In */
+ tl_in_cnt, /* In */
+ tl_tgt_list_type, /* In */
+ tl_out_cnt, /* Out */
+ tl_oid_list[1]; /* Out */
+} iscsi_target_list_t;
+
+typedef struct _iSCSIStaticTargetProperties {
+ uint32_t p_vers, /* In */
+ p_oid; /* In */
+ uchar_t p_name[ISCSI_MAX_NAME_LEN]; /* Out */
+ uint_t p_name_len; /* Out */
+ iscsi_addr_list_t p_addr_list; /* Out */
+} iscsi_static_property_t;
+
+typedef enum iscsi_lun_status {
+ LunValid, LunDoesNotExist
+} iscsi_lun_status_t;
+
+/*
+ * SCSI inquiry vendor and product identifier buffer length - these values are
+ * defined by the identifier length plus 1 byte for the
+ * null termination.
+ */
+#define ISCSI_INQ_VID_BUF_LEN 9 /* 8 byte ID */
+#define ISCSI_INQ_PID_BUF_LEN 17 /* 16 byte ID */
+
+typedef struct iscsi_lun_props {
+ uint32_t lp_vers, /* In */
+ lp_tgt_oid, /* In */
+ lp_oid, /* In */
+ lp_num, /* Out */
+ lp_status; /* Out */
+ char lp_pathname[MAXPATHLEN], /* Out */
+ lp_vid[ISCSI_INQ_VID_BUF_LEN], /* Out */
+ lp_pid[ISCSI_INQ_PID_BUF_LEN]; /* Out */
+ time_t lp_time_online; /* Out */
+} iscsi_lun_props_t;
+
+typedef struct iscsi_if_lun {
+ uint32_t l_tgt_oid,
+ l_oid,
+ l_num;
+} iscsi_if_lun_t;
+
+typedef struct iscsi_lun_list {
+ uint32_t ll_vers; /* In */
+ boolean_t ll_all_tgts; /* In */
+ uint32_t ll_tgt_oid, /* In */
+ ll_in_cnt, /* In */
+ ll_out_cnt; /* Out */
+ iscsi_if_lun_t ll_luns[1]; /* Out */
+} iscsi_lun_list_t;
+
+typedef struct iscsi_conn_props {
+ uint32_t cp_vers, /* In */
+ cp_oid, /* In */
+ cp_cid, /* In */
+ cp_sess_oid; /* In */
+ union {
+ struct sockaddr_in soa4;
+ struct sockaddr_in6 soa6;
+ } cp_local; /* Out */
+ union {
+ struct sockaddr_in soa4;
+ struct sockaddr_in6 soa6;
+ } cp_peer; /* Out */
+
+
+ iscsi_login_params_t cp_params;
+ boolean_t cp_params_valid;
+
+} iscsi_conn_props_t;
+
+typedef struct iscsi_if_conn {
+ uint32_t c_sess_oid,
+ c_oid,
+ c_cid;
+} iscsi_if_conn_t;
+
+typedef struct iscsi_conn_list {
+ uint32_t cl_vers; /* In */
+ boolean_t cl_all_sess; /* In */
+ uint32_t cl_sess_oid, /* In */
+ cl_in_cnt, /* In */
+ cl_out_cnt; /* Out */
+ iscsi_if_conn_t cl_list[1]; /* Out */
+} iscsi_conn_list_t;
+
+typedef enum iSNSDiscoveryMethod {
+ iSNSDiscoveryMethodStatic = 0,
+ iSNSDiscoveryMethodDHCP = 1,
+ iSNSDiscoveryMethodSLP = 2
+} isns_method_t;
+
+typedef struct iSCSIDiscoveryProperties {
+ uint32_t vers;
+ boolean_t iSNSDiscoverySettable;
+ boolean_t iSNSDiscoveryEnabled;
+ isns_method_t iSNSDiscoveryMethod;
+ unsigned char iSNSDomainName[256];
+ boolean_t SLPDiscoverySettable;
+ boolean_t SLPDiscoveryEnabled;
+ boolean_t StaticDiscoverySettable;
+ boolean_t StaticDiscoveryEnabled;
+ boolean_t SendTargetsDiscoverySettable;
+ boolean_t SendTargetsDiscoveryEnabled;
+} iSCSIDiscoveryProperties_t;
+
+typedef struct iscsi_uscsi {
+ uint32_t iu_vers;
+ uint32_t iu_oid;
+ int iu_tpgt;
+ uint32_t iu_len;
+ uint32_t iu_lun;
+ struct uscsi_cmd iu_ucmd;
+} iscsi_uscsi_t;
+
+#if defined(_SYSCALL32)
+typedef struct iscsi_uscsi32 {
+ uint32_t iu_vers;
+ uint32_t iu_oid;
+ int iu_tpgt;
+ uint32_t iu_len;
+ uint32_t iu_lun;
+ struct uscsi_cmd32 iu_ucmd;
+} iscsi_uscsi32_t;
+#endif /* _SYSCALL32 */
+
+typedef struct iscsi_sendtgts_entry {
+ /* ---- Node name, NULL terminated UTF-8 string ---- */
+ uchar_t ste_name[ISCSI_MAX_NAME_LEN];
+
+ iscsi_addr_t ste_ipaddr;
+ int ste_tpgt;
+} iscsi_sendtgts_entry_t;
+
+typedef struct iscsi_sendtgts_list {
+ entry_t stl_entry; /* In */
+ uint32_t stl_in_cnt, /* In */
+ stl_out_cnt; /* Out */
+ iscsi_sendtgts_entry_t stl_list[1]; /* Out */
+} iscsi_sendtgts_list_t;
+
+typedef struct iscsi_statictgt_entry {
+ entry_t te_entry; /* In */
+ uchar_t te_name[ISCSI_MAX_NAME_LEN]; /* In */
+} iscsi_target_entry_t;
+
+/* iSNS Draft - section 4.1.1. */
+typedef struct isns_portal_group {
+ uint8_t pg_iscsi_name[ISCSI_MAX_NAME_LEN];
+ union {
+ in_addr_t u_ip4;
+ in6_addr_t u_ip6;
+ } pg_ip_addr;
+ int insize;
+
+ in_port_t pg_port;
+ uint16_t pg_tag;
+
+ iscsi_ipaddr_t isns_server_ip;
+ uint32_t isns_server_port;
+} isns_portal_group_t;
+
+typedef struct isns_portal_group_list {
+ uint32_t pg_in_cnt,
+ pg_out_cnt;
+ isns_portal_group_t pg_list[1];
+} isns_portal_group_list_t;
+
+typedef struct isns_server_portal_group_list {
+ iscsi_addr_t addr;
+ isns_portal_group_list_t addr_port_list;
+} isns_server_portal_group_list_t;
+
+#define ISCSI_MIN_CONFIG_SESSIONS 1
+/* lowered max config sessions due to ct_power_cnt >= 0 assert */
+#define ISCSI_MAX_CONFIG_SESSIONS 4
+
+typedef struct iscsi_config_sess {
+ uint32_t ics_ver;
+ uint32_t ics_oid;
+ boolean_t ics_bound;
+ uint_t ics_in;
+ uint_t ics_out;
+ iscsi_ipaddr_t ics_bindings[1];
+} iscsi_config_sess_t;
+
+#define ISCSI_SESSION_CONFIG_SIZE(SIZE) \
+ (sizeof (iscsi_config_sess_t) + \
+ ((SIZE - 1) * sizeof (iscsi_ipaddr_t)))
+
+/*
+ * Event class and subclass information
+ */
+#define EC_ISCSI "EC_iSCSI"
+#define ESC_ISCSI_STATIC_START "ESC_static_start"
+#define ESC_ISCSI_STATIC_END "ESC_static_end"
+#define ESC_ISCSI_SEND_TARGETS_START "ESC_send_targets_start"
+#define ESC_ISCSI_SEND_TARGETS_END "ESC_send_targets_end"
+#define ESC_ISCSI_SLP_START "ESC_slp_start"
+#define ESC_ISCSI_SLP_END "ESC_slp_end"
+#define ESC_ISCSI_ISNS_START "ESC_isns_start"
+#define ESC_ISCSI_ISNS_END "ESC_isns_end"
+#define ESC_ISCSI_PROP_CHANGE "ESC_prop_change"
+
+#ifdef _KERNEL
+/* ---- iscsi_utils.c ---- */
+extern int iscsid_open(char *, int, int);
+extern int iscsid_close(int);
+extern int iscsid_remove(char *filename);
+extern int iscsid_rename(char *oldname, char *newname);
+extern ssize_t iscsid_write(int, void *, ssize_t);
+extern ssize_t iscsid_read(int, void *, ssize_t);
+extern ssize_t iscsid_sendto(struct sonode *, void *, size_t,
+ struct sockaddr *, socklen_t);
+extern ssize_t iscsid_recvfrom(struct sonode *, void *buffer,
+ size_t len);
+extern int iscsid_errno;
+#endif
+
+/*
+ * Function prototypes for those routines found in the common code
+ */
+/* ---- utils.c ---- */
+extern boolean_t utils_iqn_create(char *, int);
+extern char *prt_bitmap(int, char *, char *, int);
+extern char *utils_map_param(int);
+extern boolean_t parse_addr_port_tpgt(char *in, char **addr,
+ int *type, char **port, char **tpgt);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ISCSI_IF_H */
diff --git a/usr/src/uts/common/sys/iscsit/iscsit_common.h b/usr/src/uts/common/sys/iscsit/iscsit_common.h
new file mode 100644
index 0000000000..1675fedb2d
--- /dev/null
+++ b/usr/src/uts/common/sys/iscsit/iscsit_common.h
@@ -0,0 +1,292 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _ISCSIT_COMMON_H_
+#define _ISCSIT_COMMON_H_
+
+#ifdef _KERNEL
+#include <sys/nvpair.h>
+#else
+#include <libnvpair.h>
+#endif
+/*
+ * XXX Need to reverse this dependency, libiscsit.h should include
+ * iscsit_common.h, and iscsit_common.h should have all the core
+ * definitions. Kernel drivers should not pull in library header files.
+ */
+#include <libiscsit.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ISCSIT_API_VERS0 0
+
+typedef enum {
+ ITCFG_SUCCESS = 0,
+ ITCFG_INVALID,
+ ITCFG_TGT_CREATE_ERR,
+ ITCFG_MISC_ERR
+} it_cfg_status_t;
+
+/*
+ * This structure is passed back to the driver during ISCSIT_IOC_ENABLE_SVC
+ * in order to provide the fully qualified hostname for use as the EID
+ * by iSNS.
+ */
+
+#define ISCSIT_MAX_HOSTNAME_LEN 256
+
+typedef struct iscsit_hostinfo_s {
+ uint32_t length;
+ char fqhn[ISCSIT_MAX_HOSTNAME_LEN];
+} iscsit_hostinfo_t;
+
+#define ISCSIT_IOC_SET_CONFIG 1
+#define ISCSIT_IOC_GET_STATE 2
+#define ISCSIT_IOC_ENABLE_SVC 101
+#define ISCSIT_IOC_DISABLE_SVC 102
+
+/* XXX Rationalize these with other error values (used in it_smf.c */
+#define ITADM_SUCCESS 0
+#define ITADM_FATAL_ERROR 0x1
+#define ITADM_NO_MEM 0x2
+#define ITADM_INVALID 0x4
+#define ITADM_NODATA 0x8
+#define ITADM_PERM 0x10
+
+
+#define PROP_AUTH "auth"
+#define PROP_ALIAS "alias"
+#define PROP_CHAP_USER "chapuser"
+#define PROP_CHAP_SECRET "chapsecret"
+#define PROP_TARGET_CHAP_USER "targetchapuser"
+#define PROP_TARGET_CHAP_SECRET "targetchapsecret"
+#define PROP_RADIUS_SERVER "radiusserver"
+#define PROP_RADIUS_SECRET "radiussecret"
+#define PROP_ISNS_ENABLED "isns"
+#define PROP_ISNS_SERVER "isnsserver"
+#define PROP_OLD_TARGET_NAME "oldtargetname"
+
+#define PA_AUTH_RADIUS "radius"
+#define PA_AUTH_CHAP "chap"
+#define PA_AUTH_NONE "none"
+
+typedef struct {
+ int set_cfg_vers;
+ int set_cfg_pnvlist_len;
+ caddr_t set_cfg_pnvlist;
+} iscsit_ioc_set_config_t;
+
+typedef struct {
+ int getst_vers;
+ int getst_pnvlist_len;
+ char *getst_pnvlist;
+} iscsit_ioc_getstate_t;
+
+#ifdef _SYSCALL32
+typedef struct {
+ int set_cfg_vers;
+ int set_cfg_pnvlist_len;
+ caddr32_t set_cfg_pnvlist;
+} iscsit_ioc_set_config32_t;
+
+typedef struct {
+ int getst_vers;
+ int getst_pnvlist_len;
+ caddr32_t getst_pnvlist;
+} iscsit_ioc_getstate32_t;
+#endif /* _SYSCALL32 */
+
+/* Functions to convert iSCSI target structures to/from nvlists. */
+int
+it_config_to_nv(it_config_t *cfg, nvlist_t **nvl);
+
+/*
+ * nvlist version of config is 3 list-of-list, + 1 proplist. arrays
+ * are interesting, but lists-of-lists are more useful when doing
+ * individual lookups when we later add support for it. Also, no
+ * need to store name in individual struct representation.
+ */
+int
+it_nv_to_config(nvlist_t *nvl, it_config_t **cfg);
+
+int
+it_nv_to_tgtlist(nvlist_t *nvl, uint32_t *count, it_tgt_t **tgtlist);
+
+int
+it_tgtlist_to_nv(it_tgt_t *tgtlist, nvlist_t **nvl);
+
+int
+it_tgt_to_nv(it_tgt_t *tgt, nvlist_t **nvl);
+
+int
+it_nv_to_tgt(nvlist_t *nvl, char *name, it_tgt_t **tgt);
+
+int
+it_tpgt_to_nv(it_tpgt_t *tpgt, nvlist_t **nvl);
+
+int
+it_nv_to_tpgt(nvlist_t *nvl, char *name, it_tpgt_t **tpgt);
+
+int
+it_tpgtlist_to_nv(it_tpgt_t *tpgtlist, nvlist_t **nvl);
+
+int
+it_nv_to_tpgtlist(nvlist_t *nvl, uint32_t *count, it_tpgt_t **tpgtlist);
+
+int
+it_tpg_to_nv(it_tpg_t *tpg, nvlist_t **nvl);
+
+int
+it_nv_to_tpg(nvlist_t *nvl, char *name, it_tpg_t **tpg);
+
+int
+it_tpglist_to_nv(it_tpg_t *tpglist, nvlist_t **nvl);
+
+int
+it_nv_to_tpglist(nvlist_t *nvl, uint32_t *count, it_tpg_t **tpglist);
+
+int
+it_ini_to_nv(it_ini_t *ini, nvlist_t **nvl);
+
+int
+it_nv_to_ini(nvlist_t *nvl, char *name, it_ini_t **ini);
+
+int
+it_inilist_to_nv(it_ini_t *inilist, nvlist_t **nvl);
+
+int
+it_nv_to_inilist(nvlist_t *nvl, uint32_t *count, it_ini_t **inilist);
+
+it_tgt_t *
+it_tgt_lookup(it_config_t *cfg, char *tgt_name);
+
+it_tpg_t *
+it_tpg_lookup(it_config_t *cfg, char *tpg_name);
+
+it_portal_t *
+it_sns_svr_lookup(it_config_t *cfg, struct sockaddr_storage *sa);
+
+it_portal_t *
+it_portal_lookup(it_tpg_t *cfg_tpg, struct sockaddr_storage *sa);
+
+int
+it_sa_compare(struct sockaddr_storage *sa1, struct sockaddr_storage *sa2);
+
+/*
+ * Convert a sockaddr to the string representation, suitable for
+ * storing in an nvlist or printing out in a list.
+ */
+int
+sockaddr_to_str(struct sockaddr_storage *sa, char **addr);
+
+/*
+ * Convert a char string to a sockaddr structure
+ *
+ * default_port should be the port to be used, if not specified
+ * as part of the supplied string 'arg'.
+ */
+struct sockaddr_storage *
+it_common_convert_sa(char *arg, struct sockaddr_storage *buf,
+ uint32_t default_port);
+
+/*
+ * Convert an string array of IP-addr:port to a portal list
+ */
+int
+it_array_to_portallist(char **arr, uint32_t count, uint32_t default_port,
+ it_portal_t **portallist, uint32_t *list_count);
+
+/*
+ * Function: it_config_free_cmn()
+ *
+ * Free any resources associated with the it_config_t structure.
+ *
+ * Parameters:
+ * cfg A C representation of the current iSCSI configuration
+ */
+void
+it_config_free_cmn(it_config_t *cfg);
+
+/*
+ * Function: it_tgt_free_cmn()
+ *
+ * Frees an it_tgt_t structure. If tgt_next is not NULL, frees
+ * all structures in the list.
+ */
+void
+it_tgt_free_cmn(it_tgt_t *tgt);
+
+/*
+ * Function: it_tpgt_free_cmn()
+ *
+ * Deallocates resources of an it_tpgt_t structure. If tpgt->next
+ * is not NULL, frees all members of the list.
+ */
+void
+it_tpgt_free_cmn(it_tpgt_t *tpgt);
+
+/*
+ * Function: it_tpg_free_cmn()
+ *
+ * Deallocates resources associated with an it_tpg_t structure.
+ * If tpg->next is not NULL, frees all members of the list.
+ */
+void
+it_tpg_free_cmn(it_tpg_t *tpg);
+
+/*
+ * Function: it_ini_free_cmn()
+ *
+ * Deallocates resources of an it_ini_t structure. If ini->next is
+ * not NULL, frees all members of the list.
+ */
+void
+it_ini_free_cmn(it_ini_t *ini);
+
+/*
+ * Function: iscsi_binary_to_base64_str()
+ *
+ * Encodes a byte array into a base64 string.
+ */
+int
+iscsi_binary_to_base64_str(uint8_t *in_buf, int in_buf_len,
+ char *base64_str_buf, int base64_buf_len);
+
+/*
+ * Function: iscsi_base64_str_to_binary()
+ *
+ * Decodes a base64 string into a byte array
+ */
+int
+iscsi_base64_str_to_binary(char *hstr, int hstr_len,
+ uint8_t *binary, int binary_buf_len, int *out_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ISCSIT_COMMON_H_ */
diff --git a/usr/src/uts/common/sys/iscsit/isns_protocol.h b/usr/src/uts/common/sys/iscsit/isns_protocol.h
new file mode 100644
index 0000000000..0480fd0052
--- /dev/null
+++ b/usr/src/uts/common/sys/iscsit/isns_protocol.h
@@ -0,0 +1,281 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _ISNS_PROTOCOL_H
+#define _ISNS_PROTOCOL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+
+#define ISNSP_VERSION (0x01)
+
+#define ISNS_DEFAULT_SERVER_PORT (3205)
+
+#define ISNSP_HEADER_SIZE (12)
+#define ISNSP_RSP_CODE_SIZE (4)
+#define ISNSP_MAX_PAYLOAD_SIZE (65532)
+#define ISNSP_MAX_PDU_SIZE (ISNSP_HEADER_SIZE + \
+ ISNSP_MAX_PAYLOAD_SIZE)
+
+#define ISNS_TLV_ATTR_ID_LEN (4)
+#define ISNS_TLV_ATTR_LEN_LEN (4)
+#define MAX_ISNS_MESG_ATTR_ENTRIES (8)
+#define MAX_ISNS_OPER_ATTR_ENTRIES (32)
+
+/* iSNS Entity Protocol, iSNS Draft - section 6.2.2. */
+#define ISNS_ENTITY_PROTOCOL_NO (1)
+#define ISNS_ENTITY_PROTOCOL_ISCSI (2)
+#define ISNS_ENTITY_PROTOCOL_FCP (3)
+
+/* iSNS Function IDs, iSNS Draft - section 4.1.3. */
+#define ISNS_DEV_ATTR_REG (0x0001)
+#define ISNS_DEV_ATTR_QRY (0x0002)
+#define ISNS_DEV_GET_NEXT (0x0003)
+#define ISNS_DEV_DEREG (0x0004)
+#define ISNS_SCN_REG (0x0005)
+#define ISNS_SCN_DEREG (0x0006)
+#define ISNS_SCN_EVENT (0x0007)
+#define ISNS_SCN (0x0008)
+#define ISNS_DD_REG (0x0009)
+#define ISNS_DD_DEREG (0x000A)
+#define ISNS_DDS_REG (0x000B)
+#define ISNS_DDS_DEREG (0x000C)
+#define ISNS_ESI (0x000D)
+#define ISNS_HEARTBEAT (0x000E)
+#define ISNS_DEV_ATTR_REG_RSP (0x8001)
+#define ISNS_DEV_ATTR_QRY_RSP (0x8002)
+#define ISNS_DEV_GET_NEXT_RSP (0x8003)
+#define ISNS_DEV_DEREG_RSP (0x8004)
+#define ISNS_SCN_REG_RSP (0x8005)
+#define ISNS_SCN_DEREG_RSP (0x8006)
+#define ISNS_SCN_EVENT_RSP (0x8007)
+#define ISNS_SCN_RSP (0x8008)
+#define ISNS_DD_REG_RSP (0x8009)
+#define ISNS_DD_DEREG_RSP (0x800A)
+#define ISNS_DDS_REG_RSP (0x800B)
+#define ISNS_DDS_DEREG_RSP (0x800C)
+#define ISNS_ESI_RSP (0x800D)
+
+/* iSNS Flags, iSNS Draft - section 5.1.4. */
+#define ISNS_FLAG_FIRST_PDU (0x0400)
+#define ISNS_FLAG_LAST_PDU (0x0800)
+#define ISNS_FLAG_REPLACE_REG (0x1000)
+#define ISNS_FLAG_AUTH_BLK_PRESENTED (0x2000)
+#define ISNS_FLAG_SERVER (0x4000)
+#define ISNS_FLAG_CLIENT (0x8000)
+
+/* iSNS Response Status, iSNS Draft - section 5.4 */
+#define ISNS_RSP_SUCCESSFUL (0)
+#define ISNS_RSP_UNKNOWN_ERROR (1)
+#define ISNS_RSP_MSG_FORMAT_ERROR (2)
+#define ISNS_RSP_INVALID_REGIS (3)
+#define ISNS_RSP_INVALID_QRY (5)
+#define ISNS_RSP_SRC_UNKNOWN (6)
+#define ISNS_RSP_SRC_ABSENT (7)
+#define ISNS_RSP_SRC_UNAUTHORIZED (8)
+#define ISNS_RSP_NO_SUCH_ENTRY (9)
+#define ISNS_RSP_VER_NOT_SUPPORTED (10)
+#define ISNS_RSP_INTERNAL_ERROR (11)
+#define ISNS_RSP_BUSY (12)
+#define ISNS_RSP_OPTION_NOT_UNDERSTOOD (13)
+#define ISNS_RSP_INVALID_UPDATE (14)
+#define ISNS_RSP_MSG_NOT_SUPPORTED (15)
+#define ISNS_RSP_SCN_EVENT_REJECTED (16)
+#define ISNS_RSP_SCN_REGIS_REJECTED (17)
+#define ISNS_RSP_ATTR_NOT_IMPL (18)
+#define ISNS_RSP_ESI_NOT_AVAILABLE (21)
+#define ISNS_RSP_INVALID_DEREGIS (22)
+#define ISNS_RSP_REGIS_NOT_SUPPORTED (23)
+
+/* iSNS Attribute IDs, iSNS Draft - section 6.1. */
+#define ISNS_DELIMITER_ATTR_ID (0)
+#define ISNS_EID_ATTR_ID (1)
+#define ISNS_ENTITY_PROTOCOL_ATTR_ID (2)
+#define ISNS_MGMT_IP_ADDR_ATTR_ID (3)
+#define ISNS_TIMESTAMP_ATTR_ID (4)
+#define ISNS_VERSION_RANGE_ATTR_ID (5)
+#define ISNS_ENTITY_REG_PERIOD_ATTR_ID (6)
+#define ISNS_ENTITY_INDEX_ATTR_ID (7)
+#define ISNS_ENTITY_NEXT_INDEX_ATTR_ID (8)
+#define ISNS_ENTITY_ISAKMP_P1_ATTR_ID (11)
+#define ISNS_ENTITY_CERT_ATTR_ID (12)
+#define ISNS_PORTAL_IP_ADDR_ATTR_ID (16)
+#define ISNS_PORTAL_PORT_ATTR_ID (17)
+#define ISNS_PORTAL_NAME_ATTR_ID (18)
+#define ISNS_ESI_INTERVAL_ATTR_ID (19)
+#define ISNS_ESI_PORT_ATTR_ID (20)
+#define ISNS_PORTAL_INDEX_ATTR_ID (22)
+#define ISNS_SCN_PORT_ATTR_ID (23)
+#define ISNS_PORTAL_NEXT_INDEX_ATTR_ID (24)
+#define ISNS_PORTAL_SEC_BMP_ATTR_ID (27)
+#define ISNS_PORTAL_ISAKMP_P1_ATTR_ID (28)
+#define ISNS_PORTAL_ISAKMP_P2_ATTR_ID (29)
+#define ISNS_PORTAL_CERT_ATTR_ID (31)
+#define ISNS_ISCSI_NAME_ATTR_ID (32)
+#define ISNS_ISCSI_NODE_TYPE_ATTR_ID (33)
+#define ISNS_ISCSI_ALIAS_ATTR_ID (34)
+#define ISNS_ISCSI_SCN_BITMAP_ATTR_ID (35)
+#define ISNS_ISCSI_NODE_INDEX_ATTR_ID (36)
+#define ISNS_WWNN_TOKEN_ATTR_ID (37)
+#define ISNS_NODE_NEXT_INDEX_ATTR_ID (38)
+#define ISNS_ISCSI_AUTH_METHOD_ATTR_ID (42)
+#define ISNS_PG_ISCSI_NAME_ATTR_ID (48)
+#define ISNS_PG_PORTAL_IP_ADDR_ATTR_ID (49)
+#define ISNS_PG_PORTAL_PORT_ATTR_ID (50)
+#define ISNS_PG_TAG_ATTR_ID (51)
+#define ISNS_PG_INDEX_ATTR_ID (52)
+#define ISNS_PG_NEXT_ID_ATTR_ID (53)
+#define ISNS_DD_SET_ID_ATTR_ID (2049)
+#define ISNS_DD_SET_NAME_ATTR_ID (2050)
+#define ISNS_DD_SET_STATUS_ATTR_ID (2051)
+#define ISNS_DD_ID_ATTR_ID (2065)
+#define ISNS_DD_NAME_ATTR_ID (2066)
+#define ISNS_DD_ISCSI_INDEX_ATTR_ID (2067)
+#define ISNS_DD_ISCSI_NAME_ATTR_ID (2068)
+#define ISNS_DD_FC_PORT_NAME_ATTR_ID (2069)
+#define ISNS_DD_PORTAL_INDEX_ATTR_ID (2070)
+#define ISNS_DD_PORTAL_IP_ADDR_ATTR_ID (2071)
+#define ISNS_DD_PORTAL_PORT_ATTR_ID (2072)
+#define ISNS_DD_FEATURES_ATTR_ID (2078)
+
+/* Entity Protocol, RFC 4171 - section 6.2.2. */
+#define ISNS_ENTITY_NO_PROTOCOL (1)
+#define ISNS_ENTITY_ISCSI (2)
+#define ISNS_ENTITY_IFCP (3)
+
+/* Protocol Version Range, RFC 4171 - section 6.2.5. */
+#define ISNS_VER_SHIFT (16)
+#define ISNS_VERSION (0x0000FFFF)
+
+/* Portal Port, RFC 4171 - section 6.3.2. */
+#define ISNS_PORT_BITS (0x0000FFFF) /* Bits 16 - 31 */
+#define ISNS_PORT_TYPE (0x00010000) /* Bit 15 */
+
+/* Portal Security Bitmap, RFC 4171 - section 6.3.9. */
+#define ISNS_TUNNEL_MODE_PREFERRED (0x0040) /* Bit 25 */
+#define ISNS_TRANS_MODE_PREFERRED (0x0020) /* Bit 26 */
+#define ISNS_PFS_ENABLED (0x0010) /* Bit 27 */
+#define ISNS_AGGR_MODE_ENABLED (0x0008) /* Bit 28 */
+#define ISNS_MAIN_MODE_ENABLED (0x0004) /* Bit 29 */
+#define ISNS_IKE_IPSEC_ENABLED (0x0002) /* Bit 30 */
+#define ISNS_BITMAP_VALID (0x0001) /* Bit 31 */
+
+/* iSCSI Node Type, RFC 4171 - section 6.4.2. */
+#define ISNS_TARGET_NODE_TYPE (0x0001)
+#define ISNS_INITIATOR_NODE_TYPE (0x0002)
+#define ISNS_CONTROL_NODE_TYPE (0x0004)
+
+/* iSCSI Node SCN Bitmap, RFC 4171 - section 6.4.4. */
+#define ISNS_INIT_SELF_INFO_ONLY (0x0080) /* Bit 24 */
+#define ISNS_TARGET_SELF_INFO_ONLY (0x0040) /* Bit 25 */
+#define ISNS_MGMT_REG (0x0020) /* Bit 26 */
+#define ISNS_OBJECT_REMOVED (0x0010) /* Bit 27 */
+#define ISNS_OBJECT_ADDED (0x0008) /* Bit 28 */
+#define ISNS_OBJECT_UPDATED (0x0004) /* Bit 29 */
+#define ISNS_MEMBER_REMOVED (0x0002) /* Bit 30 */
+#define ISNS_MEMBER_ADDED (0x0001) /* Bit 31 */
+
+/* Portal Group Tag, RFC 4171 - section 6.5.4. */
+#define ISNS_PG_TAG (0x0000FFFF) /* Bits 16 - 31 */
+
+/* DDS Status, RFC 4171 - section 6.11.1.3. */
+#define ISNS_DDS_STATUS (0x0001) /* Bit 31 */
+
+/* DD Feature, RFC 4171 - section 6.11.2.9. */
+#define ISNS_DD_BOOTLIST (0x0001) /* Bit 31 */
+
+/* iSNS Defaults */
+#define ISNS_DEFAULT_PGT (0x00000001)
+#define ISNS_DEFAULT_DD_SET_ID (1)
+#define ISNS_DEFAULT_DD_ID (1)
+
+/* Min/Max length of names */
+#define ISNS_DDS_MAX_NAME_LEN (256)
+#define ISNS_DD_MAX_NAME_LEN (256)
+#define ISNS_ISCSI_MAX_NAME_LEN (224)
+#define ISNS_ISCSI_MAX_ALIAS_LEN (256)
+#define ISNS_ENTITY_MIN_EID_LEN (3)
+#define ISNS_ENTITY_MAX_EID_LEN (255)
+
+
+typedef struct isns_tlv {
+ uint32_t attr_id;
+ uint32_t attr_len;
+ uint8_t attr_value[1];
+} isns_tlv_t;
+
+typedef struct isns_packet_data {
+ uint16_t version;
+ uint16_t func_id;
+ uint16_t payload_len;
+ uint16_t flags;
+ uint16_t xid;
+ uint16_t seq;
+
+ int num_of_tlvs;
+ isns_tlv_t tlvs[MAX_ISNS_OPER_ATTR_ENTRIES];
+} isns_packet_data_t;
+
+typedef struct isns_reg_mesg {
+ isns_tlv_t src_attr;
+ int num_of_mesg_attrs;
+ isns_tlv_t *mesg_attrs[MAX_ISNS_MESG_ATTR_ENTRIES];
+ isns_tlv_t delimiter_attr;
+ isns_tlv_t *operating_attrs[MAX_ISNS_OPER_ATTR_ENTRIES];
+} isns_reg_mesg_t;
+
+typedef struct isns_resp_mesg {
+ uint8_t status[4];
+ isns_tlv_t messages_attrs[MAX_ISNS_MESG_ATTR_ENTRIES];
+ isns_tlv_t delimiter_attr;
+ isns_tlv_t operating_attrs[MAX_ISNS_OPER_ATTR_ENTRIES];
+} isns_resp_mesg_t;
+
+typedef struct isns_pdu {
+ uint16_t version;
+ uint16_t func_id;
+ uint16_t payload_len;
+ uint16_t flags;
+ uint16_t xid;
+ uint16_t seq;
+ uint8_t payload[1];
+} isns_pdu_t;
+
+typedef struct isns_resp {
+ uint32_t status;
+ uint8_t data[1];
+} isns_resp_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _ISNS_PROTOCOL_H */
diff --git a/usr/src/uts/common/sys/iscsit/radius_packet.h b/usr/src/uts/common/sys/iscsit/radius_packet.h
new file mode 100644
index 0000000000..bbf96d5cb2
--- /dev/null
+++ b/usr/src/uts/common/sys/iscsit/radius_packet.h
@@ -0,0 +1,98 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _RADIUS_PACKET_H
+#define _RADIUS_PACKET_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <netinet/in.h>
+#include <sys/types.h>
+
+#include <sys/iscsit/radius_protocol.h>
+
+/* A total of RAD_RCV_TIMEOUT * RAD_RETRY_MAX seconds timeout. */
+#define RAD_RCV_TIMEOUT 5 /* Timeout for receiving RADIUS packet in */
+ /* sec. */
+#define RAD_RETRY_MAX 2 /* Max. # of times to retry receiving */
+ /* packet. */
+
+/* Describes a RADIUS attribute */
+typedef struct radius_attr {
+ int attr_type_code; /* RADIUS attribute type code, */
+ /* e.g. RAD_USER_PASSWORD, etc. */
+ int attr_value_len;
+ uint8_t attr_value[MAX_RAD_ATTR_VALUE_LEN];
+} radius_attr_t;
+
+/* Describes data fields of a RADIUS packet. */
+typedef struct radius_packet_data {
+ uint8_t code; /* RADIUS code, section 3, RFC 2865. */
+ uint8_t identifier;
+ uint8_t authenticator[RAD_AUTHENTICATOR_LEN];
+ int num_of_attrs;
+ radius_attr_t attrs[4]; /* For this implementation each */
+ /* outbound RADIUS packet will only */
+ /* have 3 attributes associated with */
+ /* it thus the chosen size should be */
+ /* good enough. */
+} radius_packet_data_t;
+
+/*
+ * Send a request to a RADIUS server.
+ *
+ * Returns > 0 on success, <= 0 on failure .
+ *
+ */
+int
+iscsit_snd_radius_request(void *socket,
+ iscsi_ipaddr_t rsvr_ip_addr,
+ uint32_t rsvr_port,
+ radius_packet_data_t *packet_data);
+
+#define RAD_RSP_RCVD_SUCCESS 0
+#define RAD_RSP_RCVD_NO_DATA 1
+#define RAD_RSP_RCVD_TIMEOUT 2
+#define RAD_RSP_RCVD_PROTOCOL_ERR 3
+#define RAD_RSP_RCVD_AUTH_FAILED 4
+/*
+ * Receives a response from a RADIUS server.
+ *
+ * Return receive status.
+ */
+int
+iscsit_rcv_radius_response(void *socket,
+ uint8_t *shared_secret,
+ uint32_t shared_secret_len,
+ uint8_t *req_authenticator,
+ radius_packet_data_t *resp_data);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RADIUS_PACKET_H */
diff --git a/usr/src/uts/common/sys/iscsit/radius_protocol.h b/usr/src/uts/common/sys/iscsit/radius_protocol.h
new file mode 100644
index 0000000000..76be62909c
--- /dev/null
+++ b/usr/src/uts/common/sys/iscsit/radius_protocol.h
@@ -0,0 +1,83 @@
+/*
+ * 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 2008 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _RADIUS_PROTOCOL_H
+#define _RADIUS_PROTOCOL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Packet type. RFC 2865 section 4. */
+#define RAD_ACCESS_REQ 1 /* Authentication Request */
+#define RAD_ACCESS_ACPT 2 /* Authentication Accepted */
+#define RAD_ACCESS_REJ 3 /* Authentication Rejected */
+
+/* RADIUS Attribute Types. RFC 2865 section 5. */
+#define RAD_USER_NAME 1
+#define RAD_CHAP_PASSWORD 3
+#define RAD_CHAP_CHALLENGE 60
+
+/* RFC 2865 Section 3. The Identifier field is one octet. */
+#define RAD_IDENTIFIER_LEN 1
+
+/* RFC 2865 Section 5.3. The String field is 16 octets. */
+#define RAD_CHAP_PASSWD_STR_LEN 16
+
+/* RFC 2865 Section 3. Authenticator field is 16 octets. */
+#define RAD_AUTHENTICATOR_LEN 16
+
+/* RFC 2865 Section 5: 1-253 octets */
+#define MAX_RAD_ATTR_VALUE_LEN 253
+
+/* RFC 2865 Section 3. Minimum length 20 octets. */
+#define MIN_RAD_PACKET_LEN 20
+
+/* RFC 2865 Section 3. Maximum length 4096 octets. */
+#define MAX_RAD_PACKET_LEN 4096
+
+/* Maximum RADIUS shared secret length (in fact there is no defined limit) */
+#define MAX_RAD_SHARED_SECRET_LEN 128
+
+/* RFC 2865 Section 3. Minimum RADIUS shared secret length */
+#define MIN_RAD_SHARED_SECRET_LEN 16
+
+/* Raw RADIUS packet. RFC 2865 section 3. */
+typedef struct radius_packet {
+ uint8_t code; /* RADIUS code, section 3, RFC 2865 */
+ uint8_t identifier; /* 1 octet in length. RFC 2865 section 3 */
+ uint8_t length[2]; /* 2 octets, or sizeof (u_short) */
+ uint8_t authenticator[RAD_AUTHENTICATOR_LEN];
+ uint8_t data[1];
+} radius_packet_t;
+
+/* Length of a RADIUS packet minus the payload */
+#define RAD_PACKET_HDR_LEN 20
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RADIUS_PROTOCOL_H */
diff --git a/usr/src/uts/intel/Makefile.intel.shared b/usr/src/uts/intel/Makefile.intel.shared
index 8608fcf00d..22d63f05ae 100644
--- a/usr/src/uts/intel/Makefile.intel.shared
+++ b/usr/src/uts/intel/Makefile.intel.shared
@@ -329,6 +329,7 @@ DRV_KMODS += stmf
DRV_KMODS += stmf_sbd
DRV_KMODS += fct
DRV_KMODS += qlt
+DRV_KMODS += iscsit
DRV_KMODS += ncall nsctl sdbc nskern sv
DRV_KMODS += ii rdc rdcsrv rdcstub
@@ -552,6 +553,7 @@ MISC_KMODS += ibcm
MISC_KMODS += ibdm
MISC_KMODS += ibmf
MISC_KMODS += ibtl
+MISC_KMODS += idm
MISC_KMODS += idmap
MISC_KMODS += iommulib
MISC_KMODS += ipc
diff --git a/usr/src/uts/intel/idm/Makefile b/usr/src/uts/intel/idm/Makefile
new file mode 100644
index 0000000000..463a8be02a
--- /dev/null
+++ b/usr/src/uts/intel/idm/Makefile
@@ -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
+#
+#
+# uts/intel/io/idm/Makefile
+#
+# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# This makefile drives the production of the idm kernel module.
+#
+# intel architecture dependent
+#
+
+#
+# Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+UTSBASE = ../..
+
+#
+# Define the module and object file sets.
+#
+MODULE = idm
+OBJECTS = $(IDM_OBJS:%=$(OBJS_DIR)/%)
+LINTS = $(IDM_OBJS:%.o=$(LINTS_DIR)/%.ln)
+ROOTMODULE = $(ROOT_MISC_DIR)/$(MODULE)
+
+#
+# Include common rules.
+#
+include $(UTSBASE)/intel/Makefile.intel
+
+#
+# Define targets
+#
+ALL_TARGET = $(BINARY)
+LINT_TARGET = $(MODULE).lint
+INSTALL_TARGET = $(BINARY) $(ROOTMODULE)
+
+#
+# Overrides.
+#
+DEBUG_FLGS =
+DEBUG_DEFS += $(DEBUG_FLGS)
+LDFLAGS += -dy -Nfs/sockfs
+
+#
+# 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
diff --git a/usr/src/uts/intel/iscsit/Makefile b/usr/src/uts/intel/iscsit/Makefile
new file mode 100644
index 0000000000..a9c9af0b4f
--- /dev/null
+++ b/usr/src/uts/intel/iscsit/Makefile
@@ -0,0 +1,96 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# This makefile drives the production of the iscsit pseudo-driver for
+# COMSTAR.
+
+#
+# Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+
+
+UTSBASE = ../..
+
+ARCHDIR:sh = cd ..; basename `pwd`
+
+#
+# Define the module and object file sets.
+#
+MODULE = iscsit
+OBJECTS = $(ISCSIT_OBJS:%=$(OBJS_DIR)/%)
+LINTS = $(ISCSIT_OBJS:%.o=$(LINTS_DIR)/%.ln)
+ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE)
+CONF_SRCDIR = $(UTSBASE)/common/io/comstar/port/iscsit
+
+#
+# Include common rules.
+#
+include ../Makefile.$(ARCHDIR)
+
+#
+# Define targets
+#
+ALL_TARGET = $(BINARY) $(SRC_CONFILE)
+LINT_TARGET = $(MODULE).lint
+INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE)
+
+#
+# Overrides and depends_on
+#
+MODSTUBS_DIR = $(OBJS_DIR)
+LDFLAGS += -dy -Ndrv/stmf -Nmisc/idm -Nfs/sockfs -Nmisc/md5
+
+INC_PATH += -I$(UTSBASE)/common/io/comstar/port/iscsit
+# This path should be remove with the libiscsit.h/iscsit_common.h issue is
+# resolved.
+INC_PATH += -I../../../lib/libiscsit/common/
+
+C99MODE= -xc99=%all
+C99LMODE= -Xc99=%all
+
+#
+# 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 ../Makefile.targ
diff --git a/usr/src/uts/sparc/Makefile.sparc.shared b/usr/src/uts/sparc/Makefile.sparc.shared
index 8f52723273..0141a71a81 100644
--- a/usr/src/uts/sparc/Makefile.sparc.shared
+++ b/usr/src/uts/sparc/Makefile.sparc.shared
@@ -294,6 +294,7 @@ DRV_KMODS += stmf
DRV_KMODS += stmf_sbd
DRV_KMODS += fct
DRV_KMODS += qlt
+DRV_KMODS += iscsit
DRV_KMODS += ncall nsctl sdbc nskern sv
DRV_KMODS += ii rdc rdcsrv rdcstub
DRV_KMODS += iscsi
@@ -387,6 +388,7 @@ MISC_KMODS += ibcm
MISC_KMODS += ibdm
MISC_KMODS += ibmf
MISC_KMODS += ibtl
+MISC_KMODS += idm
MISC_KMODS += idmap
MISC_KMODS += hook
MISC_KMODS += neti
diff --git a/usr/src/uts/sparc/idm/Makefile b/usr/src/uts/sparc/idm/Makefile
new file mode 100644
index 0000000000..6b03fb56df
--- /dev/null
+++ b/usr/src/uts/sparc/idm/Makefile
@@ -0,0 +1,87 @@
+#
+# 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
+#
+#
+# uts/intel/io/idm/Makefile
+#
+# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#
+# This makefile drives the production of the idm kernel module.
+#
+
+#
+# Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+UTSBASE = ../..
+
+#
+# Define the module and object file sets.
+#
+MODULE = idm
+OBJECTS = $(IDM_OBJS:%=$(OBJS_DIR)/%)
+LINTS = $(IDM_OBJS:%.o=$(LINTS_DIR)/%.ln)
+ROOTMODULE = $(ROOT_MISC_DIR)/$(MODULE)
+
+#
+# Include common rules.
+#
+include $(UTSBASE)/sparc/Makefile.sparc
+
+#
+# Define targets
+#
+ALL_TARGET = $(BINARY)
+LINT_TARGET = $(MODULE).lint
+INSTALL_TARGET = $(BINARY) $(ROOTMODULE)
+
+#
+# Overrides.
+#
+DEBUG_FLGS =
+DEBUG_DEFS += $(DEBUG_FLGS)
+LDFLAGS += -dy -Nfs/sockfs
+
+#
+# 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
diff --git a/usr/src/uts/sparc/iscsit/Makefile b/usr/src/uts/sparc/iscsit/Makefile
new file mode 100644
index 0000000000..a9c9af0b4f
--- /dev/null
+++ b/usr/src/uts/sparc/iscsit/Makefile
@@ -0,0 +1,96 @@
+#
+# 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 2008 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# This makefile drives the production of the iscsit pseudo-driver for
+# COMSTAR.
+
+#
+# Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+
+
+UTSBASE = ../..
+
+ARCHDIR:sh = cd ..; basename `pwd`
+
+#
+# Define the module and object file sets.
+#
+MODULE = iscsit
+OBJECTS = $(ISCSIT_OBJS:%=$(OBJS_DIR)/%)
+LINTS = $(ISCSIT_OBJS:%.o=$(LINTS_DIR)/%.ln)
+ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE)
+CONF_SRCDIR = $(UTSBASE)/common/io/comstar/port/iscsit
+
+#
+# Include common rules.
+#
+include ../Makefile.$(ARCHDIR)
+
+#
+# Define targets
+#
+ALL_TARGET = $(BINARY) $(SRC_CONFILE)
+LINT_TARGET = $(MODULE).lint
+INSTALL_TARGET = $(BINARY) $(ROOTMODULE) $(ROOT_CONFFILE)
+
+#
+# Overrides and depends_on
+#
+MODSTUBS_DIR = $(OBJS_DIR)
+LDFLAGS += -dy -Ndrv/stmf -Nmisc/idm -Nfs/sockfs -Nmisc/md5
+
+INC_PATH += -I$(UTSBASE)/common/io/comstar/port/iscsit
+# This path should be remove with the libiscsit.h/iscsit_common.h issue is
+# resolved.
+INC_PATH += -I../../../lib/libiscsit/common/
+
+C99MODE= -xc99=%all
+C99LMODE= -Xc99=%all
+
+#
+# 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 ../Makefile.targ