summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormlf <none@none>2006-01-13 09:07:56 -0800
committermlf <none@none>2006-01-13 09:07:56 -0800
commit66f9d5cb3cc0652e2d9d1366fb950efbe4ca2f24 (patch)
treefbf77a0c942a6159b219a211ebae2372b5724151
parentdbd19a093870d9271e11398b346816e7ea2e7eaa (diff)
downloadillumos-joyent-66f9d5cb3cc0652e2d9d1366fb950efbe4ca2f24.tar.gz
PSARC/2004/779 SATA HBA Framework Support
PSARC/2005/679 SATA HBA Framework Support (update) 6296430 x86: support for Silicon Image 3124/3132 sata controllers 6326505 SATA framework needed to support controller-specific SATA HBA drivers operating in sata-native mode 6326531 cfgadm sata plugin needed for sata framework
-rw-r--r--usr/src/Makefile.lint8
-rw-r--r--usr/src/cmd/devfsadm/cfg_link.c53
-rw-r--r--usr/src/lib/cfgadm_plugins/Makefile9
-rw-r--r--usr/src/lib/cfgadm_plugins/sata/Makefile73
-rw-r--r--usr/src/lib/cfgadm_plugins/sata/Makefile.com85
-rw-r--r--usr/src/lib/cfgadm_plugins/sata/amd64/Makefile35
-rw-r--r--usr/src/lib/cfgadm_plugins/sata/common/cfga_rcm.c323
-rw-r--r--usr/src/lib/cfgadm_plugins/sata/common/cfga_sata.c1850
-rw-r--r--usr/src/lib/cfgadm_plugins/sata/common/cfga_sata.h223
-rw-r--r--usr/src/lib/cfgadm_plugins/sata/i386/Makefile36
-rw-r--r--usr/src/lib/cfgadm_plugins/sata/sata.xcl107
-rw-r--r--usr/src/lib/cfgadm_plugins/sata/spec/Makefile31
-rw-r--r--usr/src/lib/cfgadm_plugins/sata/spec/Makefile.targ37
-rw-r--r--usr/src/lib/cfgadm_plugins/sata/spec/amd64/Makefile43
-rw-r--r--usr/src/lib/cfgadm_plugins/sata/spec/cfga_sata.spec77
-rw-r--r--usr/src/lib/cfgadm_plugins/sata/spec/i386/Makefile43
-rw-r--r--usr/src/lib/cfgadm_plugins/sata/spec/versions40
-rw-r--r--usr/src/pkgdefs/Makefile1
-rw-r--r--usr/src/pkgdefs/SUNWckr/prototype_i3862
-rw-r--r--usr/src/pkgdefs/SUNWcsl/prototype_i38612
-rw-r--r--usr/src/pkgdefs/SUNWhea/prototype_i3863
-rw-r--r--usr/src/pkgdefs/SUNWsi3124/Makefile37
-rw-r--r--usr/src/pkgdefs/SUNWsi3124/pkginfo.tmpl45
-rw-r--r--usr/src/pkgdefs/SUNWsi3124/postinstall130
-rw-r--r--usr/src/pkgdefs/SUNWsi3124/postremove35
-rw-r--r--usr/src/pkgdefs/SUNWsi3124/prototype_com38
-rw-r--r--usr/src/pkgdefs/SUNWsi3124/prototype_i38657
-rw-r--r--usr/src/pkgdefs/etc/exception_list_i38620
-rw-r--r--usr/src/uts/common/Makefile.files4
-rw-r--r--usr/src/uts/common/Makefile.rules20
-rw-r--r--usr/src/uts/common/io/sata/adapters/si3124/si3124.c5328
-rw-r--r--usr/src/uts/common/io/sata/impl/sata.c9747
-rw-r--r--usr/src/uts/common/sys/Makefile6
-rw-r--r--usr/src/uts/common/sys/Makefile.syshdrs5
-rw-r--r--usr/src/uts/common/sys/sata/adapters/si3124/si3124reg.h388
-rw-r--r--usr/src/uts/common/sys/sata/adapters/si3124/si3124var.h287
-rw-r--r--usr/src/uts/common/sys/sata/impl/sata.h694
-rw-r--r--usr/src/uts/common/sys/sata/sata_cfgadm.h90
-rw-r--r--usr/src/uts/common/sys/sata/sata_defs.h339
-rw-r--r--usr/src/uts/common/sys/sata/sata_hba.h681
-rw-r--r--usr/src/uts/common/sys/sunddi.h7
-rw-r--r--usr/src/uts/intel/Makefile.intel.shared2
-rw-r--r--usr/src/uts/intel/sata/Makefile103
-rw-r--r--usr/src/uts/intel/si3124/Makefile102
-rw-r--r--usr/src/uts/sparc/sata/Makefile123
-rw-r--r--usr/src/uts/sparc/si3124/Makefile148
-rw-r--r--usr/src/uts/sparc/si3124/si3124.wlcmd119
-rw-r--r--usr/src/uts/sparc/warlock/Makefile14
48 files changed, 21628 insertions, 32 deletions
diff --git a/usr/src/Makefile.lint b/usr/src/Makefile.lint
index 7d3743a70b..07302499f5 100644
--- a/usr/src/Makefile.lint
+++ b/usr/src/Makefile.lint
@@ -2,9 +2,8 @@
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
-# Common Development and Distribution License, Version 1.0 only
-# (the "License"). You may not use this file except in compliance
-# with the License.
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
@@ -391,7 +390,8 @@ $(CLOSED_BUILD)COMMON_SUBDIRS += \
i386_SUBDIRS= \
cmd/biosdev \
cmd/rtc \
- cmd/fs.d/xmemfs
+ cmd/fs.d/xmemfs \
+ lib/cfgadm_plugins/sata
sparc_SUBDIRS= \
cmd/datadm \
diff --git a/usr/src/cmd/devfsadm/cfg_link.c b/usr/src/cmd/devfsadm/cfg_link.c
index 28aa6fd5f2..0951eb46b8 100644
--- a/usr/src/cmd/devfsadm/cfg_link.c
+++ b/usr/src/cmd/devfsadm/cfg_link.c
@@ -2,9 +2,8 @@
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License"). You may not use this file except in compliance
- * with the License.
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
@@ -19,8 +18,9 @@
*
* CDDL HEADER END
*/
+
/*
- * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
+ * Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
@@ -37,6 +37,7 @@
#define USB_CFG_LINK_RE "^cfg/((usb[0-9]+)/([0-9]+)([.]([0-9])+)*)$"
#define PCI_CFG_LINK_RE "^cfg/[:alnum:]$"
#define IB_CFG_LINK_RE "^cfg/(hca[0-9A-F]+)$"
+#define SATA_CFG_LINK_RE "^cfg/((sata[0-9]+)/([0-9]+)([.]([0-9])+)*)$"
#define CFG_DIRNAME "cfg"
@@ -46,6 +47,7 @@ static int usb_cfg_creat_cb(di_minor_t minor, di_node_t node);
static char *get_roothub(const char *path, void *cb_arg);
static int pci_cfg_creat_cb(di_minor_t minor, di_node_t node);
static int ib_cfg_creat_cb(di_minor_t minor, di_node_t node);
+static int sata_cfg_creat_cb(di_minor_t minor, di_node_t node);
/*
* NOTE: The CREATE_DEFER flag is private to this module.
@@ -69,6 +71,9 @@ static devfsadm_create_t cfg_create_cbt[] = {
},
{ "attachment-point", "ddi_ctl:attachment_point:ib", NULL,
TYPE_EXACT, ILEVEL_0, ib_cfg_creat_cb
+ },
+ { "attachment-point", "ddi_ctl:attachment_point:sata", NULL,
+ TYPE_EXACT, ILEVEL_0, sata_cfg_creat_cb
}
};
@@ -92,6 +97,9 @@ static devfsadm_remove_t cfg_remove_cbt[] = {
},
{ "attachment-point", IB_CFG_LINK_RE, RM_POST|RM_HOT|RM_ALWAYS,
ILEVEL_0, devfsadm_rm_all
+ },
+ { "attachment-point", SATA_CFG_LINK_RE, RM_POST|RM_HOT|RM_ALWAYS,
+ ILEVEL_0, devfsadm_rm_all
}
};
@@ -182,6 +190,43 @@ usb_cfg_creat_cb(di_minor_t minor, di_node_t node)
}
+static int
+sata_cfg_creat_cb(di_minor_t minor, di_node_t node)
+{
+ char path[PATH_MAX + 1], l_path[PATH_MAX], *buf, *devfspath;
+ char *minor_nm;
+ devfsadm_enumerate_t rules[1] =
+ {"^cfg$/^sata([0-9]+)$", 1, MATCH_ADDR};
+
+ minor_nm = di_minor_name(minor);
+ if (minor_nm == NULL)
+ return (DEVFSADM_CONTINUE);
+
+ devfspath = di_devfs_path(node);
+ if (devfspath == NULL)
+ return (DEVFSADM_CONTINUE);
+
+ (void) strlcpy(path, devfspath, sizeof (path));
+ (void) strlcat(path, ":", sizeof (path));
+ (void) strlcat(path, minor_nm, sizeof (path));
+ di_devfs_path_free(devfspath);
+
+ /* build the physical path from the components */
+ if (devfsadm_enumerate_int(path, 0, &buf, rules, 1) ==
+ DEVFSADM_FAILURE) {
+ return (DEVFSADM_CONTINUE);
+ }
+
+ (void) snprintf(l_path, sizeof (l_path), "%s/sata%s/%s", CFG_DIRNAME,
+ buf, minor_nm);
+ free(buf);
+
+ (void) devfsadm_mklink(l_path, node, minor, 0);
+
+ return (DEVFSADM_CONTINUE);
+}
+
+
/*
* get_roothub:
* figure out the root hub path to calculate /dev/cfg/usbN
diff --git a/usr/src/lib/cfgadm_plugins/Makefile b/usr/src/lib/cfgadm_plugins/Makefile
index 64b5cd9962..a2afd72d10 100644
--- a/usr/src/lib/cfgadm_plugins/Makefile
+++ b/usr/src/lib/cfgadm_plugins/Makefile
@@ -2,9 +2,8 @@
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
-# Common Development and Distribution License, Version 1.0 only
-# (the "License"). You may not use this file except in compliance
-# with the License.
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
@@ -23,7 +22,7 @@
# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
-#ident "%Z%%M% %I% %E% SMI"
+# ident "%Z%%M% %I% %E% SMI"
#
# lib/cfgadm_plugins/Makefile
#
@@ -35,7 +34,7 @@ CLOSED_PLUGIN = $(CLOSED)/lib/cfgadm_plugins/
COMMON_SUBDIRS= scsi pci usb ib
sparc_SUBDIRS= sbd
-i386_SUBDIRS=
+i386_SUBDIRS= sata
$(CLOSED_BUILD)sparc_SUBDIRS += $(CLOSED_PLUGIN)/ac
$(CLOSED_BUILD)sparc_SUBDIRS += $(CLOSED_PLUGIN)/sysctrl
diff --git a/usr/src/lib/cfgadm_plugins/sata/Makefile b/usr/src/lib/cfgadm_plugins/sata/Makefile
new file mode 100644
index 0000000000..691964397e
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/sata/Makefile
@@ -0,0 +1,73 @@
+#
+# 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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# lib/cfgadm_plugins/sata/Makefile
+
+include ../../Makefile.lib
+
+SUBDIRS= spec .WAIT $(MACH) $(BUILD64) $(MACH64)
+
+all := TARGET= all
+clean := TARGET= clean
+clobber := TARGET= clobber
+delete := TARGET= delete
+install := TARGET= install
+lint := TARGET= lint
+_msg := TARGET= _msg
+package := TARGET= package
+
+TEXT_DOMAIN= SUNW_OST_OSLIB
+XGETFLAGS= -a -x sata.xcl
+POFILE= sata.po
+POFILES= generic.po
+
+SED= sed
+GREP= grep
+CP= cp
+
+.KEEP_STATE:
+
+all clean clobber delete install lint package: $(SUBDIRS)
+
+$(MACH) $(MACH64) spec: FRC
+ @cd $@; pwd; $(MAKE) $(TARGET)
+
+_msg: $(MSGDOMAIN) $(POFILE)
+ $(RM) $(MSGDOMAIN)/$(POFILE)
+ $(CP) $(POFILE) $(MSGDOMAIN)
+
+$(POFILE): $(POFILES)
+ $(RM) $@
+ $(CAT) $(POFILES) > $@
+
+$(POFILES):
+ $(RM) messages.po
+ $(XGETTEXT) $(XGETFLAGS) `$(GREP) -l gettext */*.[ch]`
+ $(SED) -e '/^# msg/d' -e '/^domain/d' messages.po > $@
+ $(RM) messages.po
+
+FRC:
diff --git a/usr/src/lib/cfgadm_plugins/sata/Makefile.com b/usr/src/lib/cfgadm_plugins/sata/Makefile.com
new file mode 100644
index 0000000000..dd1fb7e8b9
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/sata/Makefile.com
@@ -0,0 +1,85 @@
+#
+# 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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# lib/cfgadm_plugins/sata/Makefile.com
+#
+
+LIBRARY= sata.a
+VERS= .1
+
+OBJECTS= cfga_sata.o cfga_rcm.o
+
+# include library definitions
+include ../../../Makefile.lib
+
+ROOTLIBDIR= $(ROOT)/usr/lib/cfgadm
+ROOTLIBDIR64= $(ROOTLIBDIR)/$(MACH64)
+
+MAPFILE= $(MAPDIR)/mapfile
+CLOBBERFILES += $(MAPFILE)
+
+SRCS= $(OBJECTS:%.o=../common/%.c)
+
+LIBS= $(DYNLIB)
+
+LINTFLAGS += -DDEBUG
+LINTFLAGS64 += -DDEBUG
+
+CFLAGS += $(CCVERBOSE)
+CFLAGS64 += $(CCVERBOSE)
+
+DYNFLAGS += -M $(MAPFILE)
+LDLIBS += -lc -ldevice -ldevinfo -lrcm -lnvpair
+
+.KEEP_STATE:
+
+all: $(LIBS)
+
+lint: lintcheck
+
+$(DYNLIB): $(MAPFILE)
+
+$(MAPFILE):
+ @cd $(MAPDIR); $(MAKE) mapfile
+
+# Install rules
+
+$(ROOTLIBDIR)/%: % $(ROOTLIBDIR)
+ $(INS.file)
+
+$(ROOTLIBDIR64)/%: % $(ROOTLIBDIR64)
+ $(INS.file)
+
+$(ROOTLIBDIR) $(ROOTLIBDIR64):
+ $(INS.dir)
+
+# include library targets
+include ../../../Makefile.targ
+
+objs/%.o pics/%.o: ../common/%.c
+ $(COMPILE.c) -o $@ $<
+ $(POST_PROCESS_O)
diff --git a/usr/src/lib/cfgadm_plugins/sata/amd64/Makefile b/usr/src/lib/cfgadm_plugins/sata/amd64/Makefile
new file mode 100644
index 0000000000..62c2fdcc88
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/sata/amd64/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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+MAPDIR= ../spec/amd64
+include ../Makefile.com
+include ../../../Makefile.lib.64
+
+.KEEP_STATE:
+
+install: all $(ROOTLIBS64) $(ROOTLINKS64)
diff --git a/usr/src/lib/cfgadm_plugins/sata/common/cfga_rcm.c b/usr/src/lib/cfgadm_plugins/sata/common/cfga_rcm.c
new file mode 100644
index 0000000000..979ff77f3d
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/sata/common/cfga_rcm.c
@@ -0,0 +1,323 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "cfga_sata.h"
+
+
+
+#define MAX_FORMAT 80 /* for info table */
+
+cfga_sata_ret_t sata_rcm_offline(const char *, char **, char *,
+ cfga_flags_t);
+cfga_sata_ret_t sata_rcm_online(const char *, char **, char *,
+ cfga_flags_t);
+cfga_sata_ret_t sata_rcm_remove(const char *, char **, char *,
+ cfga_flags_t);
+static cfga_sata_ret_t sata_rcm_info_table(rcm_info_t *, char **);
+static cfga_sata_ret_t sata_rcm_init(const char *, cfga_flags_t, char **,
+ uint_t *);
+
+
+static rcm_handle_t *rcm_handle = NULL;
+static mutex_t rcm_handle_lock = DEFAULTMUTEX;
+
+/*
+ * sata_rcm_offline:
+ * Offline SATA resource consumers.
+ */
+cfga_sata_ret_t
+sata_rcm_offline(const char *rsrc, char **errstring, char *rsrc_fixed,
+ cfga_flags_t flags)
+{
+ int rret;
+ uint_t rflags = 0;
+ rcm_info_t *rinfo = NULL;
+ cfga_sata_ret_t ret = CFGA_SATA_OK;
+
+ if ((ret = sata_rcm_init(rsrc, flags, errstring, &rflags)) !=
+ CFGA_SATA_OK) {
+
+ return (ret);
+ }
+
+ if ((rret = rcm_request_offline(rcm_handle, rsrc_fixed, rflags,
+ &rinfo)) != RCM_SUCCESS) {
+ if (rinfo) {
+ (void) sata_rcm_info_table(rinfo, errstring);
+ rcm_free_info(rinfo);
+ rinfo = NULL;
+ }
+
+ if (rret == RCM_FAILURE) {
+ (void) sata_rcm_online(rsrc, errstring,
+ rsrc_fixed, flags);
+ }
+ ret = CFGA_SATA_RCM_OFFLINE;
+ }
+ return (ret);
+}
+
+
+/*
+ * sata_rcm_online:
+ * Online SATA resource consumers that were previously offlined.
+ */
+cfga_sata_ret_t
+sata_rcm_online(const char *rsrc, char **errstring, char *rsrc_fixed,
+ cfga_flags_t flags)
+{
+ rcm_info_t *rinfo = NULL;
+ cfga_sata_ret_t ret = CFGA_SATA_OK;
+
+ if ((ret = sata_rcm_init(rsrc, flags, errstring, NULL)) !=
+ CFGA_SATA_OK) {
+
+ return (ret);
+ }
+
+ if (rcm_notify_online(rcm_handle, rsrc_fixed, 0, &rinfo) !=
+ RCM_SUCCESS && (rinfo != NULL)) {
+ (void) sata_rcm_info_table(rinfo, errstring);
+ rcm_free_info(rinfo);
+ rinfo = NULL;
+ ret = CFGA_SATA_RCM_ONLINE;
+ }
+
+ return (ret);
+}
+
+/*
+ * sata_rcm_remove:
+ * Remove SATA resource consumers after their kernel removal.
+ */
+cfga_sata_ret_t
+sata_rcm_remove(const char *rsrc, char **errstring, char *rsrc_fixed,
+ cfga_flags_t flags)
+{
+ rcm_info_t *rinfo = NULL;
+ cfga_sata_ret_t ret = CFGA_SATA_OK;
+
+ if ((ret = sata_rcm_init(rsrc, flags, errstring, NULL)) !=
+ CFGA_SATA_OK) {
+
+ return (ret);
+ }
+
+ if (rcm_notify_remove(rcm_handle, rsrc_fixed, 0, &rinfo) !=
+ RCM_SUCCESS && (rinfo != NULL)) {
+
+ (void) sata_rcm_info_table(rinfo, errstring);
+ rcm_free_info(rinfo);
+ rinfo = NULL;
+ ret = CFGA_SATA_RCM_ONLINE;
+ }
+
+ return (ret);
+}
+
+
+/*
+ * sata_rcm_init:
+ * Contains common initialization code for entering a sata_rcm_xx() routine.
+ */
+/* ARGSUSED */
+static cfga_sata_ret_t
+sata_rcm_init(const char *rsrc, cfga_flags_t flags, char **errstring,
+ uint_t *rflags)
+{
+ /* Validate the rsrc argument */
+ if (rsrc == NULL) {
+ return (CFGA_SATA_INTERNAL_ERROR);
+ }
+
+ /* Translate the cfgadm flags to RCM flags */
+ if (rflags && (flags & CFGA_FLAG_FORCE)) {
+ *rflags |= RCM_FORCE;
+ }
+
+ /* Get a handle for the RCM operations */
+ (void) mutex_lock(&rcm_handle_lock);
+ if (rcm_handle == NULL) {
+ if (rcm_alloc_handle(NULL, RCM_NOPID, NULL, &rcm_handle) !=
+ RCM_SUCCESS) {
+ (void) mutex_unlock(&rcm_handle_lock);
+
+ return (CFGA_SATA_RCM_HANDLE);
+ }
+ }
+ (void) mutex_unlock(&rcm_handle_lock);
+
+ return (CFGA_SATA_OK);
+}
+
+
+/*
+ * sata_rcm_info_table:
+ * Takes an opaque rcm_info_t pointer and a character pointer,
+ * and appends the rcm_info_t data in the form of a table to the
+ * given character pointer.
+ */
+static cfga_sata_ret_t
+sata_rcm_info_table(rcm_info_t *rinfo, char **table)
+{
+ int i;
+ size_t w;
+ size_t width = 0;
+ size_t w_rsrc = 0;
+ size_t w_info = 0;
+ size_t table_size = 0;
+ uint_t tuples = 0;
+ rcm_info_tuple_t *tuple = NULL;
+ char *rsrc;
+ char *info;
+ char *newtable;
+ static char format[MAX_FORMAT];
+ const char *infostr;
+
+ /* Protect against invalid arguments */
+ if (rinfo == NULL || table == NULL) {
+ return (CFGA_SATA_INTERNAL_ERROR);
+ }
+
+ /* Set localized table header strings */
+ rsrc = dgettext(TEXT_DOMAIN, "Resource");
+ info = dgettext(TEXT_DOMAIN, "Information");
+
+
+ /* A first pass, to size up the RCM information */
+ while (tuple = rcm_info_next(rinfo, tuple)) {
+ if ((infostr = rcm_info_info(tuple)) != NULL) {
+ tuples++;
+ if ((w = strlen(rcm_info_rsrc(tuple))) > w_rsrc)
+ w_rsrc = w;
+ if ((w = strlen(infostr)) > w_info)
+ w_info = w;
+ }
+ }
+
+ /* If nothing was sized up above, stop early */
+ if (tuples == 0) {
+ return (CFGA_SATA_OK);
+ }
+
+ /* Adjust column widths for column headings */
+ if ((w = strlen(rsrc)) > w_rsrc) {
+ w_rsrc = w;
+ } else if ((w_rsrc - w) % 2) {
+ w_rsrc++;
+ }
+
+ if ((w = strlen(info)) > w_info) {
+ w_info = w;
+ } else if ((w_info - w) % 2) {
+ w_info++;
+ }
+
+
+ /*
+ * Compute the total line width of each line,
+ * accounting for intercolumn spacing.
+ */
+ width = w_info + w_rsrc + 4;
+
+ /* Allocate space for the table */
+ table_size = (2 + tuples) * (width + 1) + 2;
+ if (*table == NULL) {
+ /* zero fill for the strcat() call below */
+ *table = calloc(table_size, sizeof (char));
+ if (*table == NULL) {
+ return (CFGA_SATA_ALLOC_FAIL);
+ }
+ } else {
+ newtable = realloc(*table, strlen(*table) + table_size);
+ if (newtable == NULL) {
+ return (CFGA_SATA_ALLOC_FAIL);
+ } else {
+ *table = newtable;
+ }
+ }
+
+ /* Place a table header into the string */
+
+
+ /* The resource header */
+ (void) strcat(*table, "\n");
+ w = strlen(rsrc);
+
+ for (i = 0; i < ((w_rsrc - w) / 2); i++) {
+ (void) strcat(*table, " ");
+ }
+ (void) strcat(*table, rsrc);
+
+ for (i = 0; i < ((w_rsrc - w) / 2); i++) {
+ (void) strcat(*table, " ");
+ }
+
+ /* The information header */
+ (void) strcat(*table, " ");
+ w = strlen(info);
+ for (i = 0; i < ((w_info - w) / 2); i++) {
+ (void) strcat(*table, " ");
+ }
+ (void) strcat(*table, info);
+
+ for (i = 0; i < ((w_info - w) / 2); i++) {
+ (void) strcat(*table, " ");
+ }
+
+ (void) strcat(*table, "\n");
+
+ /* Underline the headers */
+ for (i = 0; i < w_rsrc; i++) {
+ (void) strcat(*table, "-");
+ }
+
+ (void) strcat(*table, " ");
+ for (i = 0; i < w_info; i++) {
+ (void) strcat(*table, "-");
+ }
+
+
+ (void) strcat(*table, "\n");
+
+ /* Construct the format string */
+ (void) snprintf(format, MAX_FORMAT, "%%-%ds %%-%ds",
+ (int)w_rsrc, (int)w_info);
+
+ /* Add the tuples to the table string */
+ tuple = NULL;
+ while ((tuple = rcm_info_next(rinfo, tuple)) != NULL) {
+ if ((infostr = rcm_info_info(tuple)) != NULL) {
+ (void) sprintf(&((*table)[strlen(*table)]),
+ format, rcm_info_rsrc(tuple), infostr);
+ (void) strcat(*table, "\n");
+ }
+ }
+
+ return (CFGA_SATA_OK);
+}
diff --git a/usr/src/lib/cfgadm_plugins/sata/common/cfga_sata.c b/usr/src/lib/cfgadm_plugins/sata/common/cfga_sata.c
new file mode 100644
index 0000000000..c796c8284e
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/sata/common/cfga_sata.c
@@ -0,0 +1,1850 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include "cfga_sata.h"
+
+/*
+ * This file contains the entry points to the plug-in as defined in the
+ * config_admin(3X) man page.
+ */
+
+/*
+ * Set the version number for the cfgadm library's use.
+ */
+int cfga_version = CFGA_HSL_V2;
+
+enum {
+ HELP_HEADER = 1,
+ HELP_CONFIG,
+ HELP_RESET_PORT,
+ HELP_RESET_DEVICE,
+ HELP_RESET_ALL,
+ HELP_PORT_DEACTIVATE,
+ HELP_PORT_ACTIVATE,
+ HELP_PORT_SELF_TEST,
+ HELP_CNTRL_SELF_TEST,
+ HELP_UNKNOWN
+};
+
+/* SATA specific help messages */
+static char *sata_help[] = {
+ NULL,
+ "SATA specific commands:\n",
+ " cfgadm -c [configure|unconfigure|disconnect|connect] ap_id "
+ "[ap_id...]\n",
+ " cfgadm -x sata_reset_port ap_id [ap_id...]\n",
+ " cfgadm -x sata_reset_device ap_id [ap_id...]\n",
+ " cfgadm -x sata_reset_all ap_id\n",
+ " cfgadm -x sata_port_deactivate ap_id [ap_id...]\n",
+ " cfgadm -x sata_port_activate ap_id [ap_id...]\n",
+ " cfgadm -x sata_port_self_test ap_id [ap_id...]\n",
+ " cfgadm -t ap_id\n",
+ "\tunknown command or option:\n",
+ NULL
+}; /* End help messages */
+
+
+/*
+ * Messages.
+ */
+static msgcvt_t sata_msgs[] = {
+ /* CFGA_SATA_OK */
+ { CVT, CFGA_OK, "" },
+
+ /* CFGA_SATA_NACK */
+ { CVT, CFGA_NACK, "" },
+
+ /* CFGA_SATA_DEVICE_UNCONFIGURED */
+ { CVT, CFGA_OK, "Device unconfigured prior to disconnect" },
+
+ /* CFGA_SATA_UNKNOWN / CFGA_LIB_ERROR -> "Library error" */
+ { CVT, CFGA_LIB_ERROR, "Unknown message; internal error" },
+
+ /* CFGA_SATA_INTERNAL_ERROR / CFGA_LIB_ERROR -> "Library error" */
+ { CVT, CFGA_LIB_ERROR, "Internal error" },
+
+ /* CFGA_SATA_DATA_ERROR / CFGA_DATA_ERROR -> "Data error" */
+ { CVT, CFGA_DATA_ERROR, "cfgadm data error" },
+
+ /* CFGA_SATA_OPTIONS / CFGA_ERROR -> "Hardware specific failure" */
+ { CVT, CFGA_ERROR, "Hardware specific option not supported" },
+
+ /* CFGA_SATA_HWOPNOTSUPP / CFGA_ERROR -> "Hardware specific failure" */
+ { CVT, CFGA_ERROR, "Hardware specific operation not supported" },
+
+ /*
+ * CFGA_SATA_DYNAMIC_AP /
+ * CFGA_LIB_ERROR -> "Configuration operation invalid"
+ */
+ { CVT, CFGA_INVAL, "Cannot identify attached device" },
+
+ /* CFGA_SATA_AP / CFGA_APID_NOEXIST -> "Attachment point not found" */
+ { CVT, CFGA_APID_NOEXIST, "" },
+
+ /* CFGA_SATA_PORT / CFGA_LIB_ERROR -> "Library error" */
+ { CVT, CFGA_LIB_ERROR, "Cannot determine sata port number for " },
+
+ /* CFGA_SATA_DEVCTL / CFGA_LIB_ERROR -> "Library error" */
+ { CVT, CFGA_LIB_ERROR, "Internal error: "
+ "Cannot allocate devctl handle " },
+
+ /*
+ * CFGA_SATA_DEV_CONFIGURE /
+ * CFGA_ERROR -> "Hardware specific failure"
+ */
+ { CVT, CFGA_ERROR, "Failed to config device at " },
+
+ /*
+ * CFGA_SATA_DEV_UNCONFIGURE /
+ * CFGA_ERROR -> "Hardware specific failure"
+ */
+ { CVT, CFGA_ERROR, "Failed to unconfig device at " },
+
+ /*
+ * CFGA_SATA_DISCONNECTED
+ * CFGA_INVAL -> "Configuration operation invalid"
+ */
+ { CVT, CFGA_INVAL, "Port already disconnected " },
+
+ /*
+ * CFGA_SATA_NOT_CONNECTED
+ * CFGA_INVAL -> "Configuration operation invalid"
+ */
+ { CVT, CFGA_INVAL, "No device connected to " },
+
+ /*
+ * CFGA_SATA_NOT_CONFIGURED /
+ * CFGA_INVAL -> "Configuration operation invalid"
+ */
+ { CVT, CFGA_INVAL, "No device configured at " },
+
+ /*
+ * CFGA_SATA_ALREADY_CONNECTED /
+ * CFGA_INVAL -> "Configuration operation invalid"
+ */
+ { CVT, CFGA_INVAL, "Device already connected to " },
+
+ /*
+ * CFGA_SATA_ALREADY_CONFIGURED /
+ * CFGA_INVAL -> "Configuration operation invalid"
+ */
+ { CVT, CFGA_INVAL, "Device already configured at " },
+
+ /*
+ * CFGA_SATA_INVALID_DEVNAME /
+ * CFGA_INVAL -> "Configuration operation invalid"
+ */
+ { CVT, CFGA_INVAL, "Cannot specify device name" },
+
+ /* CFGA_SATA_OPEN / CFGA_LIB_ERROR -> "Library error" */
+ { CVT, CFGA_LIB_ERROR, "Cannot open " },
+
+ /* CFGA_SATA_IOCTL / CFGA_ERROR -> "Hardware specific failure" */
+ { CVT, CFGA_ERROR, "Driver ioctl failed " },
+
+ /*
+ * CFGA_SATA_BUSY /
+ * CFGA_SYSTEM_BUSY -> "System is busy, try again"
+ */
+ { CVT, CFGA_SYSTEM_BUSY, "" },
+
+ /* CFGA_SATA_ALLOC_FAIL / CFGA_LIB_ERROR -> "Library error" */
+ { CVT, CFGA_LIB_ERROR, "Memory allocation failure" },
+
+ /*
+ * CFGA_SATA_OPNOTSUPP /
+ * CFGA_OPNOTSUPP -> "Configuration operation not supported"
+ */
+ { CVT, CFGA_OPNOTSUPP, "Operation not supported" },
+
+ /* CFGA_SATA_DEVLINK / CFGA_LIB_ERROR -> "Library error" */
+ { CVT, CFGA_LIB_ERROR, "Could not find /dev/cfg link for " },
+
+ /* CFGA_SATA_STATE / CFGA_LIB_ERROR -> "Library error" */
+ { CVT, CFGA_LIB_ERROR, "Internal error: Unrecognized ap state" },
+
+ /* CFGA_SATA_PRIV / CFGA_PRIV -> "Insufficient privileges" */
+ { CVT, CFGA_PRIV, "" },
+
+ /* CFGA_SATA_NVLIST / CFGA_ERROR -> "Hardware specific failure" */
+ { CVT, CFGA_ERROR, "Internal error (nvlist)" },
+
+ /* CFGA_SATA_ZEROLEN / CFGA_ERROR -> "Hardware specific failure" */
+ { CVT, CFGA_ERROR, "Internal error (zerolength string)" },
+
+ /* CFGA_SATA_RCM_HANDLE / CFGA_ERROR -> "Hardware specific failure" */
+ { CVT, CFGA_ERROR, "cannot get RCM handle"},
+
+ /*
+ * CFGA_SATA_RCM_ONLINE /
+ * CFGA_SYSTEM_BUSY -> "System is busy, try again"
+ */
+ { CVT, CFGA_SYSTEM_BUSY, "failed to online: "},
+
+ /*
+ * CFGA_SATA_RCM_OFFLINE /
+ * CFGA_SYSTEM_BUSY -> "System is busy, try again"
+ */
+ { CVT, CFGA_SYSTEM_BUSY, "failed to offline: "},
+
+ /* CFGA_SATA_RCM_INFO / CFGA_ERROR -> "Hardware specific failure" */
+ { CVT, CFGA_ERROR, "failed to query: "}
+
+}; /* End error messages */
+
+static cfga_sata_ret_t
+verify_params(const char *ap_id, const char *options, char **errstring);
+
+
+static cfga_sata_ret_t
+setup_for_devctl_cmd(const char *ap_id, devctl_hdl_t *devctl_hdl,
+ nvlist_t **user_nvlistp, uint_t oflag);
+
+static cfga_sata_ret_t
+port_state(devctl_hdl_t hdl, nvlist_t *list,
+ ap_rstate_t *rstate, ap_ostate_t *ostate);
+
+static cfga_sata_ret_t
+do_control_ioctl(const char *ap_id, sata_cfga_apctl_t subcommand, uint_t arg,
+ void **descrp, size_t *sizep);
+
+static int
+get_link(di_devlink_t devlink, void *arg);
+
+static void
+cleanup_after_devctl_cmd(devctl_hdl_t devctl_hdl, nvlist_t *user_nvlist);
+
+static char *
+sata_get_devicepath(const char *ap_id);
+
+static int
+sata_confirm(struct cfga_confirm *confp, char *msg);
+
+
+/* Utilities */
+
+/*
+ * The next two funcs are similar to functions from cfgadm_scsi.
+ * physpath_to_devlink is the only func directly used by cfgadm_sata.
+ * get_link supports it.
+ */
+/*
+ * Routine to search the /dev directory or a subtree of /dev.
+ */
+static int
+get_link(di_devlink_t devlink, void *arg)
+{
+ walk_link_t *larg = (walk_link_t *)arg;
+
+ /*
+ * When path is specified, it's the node path without minor
+ * name. Therefore, the ../.. prefixes needs to be stripped.
+ */
+ if (larg->path) {
+ char *content = (char *)di_devlink_content(devlink);
+ char *start = strstr(content, "/devices/");
+
+ /* line content must have minor node */
+ if (start == NULL ||
+ strncmp(start, larg->path, larg->len) != 0 ||
+ start[larg->len] != ':') {
+
+ return (DI_WALK_CONTINUE);
+ }
+ }
+
+
+ *(larg->linkpp) = strdup(di_devlink_path(devlink));
+
+ return (DI_WALK_TERMINATE);
+}
+
+/* ARGSUSED */
+static sata_cfga_ret_t
+physpath_to_devlink(
+ const char *basedir,
+ const char *node_path,
+ char **logpp,
+ int *l_errnop,
+ int match_minor)
+{
+ walk_link_t larg;
+ di_devlink_handle_t hdl;
+ char *minor_path;
+
+ if ((hdl = di_devlink_init(NULL, 0)) == NULL) {
+ *l_errnop = errno;
+ return (SATA_CFGA_LIB_ERR);
+ }
+
+ *logpp = NULL;
+ larg.linkpp = logpp;
+ minor_path = (char *)node_path + strlen("/devices");
+ if (match_minor) {
+ larg.path = NULL;
+ (void) di_devlink_walk(hdl, NULL, minor_path, DI_PRIMARY_LINK,
+ (void *)&larg, get_link);
+ } else {
+ minor_path = NULL;
+ larg.len = strlen(node_path);
+ larg.path = (char *)node_path;
+ (void) di_devlink_walk(hdl, "/", minor_path, DI_PRIMARY_LINK,
+ (void *)&larg, get_link);
+ }
+
+ (void) di_devlink_fini(&hdl);
+
+ if (*logpp == NULL) {
+ *l_errnop = errno;
+ return (SATA_CFGA_LIB_ERR);
+ }
+
+ return (SATA_CFGA_OK);
+}
+
+
+/*
+ * Given the index into a table (msgcvt_t) of messages, get the message
+ * string, converting it to the proper locale if necessary.
+ * NOTE: Indexes are defined in cfga_sata.h
+ */
+static const char *
+get_msg(uint_t msg_index, msgcvt_t *msg_tbl, uint_t tbl_size)
+{
+ if (msg_index >= tbl_size) {
+ msg_index = CFGA_SATA_UNKNOWN;
+ }
+
+ return ((msg_tbl[msg_index].intl) ?
+ dgettext(TEXT_DOMAIN, msg_tbl[msg_index].msgstr) :
+ msg_tbl[msg_index].msgstr);
+}
+
+/*
+ * Allocates and creates a message string (in *ret_str),
+ * by concatenating all the (char *) args together, in order.
+ * Last arg MUST be NULL.
+ */
+static void
+set_msg(char **ret_str, ...)
+{
+ char *str;
+ size_t total_len;
+ va_list valist;
+
+ va_start(valist, ret_str);
+
+ total_len = (*ret_str == NULL) ? 0 : strlen(*ret_str);
+
+ while ((str = va_arg(valist, char *)) != NULL) {
+ size_t len = strlen(str);
+ char *old_str = *ret_str;
+
+ *ret_str = (char *)realloc(*ret_str, total_len + len + 1);
+ if (*ret_str == NULL) {
+ /* We're screwed */
+ free(old_str);
+ va_end(valist);
+ return;
+ }
+
+ (void) strcpy(*ret_str + total_len, str);
+ total_len += len;
+ }
+
+ va_end(valist);
+}
+
+/*
+ * Error message handling.
+ * For the rv passed in, looks up the corresponding error message string(s),
+ * internationalized if necessary, and concatenates it into a new
+ * memory buffer, and points *errstring to it.
+ * Note not all rvs will result in an error message return, as not all
+ * error conditions warrant a SATA-specific error message - for those
+ * conditions the cfgadm generic messages are sufficient.
+ *
+ * Some messages may display ap_id or errno, which is why they are passed
+ * in.
+ */
+
+cfga_err_t
+sata_err_msg(
+ char **errstring,
+ cfga_sata_ret_t rv,
+ const char *ap_id,
+ int l_errno)
+{
+ if (errstring == NULL) {
+ return (sata_msgs[rv].cfga_err);
+ }
+
+ /*
+ * Generate the appropriate SATA-specific error message(s) (if any).
+ */
+ switch (rv) {
+ case CFGA_SATA_OK:
+ case CFGA_NACK:
+ /* Special case - do nothing. */
+ break;
+
+ case CFGA_SATA_UNKNOWN:
+ case CFGA_SATA_DYNAMIC_AP:
+ case CFGA_SATA_INTERNAL_ERROR:
+ case CFGA_SATA_OPTIONS:
+ case CFGA_SATA_ALLOC_FAIL:
+ case CFGA_SATA_STATE:
+ case CFGA_SATA_PRIV:
+ case CFGA_SATA_OPNOTSUPP:
+ case CFGA_SATA_DATA_ERROR:
+ /* These messages require no additional strings passed. */
+ set_msg(errstring, ERR_STR(rv), NULL);
+ break;
+
+ case CFGA_SATA_HWOPNOTSUPP:
+ /* hardware-specific help needed */
+ set_msg(errstring, ERR_STR(rv), NULL);
+ set_msg(errstring, "\n",
+ dgettext(TEXT_DOMAIN, sata_help[HELP_HEADER]), NULL);
+ set_msg(errstring, sata_help[HELP_RESET_PORT], NULL);
+ set_msg(errstring, sata_help[HELP_RESET_DEVICE], NULL);
+ set_msg(errstring, sata_help[HELP_RESET_ALL], NULL);
+ set_msg(errstring, sata_help[HELP_PORT_ACTIVATE], NULL);
+ set_msg(errstring, sata_help[HELP_PORT_DEACTIVATE], NULL);
+ set_msg(errstring, sata_help[HELP_PORT_SELF_TEST], NULL);
+ set_msg(errstring, sata_help[HELP_CNTRL_SELF_TEST], NULL);
+ break;
+
+ case CFGA_SATA_AP:
+ case CFGA_SATA_PORT:
+ case CFGA_SATA_NOT_CONNECTED:
+ case CFGA_SATA_NOT_CONFIGURED:
+ case CFGA_SATA_ALREADY_CONNECTED:
+ case CFGA_SATA_ALREADY_CONFIGURED:
+ case CFGA_SATA_BUSY:
+ case CFGA_SATA_DEVLINK:
+ case CFGA_SATA_RCM_HANDLE:
+ case CFGA_SATA_RCM_ONLINE:
+ case CFGA_SATA_RCM_OFFLINE:
+ case CFGA_SATA_RCM_INFO:
+ case CFGA_SATA_DEV_CONFIGURE:
+ case CFGA_SATA_DEV_UNCONFIGURE:
+ case CFGA_SATA_DISCONNECTED:
+ /* These messages also print ap_id. */
+ set_msg(errstring, ERR_STR(rv), "ap_id: ", ap_id, "", NULL);
+ break;
+
+
+ case CFGA_SATA_IOCTL:
+ case CFGA_SATA_NVLIST:
+ /* These messages also print errno. */
+ {
+ char *errno_str = l_errno ? strerror(l_errno) : "";
+
+ set_msg(errstring, ERR_STR(rv), errno_str,
+ l_errno ? "\n" : "", NULL);
+ break;
+ }
+
+ case CFGA_SATA_OPEN:
+ /* These messages also apid and errno. */
+ {
+ char *errno_str = l_errno ? strerror(l_errno) : "";
+
+ set_msg(errstring, ERR_STR(rv), "ap_id: ", ap_id, "\n",
+ errno_str, l_errno ? "\n" : "", NULL);
+ break;
+ }
+
+ default:
+ set_msg(errstring, ERR_STR(CFGA_SATA_INTERNAL_ERROR), NULL);
+
+ } /* end switch */
+
+
+ /*
+ * Determine the proper error code to send back to the cfgadm library.
+ */
+ return (sata_msgs[rv].cfga_err);
+}
+
+
+/*
+ * Entry points
+ */
+/* cfgadm entry point */
+/*ARGSUSED*/
+cfga_err_t
+cfga_change_state(
+ cfga_cmd_t state_change_cmd,
+ const char *ap_id,
+ const char *options,
+ struct cfga_confirm *confp,
+ struct cfga_msg *msgp,
+ char **errstring,
+ cfga_flags_t flags)
+{
+ int ret;
+ int len;
+ char *msg;
+ char *devpath;
+ nvlist_t *nvl = NULL;
+ ap_rstate_t rstate;
+ ap_ostate_t ostate;
+ devctl_hdl_t hdl = NULL;
+ cfga_sata_ret_t rv = CFGA_SATA_OK;
+ char *pdyn;
+
+ /*
+ * All sub-commands which can change state of device require
+ * root privileges.
+ */
+ if (geteuid() != 0) {
+ rv = CFGA_SATA_PRIV;
+ goto bailout;
+ }
+
+ if ((rv = verify_params(ap_id, options, errstring)) != CFGA_SATA_OK) {
+ (void) cfga_help(msgp, options, flags);
+ goto bailout;
+ }
+
+ if ((rv = setup_for_devctl_cmd(ap_id, &hdl, &nvl,
+ DC_RDONLY)) != CFGA_SATA_OK) {
+ goto bailout;
+ }
+
+ switch (state_change_cmd) {
+ case CFGA_CMD_CONFIGURE:
+ if ((rv = port_state(hdl, nvl, &rstate, &ostate)) !=
+ CFGA_SATA_OK)
+ goto bailout;
+
+ if (ostate == AP_OSTATE_CONFIGURED) {
+ rv = CFGA_SATA_ALREADY_CONFIGURED;
+ goto bailout;
+ }
+ /* Disallow dynamic AP name component */
+ if (GET_DYN(ap_id) != NULL) {
+ rv = CFGA_SATA_INVALID_DEVNAME;
+ goto bailout;
+ }
+
+ if (rstate == AP_RSTATE_EMPTY) {
+ rv = CFGA_SATA_NOT_CONNECTED;
+ goto bailout;
+ }
+ rv = CFGA_SATA_OK;
+
+ if (devctl_ap_configure(hdl, nvl) != 0) {
+ rv = CFGA_SATA_DEV_CONFIGURE;
+ goto bailout;
+ }
+
+ devpath = sata_get_devicepath(ap_id);
+ if (devpath == NULL) {
+ int i;
+ /*
+ * Try for some time as SATA hotplug thread
+ * takes a while to create the path then
+ * eventually give up.
+ */
+ for (i = 0; i < 12 && (devpath == NULL); i++) {
+ (void) sleep(6);
+ devpath = sata_get_devicepath(ap_id);
+ }
+
+ if (devpath == NULL) {
+ rv = CFGA_SATA_DEV_CONFIGURE;
+ break;
+ }
+ }
+
+ S_FREE(devpath);
+ break;
+
+ case CFGA_CMD_UNCONFIGURE:
+ if ((rv = port_state(hdl, nvl, &rstate, &ostate)) !=
+ CFGA_SATA_OK)
+ goto bailout;
+
+ if (rstate != AP_RSTATE_CONNECTED) {
+ rv = CFGA_SATA_NOT_CONNECTED;
+ goto bailout;
+ }
+
+ if (ostate != AP_OSTATE_CONFIGURED) {
+ rv = CFGA_SATA_NOT_CONFIGURED;
+ goto bailout;
+ }
+ /* Strip off AP name dynamic component, if present */
+ if ((pdyn = GET_DYN(ap_id)) != NULL) {
+ *pdyn = '\0';
+ }
+
+ rv = CFGA_SATA_OK;
+
+ len = strlen(SATA_CONFIRM_DEVICE) +
+ strlen(SATA_CONFIRM_DEVICE_SUSPEND) +
+ strlen("Unconfigure") + strlen(ap_id);
+ if ((msg = (char *)calloc(len +3, 1)) != NULL) {
+ (void) snprintf(msg, len + 3, "Unconfigure"
+ " %s%s\n%s",
+ SATA_CONFIRM_DEVICE, ap_id,
+ SATA_CONFIRM_DEVICE_SUSPEND);
+ }
+
+ if (!sata_confirm(confp, msg)) {
+ free(msg);
+ rv = CFGA_SATA_NACK;
+ break;
+ }
+ free(msg);
+
+ devpath = sata_get_devicepath(ap_id);
+ if (devpath == NULL) {
+ (void) printf(
+ "cfga_change_state: get device path failed\n");
+ rv = CFGA_SATA_DEV_UNCONFIGURE;
+ break;
+ }
+
+ if ((rv = sata_rcm_offline(ap_id, errstring, devpath, flags))
+ != CFGA_SATA_OK) {
+ break;
+ }
+
+ ret = devctl_ap_unconfigure(hdl, nvl);
+
+ if (ret != 0) {
+ rv = CFGA_SATA_DEV_UNCONFIGURE;
+ if (errno == EBUSY) {
+ rv = CFGA_SATA_BUSY;
+ }
+ (void) sata_rcm_online(ap_id, errstring, devpath,
+ flags);
+ } else {
+ (void) sata_rcm_remove(ap_id, errstring, devpath,
+ flags);
+
+ }
+ S_FREE(devpath);
+
+ break;
+
+ case CFGA_CMD_DISCONNECT:
+ if ((rv = port_state(hdl, nvl, &rstate, &ostate)) !=
+ CFGA_SATA_OK)
+ goto bailout;
+
+ if (rstate == AP_RSTATE_DISCONNECTED) {
+ rv = CFGA_SATA_DISCONNECTED;
+ goto bailout;
+ }
+
+ /* Strip off AP name dynamic component, if present */
+ if ((pdyn = GET_DYN(ap_id)) != NULL) {
+ *pdyn = '\0';
+ }
+
+
+ rv = CFGA_SATA_OK; /* other statuses don't matter */
+
+
+ /*
+ * If the port originally with device attached and was
+ * unconfigured already, the devicepath for the sd will be
+ * removed. sata_get_devicepath in this case is not necessary.
+ */
+
+ /* only call rcm_offline if the state was CONFIGURED */
+ if (ostate == AP_OSTATE_CONFIGURED) {
+ devpath = sata_get_devicepath(ap_id);
+ if (devpath == NULL) {
+ (void) printf(
+ "cfga_change_state: get path failed\n");
+ rv = CFGA_SATA_DEV_UNCONFIGURE;
+ break;
+ }
+
+ len = strlen(SATA_CONFIRM_DEVICE) +
+ strlen(SATA_CONFIRM_DEVICE_SUSPEND) +
+ strlen("Disconnect") + strlen(ap_id);
+ if ((msg = (char *)calloc(len +3, 1)) != NULL) {
+ (void) snprintf(msg, len + 3,
+ "Disconnect"
+ " %s%s\n%s",
+ SATA_CONFIRM_DEVICE, ap_id,
+ SATA_CONFIRM_DEVICE_SUSPEND);
+ }
+ if (!sata_confirm(confp, msg)) {
+ free(msg);
+ rv = CFGA_SATA_NACK;
+ break;
+ }
+ free(msg);
+
+ if ((rv = sata_rcm_offline(ap_id, errstring,
+ devpath, flags)) != CFGA_SATA_OK) {
+ break;
+ }
+
+ ret = devctl_ap_unconfigure(hdl, nvl);
+ if (ret != 0) {
+ (void) printf(
+ "devctl_ap_unconfigure failed\n");
+ rv = CFGA_SATA_DEV_UNCONFIGURE;
+ if (errno == EBUSY)
+ rv = CFGA_SATA_BUSY;
+ (void) sata_rcm_online(ap_id, errstring,
+ devpath, flags);
+ S_FREE(devpath);
+
+ /*
+ * The current policy is that if unconfigure
+ * failed, do not continue with disconnect.
+ * If the port needs to be forced into the
+ * disconnect (shutdown) state,
+ * the -x sata_port_poweroff command should be
+ * used instead of -c disconnect
+ */
+ break;
+ } else {
+ (void) printf("%s\n",
+ ERR_STR(CFGA_SATA_DEVICE_UNCONFIGURED));
+ (void) sata_rcm_remove(ap_id, errstring,
+ devpath, flags);
+ }
+ S_FREE(devpath);
+ } else if (rstate == AP_RSTATE_CONNECTED ||
+ rstate == AP_RSTATE_EMPTY) {
+ len = strlen(SATA_CONFIRM_PORT) +
+ strlen(SATA_CONFIRM_PORT_DISABLE) +
+ strlen("Deactivate Port") + strlen(ap_id);
+ if ((msg = (char *)calloc(len +3, 1)) != NULL) {
+ (void) snprintf(msg, len +3,
+ "Disconnect"
+ " %s%s\n%s",
+ SATA_CONFIRM_PORT, ap_id,
+ SATA_CONFIRM_PORT_DISABLE);
+ }
+ if (!sata_confirm(confp, msg)) {
+ free(msg);
+ rv = CFGA_SATA_NACK;
+ break;
+ }
+ }
+ ret = devctl_ap_disconnect(hdl, nvl);
+ if (ret != 0) {
+ rv = CFGA_SATA_IOCTL;
+ if (errno == EBUSY) {
+ rv = CFGA_SATA_BUSY;
+ }
+ }
+ break;
+
+ case CFGA_CMD_CONNECT:
+ if ((rv = port_state(hdl, nvl, &rstate, &ostate)) !=
+ CFGA_SATA_OK)
+ goto bailout;
+
+ if (rstate == AP_RSTATE_CONNECTED) {
+ rv = CFGA_SATA_ALREADY_CONNECTED;
+ goto bailout;
+ }
+
+ len = strlen(SATA_CONFIRM_PORT) +
+ strlen(SATA_CONFIRM_PORT_ENABLE) +
+ strlen("Activate Port") + strlen(ap_id);
+ if ((msg = (char *)calloc(len +3, 1)) != NULL) {
+ (void) snprintf(msg, len +3, "Activate"
+ " %s%s\n%s",
+ SATA_CONFIRM_PORT, ap_id,
+ SATA_CONFIRM_PORT_ENABLE);
+ }
+ if (!sata_confirm(confp, msg)) {
+ rv = CFGA_SATA_NACK;
+ break;
+ }
+
+ /* Disallow dynamic AP name component */
+ if (GET_DYN(ap_id) != NULL) {
+ rv = CFGA_SATA_INVALID_DEVNAME;
+ goto bailout;
+ }
+
+ ret = devctl_ap_connect(hdl, nvl);
+ if (ret != 0) {
+ rv = CFGA_SATA_IOCTL;
+ } else {
+ rv = CFGA_SATA_OK;
+ }
+
+ break;
+
+ case CFGA_CMD_LOAD:
+ case CFGA_CMD_UNLOAD:
+ (void) cfga_help(msgp, options, flags);
+ rv = CFGA_SATA_OPNOTSUPP;
+ break;
+
+ case CFGA_CMD_NONE:
+ default:
+ (void) cfga_help(msgp, options, flags);
+ rv = CFGA_SATA_INTERNAL_ERROR;
+ }
+
+bailout:
+ cleanup_after_devctl_cmd(hdl, nvl);
+
+ return (sata_err_msg(errstring, rv, ap_id, errno));
+}
+
+/* cfgadm entry point */
+cfga_err_t
+cfga_private_func(
+ const char *func,
+ const char *ap_id,
+ const char *options,
+ struct cfga_confirm *confp,
+ struct cfga_msg *msgp,
+ char **errstring,
+ cfga_flags_t flags)
+{
+ int len;
+ char *msg;
+ nvlist_t *list = NULL;
+ ap_ostate_t ostate;
+ ap_rstate_t rstate;
+ devctl_hdl_t hdl = NULL;
+ cfga_sata_ret_t rv;
+ char *str_p;
+ size_t size;
+
+ if ((rv = verify_params(ap_id, NULL, errstring)) != CFGA_SATA_OK) {
+ (void) cfga_help(msgp, options, flags);
+ return (sata_err_msg(errstring, rv, ap_id, errno));
+ }
+
+ /*
+ * All subcommands which can change state of device require
+ * root privileges.
+ */
+ if (geteuid() != 0) {
+ rv = CFGA_SATA_PRIV;
+ goto bailout;
+ }
+
+ if (func == NULL) {
+ (void) printf("No valid option specified\n");
+ rv = CFGA_SATA_OPTIONS;
+ goto bailout;
+ }
+
+ if ((rv = setup_for_devctl_cmd(ap_id, &hdl, &list, 0)) !=
+ CFGA_SATA_OK) {
+ goto bailout;
+ }
+
+ /* We do not care here about dynamic AP name component */
+ if ((str_p = GET_DYN(ap_id)) != NULL) {
+ *str_p = '\0';
+ }
+
+ rv = CFGA_SATA_OK;
+
+ if (strcmp(func, SATA_RESET_PORT) == 0) {
+ len = strlen(SATA_CONFIRM_PORT) +
+ strlen(SATA_CONFIRM_DEVICE_ABORT) +
+ strlen("Reset Port") + strlen(ap_id);
+
+ if ((msg = (char *)calloc(len +3, 1)) != NULL) {
+ (void) snprintf(msg, len +3, "Reset"
+ " %s%s\n%s",
+ SATA_CONFIRM_PORT, ap_id,
+ SATA_CONFIRM_DEVICE_ABORT);
+ } else {
+ rv = CFGA_SATA_NACK;
+ goto bailout;
+ }
+
+ if (!sata_confirm(confp, msg)) {
+ rv = CFGA_SATA_NACK;
+ goto bailout;
+ }
+
+ rv = do_control_ioctl(ap_id, SATA_CFGA_RESET_PORT, NULL,
+ (void **)&str_p, &size);
+
+ } else if (strcmp(func, SATA_RESET_DEVICE) == 0) {
+ if ((rv = port_state(hdl, list, &rstate, &ostate)) !=
+ CFGA_SATA_OK)
+ goto bailout;
+ /*
+ * Reset device function requires device to be connected
+ */
+ if (rstate != AP_RSTATE_CONNECTED) {
+ rv = CFGA_SATA_NOT_CONNECTED;
+ goto bailout;
+ }
+
+ len = strlen(SATA_CONFIRM_DEVICE) +
+ strlen(SATA_CONFIRM_DEVICE_ABORT) +
+ strlen("Reset Device") + strlen(ap_id);
+
+ if ((msg = (char *)calloc(len +3, 1)) != NULL) {
+ (void) snprintf(msg, len +3, "Reset"
+ " %s%s\n%s",
+ SATA_CONFIRM_DEVICE, ap_id,
+ SATA_CONFIRM_DEVICE_ABORT);
+ } else {
+ rv = CFGA_SATA_NACK;
+ goto bailout;
+ }
+
+ if (!sata_confirm(confp, msg)) {
+ rv = CFGA_SATA_NACK;
+ goto bailout;
+ }
+
+ rv = do_control_ioctl(ap_id, SATA_CFGA_RESET_DEVICE, NULL,
+ (void **)&str_p, &size);
+
+ } else if (strcmp(func, SATA_RESET_ALL) == 0) {
+ len = strlen(SATA_CONFIRM_CONTROLLER) +
+ strlen(SATA_CONFIRM_CONTROLLER_ABORT) +
+ strlen("Reset All") + strlen(ap_id);
+
+ if ((msg = (char *)calloc(len +3, 1)) != NULL) {
+ (void) snprintf(msg, len +3, "Reset"
+ " %s%s\n%s",
+ SATA_CONFIRM_CONTROLLER, ap_id,
+ SATA_CONFIRM_CONTROLLER_ABORT);
+ } else {
+ rv = CFGA_SATA_NACK;
+ goto bailout;
+ }
+
+ if (!sata_confirm(confp, msg)) {
+ rv = CFGA_SATA_NACK;
+ goto bailout;
+ }
+ rv = do_control_ioctl(ap_id, SATA_CFGA_RESET_ALL, NULL,
+ (void **)&str_p, &size);
+
+ } else if (strcmp(func, SATA_PORT_DEACTIVATE) == 0) {
+ len = strlen(SATA_CONFIRM_PORT) +
+ strlen(SATA_CONFIRM_PORT_DISABLE) +
+ strlen("Deactivate Port") + strlen(ap_id);
+
+ if ((msg = (char *)calloc(len +3, 1)) != NULL) {
+ (void) snprintf(msg, len +3, "Deactivate"
+ " %s%s\n%s",
+ SATA_CONFIRM_PORT, ap_id,
+ SATA_CONFIRM_PORT_DISABLE);
+ } else {
+ rv = CFGA_SATA_NACK;
+ goto bailout;
+ }
+ if (!sata_confirm(confp, msg)) {
+ rv = CFGA_SATA_NACK;
+ goto bailout;
+ }
+
+ rv = do_control_ioctl(ap_id, SATA_CFGA_PORT_DEACTIVATE, NULL,
+ (void **)&str_p, &size);
+
+ } else if (strcmp(func, SATA_PORT_ACTIVATE) == 0) {
+ len = strlen(SATA_CONFIRM_PORT) +
+ strlen(SATA_CONFIRM_PORT_ENABLE) +
+ strlen("Activate Port") + strlen(ap_id);
+
+ if ((msg = (char *)calloc(len +3, 1)) != NULL) {
+ (void) snprintf(msg, len +3, "Activate"
+ " %s%s\n%s",
+ SATA_CONFIRM_PORT, ap_id,
+ SATA_CONFIRM_PORT_ENABLE);
+ } else {
+ rv = CFGA_SATA_NACK;
+ goto bailout;
+ }
+ if (!sata_confirm(confp, msg)) {
+ rv = CFGA_SATA_NACK;
+ goto bailout;
+ }
+
+ rv = do_control_ioctl(ap_id, SATA_CFGA_PORT_ACTIVATE,
+ NULL, (void **)&str_p, &size);
+ goto bailout;
+
+ } else if (strcmp(func, SATA_PORT_SELF_TEST) == 0) {
+ len = strlen(SATA_CONFIRM_PORT) +
+ strlen(SATA_CONFIRM_DEVICE_SUSPEND) +
+ strlen("Self Test Port") + strlen(ap_id);
+
+ if ((msg = (char *)calloc(len +3, 1)) != NULL) {
+ (void) snprintf(msg, len +3, "Self Test"
+ " %s%s\n%s",
+ SATA_CONFIRM_PORT, ap_id,
+ SATA_CONFIRM_DEVICE_SUSPEND);
+ } else {
+ rv = CFGA_SATA_NACK;
+ goto bailout;
+ }
+ if (!sata_confirm(confp, msg)) {
+ rv = CFGA_SATA_NACK;
+ goto bailout;
+ }
+
+ rv = do_control_ioctl(ap_id, SATA_CFGA_PORT_SELF_TEST,
+ NULL, (void **)&str_p, &size);
+ } else {
+ /* Unrecognized operation request */
+ rv = CFGA_SATA_HWOPNOTSUPP;
+ }
+
+bailout:
+ cleanup_after_devctl_cmd(hdl, list);
+
+ return (sata_err_msg(errstring, rv, ap_id, errno));
+
+}
+
+/* cfgadm entry point */
+/*ARGSUSED*/
+cfga_err_t
+cfga_test(
+ const char *ap_id,
+ const char *options,
+ struct cfga_msg *msgp,
+ char **errstring,
+ cfga_flags_t flags)
+{
+ /* Should call ioctl for self test - phase 2 */
+ return (CFGA_OPNOTSUPP);
+}
+
+
+int
+sata_check_target_node(di_node_t node, void *arg)
+{
+ char *minorpath;
+ char *cp;
+
+ minorpath = di_devfs_minor_path(di_minor_next(node, DI_MINOR_NIL));
+ if (minorpath != NULL) {
+ if (strstr(minorpath, arg) != NULL) {
+ cp = strrchr(minorpath, (int)*MINOR_SEP);
+ if (cp != NULL) {
+ (void) strcpy(arg, cp);
+ }
+ free(minorpath);
+ return (DI_WALK_TERMINATE);
+ }
+ free(minorpath);
+ }
+ return (DI_WALK_CONTINUE);
+}
+
+
+/*
+ * The dynamic component buffer returned by this function has to be freed!
+ */
+int
+sata_make_dyncomp(const char *ap_id, char **dyncomp)
+{
+ char *devpath = NULL;
+ char *cp = NULL;
+ int l_errno;
+
+ assert(dyncomp != NULL);
+
+ /*
+ * Get target node path
+ */
+ devpath = sata_get_devicepath(ap_id);
+ if (devpath == NULL) {
+ (void) printf("cfga_list_ext: cannot locate target device\n");
+ return (CFGA_SATA_DYNAMIC_AP);
+ } else {
+ di_node_t root, walk_root;
+ char minor_path[MAXPATHLEN];
+ char devstr[128];
+ char *devlink = NULL;
+
+ (void) strcpy(minor_path, devpath);
+ cp = strrchr(minor_path, (int)*PATH_SEP);
+ if (cp != NULL)
+ *cp = '\0';
+ (void) strcpy(devstr, cp + 1);
+
+ /* Get a snapshot */
+ if ((root = di_init("/", DINFOCACHE)) == DI_NODE_NIL) {
+ goto bailout;
+ }
+
+ /*
+ * Lookup the subtree of interest
+ */
+ walk_root = di_lookup_node(root,
+ minor_path + strlen("/devices"));
+
+ if (walk_root == DI_NODE_NIL) {
+ di_fini(root);
+ goto bailout;
+ }
+
+ if (di_walk_node(walk_root, DI_WALK_CLDFIRST, devstr,
+ sata_check_target_node) != 0) {
+ di_fini(root);
+ goto bailout;
+ }
+ di_fini(root);
+
+ /* fix the minor node path */
+ (void) strlcpy(minor_path, devpath, (size_t)MAXPATHLEN);
+ (void) strlcat(minor_path, devstr, (size_t)MAXPATHLEN);
+ free(devpath);
+
+ (void) (cfga_sata_ret_t)physpath_to_devlink(
+ CFGA_DEV_DIR, minor_path, &devlink, &l_errno, 1);
+
+ /* postprocess and copy logical name here */
+ if (devlink != NULL) {
+ /*
+ * For disks, remove partition info
+ */
+ if (strstr(devlink, "/dsk/") ||
+ strstr(devlink, "/rdsk/")) {
+ if ((cp = strrchr(devlink, (int)*SLICE)) !=
+ NULL) {
+ *cp = '\0';
+ } else if ((cp = strrchr(devlink,
+ (int)*PARTITION)) != NULL) {
+ *cp = '\0';
+ }
+ }
+ cp = strstr(devlink, "/dev/");
+ if (cp == NULL)
+ cp = devlink;
+ else
+ cp = devlink + strlen("/dev/");
+ *dyncomp = strdup(cp);
+ free(devlink);
+ }
+ return (SATA_CFGA_OK);
+ }
+bailout:
+ if (devpath != NULL)
+ free(devpath);
+ return (CFGA_SATA_DYNAMIC_AP);
+}
+
+/* cfgadm entry point */
+/*ARGSUSED*/
+cfga_err_t
+cfga_list_ext(
+ const char *ap_id,
+ cfga_list_data_t **ap_id_list,
+ int *nlistp,
+ const char *options,
+ const char *listopts,
+ char **errstring,
+ cfga_flags_t flags)
+{
+ int l_errno;
+ char *ap_id_log = NULL;
+ size_t size;
+ nvlist_t *user_nvlist = NULL;
+ devctl_hdl_t devctl_hdl = NULL;
+ cfga_sata_ret_t rv = CFGA_SATA_OK;
+ devctl_ap_state_t devctl_ap_state;
+ char *pdyn;
+
+
+ if ((rv = verify_params(ap_id, options, errstring)) != CFGA_SATA_OK) {
+ (void) cfga_help(NULL, options, flags);
+ goto bailout;
+ }
+ /* We do not care here about dynamic AP name component */
+ if ((pdyn = GET_DYN(ap_id)) != NULL) {
+ *pdyn = '\0';
+ }
+
+ if (ap_id_list == NULL || nlistp == NULL) {
+ rv = CFGA_SATA_DATA_ERROR;
+ (void) cfga_help(NULL, options, flags);
+ goto bailout;
+ }
+
+ /* Get ap status */
+ if ((rv = setup_for_devctl_cmd(ap_id, &devctl_hdl, &user_nvlist,
+ DC_RDONLY)) != CFGA_SATA_OK) {
+ goto bailout;
+ }
+
+ /* will call dc_cmd to send IOCTL to kernel */
+ if (devctl_ap_getstate(devctl_hdl, user_nvlist,
+ &devctl_ap_state) == -1) {
+ cleanup_after_devctl_cmd(devctl_hdl, user_nvlist);
+ rv = CFGA_SATA_IOCTL;
+ goto bailout;
+ }
+
+ cleanup_after_devctl_cmd(devctl_hdl, user_nvlist);
+
+ /*
+ * Create cfga_list_data_t struct.
+ */
+ if ((*ap_id_list =
+ (cfga_list_data_t *)malloc(sizeof (**ap_id_list))) == NULL) {
+ rv = CFGA_SATA_ALLOC_FAIL;
+ goto bailout;
+ }
+ *nlistp = 1;
+
+ /*
+ * Rest of the code fills in the cfga_list_data_t struct.
+ */
+
+ /* Get /dev/cfg path to corresponding to the physical ap_id */
+ /* Remember ap_id_log must be freed */
+ rv = (cfga_sata_ret_t)physpath_to_devlink(CFGA_DEV_DIR, (char *)ap_id,
+ &ap_id_log, &l_errno, MATCH_MINOR_NAME);
+
+ if (rv != 0) {
+ rv = CFGA_SATA_DEVLINK;
+ goto bailout;
+ }
+ assert(ap_id_log != NULL);
+
+ /* Get logical ap_id corresponding to the physical */
+ if (strstr(ap_id_log, CFGA_DEV_DIR) == NULL) {
+ rv = CFGA_SATA_DEVLINK;
+ goto bailout;
+ }
+
+ (void) strlcpy((*ap_id_list)->ap_log_id,
+ /* Strip off /dev/cfg/ */ ap_id_log + strlen(CFGA_DEV_DIR)+ 1,
+ sizeof ((*ap_id_list)->ap_log_id));
+
+ free(ap_id_log);
+ ap_id_log = NULL;
+
+ (void) strlcpy((*ap_id_list)->ap_phys_id, ap_id,
+ sizeof ((*ap_id_list)->ap_phys_id));
+
+ switch (devctl_ap_state.ap_rstate) {
+ case AP_RSTATE_EMPTY:
+ (*ap_id_list)->ap_r_state = CFGA_STAT_EMPTY;
+ break;
+
+ case AP_RSTATE_DISCONNECTED:
+ (*ap_id_list)->ap_r_state = CFGA_STAT_DISCONNECTED;
+ break;
+
+ case AP_RSTATE_CONNECTED:
+ (*ap_id_list)->ap_r_state = CFGA_STAT_CONNECTED;
+ break;
+
+ default:
+ rv = CFGA_SATA_STATE;
+ goto bailout;
+ }
+
+ switch (devctl_ap_state.ap_ostate) {
+ case AP_OSTATE_CONFIGURED:
+ (*ap_id_list)->ap_o_state = CFGA_STAT_CONFIGURED;
+ break;
+
+ case AP_OSTATE_UNCONFIGURED:
+ (*ap_id_list)->ap_o_state = CFGA_STAT_UNCONFIGURED;
+ break;
+
+ default:
+ rv = CFGA_SATA_STATE;
+ goto bailout;
+ }
+
+ switch (devctl_ap_state.ap_condition) {
+ case AP_COND_OK:
+ (*ap_id_list)->ap_cond = CFGA_COND_OK;
+ break;
+
+ case AP_COND_FAILING:
+ (*ap_id_list)->ap_cond = CFGA_COND_FAILING;
+ break;
+
+ case AP_COND_FAILED:
+ (*ap_id_list)->ap_cond = CFGA_COND_FAILED;
+ break;
+
+ case AP_COND_UNUSABLE:
+ (*ap_id_list)->ap_cond = CFGA_COND_UNUSABLE;
+ break;
+
+ case AP_COND_UNKNOWN:
+ (*ap_id_list)->ap_cond = CFGA_COND_UNKNOWN;
+ break;
+
+ default:
+ rv = CFGA_SATA_STATE;
+ goto bailout;
+ }
+
+ (*ap_id_list)->ap_class[0] = '\0'; /* Filled by libcfgadm */
+ (*ap_id_list)->ap_busy = devctl_ap_state.ap_in_transition;
+ (*ap_id_list)->ap_status_time = devctl_ap_state.ap_last_change;
+ (*ap_id_list)->ap_info[0] = NULL;
+
+ if ((*ap_id_list)->ap_r_state == CFGA_STAT_CONNECTED) {
+ char *str_p;
+ int skip, i;
+
+ /*
+ * Fill in the 'Information' field for the -v option
+ * Model (MOD:)
+ */
+ if ((rv = do_control_ioctl(ap_id, SATA_CFGA_GET_MODEL_INFO,
+ NULL, (void **)&str_p, &size)) != CFGA_SATA_OK) {
+ (void) printf(
+ "SATA_CFGA_GET_MODULE_INFO ioctl failed\n");
+ goto bailout;
+ }
+ /* drop leading and trailing spaces */
+ skip = strspn(str_p, " ");
+ for (i = size - 1; i >= 0; i--) {
+ if (str_p[i] == '\040')
+ str_p[i] = '\0';
+ else if (str_p[i] != '\0')
+ break;
+ }
+
+ (void) strlcpy((*ap_id_list)->ap_info, "Mod: ",
+ sizeof ((*ap_id_list)->ap_info));
+ (void) strlcat((*ap_id_list)->ap_info, str_p + skip,
+ sizeof ((*ap_id_list)->ap_info));
+
+ free(str_p);
+
+ /*
+ * Fill in the 'Information' field for the -v option
+ * Firmware revision (FREV:)
+ */
+ if ((rv = do_control_ioctl(ap_id,
+ SATA_CFGA_GET_REVFIRMWARE_INFO,
+ NULL, (void **)&str_p, &size)) != CFGA_SATA_OK) {
+ (void) printf(
+ "SATA_CFGA_GET_REVFIRMWARE_INFO ioctl failed\n");
+ goto bailout;
+ }
+ /* drop leading and trailing spaces */
+ skip = strspn(str_p, " ");
+ for (i = size - 1; i >= 0; i--) {
+ if (str_p[i] == '\040')
+ str_p[i] = '\0';
+ else if (str_p[i] != '\0')
+ break;
+ }
+ (void) strlcat((*ap_id_list)->ap_info, " FRev: ",
+ sizeof ((*ap_id_list)->ap_info));
+ (void) strlcat((*ap_id_list)->ap_info, str_p + skip,
+ sizeof ((*ap_id_list)->ap_info));
+
+ free(str_p);
+
+
+ /*
+ * Fill in the 'Information' field for the -v option
+ * Serial Number (SN:)
+ */
+ if ((rv = do_control_ioctl(ap_id,
+ SATA_CFGA_GET_SERIALNUMBER_INFO,
+ NULL, (void **)&str_p, &size)) != CFGA_SATA_OK) {
+ (void) printf(
+ "SATA_CFGA_GET_SERIALNUMBER_INFO ioctl failed\n");
+ goto bailout;
+ }
+ /* drop leading and trailing spaces */
+ skip = strspn(str_p, " ");
+ for (i = size - 1; i >= 0; i--) {
+ if (str_p[i] == '\040')
+ str_p[i] = '\0';
+ else if (str_p[i] != '\0')
+ break;
+ }
+ (void) strlcat((*ap_id_list)->ap_info, " SN: ",
+ sizeof ((*ap_id_list)->ap_info));
+ (void) strlcat((*ap_id_list)->ap_info, str_p + skip,
+ sizeof ((*ap_id_list)->ap_info));
+
+ free(str_p);
+
+
+
+ /* Fill in ap_type which is collected from HBA driver */
+ /* call do_control_ioctl TBD */
+ if ((rv = do_control_ioctl(ap_id, SATA_CFGA_GET_AP_TYPE, NULL,
+ (void **)&str_p, &size)) != CFGA_SATA_OK) {
+ (void) printf(
+ "SATA_CFGA_GET_AP_TYPE ioctl failed\n");
+ goto bailout;
+ }
+
+ (void) strlcpy((*ap_id_list)->ap_type, str_p,
+ sizeof ((*ap_id_list)->ap_type));
+
+ free(str_p);
+
+ if ((*ap_id_list)->ap_o_state == CFGA_STAT_CONFIGURED) {
+
+ char *dyncomp = NULL;
+
+ /*
+ * This is the case where we need to generate
+ * a dynamic component of the ap_id, i.e. device.
+ */
+ rv = sata_make_dyncomp(ap_id, &dyncomp);
+ if (rv != CFGA_SATA_OK)
+ goto bailout;
+ if (dyncomp != NULL) {
+ (void) strcat((*ap_id_list)->ap_log_id,
+ DYN_SEP);
+ (void) strlcat((*ap_id_list)->ap_log_id,
+ dyncomp,
+ sizeof ((*ap_id_list)->ap_log_id));
+ free(dyncomp);
+ }
+ }
+
+ } else {
+ /* Change it when port multiplier is supported */
+ (void) strlcpy((*ap_id_list)->ap_type, "sata-port",
+ sizeof ((*ap_id_list)->ap_type));
+ }
+
+ return (sata_err_msg(errstring, rv, ap_id, errno));
+
+bailout:
+ if (*ap_id_list != NULL) {
+ free(*ap_id_list);
+ }
+ if (ap_id_log != NULL) {
+ free(ap_id_log);
+ }
+
+ return (sata_err_msg(errstring, rv, ap_id, errno));
+}
+/*
+ * This routine accepts a string adn prints it using
+ * the message print routine argument.
+ */
+static void
+cfga_msg(struct cfga_msg *msgp, const char *str)
+{
+ int len;
+ char *q;
+
+ if (msgp == NULL || msgp->message_routine == NULL) {
+ (void) printf("cfga_msg: NULL msgp\n");
+ return;
+ }
+
+ if ((len = strlen(str)) == 0) {
+ (void) printf("cfga_msg: null str\n");
+ return;
+ }
+
+ if ((q = (char *)calloc(len + 1, 1)) == NULL) {
+ perror("cfga_msg"
+);
+ return;
+ }
+
+ (void) strcpy(q, str);
+ (*msgp->message_routine)(msgp->appdata_ptr, q);
+
+ free(q);
+}
+
+/* cfgadm entry point */
+/* ARGSUSED */
+cfga_err_t
+cfga_help(struct cfga_msg *msgp, const char *options, cfga_flags_t flags)
+{
+ if (options != NULL) {
+ cfga_msg(msgp, dgettext(TEXT_DOMAIN, sata_help[HELP_UNKNOWN]));
+ cfga_msg(msgp, options);
+ }
+ cfga_msg(msgp, dgettext(TEXT_DOMAIN, sata_help[HELP_HEADER]));
+ cfga_msg(msgp, sata_help[HELP_CONFIG]);
+ cfga_msg(msgp, sata_help[HELP_RESET_PORT]);
+ cfga_msg(msgp, sata_help[HELP_RESET_DEVICE]);
+ cfga_msg(msgp, sata_help[HELP_RESET_ALL]);
+ cfga_msg(msgp, sata_help[HELP_PORT_ACTIVATE]);
+ cfga_msg(msgp, sata_help[HELP_PORT_DEACTIVATE]);
+ cfga_msg(msgp, sata_help[HELP_PORT_SELF_TEST]);
+ cfga_msg(msgp, sata_help[HELP_CNTRL_SELF_TEST]);
+
+ return (CFGA_OK);
+}
+
+
+/*
+ * Ensure the ap_id passed is in the correct (physical ap_id) form:
+ * path/device:xx[.xx]
+ * where xx is a one or two-digit number.
+ *
+ * Note the library always calls the plugin with a physical ap_id.
+ */
+static int
+verify_valid_apid(const char *ap_id)
+{
+ char *l_ap_id;
+
+ if (ap_id == NULL)
+ return (-1);
+
+ l_ap_id = strrchr(ap_id, (int)*MINOR_SEP);
+ l_ap_id++;
+
+ if (strspn(l_ap_id, "0123456789.") != strlen(l_ap_id)) {
+ /* Bad characters in the ap_id */
+ return (-1);
+ }
+
+ if (strstr(l_ap_id, "..") != NULL) {
+ /* ap_id has 1..2 or more than 2 dots */
+ return (-1);
+ }
+
+ return (0);
+}
+
+
+
+/*
+ * Verify the params passed in are valid.
+ */
+static cfga_sata_ret_t
+verify_params(
+ const char *ap_id,
+ const char *options,
+ char **errstring)
+{
+ char *pdyn, *lap_id;
+ int rv;
+
+ if (errstring != NULL) {
+ *errstring = NULL;
+ }
+
+ if (options != NULL) {
+ return (CFGA_SATA_OPTIONS);
+ }
+
+ /* Strip dynamic AP name component if it is present. */
+ lap_id = strdup(ap_id);
+ if (lap_id == NULL) {
+ return (CFGA_SATA_ALLOC_FAIL);
+ }
+ if ((pdyn = GET_DYN(lap_id)) != NULL) {
+ *pdyn = '\0';
+ }
+
+ if (verify_valid_apid(lap_id) != 0) {
+ rv = CFGA_SATA_AP;
+ } else {
+ rv = CFGA_SATA_OK;
+ }
+ free(lap_id);
+
+ return (rv);
+}
+
+/*
+ * Takes a validated ap_id and extracts the port number.
+ * For now, we do not support port multiplier port .
+ */
+static cfga_sata_ret_t
+get_port_num(const char *ap_id, uint32_t *port)
+{
+ char *port_nbr_str;
+ char *temp;
+ uint32_t cport;
+ uint32_t pmport = 0; /* port multiplier not supported yet */
+ int pmport_qual = 0; /* port multiplier not supported yet */
+
+ port_nbr_str = strrchr(ap_id, (int)*MINOR_SEP) + strlen(MINOR_SEP);
+ if ((temp = strrchr(ap_id, (int)*PORT_SEPARATOR)) != 0) {
+ port_nbr_str = temp + strlen(PORT_SEPARATOR);
+ }
+
+ errno = 0;
+ cport = strtol(port_nbr_str, NULL, 10);
+ if ((cport & ~SATA_CFGA_CPORT_MASK) != 0 || errno != 0)
+ return (CFGA_SATA_PORT);
+
+ *port = cport + (pmport << SATA_CFGA_PMPORT_SHIFT) + pmport_qual;
+
+ return (CFGA_SATA_OK);
+}
+
+/*
+ * Pair of routines to set up for/clean up after a devctl_ap_* lib call.
+ */
+static void
+cleanup_after_devctl_cmd(devctl_hdl_t devctl_hdl, nvlist_t *user_nvlist)
+{
+ if (user_nvlist != NULL) {
+ nvlist_free(user_nvlist);
+ }
+ if (devctl_hdl != NULL) {
+ devctl_release(devctl_hdl);
+ }
+}
+
+
+static cfga_sata_ret_t
+setup_for_devctl_cmd(
+ const char *ap_id,
+ devctl_hdl_t *devctl_hdl,
+ nvlist_t **user_nvlistp,
+ uint_t oflag)
+{
+
+ uint_t port;
+ cfga_sata_ret_t rv = CFGA_SATA_OK;
+ char *lap_id, *pdyn;
+
+ lap_id = strdup(ap_id);
+ if (lap_id == NULL)
+ return (CFGA_SATA_ALLOC_FAIL);
+ if ((pdyn = GET_DYN(lap_id)) != NULL) {
+ *pdyn = '\0';
+ }
+
+ /* Get a devctl handle to pass to the devctl_ap_XXX functions */
+ if ((*devctl_hdl = devctl_ap_acquire((char *)lap_id, oflag)) == NULL) {
+ (void) printf("devctl_ap_acquire failed\n");
+ rv = CFGA_SATA_DEVCTL;
+ goto bailout;
+ }
+
+ /* Set up nvlist to pass the port number down to the driver */
+ if (nvlist_alloc(user_nvlistp, NV_UNIQUE_NAME_TYPE, NULL) != 0) {
+ *user_nvlistp = NULL;
+ rv = CFGA_SATA_NVLIST;
+ (void) printf("nvlist_alloc failed\n");
+ goto bailout;
+ }
+
+ /*
+ * Get port id, for Port Multiplier port, things could be a little bit
+ * complicated because of "port.port" format in ap_id, thus for
+ * port multiplier port, port number should be coded as 32bit int
+ * with the sig 16 bit as sata channel number, least 16 bit as
+ * the port number of sata port multiplier port.
+ */
+ if ((rv = get_port_num(lap_id, &port)) != CFGA_SATA_OK) {
+ (void) printf(
+ "setup_for_devctl_cmd: get_port_num, errno: %d\n",
+ errno);
+ goto bailout;
+ }
+
+ /* Creates an int32_t entry */
+ if (nvlist_add_int32(*user_nvlistp, PORT, port) == -1) {
+ (void) printf("nvlist_add_int32 failed\n");
+ rv = CFGA_SATA_NVLIST;
+ goto bailout;
+ }
+
+ return (rv);
+
+bailout:
+ free(lap_id);
+ (void) cleanup_after_devctl_cmd(*devctl_hdl, *user_nvlistp);
+
+ return (rv);
+}
+
+
+static cfga_sata_ret_t
+port_state(devctl_hdl_t hdl, nvlist_t *list,
+ ap_rstate_t *rstate, ap_ostate_t *ostate)
+{
+ devctl_ap_state_t devctl_ap_state;
+
+ if (devctl_ap_getstate(hdl, list, &devctl_ap_state) == -1) {
+ (void) printf("devctl_ap_getstate failed, errno: %d\n", errno);
+ return (CFGA_SATA_IOCTL);
+ }
+ *rstate = devctl_ap_state.ap_rstate;
+ *ostate = devctl_ap_state.ap_ostate;
+ return (CFGA_SATA_OK);
+}
+
+
+/*
+ * Given a subcommand to the DEVCTL_AP_CONTROL ioctl, rquest the size of
+ * the data to be returned, allocate a buffer, then get the data.
+ * Returns *descrp (which must be freed) and size.
+ *
+ * Note SATA_DESCR_TYPE_STRING returns an ASCII NULL-terminated string,
+ * not a string descr.
+ */
+cfga_sata_ret_t
+do_control_ioctl(const char *ap_id, sata_cfga_apctl_t subcommand, uint_t arg,
+ void **descrp, size_t *sizep)
+{
+ int fd = -1;
+ uint_t port;
+ uint32_t local_size;
+ cfga_sata_ret_t rv = CFGA_SATA_OK;
+ struct sata_ioctl_data ioctl_data;
+
+ assert(descrp != NULL);
+ *descrp = NULL;
+ assert(sizep != NULL);
+
+ if ((rv = get_port_num(ap_id, &port)) != CFGA_SATA_OK) {
+ goto bailout;
+ }
+
+ if ((fd = open(ap_id, O_RDONLY)) == -1) {
+ (void) printf("do_control_ioctl: open failed: errno:%d\n",
+ errno);
+ rv = CFGA_SATA_OPEN;
+ if (errno == EBUSY) {
+ rv = CFGA_SATA_BUSY;
+ }
+ goto bailout;
+ }
+
+ ioctl_data.cmd = subcommand;
+ ioctl_data.port = port;
+ ioctl_data.misc_arg = (uint_t)arg;
+
+ /*
+ * Find out how large a buf we need to get the data.
+ * Note the ioctls only accept/return a 32-bit int for a get_size
+ * to avoid 32/64 and BE/LE issues.
+ */
+ if ((subcommand == SATA_CFGA_GET_AP_TYPE) ||
+ (subcommand == SATA_CFGA_GET_DEVICE_PATH) ||
+ (subcommand == SATA_CFGA_GET_MODEL_INFO) ||
+ (subcommand == SATA_CFGA_GET_REVFIRMWARE_INFO) ||
+ (subcommand == SATA_CFGA_GET_SERIALNUMBER_INFO)) {
+ ioctl_data.get_size = B_TRUE;
+ ioctl_data.buf = (caddr_t)&local_size;
+ ioctl_data.bufsiz = sizeof (local_size);
+
+ if (ioctl(fd, DEVCTL_AP_CONTROL, &ioctl_data) != 0) {
+ perror("ioctl failed (size)");
+ rv = CFGA_SATA_IOCTL;
+ goto bailout;
+ }
+ *sizep = local_size;
+
+ if (local_size == 0) {
+ (void) printf("zero length data\n");
+ rv = CFGA_SATA_ZEROLEN;
+ goto bailout;
+ }
+ if ((*descrp = malloc(*sizep)) == NULL) {
+ (void) printf("do_control_ioctl: malloc failed\n");
+ rv = CFGA_SATA_ALLOC_FAIL;
+ goto bailout;
+ }
+ } else {
+ *sizep = 0;
+ }
+ ioctl_data.get_size = B_FALSE;
+ ioctl_data.buf = *descrp;
+ ioctl_data.bufsiz = *sizep;
+
+ /* Execute IOCTL */
+
+ if (ioctl(fd, DEVCTL_AP_CONTROL, &ioctl_data) != 0) {
+ rv = CFGA_SATA_IOCTL;
+ goto bailout;
+ }
+
+ (void) close(fd);
+
+ return (rv);
+
+bailout:
+ if (fd != -1) {
+ (void) close(fd);
+ }
+ if (*descrp != NULL) {
+ free(*descrp);
+ *descrp = NULL;
+ }
+
+ if (rv == CFGA_SATA_IOCTL && errno == EBUSY) {
+ rv = CFGA_SATA_BUSY;
+ }
+
+ return (rv);
+}
+
+
+static int
+sata_confirm(struct cfga_confirm *confp, char *msg)
+{
+ int rval;
+
+ if (confp == NULL || confp->confirm == NULL) {
+ return (0);
+ }
+ rval = (*confp->confirm)(confp->appdata_ptr, msg);
+
+ return (rval);
+}
+
+
+static char *
+sata_get_devicepath(const char *ap_id)
+{
+ char *devpath = NULL;
+ size_t size;
+ cfga_sata_ret_t rv;
+
+ rv = do_control_ioctl(ap_id, SATA_CFGA_GET_DEVICE_PATH, NULL,
+ (void **)&devpath, &size);
+
+ if (rv == CFGA_SATA_OK) {
+ return (devpath);
+ } else {
+ return ((char *)NULL);
+ }
+
+}
diff --git a/usr/src/lib/cfgadm_plugins/sata/common/cfga_sata.h b/usr/src/lib/cfgadm_plugins/sata/common/cfga_sata.h
new file mode 100644
index 0000000000..414d7478a2
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/sata/common/cfga_sata.h
@@ -0,0 +1,223 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _CFGA_SATA_H
+#define _CFGA_SATA_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdlib.h>
+#include <strings.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <libintl.h>
+#include <libdevice.h>
+#include <sys/varargs.h>
+
+#include <sys/sata/sata_cfgadm.h>
+
+#include <libdevinfo.h>
+#include <libdevice.h>
+#include <librcm.h>
+#include <synch.h>
+#include <thread.h>
+#include <assert.h>
+
+#define CFGA_PLUGIN_LIB
+#include <config_admin.h>
+
+/*
+ * Debug stuff
+ */
+#ifdef DEBUG
+#define DPRINTF printf
+#else
+#define DPRINTF 0 &&
+#endif /* DEBUG */
+
+typedef enum {
+ CFGA_SATA_TERMINATE = 0,
+ CFGA_SATA_CONTINUE
+} sata_cfga_recur_t;
+
+/* for walking links */
+typedef struct walk_link {
+ char *path;
+ char len;
+ char **linkpp;
+} walk_link_t;
+
+#define MATCH_MINOR_NAME 1
+
+/* Misc text strings */
+#define CFGA_DEV_DIR "/dev/cfg"
+#define MINOR_SEP ":"
+#define DYN_SEP "::"
+#define PORT "port"
+#define PORT_SEPARATOR "."
+#define SATA "sata"
+#define CFGA_DEVCTL_NODE ":devctl"
+#define SATA_CFGADM_DEFAULT_AP_TYPE "unknown"
+#define SLICE "s"
+#define PARTITION "p"
+#define PATH_SEP "/"
+
+/* these set of defines are -lav listing */
+#define SATA_UNDEF_STR "<undef>"
+#define SATA_NO_CFG_STR "<no cfg str descr>"
+
+/* -x commands */
+#define SATA_RESET_ALL "sata_reset_all"
+#define SATA_RESET_PORT "sata_reset_port"
+#define SATA_RESET_DEVICE "sata_reset_device"
+#define SATA_PORT_DEACTIVATE "sata_port_deactivate"
+#define SATA_PORT_ACTIVATE "sata_port_activate"
+#define SATA_PORT_SELF_TEST "sata_port_self_test"
+
+/* -t command */
+#define SATA_CNTRL_SELF_TEST "sata_cntrl_self_test"
+
+/* for confirm operation */
+#define SATA_CONFIRM_DEVICE "the device at: "
+#define SATA_CONFIRM_DEVICE_SUSPEND \
+ "This operation will suspend activity on the SATA device\nContinue"
+#define SATA_CONFIRM_DEVICE_ABORT \
+ "This operation will arbitrarily abort all commands " \
+ "on SATA device\nContinue"
+#define SATA_CONFIRM_CONTROLLER "the controller: "
+#define SATA_CONFIRM_CONTROLLER_ABORT \
+ "This operation will arbitrarirly abort all commands " \
+ "on the SATA controller\nContinue"
+#define SATA_CONFIRM_PORT "the port: "
+#define SATA_CONFIRM_PORT_DISABLE \
+ "This operation will disable activity on the SATA port\nContinue"
+#define SATA_CONFIRM_PORT_ENABLE \
+ "This operation will enable activity on the SATA port\nContinue"
+
+#define S_FREE(x) (((x) != NULL) ? \
+ (free(x), (x) = NULL) : (void *)0)
+
+#define GET_DYN(a) (((a) != NULL) ? \
+ strstr((a), DYN_SEP) : (void *)0)
+
+typedef struct sata_apid {
+ char *hba_phys;
+ char *dyncomp;
+ char *path;
+ uint_t flags;
+} sata_apid_t;
+
+
+/* Messages */
+
+typedef struct msgcvt {
+ int intl; /* Flag: if 1, internationalize */
+ cfga_err_t cfga_err; /* Error code libcfgadm understands */
+ const char *msgstr;
+} msgcvt_t;
+
+#define NO_CVT 0
+#define CVT 1
+
+#define MSG_TBL_SZ(table) (sizeof ((table)) / sizeof (msgcvt_t))
+
+typedef enum {
+ SATA_CFGA_ERR = -2,
+ SATA_CFGA_LIB_ERR,
+ SATA_CFGA_OK,
+ SATA_CFGA_BUSY,
+ SATA_CFGA_NO_REC
+} sata_cfga_ret_t;
+
+/* Messages */
+
+
+/* Error message ids (and indices into sata_error_msgs) */
+typedef enum {
+ CFGA_SATA_OK = 0,
+ CFGA_SATA_NACK,
+ CFGA_SATA_DEVICE_UNCONFIGURED,
+ CFGA_SATA_UNKNOWN,
+ CFGA_SATA_INTERNAL_ERROR,
+ CFGA_SATA_DATA_ERROR,
+ CFGA_SATA_OPTIONS,
+ CFGA_SATA_HWOPNOTSUPP,
+ CFGA_SATA_DYNAMIC_AP,
+ CFGA_SATA_AP,
+ CFGA_SATA_PORT,
+ CFGA_SATA_DEVCTL,
+ CFGA_SATA_DEV_CONFIGURE,
+ CFGA_SATA_DEV_UNCONFIGURE,
+ CFGA_SATA_DISCONNECTED,
+ CFGA_SATA_NOT_CONNECTED,
+ CFGA_SATA_NOT_CONFIGURED,
+ CFGA_SATA_ALREADY_CONNECTED,
+ CFGA_SATA_ALREADY_CONFIGURED,
+ CFGA_SATA_INVALID_DEVNAME,
+ CFGA_SATA_OPEN,
+ CFGA_SATA_IOCTL,
+ CFGA_SATA_BUSY,
+ CFGA_SATA_ALLOC_FAIL,
+ CFGA_SATA_OPNOTSUPP,
+ CFGA_SATA_DEVLINK,
+ CFGA_SATA_STATE,
+ CFGA_SATA_PRIV,
+ CFGA_SATA_NVLIST,
+ CFGA_SATA_ZEROLEN,
+
+ /* RCM Errors */
+ CFGA_SATA_RCM_HANDLE,
+ CFGA_SATA_RCM_ONLINE,
+ CFGA_SATA_RCM_OFFLINE,
+ CFGA_SATA_RCM_INFO
+
+} cfga_sata_ret_t;
+
+/*
+ * Given an error msg index, look up the associated string, and
+ * convert it to the current locale if required.
+ */
+#define ERR_STR(msg_idx) \
+ (get_msg((msg_idx), sata_msgs, MSG_TBL_SZ(sata_msgs)))
+
+/* Prototypes */
+
+cfga_err_t sata_err_msg(char **, cfga_sata_ret_t, const char *, int);
+cfga_sata_ret_t sata_rcm_offline(const char *, char **, char *, cfga_flags_t);
+cfga_sata_ret_t sata_rcm_online(const char *, char **, char *, cfga_flags_t);
+cfga_sata_ret_t sata_rcm_remove(const char *, char **, char *, cfga_flags_t);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _CFGA_SATA_H */
diff --git a/usr/src/lib/cfgadm_plugins/sata/i386/Makefile b/usr/src/lib/cfgadm_plugins/sata/i386/Makefile
new file mode 100644
index 0000000000..d78a79b120
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/sata/i386/Makefile
@@ -0,0 +1,36 @@
+#
+# 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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# lib/cfgadm_plugins/sata/i386/Makefile
+#
+
+MAPDIR= ../spec/i386
+include ../Makefile.com
+
+.KEEP_STATE:
+
+install: all $(ROOTLIBS) $(ROOTLINKS)
diff --git a/usr/src/lib/cfgadm_plugins/sata/sata.xcl b/usr/src/lib/cfgadm_plugins/sata/sata.xcl
new file mode 100644
index 0000000000..84d858a169
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/sata/sata.xcl
@@ -0,0 +1,107 @@
+#
+# 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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# lib/cfgadm_plugins/sata/sata.xcl
+#
+msgid "/devices/"
+msgstr
+msgid "/devices"
+msgstr
+msgid "/"
+msgstr
+msgid "/n"
+msgstr
+msgid "ap_id: "
+msgstr
+msgid " %s%s\n%s"
+msgstr
+msgid "cfga_change_state: get device path failed\n"
+msgstr
+msgid "cfga_change_state: get path failed\n"
+msgstr
+msgid "devctl_ap_unconfigure failed\n"
+msgstr
+msgid "No valid option specified\n"
+msgstr
+msgid "cfga_list_ext: cannot locate target device\n"
+msgstr
+msgid "/dsk/"
+msgstr
+msgid "/rdsk/"
+msgstr
+msgid "/dev/"
+msgstr
+msgid "SATA_CFGA_GET_MODULE_INFO ioctl failed\n"
+msgstr
+msgid "Mod: "
+msgstr
+msgid "SATA_CFGA_GET_REVFIRMWARE_INFO ioctl failed\n"
+msgstr
+msgid " FRev: "
+msgstr
+msgid "SATA_CFGA_GET_SERIALNUMBER_INFO ioctl failed\n"
+msgstr
+msgid " SN: "
+msgstr
+msgid "SATA_CFGA_GET_AP_TYPE ioctl failed\n"
+msgstr
+msgid "::"
+msgstr
+msgid "sata-port"
+msgstr
+msgid "cfga_msg: NULL msgp\n"
+msgstr
+msgid "cfga_msg: null str\n"
+msgstr
+msgid "cfga_msg"
+msgstr
+msgid "0123456789."
+msgstr
+msgid ".."
+msgstr
+msgid "devctl_ap_acquire failed\n"
+msgstr
+msgid "nvlist_alloc failed\n"
+msgstr
+msgid "setup_for_devctl_cmd: get_port_num, errno: %d\n"
+msgstr
+msgid "nvlist_add_int32 failed\n"
+msgstr
+msgid "devctl_ap_getstate failed, errno: %d\n"
+msgstr
+msgid "do_control_ioctl: open failed: errno:%d\n"
+msgstr
+msgid "ioctl failed (size)"
+msgstr
+msgid "zero length data\n"
+msgstr
+msgid "do_control_ioctl: malloc failed\n"
+msgstr
+msgid " "
+msgstr
+msgid "%%-%ds %%-%ds"
+msgstr
diff --git a/usr/src/lib/cfgadm_plugins/sata/spec/Makefile b/usr/src/lib/cfgadm_plugins/sata/spec/Makefile
new file mode 100644
index 0000000000..be85571cfa
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/sata/spec/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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+
+#
+# lib/cfgadm_plugins/sata/spec/Makefile
+
+include $(SRC)/lib/Makefile.spec.arch
diff --git a/usr/src/lib/cfgadm_plugins/sata/spec/Makefile.targ b/usr/src/lib/cfgadm_plugins/sata/spec/Makefile.targ
new file mode 100644
index 0000000000..ebf2c3ed62
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/sata/spec/Makefile.targ
@@ -0,0 +1,37 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License"). You may not use this file except in compliance
+# with the License.
+#
+# 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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+#pragma ident "%Z%%M% %I% %E% SMI"
+#
+# lib/cfgadm_plugins/sata/spec/Makefile.targ
+
+LIBRARY= sata.a
+VERS= .1
+
+OBJECTS= cfga_sata.o
+
+TRANSCPP =
+SPECCPP =
+
diff --git a/usr/src/lib/cfgadm_plugins/sata/spec/amd64/Makefile b/usr/src/lib/cfgadm_plugins/sata/spec/amd64/Makefile
new file mode 100644
index 0000000000..a06ecae4b3
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/sata/spec/amd64/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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+include ../Makefile.targ
+
+# Add arch specific objects here
+OBJECTS +=
+
+include $(SRC)/lib/Makefile.lib
+include $(SRC)/lib/Makefile.lib.64
+
+# Uncomment the following if the linker complains
+#amd64_C_PICFLAGS = $(amd64_C_BIGPICFLAGS)
+
+include $(SRC)/lib/Makefile.spec
+
+install: $(ROOTABILIB64)
+
diff --git a/usr/src/lib/cfgadm_plugins/sata/spec/cfga_sata.spec b/usr/src/lib/cfgadm_plugins/sata/spec/cfga_sata.spec
new file mode 100644
index 0000000000..83793393bd
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/sata/spec/cfga_sata.spec
@@ -0,0 +1,77 @@
+#
+# 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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# lib/cfgadm_plugins/sata/spec/cfga_sata.spec
+
+function cfga_change_state
+include <sys/param.h>, <config_admin.h>
+declaration cfga_err_t cfga_change_state(cfga_cmd_t, const char *, \
+ const char *, struct cfga_confirm *, \
+ struct cfga_msg *, char **, cfga_flags_t)
+version SUNWprivate_1.1
+end
+
+
+function cfga_help
+include <sys/param.h>, <config_admin.h>
+declaration cfga_err_t cfga_help(struct cfga_msg *, const char *, \
+ cfga_flags_t)
+version SUNWprivate_1.1
+end
+
+
+function cfga_list_ext
+include <sys/param.h>, <config_admin.h>
+declaration cfga_err_t cfga_list_ext(const char *, \
+ struct cfga_list_data **, int *, const char *, \
+ const char *, char **, cfga_flags_t)
+version SUNWprivate_1.1
+end
+
+
+function cfga_private_func
+include <sys/param.h>, <config_admin.h>
+declaration cfga_err_t cfga_private_func(const char *, const char *, \
+ const char *, struct cfga_confirm *, \
+ struct cfga_msg *, char **, cfga_flags_t)
+version SUNWprivate_1.1
+end
+
+
+function cfga_test
+include <sys/param.h>, <config_admin.h>
+declaration cfga_err_t cfga_test(const char *, const char *, \
+ struct cfga_msg *, char **, cfga_flags_t)
+version SUNWprivate_1.1
+end
+
+data cfga_version
+declaration int cfga_version
+version SUNWprivate_1.1
+end
+
+
diff --git a/usr/src/lib/cfgadm_plugins/sata/spec/i386/Makefile b/usr/src/lib/cfgadm_plugins/sata/spec/i386/Makefile
new file mode 100644
index 0000000000..2b765a917c
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/sata/spec/i386/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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+# lib/cfgadm_plugins/sata/spec/i386/Makefile
+
+include ../Makefile.targ
+
+# Add arch specific objects here
+OBJECTS +=
+
+include $(SRC)/lib/Makefile.lib
+
+# Uncomment the following if the linker complains
+#i386_C_PICFLAGS = -K PIC
+
+include $(SRC)/lib/Makefile.spec
+
+install: $(ROOTABILIB)
+
diff --git a/usr/src/lib/cfgadm_plugins/sata/spec/versions b/usr/src/lib/cfgadm_plugins/sata/spec/versions
new file mode 100644
index 0000000000..9bd9c66def
--- /dev/null
+++ b/usr/src/lib/cfgadm_plugins/sata/spec/versions
@@ -0,0 +1,40 @@
+#
+# 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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+sparc {
+ SUNWprivate_1.1;
+}
+sparcv9 {
+ SUNWprivate_1.1;
+}
+i386 {
+ SUNWprivate_1.1;
+}
+amd64 {
+ SUNWprivate_1.1;
+}
diff --git a/usr/src/pkgdefs/Makefile b/usr/src/pkgdefs/Makefile
index 0d8c472d00..38a70b7a13 100644
--- a/usr/src/pkgdefs/Makefile
+++ b/usr/src/pkgdefs/Makefile
@@ -110,6 +110,7 @@ i386_SUBDIRS= \
SUNWrmodr \
SUNWrmodu \
SUNWrtls \
+ SUNWsi3124 \
SUNWvia823x \
SUNWatheros
diff --git a/usr/src/pkgdefs/SUNWckr/prototype_i386 b/usr/src/pkgdefs/SUNWckr/prototype_i386
index 283ee629d8..bb25e00638 100644
--- a/usr/src/pkgdefs/SUNWckr/prototype_i386
+++ b/usr/src/pkgdefs/SUNWckr/prototype_i386
@@ -167,6 +167,7 @@ f none kernel/misc/pcicfg 755 root sys
f none kernel/misc/pcihp 755 root sys
f none kernel/misc/pcmcia 755 root sys
f none kernel/misc/rpcsec 755 root sys
+f none kernel/misc/sata 755 root sys
f none kernel/misc/scsi 755 root sys
l none kernel/misc/sha1=../../kernel/crypto/sha1
l none kernel/misc/sha2=../../kernel/crypto/sha2
@@ -333,6 +334,7 @@ f none kernel/misc/amd64/pcicfg 755 root sys
f none kernel/misc/amd64/pcihp 755 root sys
f none kernel/misc/amd64/pcmcia 755 root sys
f none kernel/misc/amd64/rpcsec 755 root sys
+f none kernel/misc/amd64/sata 755 root sys
f none kernel/misc/amd64/scsi 755 root sys
l none kernel/misc/amd64/sha1=../../../kernel/crypto/amd64/sha1
l none kernel/misc/amd64/sha2=../../../kernel/crypto/amd64/sha2
diff --git a/usr/src/pkgdefs/SUNWcsl/prototype_i386 b/usr/src/pkgdefs/SUNWcsl/prototype_i386
index 93fa1f13b2..b630d4812e 100644
--- a/usr/src/pkgdefs/SUNWcsl/prototype_i386
+++ b/usr/src/pkgdefs/SUNWcsl/prototype_i386
@@ -2,9 +2,8 @@
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
-# Common Development and Distribution License, Version 1.0 only
-# (the "License"). You may not use this file except in compliance
-# with the License.
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
@@ -19,11 +18,12 @@
#
# CDDL HEADER END
#
+
#
# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
-#pragma ident "%Z%%M% %I% %E% SMI"
+# ident "%Z%%M% %I% %E% SMI"
#
# This required package information file contains a list of package contents.
# The 'pkgmk' command uses this file to identify the contents of a package
@@ -72,6 +72,10 @@ f none usr/lib/cfgadm/amd64/usb.so.1 755 root bin
s none usr/lib/cfgadm/amd64/usb.so=./usb.so.1
f none usr/lib/cfgadm/amd64/ib.so.1 755 root bin
s none usr/lib/cfgadm/amd64/ib.so=./ib.so.1
+f none usr/lib/cfgadm/sata.so.1 755 root bin
+s none usr/lib/cfgadm/sata.so=./sata.so.1
+f none usr/lib/cfgadm/amd64/sata.so.1 755 root bin
+s none usr/lib/cfgadm/amd64/sata.so=./sata.so.1
d none usr/lib/dns/amd64 755 root bin
# EXPORT DELETE START
f none usr/lib/dns/amd64/cylink.so.1 755 root bin
diff --git a/usr/src/pkgdefs/SUNWhea/prototype_i386 b/usr/src/pkgdefs/SUNWhea/prototype_i386
index 5231357244..66028bcc8e 100644
--- a/usr/src/pkgdefs/SUNWhea/prototype_i386
+++ b/usr/src/pkgdefs/SUNWhea/prototype_i386
@@ -108,6 +108,9 @@ f none usr/include/sys/prom_isa.h 644 root bin
f none usr/include/sys/prom_plat.h 644 root bin
f none usr/include/sys/pte.h 644 root bin
f none usr/include/sys/rtc.h 644 root bin
+d none usr/include/sys/sata 755 root sys
+f none usr/include/sys/sata/sata_hba.h 644 root bin
+f none usr/include/sys/sata/sata_defs.h 644 root bin
f none usr/include/sys/scsi/targets/stdef.h 644 root bin
f none usr/include/sys/segment.h 644 root bin
f none usr/include/sys/segments.h 644 root bin
diff --git a/usr/src/pkgdefs/SUNWsi3124/Makefile b/usr/src/pkgdefs/SUNWsi3124/Makefile
new file mode 100644
index 0000000000..5bd7f84d95
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWsi3124/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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+
+include ../Makefile.com
+
+DATAFILES += depend
+
+.KEEP_STATE:
+
+all: $(FILES) depend postinstall postremove
+install: all pkg
+
+include ../Makefile.targ
diff --git a/usr/src/pkgdefs/SUNWsi3124/pkginfo.tmpl b/usr/src/pkgdefs/SUNWsi3124/pkginfo.tmpl
new file mode 100644
index 0000000000..8300fea8d1
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWsi3124/pkginfo.tmpl
@@ -0,0 +1,45 @@
+#
+# 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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+
+PKG=SUNWsi3124
+NAME=SiliconImage 3124 sata driver
+ARCH="ISA"
+VERSION="ONVERS,REV=0.0.0"
+SUNW_PRODNAME="SunOS"
+SUNW_PRODVERS="RELEASE/VERSION"
+SUNW_PKGVERS="1.0"
+SUNW_PKGTYPE="root"
+MAXINST="1000"
+CATEGORY=system
+VENDOR="Sun Microsystems, Inc."
+DESC="SiliconImage 3124 sata driver"
+CLASSES="none"
+HOTLINE="Please contact your local service provider"
+EMAIL=""
+BASEDIR=/
+SUNW_PKG_ALLZONES="true"
+SUNW_PKG_HOLLOW="true"
diff --git a/usr/src/pkgdefs/SUNWsi3124/postinstall b/usr/src/pkgdefs/SUNWsi3124/postinstall
new file mode 100644
index 0000000000..e3bca1e1dd
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWsi3124/postinstall
@@ -0,0 +1,130 @@
+#!/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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+
+# Function: check_add_drv()
+#
+# This function will check if the module has an entry in etc/name_to_major
+# If not simply calls add_drv with the arguments given. If there is
+# such an entry in name_to_major file, it adds entries in driver_aliases
+# driver_classes and minor_perm if necessary.
+# The syntax of this function is the same as add_drv.
+
+check_add_drv()
+{
+ if [ "$BASEDIR" = "" ]
+ then
+ BASEDIR=/
+ fi
+ alias=""
+ class=""
+ ADD_ALIAS=0
+ ADD_CLASS=0
+ ADD_MINOR=0
+ OPTIND=1
+ IS_NET_DRIVER=0
+
+ cmd="add_drv"
+
+ NO_CMD=
+ while getopts i:b:m:c:N opt
+ do
+ case $opt in
+ N ) NO_CMD=1;;
+ i ) ADD_ALIAS=1
+ alias=$OPTARG
+ cmd=$cmd" -i '$alias'"
+ ;;
+ m ) ADD_MINOR=1
+ minor=$OPTARG
+ cmd=$cmd" -m '$minor'"
+ ;;
+ c) ADD_CLASS=1
+ class=$OPTARG
+ cmd=$cmd" -c $class"
+ ;;
+ b) BASEDIR=$OPTARG
+ cmd=$cmd" -b $BASEDIR"
+ ;;
+ \?) echo "check_add_drv can not handle this option"
+ return
+ ;;
+ esac
+ done
+ shift `/usr/bin/expr $OPTIND - 1`
+
+ drvname=$1
+
+ cmd=$cmd" "$drvname
+
+ drvname=`echo $drvname | /usr/bin/sed 's;.*/;;g'`
+
+ /usr/bin/grep "^$drvname[ ]" $BASEDIR/etc/name_to_major > /dev/null 2>&1
+
+ if [ "$NO_CMD" = "" -a $? -ne 0 ]
+ then
+ eval $cmd
+ else
+ # entry already in name_to_major, add alias, class, minorperm
+ # if necessary
+ if [ $ADD_ALIAS = 1 ]
+ then
+ for i in $alias
+ do
+ /usr/bin/egrep "^$drvname[ ]+$i" $BASEDIR/etc/driver_aliases>/dev/null 2>&1
+ if [ $? -ne 0 ]
+ then
+ echo "$drvname $i" >> $BASEDIR/etc/driver_aliases
+ fi
+ done
+ fi
+
+ if [ $ADD_CLASS = 1 ]
+ then
+ /usr/bin/egrep "^$drvname[ ]+$class( | |$)" $BASEDIR/etc/driver_classes > /dev/null 2>&1
+ if [ $? -ne 0 ]
+ then
+ echo "$drvname\t$class" >> $BASEDIR/etc/driver_classes
+ fi
+ fi
+
+ if [ $ADD_MINOR = 1 ]
+ then
+ /usr/bin/grep "^$drvname:" $BASEDIR/etc/minor_perm > /dev/null 2>&1
+ if [ $? -ne 0 ]
+ then
+ minorentry="$drvname:$minor"
+ echo $minorentry >> $BASEDIR/etc/minor_perm
+ fi
+ fi
+
+ fi
+
+
+}
+
+check_add_drv -i '"pci1095,3124"' -b "$BASEDIR" si3124
diff --git a/usr/src/pkgdefs/SUNWsi3124/postremove b/usr/src/pkgdefs/SUNWsi3124/postremove
new file mode 100644
index 0000000000..a5c74e89aa
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWsi3124/postremove
@@ -0,0 +1,35 @@
+#!/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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+BD=${BASEDIR:-/}
+if grep "\<si3124\>" $BD/etc/name_to_major > /dev/null 2>&1
+then
+ rem_drv -b ${BD} si3124
+fi
+exit 0
diff --git a/usr/src/pkgdefs/SUNWsi3124/prototype_com b/usr/src/pkgdefs/SUNWsi3124/prototype_com
new file mode 100644
index 0000000000..5cd78eb3e7
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWsi3124/prototype_com
@@ -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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+
+#
+# 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
+
diff --git a/usr/src/pkgdefs/SUNWsi3124/prototype_i386 b/usr/src/pkgdefs/SUNWsi3124/prototype_i386
new file mode 100644
index 0000000000..6321461ef9
--- /dev/null
+++ b/usr/src/pkgdefs/SUNWsi3124/prototype_i386
@@ -0,0 +1,57 @@
+#
+# 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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+#
+# 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
+#
+#
+i pkginfo
+i copyright
+i depend
+i postinstall
+i postremove
+
+# SiliconImage 3124 sata driver
+d none kernel 0755 root sys
+d none kernel/drv 0755 root sys
+d none kernel/drv/amd64 0755 root sys
+f none kernel/drv/si3124 0755 root sys
+f none kernel/drv/amd64/si3124 0755 root sys
diff --git a/usr/src/pkgdefs/etc/exception_list_i386 b/usr/src/pkgdefs/etc/exception_list_i386
index 101da3eec9..985e567363 100644
--- a/usr/src/pkgdefs/etc/exception_list_i386
+++ b/usr/src/pkgdefs/etc/exception_list_i386
@@ -1,13 +1,9 @@
#
-# Copyright 2005 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, Version 1.0 only
-# (the "License"). You may not use this file except in compliance
-# with the License.
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
@@ -22,8 +18,14 @@
#
# CDDL HEADER END
#
+
+#
+# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
# ident "%Z%%M% %I% %E% SMI"
#
+
# Exception List for protocmp
#
###########################################
@@ -609,6 +611,12 @@ kernel/drv/amd64/ses i386
#
usr/include/sys/usb/hubd/hubd_impl.h i386
#
+#
+# User<->kernel interface used by cfgadm/SATA only
+#
+usr/include/sys/sata/sata_cfgadm.h i386
+#
+#
# Private ucred kernel header
#
usr/include/sys/ucred.h i386
diff --git a/usr/src/uts/common/Makefile.files b/usr/src/uts/common/Makefile.files
index ade42de813..2e6001a40c 100644
--- a/usr/src/uts/common/Makefile.files
+++ b/usr/src/uts/common/Makefile.files
@@ -588,6 +588,8 @@ SCSI_OBJS += scsi_capabilities.o scsi_control.o scsi_watch.o \
scsi_hba.o scsi_transport.o scsi_confsubr.o \
scsi_reset_notify.o
+SATA_OBJS += sata.o
+
USBA_OBJS += hcdi.o usba.o usbai.o hubdi.o parser.o genconsole.o \
usbai_pipe_mgmt.o usbai_req.o usbai_util.o usbai_register.o \
usba_devdb.o usba10_calls.o usba_ugen.o
@@ -701,6 +703,8 @@ EMUL64_OBJS += emul64.o emul64_bsd.o
ZCONS_OBJS += zcons.o
+SI3124_OBJS += si3124.o
+
PCIIDE_OBJS += pci-ide.o
PCEPP_OBJS += pcepp.o
diff --git a/usr/src/uts/common/Makefile.rules b/usr/src/uts/common/Makefile.rules
index b6b89cd2ae..2e6586e4e2 100644
--- a/usr/src/uts/common/Makefile.rules
+++ b/usr/src/uts/common/Makefile.rules
@@ -2,9 +2,8 @@
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
-# Common Development and Distribution License, Version 1.0 only
-# (the "License"). You may not use this file except in compliance
-# with the License.
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
@@ -19,6 +18,7 @@
#
# CDDL HEADER END
#
+
#
# Copyright 2006 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
@@ -592,6 +592,14 @@ $(OBJS_DIR)/%.o: $(UTSBASE)/common/io/rge/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
+$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/sata/adapters/si3124/%.c
+ $(COMPILE.c) -o $@ $<
+ $(CTFCONVERT_O)
+
+$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/sata/impl/%.c
+ $(COMPILE.c) -o $@ $<
+ $(CTFCONVERT_O)
+
$(OBJS_DIR)/%.o: $(UTSBASE)/common/io/scsi/conf/%.c
$(COMPILE.c) -o $@ $<
$(CTFCONVERT_O)
@@ -1275,6 +1283,12 @@ $(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/rsm/%.c
$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/rge/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
+$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/sata/adapters/si3124/%.c
+ @($(LHEAD) $(LINT.c) $< $(LTAIL))
+
+$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/sata/impl/%.c
+ @($(LHEAD) $(LINT.c) $< $(LTAIL))
+
$(LINTS_DIR)/%.ln: $(UTSBASE)/common/io/scsi/adapters/%.c
@($(LHEAD) $(LINT.c) $< $(LTAIL))
diff --git a/usr/src/uts/common/io/sata/adapters/si3124/si3124.c b/usr/src/uts/common/io/sata/adapters/si3124/si3124.c
new file mode 100644
index 0000000000..3fc34ab1cc
--- /dev/null
+++ b/usr/src/uts/common/io/sata/adapters/si3124/si3124.c
@@ -0,0 +1,5328 @@
+/*
+ * 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 2006 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+
+
+/*
+ * SiliconImage 3124/3132 sata controller driver
+ */
+
+/*
+ *
+ *
+ * Few Design notes
+ *
+ *
+ * I. General notes
+ *
+ * Even though the driver is named as si3124, it is actually meant to
+ * work with both 3124 and 3132 controllers.
+ *
+ * The current file si3124.c is the main driver code. The si3124reg.h
+ * holds the register definitions from SiI 3124/3132 data sheets. The
+ * si3124var.h holds the driver specific definitions which are not
+ * directly derived from data sheets.
+ *
+ *
+ * II. Data structures
+ *
+ * si_ctl_state_t: This holds the driver private information for each
+ * controller instance. Each of the sata ports within a single
+ * controller are represented by si_port_state_t. The
+ * sictl_global_acc_handle and sictl_global_address map the
+ * controller-wide global register space and are derived from pci
+ * BAR 0. The sictl_port_acc_handle and sictl_port_addr map the
+ * per-port register space and are derived from pci BAR 1.
+ *
+ * si_port_state_t: This holds the per port information. The siport_mutex
+ * holds the per port mutex. The siport_pending_tags is the bit mask of
+ * commands posted to controller. The siport_slot_pkts[] holds the
+ * pending sata packets. The siport_port_type holds the device type
+ * connected directly to the port while the siport_portmult_state
+ * holds the similar information for the devices behind a port
+ * multiplier.
+ *
+ * si_prb_t: This contains the PRB being posted to the controller.
+ * The two SGE entries contained within si_prb_t itself are not
+ * really used to hold any scatter gather entries. The scatter gather
+ * list is maintained external to PRB and is linked from one
+ * of the contained SGEs inside the PRB. For atapi devices, the
+ * first contained SGE holds the PACKET and second contained
+ * SGE holds the link to an external SGT. For non-atapi devices,
+ * the first contained SGE works as link to external SGT while
+ * second SGE is blank.
+ *
+ * external SGT tables: The external SGT tables pointed to from
+ * within si_prb_t are actually abstracted as si_sgblock_t. Each
+ * si_sgblock_t contains SI_MAX_SGT_TABLES_PER_PRB number of
+ * SGT tables linked in a chain. Currently this max value of
+ * SGT tables per block is hard coded as 10 which translates
+ * to a maximum of 31 dma cookies per single dma transfer.
+ *
+ *
+ * III. Driver operation
+ *
+ * Command Issuing: We use the "indirect method of command issuance". The
+ * PRB contains the command [and atapi PACKET] and a link to the
+ * external SGT chain. We write the physical address of the PRB into
+ * command activation register. There are 31 command slots for
+ * each port. After posting a command, we remember the posted slot &
+ * the sata packet in siport_pending_tags & siport_slot_pkts[]
+ * respectively.
+ *
+ * Command completion: On a successful completion, intr_command_complete()
+ * receives the control. The slot_status register holds the outstanding
+ * commands. Any reading of slot_status register automatically clears
+ * the interrupt. By comparing the slot_status register contents with
+ * per port siport_pending_tags, we determine which of the previously
+ * posted commands have finished.
+ *
+ * Timeout handling: Every 5 seconds, the watchdog handler scans thru the
+ * pending packets. The satapkt->satapkt_hba_driver_private field is
+ * overloaded with the count of watchdog cycles a packet has survived.
+ * If a packet has not completed within satapkt->satapkt_time, it is
+ * failed with error code of SATA_PKT_TIMEOUT. There is one watchdog
+ * handler running for each instance of controller.
+ *
+ * Error handling: For 3124, whenever any single command has encountered
+ * an error, the whole port execution completely stalls; there is no
+ * way of canceling or aborting the particular failed command. If
+ * the port is connected to a port multiplier, we can however RESUME
+ * other non-error devices connected to the port multiplier.
+ * The only way to recover the failed commands is to either initialize
+ * the port or reset the port/device. Both port initialize and reset
+ * operations result in discarding any of pending commands on the port.
+ * All such discarded commands are sent up to framework with PKT_RESET
+ * satapkt_reason. The assumption is that framework [and sd] would
+ * retry these commands again. The failed command itself however is
+ * sent up with PKT_DEV_ERROR.
+ *
+ * Here is the implementation strategy based on SiliconImage email
+ * regarding how they handle the errors for their Windows driver:
+ *
+ * a) for DEVICEERROR:
+ * If the port is connected to port multiplier, then
+ * 1) Resume the port
+ * 2) Wait for all the non-failed commands to complete
+ * 3) Perform a Port Initialize
+ *
+ * If the port is not connected to port multiplier, issue
+ * a Port Initialize.
+ *
+ * b) for SDBERROR: [SDBERROR means failed command is an NCQ command]
+ * Handle exactly like DEVICEERROR handling.
+ * After the Port Initialize done, do a Read Log Extended.
+ *
+ * c) for SENDFISERROR:
+ * If the port is connected to port multiplier, then
+ * 1) Resume the port
+ * 2) Wait for all the non-failed commands to complete
+ * 3) Perform a Port Initialize
+ *
+ * If the port is not connected to port multiplier, issue
+ * a Device Reset.
+ *
+ * d) for DATAFISERROR:
+ * If the port was executing an NCQ command, issue a Device
+ * Reset.
+ *
+ * Otherwise, follow the same error recovery as DEVICEERROR.
+ *
+ * e) for any other error, simply issue a Device Reset.
+ *
+ * To synchronize the interactions between various control flows (e.g.
+ * error recovery, timeout handling, si_poll_timeout, incoming flow
+ * from framework etc.), the following precautions are taken care of:
+ * a) During mopping_in_progress, no more commands are
+ * accepted from the framework.
+ *
+ * b) While draining the port multiplier commands, we should
+ * handle the possibility of any of the other waited commands
+ * failing (possibly with a different error code)
+ *
+ * Atapi handling: For atapi devices, we use the first SGE within the PRB
+ * to fill the scsi cdb while the second SGE points to external SGT.
+ *
+ * Queuing: Queue management is achieved external to the driver inside sd.
+ * Based on sata_hba_tran->qdepth and IDENTIFY data, the framework
+ * enables or disables the queuing. The qdepth for si3124 is 31
+ * commands.
+ *
+ * Port Multiplier: Enumeration of port multiplier is handled during the
+ * controller initialization and also during the a hotplug operation.
+ * Current logic takes care of situation where a port multiplier
+ * is hotplugged into a port which had a cdisk connected previously
+ * and vice versa.
+ *
+ * Register poll timeouts: Currently most of poll timeouts on register
+ * reads is set to 0.5 seconds except for a value of 10 seconds
+ * while reading the device signature. [Such a big timeout values
+ * for device signature were found needed during cold reboots
+ * for devices behind port multiplier].
+ *
+ *
+ * IV. Known Issues
+ *
+ * 1) Currently the atapi packet length is hard coded to 12 bytes
+ * This is wrong. The framework should determine it just like they
+ * determine ad_cdb_len in legacy atapi.c. It should even reject
+ * init_pkt() for greater CDB lengths. See atapi.c. Revisit this
+ * in 2nd phase of framework project.
+ *
+ * 2) Do real REQUEST SENSE command instead of faking for ATAPI case.
+ *
+ */
+
+
+#include <sys/note.h>
+#include <sys/scsi/scsi.h>
+#include <sys/pci.h>
+#include <sys/sata/sata_hba.h>
+#include <sys/sata/adapters/si3124/si3124reg.h>
+#include <sys/sata/adapters/si3124/si3124var.h>
+
+/*
+ * Function prototypes for driver entry points
+ */
+static int si_attach(dev_info_t *, ddi_attach_cmd_t);
+static int si_detach(dev_info_t *, ddi_detach_cmd_t);
+static int si_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
+static int si_power(dev_info_t *, int, int);
+
+/*
+ * Function prototypes for SATA Framework interfaces
+ */
+static int si_register_sata_hba_tran(si_ctl_state_t *);
+static int si_unregister_sata_hba_tran(si_ctl_state_t *);
+
+static int si_tran_probe_port(dev_info_t *, sata_device_t *);
+static int si_tran_start(dev_info_t *, sata_pkt_t *spkt);
+static int si_tran_abort(dev_info_t *, sata_pkt_t *, int);
+static int si_tran_reset_dport(dev_info_t *, sata_device_t *);
+static int si_tran_hotplug_port_activate(dev_info_t *, sata_device_t *);
+static int si_tran_hotplug_port_deactivate(dev_info_t *, sata_device_t *);
+
+/*
+ * Local function prototypes
+ */
+
+static int si_alloc_port_state(si_ctl_state_t *, int);
+static void si_dealloc_port_state(si_ctl_state_t *, int);
+static int si_alloc_sgbpool(si_ctl_state_t *, int);
+static void si_dealloc_sgbpool(si_ctl_state_t *, int);
+static int si_alloc_prbpool(si_ctl_state_t *, int);
+static void si_dealloc_prbpool(si_ctl_state_t *, int);
+
+static void si_find_dev_signature(si_ctl_state_t *, si_port_state_t *,
+ int, int);
+static void si_poll_cmd(si_ctl_state_t *, si_port_state_t *, int, int,
+ sata_pkt_t *);
+static int si_claim_free_slot(si_ctl_state_t *, si_port_state_t *, int);
+static int si_deliver_satapkt(si_ctl_state_t *, si_port_state_t *, int,
+ sata_pkt_t *);
+
+static int si_initialize_controller(si_ctl_state_t *);
+static void si_deinititalize_controller(si_ctl_state_t *);
+static void si_init_port(si_ctl_state_t *, int);
+static int si_enumerate_port_multiplier(si_ctl_state_t *,
+ si_port_state_t *, int);
+static int si_read_portmult_reg(si_ctl_state_t *, si_port_state_t *,
+ int, int, int, uint32_t *);
+static int si_write_portmult_reg(si_ctl_state_t *, si_port_state_t *,
+ int, int, int, uint32_t);
+static void si_set_sense_data(sata_pkt_t *, int);
+
+static uint_t si_intr(caddr_t, caddr_t);
+static int si_intr_command_complete(si_ctl_state_t *,
+ si_port_state_t *, int);
+static int si_intr_command_error(si_ctl_state_t *,
+ si_port_state_t *, int);
+static void si_error_recovery_DEVICEERROR(si_ctl_state_t *,
+ si_port_state_t *, int);
+static void si_error_recovery_SDBERROR(si_ctl_state_t *,
+ si_port_state_t *, int);
+static void si_error_recovery_DATAFISERROR(si_ctl_state_t *,
+ si_port_state_t *, int);
+static void si_error_recovery_SENDFISERROR(si_ctl_state_t *,
+ si_port_state_t *, int);
+static void si_error_recovery_default(si_ctl_state_t *,
+ si_port_state_t *, int);
+static uint8_t si_read_log_ext(si_ctl_state_t *,
+ si_port_state_t *si_portp, int);
+static void si_log_error_message(si_ctl_state_t *, int, uint32_t);
+static int si_intr_port_ready(si_ctl_state_t *, si_port_state_t *, int);
+static int si_intr_pwr_change(si_ctl_state_t *, si_port_state_t *, int);
+static int si_intr_phy_ready_change(si_ctl_state_t *, si_port_state_t *, int);
+static int si_intr_comwake_rcvd(si_ctl_state_t *, si_port_state_t *, int);
+static int si_intr_unrecognised_fis(si_ctl_state_t *, si_port_state_t *, int);
+static int si_intr_dev_xchanged(si_ctl_state_t *, si_port_state_t *, int);
+static int si_intr_decode_err_threshold(si_ctl_state_t *,
+ si_port_state_t *, int);
+static int si_intr_crc_err_threshold(si_ctl_state_t *, si_port_state_t *, int);
+static int si_intr_handshake_err_threshold(si_ctl_state_t *,
+ si_port_state_t *, int);
+static int si_intr_set_devbits_notify(si_ctl_state_t *, si_port_state_t *, int);
+static void si_handle_attention_raised(si_ctl_state_t *,
+ si_port_state_t *, int);
+
+static void si_enable_port_interrupts(si_ctl_state_t *, int);
+static void si_enable_all_interrupts(si_ctl_state_t *);
+static void si_disable_port_interrupts(si_ctl_state_t *, int);
+static void si_disable_all_interrupts(si_ctl_state_t *);
+static void fill_dev_sregisters(si_ctl_state_t *, int, sata_device_t *);
+static int si_add_legacy_intrs(si_ctl_state_t *);
+static int si_add_msi_intrs(si_ctl_state_t *);
+static void si_rem_intrs(si_ctl_state_t *);
+
+static int si_reset_dport_wait_till_ready(si_ctl_state_t *,
+ si_port_state_t *, int, int);
+static int si_initialize_port_wait_till_ready(si_ctl_state_t *, int);
+
+static void si_timeout_pkts(si_ctl_state_t *, si_port_state_t *, int, uint32_t);
+static void si_watchdog_handler(si_ctl_state_t *);
+
+static void si_log(si_ctl_state_t *, uint_t, char *, ...);
+
+
+/*
+ * DMA attributes for the data buffer
+ */
+
+static ddi_dma_attr_t buffer_dma_attr = {
+ DMA_ATTR_V0, /* dma_attr_version */
+ 0, /* dma_attr_addr_lo: lowest bus address */
+ 0xffffffffffffffffull, /* dma_attr_addr_hi: highest bus address */
+ 0xffffffffull, /* dma_attr_count_max i.e. for one cookie */
+ 1, /* dma_attr_align: single byte aligned */
+ 1, /* dma_attr_burstsizes */
+ 1, /* dma_attr_minxfer */
+ 0xffffffffull, /* dma_attr_maxxfer i.e. includes all cookies */
+ 0xffffffffull, /* dma_attr_seg */
+ SI_MAX_SGL_LENGTH, /* dma_attr_sgllen */
+ 512, /* dma_attr_granular */
+ 0, /* dma_attr_flags */
+};
+
+/*
+ * DMA attributes for incore RPB and SGT pool
+ */
+static ddi_dma_attr_t prb_sgt_dma_attr = {
+ DMA_ATTR_V0, /* dma_attr_version */
+ 0, /* dma_attr_addr_lo: lowest bus address */
+ 0xffffffffffffffffull, /* dma_attr_addr_hi: highest bus address */
+ 0xffffffffull, /* dma_attr_count_max i.e. for one cookie */
+ 8, /* dma_attr_align: quad word aligned */
+ 1, /* dma_attr_burstsizes */
+ 1, /* dma_attr_minxfer */
+ 0xffffffffull, /* dma_attr_maxxfer i.e. includes all cookies */
+ 0xffffffffull, /* dma_attr_seg */
+ 1, /* dma_attr_sgllen */
+ 1, /* dma_attr_granular */
+ 0, /* dma_attr_flags */
+};
+
+/* Device access attributes */
+static ddi_device_acc_attr_t accattr = {
+ DDI_DEVICE_ATTR_V0,
+ DDI_STRUCTURE_LE_ACC,
+ DDI_STRICTORDER_ACC
+};
+
+
+static struct dev_ops sictl_dev_ops = {
+ DEVO_REV, /* devo_rev */
+ 0, /* refcnt */
+ si_getinfo, /* info */
+ nulldev, /* identify */
+ nulldev, /* probe */
+ si_attach, /* attach */
+ si_detach, /* detach */
+ nodev, /* no reset */
+ (struct cb_ops *)0, /* driver operations */
+ NULL, /* bus operations */
+ si_power /* power */
+};
+
+static sata_tran_hotplug_ops_t si_tran_hotplug_ops = {
+ SATA_TRAN_HOTPLUG_OPS_REV_1,
+ si_tran_hotplug_port_activate,
+ si_tran_hotplug_port_deactivate
+};
+
+
+static int si_watchdog_timeout = 5; /* 5 seconds */
+static int si_watchdog_tick;
+
+extern struct mod_ops mod_driverops;
+
+static struct modldrv modldrv = {
+ &mod_driverops, /* driverops */
+ "si3124 driver v%I%",
+ &sictl_dev_ops, /* driver ops */
+};
+
+static struct modlinkage modlinkage = {
+ MODREV_1,
+ &modldrv,
+ NULL
+};
+
+
+/* The following are needed for si_log() */
+static kmutex_t si_log_mutex;
+static char si_log_buf[512];
+uint32_t si_debug_flags = 0x0;
+static int is_msi_supported = 0;
+
+/* Opaque state pointer to be initialized by ddi_soft_state_init() */
+static void *si_statep = NULL;
+
+/*
+ * si3124 module initialization.
+ *
+ */
+int
+_init(void)
+{
+ int error;
+
+ error = ddi_soft_state_init(&si_statep, sizeof (si_ctl_state_t), 0);
+ if (error != 0) {
+ return (error);
+ }
+
+ mutex_init(&si_log_mutex, NULL, MUTEX_DRIVER, NULL);
+
+ if ((error = sata_hba_init(&modlinkage)) != 0) {
+ mutex_destroy(&si_log_mutex);
+ ddi_soft_state_fini(&si_statep);
+ return (error);
+ }
+
+ error = mod_install(&modlinkage);
+ if (error != 0) {
+ sata_hba_fini(&modlinkage);
+ mutex_destroy(&si_log_mutex);
+ ddi_soft_state_fini(&si_statep);
+ return (error);
+ }
+
+ si_watchdog_tick = drv_usectohz((clock_t)si_watchdog_timeout * 1000000);
+
+ return (error);
+}
+
+/*
+ * si3124 module uninitialize.
+ *
+ */
+int
+_fini(void)
+{
+ int error;
+
+ error = mod_remove(&modlinkage);
+ if (error != 0) {
+ return (error);
+ }
+
+ /* Remove the resources allocated in _init(). */
+ sata_hba_fini(&modlinkage);
+ mutex_destroy(&si_log_mutex);
+ ddi_soft_state_fini(&si_statep);
+
+ return (error);
+}
+
+/*
+ * _info entry point
+ *
+ */
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
+
+
+/*
+ * The attach entry point for dev_ops.
+ *
+ * We initialize the controller, initialize the soft state, register
+ * the interrupt handlers and then register ourselves with sata framework.
+ */
+static int
+si_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
+{
+ si_ctl_state_t *si_ctlp;
+ int instance;
+ int status;
+ int attach_state;
+ int intr_types;
+ sata_device_t sdevice;
+
+ SIDBG0(SIDBG_INIT|SIDBG_ENTRY, NULL, "si_attach enter");
+ instance = ddi_get_instance(dip);
+ attach_state = ATTACH_PROGRESS_NONE;
+
+ switch (cmd) {
+
+ case DDI_ATTACH:
+
+ /* Allocate si_softc. */
+ status = ddi_soft_state_zalloc(si_statep, instance);
+ if (status != DDI_SUCCESS) {
+ goto err_out;
+ }
+
+ si_ctlp = ddi_get_soft_state(si_statep, instance);
+ si_ctlp->sictl_devinfop = dip;
+
+ attach_state |= ATTACH_PROGRESS_STATEP_ALLOC;
+
+ /* Configure pci config space handle. */
+ status = pci_config_setup(dip, &si_ctlp->sictl_pci_conf_handle);
+ if (status != DDI_SUCCESS) {
+ goto err_out;
+ }
+
+ si_ctlp->sictl_devid =
+ pci_config_get16(si_ctlp->sictl_pci_conf_handle,
+ PCI_CONF_DEVID);
+ if (si_ctlp->sictl_devid == SI3132_DEV_ID) {
+ si_ctlp->sictl_num_ports = SI3132_MAX_PORTS;
+ } else {
+ si_ctlp->sictl_num_ports = SI3124_MAX_PORTS;
+ }
+
+ attach_state |= ATTACH_PROGRESS_CONF_HANDLE;
+
+ /* Now map the bar0; the bar0 contains the global registers. */
+ status = ddi_regs_map_setup(dip,
+ PCI_BAR0,
+ (caddr_t *)&si_ctlp->sictl_global_addr,
+ 0,
+ 0,
+ &accattr,
+ &si_ctlp->sictl_global_acc_handle);
+ if (status != DDI_SUCCESS) {
+ goto err_out;
+ }
+
+ attach_state |= ATTACH_PROGRESS_BAR0_MAP;
+
+ /* Now map bar1; the bar1 contains the port registers. */
+ status = ddi_regs_map_setup(dip,
+ PCI_BAR1,
+ (caddr_t *)&si_ctlp->sictl_port_addr,
+ 0,
+ 0,
+ &accattr,
+ &si_ctlp->sictl_port_acc_handle);
+ if (status != DDI_SUCCESS) {
+ goto err_out;
+ }
+
+ attach_state |= ATTACH_PROGRESS_BAR1_MAP;
+
+ /*
+ * Disable all the interrupts before adding interrupt
+ * handler(s). The interrupts shall be re-enabled selectively
+ * out of si_init_port().
+ */
+ si_disable_all_interrupts(si_ctlp);
+
+ /* Get supported interrupt types. */
+ if (ddi_intr_get_supported_types(dip, &intr_types)
+ != DDI_SUCCESS) {
+ SIDBG0(SIDBG_INIT, NULL,
+ "ddi_intr_get_supported_types failed");
+ goto err_out;
+ }
+
+ SIDBG1(SIDBG_INIT, NULL,
+ "ddi_intr_get_supported_types() returned: 0x%x",
+ intr_types);
+
+ if (is_msi_supported && (intr_types & DDI_INTR_TYPE_MSI)) {
+ SIDBG0(SIDBG_INIT, NULL, "Using MSI interrupt type");
+
+ /*
+ * Try MSI first, but fall back to legacy if MSI
+ * attach fails.
+ */
+ if (si_add_msi_intrs(si_ctlp) == DDI_SUCCESS) {
+ si_ctlp->sictl_intr_type = DDI_INTR_TYPE_MSI;
+ attach_state |= ATTACH_PROGRESS_INTR_ADDED;
+ SIDBG0(SIDBG_INIT, NULL,
+ "MSI interrupt setup done");
+ } else {
+ SIDBG0(SIDBG_INIT, NULL,
+ "MSI registration failed "
+ "will try Legacy interrupts");
+ }
+ }
+
+ if (!(attach_state & ATTACH_PROGRESS_INTR_ADDED) &&
+ (intr_types & DDI_INTR_TYPE_FIXED)) {
+ /*
+ * Either the MSI interrupt setup has failed or only
+ * fixed interrupts are available on the system.
+ */
+ SIDBG0(SIDBG_INIT, NULL, "Using Legacy interrupt type");
+
+ if (si_add_legacy_intrs(si_ctlp) == DDI_SUCCESS) {
+ si_ctlp->sictl_intr_type = DDI_INTR_TYPE_FIXED;
+ attach_state |= ATTACH_PROGRESS_INTR_ADDED;
+ SIDBG0(SIDBG_INIT, NULL,
+ "Legacy interrupt setup done");
+ } else {
+ SIDBG0(SIDBG_INIT, NULL,
+ "legacy interrupt setup failed");
+ goto err_out;
+ }
+ }
+
+ if (!(attach_state & ATTACH_PROGRESS_INTR_ADDED)) {
+ SIDBG0(SIDBG_INIT, NULL,
+ "si3124: No interrupts registered");
+ goto err_out;
+ }
+
+
+ /* Initialize the mutex. */
+ mutex_init(&si_ctlp->sictl_mutex, NULL, MUTEX_DRIVER,
+ (void *)(uint64_t)si_ctlp->sictl_intr_pri);
+
+ attach_state |= ATTACH_PROGRESS_MUTEX_INIT;
+
+ /*
+ * Initialize the controller and driver core.
+ */
+ si_ctlp->sictl_flags |= SI_ATTACH;
+ status = si_initialize_controller(si_ctlp);
+ si_ctlp->sictl_flags &= ~SI_ATTACH;
+ if (status) {
+ goto err_out;
+ }
+
+ attach_state |= ATTACH_PROGRESS_HW_INIT;
+
+ if (si_register_sata_hba_tran(si_ctlp)) {
+ SIDBG0(SIDBG_INIT, NULL,
+ "si3124: setting sata hba tran failed");
+ goto err_out;
+ }
+
+ si_ctlp->sictl_timeout_id = timeout(
+ (void (*)(void *))si_watchdog_handler,
+ (caddr_t)si_ctlp, si_watchdog_tick);
+
+ si_ctlp->sictl_power_level = PM_LEVEL_D0;
+
+ return (DDI_SUCCESS);
+
+ case DDI_RESUME:
+ si_ctlp = ddi_get_soft_state(si_statep, instance);
+
+ status = si_initialize_controller(si_ctlp);
+ if (status) {
+ return (DDI_FAILURE);
+ }
+
+ si_ctlp->sictl_timeout_id = timeout(
+ (void (*)(void *))si_watchdog_handler,
+ (caddr_t)si_ctlp, si_watchdog_tick);
+
+ (void) pm_power_has_changed(dip, 0, PM_LEVEL_D0);
+
+ /* Notify SATA framework about RESUME. */
+ if (sata_hba_attach(si_ctlp->sictl_devinfop,
+ si_ctlp->sictl_sata_hba_tran,
+ DDI_RESUME) != DDI_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+
+ /*
+ * Notify the "framework" that it should reprobe ports to see
+ * if any device got changed while suspended.
+ */
+ bzero((void *)&sdevice, sizeof (sata_device_t));
+ sata_hba_event_notify(dip, &sdevice,
+ SATA_EVNT_PWR_LEVEL_CHANGED);
+ SIDBG0(SIDBG_INIT|SIDBG_EVENT, si_ctlp,
+ "sending event up: SATA_EVNT_PWR_LEVEL_CHANGED");
+
+ (void) pm_idle_component(si_ctlp->sictl_devinfop, 0);
+
+ si_ctlp->sictl_power_level = PM_LEVEL_D0;
+
+ return (DDI_SUCCESS);
+
+ default:
+ return (DDI_FAILURE);
+
+ }
+
+err_out:
+ if (attach_state & ATTACH_PROGRESS_HW_INIT) {
+ si_ctlp->sictl_flags |= SI_DETACH;
+ /* We want to set SI_DETACH to deallocate all memory */
+ si_deinititalize_controller(si_ctlp);
+ si_ctlp->sictl_flags &= ~SI_DETACH;
+ }
+
+ if (attach_state & ATTACH_PROGRESS_MUTEX_INIT) {
+ mutex_destroy(&si_ctlp->sictl_mutex);
+ }
+
+ if (attach_state & ATTACH_PROGRESS_INTR_ADDED) {
+ si_rem_intrs(si_ctlp);
+ }
+
+ if (attach_state & ATTACH_PROGRESS_BAR1_MAP) {
+ ddi_regs_map_free(&si_ctlp->sictl_port_acc_handle);
+ }
+
+ if (attach_state & ATTACH_PROGRESS_BAR0_MAP) {
+ ddi_regs_map_free(&si_ctlp->sictl_global_acc_handle);
+ }
+
+ if (attach_state & ATTACH_PROGRESS_CONF_HANDLE) {
+ pci_config_teardown(&si_ctlp->sictl_pci_conf_handle);
+ }
+
+ if (attach_state & ATTACH_PROGRESS_STATEP_ALLOC) {
+ ddi_soft_state_free(si_statep, instance);
+ }
+
+ return (DDI_FAILURE);
+}
+
+
+/*
+ * The detach entry point for dev_ops.
+ *
+ * We undo the things we did in si_attach().
+ */
+static int
+si_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ si_ctl_state_t *si_ctlp;
+ int instance;
+
+ SIDBG0(SIDBG_INIT|SIDBG_ENTRY, NULL, "si_detach enter");
+ instance = ddi_get_instance(dip);
+ si_ctlp = ddi_get_soft_state(si_statep, instance);
+
+ switch (cmd) {
+
+ case DDI_DETACH:
+
+ mutex_enter(&si_ctlp->sictl_mutex);
+
+ /* disable the interrupts for an uninterrupted detach */
+ si_disable_all_interrupts(si_ctlp);
+
+ mutex_exit(&si_ctlp->sictl_mutex);
+ /* unregister from the sata framework. */
+ if (si_unregister_sata_hba_tran(si_ctlp) != SI_SUCCESS) {
+ si_enable_all_interrupts(si_ctlp);
+ return (DDI_FAILURE);
+ }
+ mutex_enter(&si_ctlp->sictl_mutex);
+
+ /* now cancel the timeout handler. */
+ si_ctlp->sictl_flags |= SI_NO_TIMEOUTS;
+ (void) untimeout(si_ctlp->sictl_timeout_id);
+ si_ctlp->sictl_flags &= ~SI_NO_TIMEOUTS;
+
+ /* deinitialize the controller. */
+ si_ctlp->sictl_flags |= SI_DETACH;
+ si_deinititalize_controller(si_ctlp);
+ si_ctlp->sictl_flags &= ~SI_DETACH;
+
+ /* destroy any mutexes */
+ mutex_exit(&si_ctlp->sictl_mutex);
+ mutex_destroy(&si_ctlp->sictl_mutex);
+
+ /* remove the interrupts */
+ si_rem_intrs(si_ctlp);
+
+ /* remove the reg maps. */
+ ddi_regs_map_free(&si_ctlp->sictl_port_acc_handle);
+ ddi_regs_map_free(&si_ctlp->sictl_global_acc_handle);
+ pci_config_teardown(&si_ctlp->sictl_pci_conf_handle);
+
+ /* free the soft state. */
+ ddi_soft_state_free(si_statep, instance);
+
+ return (DDI_SUCCESS);
+
+ case DDI_SUSPEND:
+ /* Inform SATA framework */
+ if (sata_hba_detach(dip, cmd) != DDI_SUCCESS) {
+ return (DDI_FAILURE);
+ }
+
+ mutex_enter(&si_ctlp->sictl_mutex);
+
+ /*
+ * Device needs to be at full power in case it is needed to
+ * handle dump(9e) to save CPR state after DDI_SUSPEND
+ * completes. This is OK since presumably power will be
+ * removed anyways. No outstanding transactions should be
+ * on the controller since the children are already quiesed.
+ *
+ * If any ioctls/cfgadm support is added that touches
+ * hardware, those entry points will need to check for
+ * suspend and then block or return errors until resume.
+ *
+ */
+ if (pm_busy_component(si_ctlp->sictl_devinfop, 0) ==
+ DDI_SUCCESS) {
+ mutex_exit(&si_ctlp->sictl_mutex);
+ (void) pm_raise_power(si_ctlp->sictl_devinfop, 0,
+ PM_LEVEL_D0);
+ mutex_enter(&si_ctlp->sictl_mutex);
+ }
+
+ si_deinititalize_controller(si_ctlp);
+
+ si_ctlp->sictl_flags |= SI_NO_TIMEOUTS;
+ (void) untimeout(si_ctlp->sictl_timeout_id);
+ si_ctlp->sictl_flags &= ~SI_NO_TIMEOUTS;
+
+ SIDBG1(SIDBG_POWER, NULL, "si3124%d: DDI_SUSPEND", instance);
+
+ mutex_exit(&si_ctlp->sictl_mutex);
+
+ return (DDI_SUCCESS);
+
+ default:
+ return (DDI_FAILURE);
+
+ }
+
+}
+
+static int
+si_power(dev_info_t *dip, int component, int level)
+{
+#ifndef __lock_lint
+ _NOTE(ARGUNUSED(component))
+#endif /* __lock_lint */
+
+ si_ctl_state_t *si_ctlp;
+ int instance = ddi_get_instance(dip);
+ int rval = DDI_SUCCESS;
+ int old_level;
+ sata_device_t sdevice;
+
+ si_ctlp = ddi_get_soft_state(si_statep, instance);
+
+ if (si_ctlp == NULL) {
+ return (DDI_FAILURE);
+ }
+
+ SIDBG0(SIDBG_ENTRY, NULL, "si_power enter");
+
+ mutex_enter(&si_ctlp->sictl_mutex);
+ old_level = si_ctlp->sictl_power_level;
+
+ switch (level) {
+ case PM_LEVEL_D0: /* fully on */
+ pci_config_put16(si_ctlp->sictl_pci_conf_handle,
+ PM_CSR(si_ctlp->sictl_devid), PCI_PMCSR_D0);
+#ifndef __lock_lint
+ delay(drv_usectohz(10000));
+#endif /* __lock_lint */
+ si_ctlp->sictl_power_level = PM_LEVEL_D0;
+ (void) pci_restore_config_regs(si_ctlp->sictl_devinfop);
+
+ SIDBG2(SIDBG_POWER, si_ctlp,
+ "si3124%d: turning power ON. old level %d",
+ instance, old_level);
+ /*
+ * If called from attach, just raise device power,
+ * restore config registers (if they were saved
+ * from a previous detach that lowered power),
+ * and exit.
+ */
+ if (si_ctlp->sictl_flags & SI_ATTACH)
+ break;
+
+ mutex_exit(&si_ctlp->sictl_mutex);
+ (void) si_initialize_controller(si_ctlp);
+ mutex_enter(&si_ctlp->sictl_mutex);
+
+ si_ctlp->sictl_timeout_id = timeout(
+ (void (*)(void *))si_watchdog_handler,
+ (caddr_t)si_ctlp, si_watchdog_tick);
+
+ bzero((void *)&sdevice, sizeof (sata_device_t));
+ sata_hba_event_notify(
+ si_ctlp->sictl_sata_hba_tran->sata_tran_hba_dip,
+ &sdevice, SATA_EVNT_PWR_LEVEL_CHANGED);
+ SIDBG0(SIDBG_EVENT|SIDBG_POWER, si_ctlp,
+ "sending event up: PWR_LEVEL_CHANGED");
+
+ break;
+
+ case PM_LEVEL_D3: /* fully off */
+ if (!(si_ctlp->sictl_flags & SI_DETACH)) {
+ si_ctlp->sictl_flags |= SI_NO_TIMEOUTS;
+ (void) untimeout(si_ctlp->sictl_timeout_id);
+ si_ctlp->sictl_flags &= ~SI_NO_TIMEOUTS;
+
+ si_deinititalize_controller(si_ctlp);
+
+ si_ctlp->sictl_power_level = PM_LEVEL_D3;
+ }
+
+ (void) pci_save_config_regs(si_ctlp->sictl_devinfop);
+
+ pci_config_put16(si_ctlp->sictl_pci_conf_handle,
+ PM_CSR(si_ctlp->sictl_devid), PCI_PMCSR_D3HOT);
+
+ SIDBG2(SIDBG_POWER, NULL, "si3124%d: turning power OFF. "
+ "old level %d", instance, old_level);
+
+ break;
+
+ default:
+ SIDBG2(SIDBG_POWER, NULL, "si3124%d: turning power OFF. "
+ "old level %d", instance, old_level);
+ rval = DDI_FAILURE;
+ break;
+ }
+
+ mutex_exit(&si_ctlp->sictl_mutex);
+
+ return (rval);
+}
+
+
+/*
+ * The info entry point for dev_ops.
+ *
+ */
+static int
+si_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd,
+ void *arg,
+ void **result)
+{
+#ifndef __lock_lint
+ _NOTE(ARGUNUSED(dip))
+#endif /* __lock_lint */
+ si_ctl_state_t *si_ctlp;
+ int instance;
+ dev_t dev;
+
+ dev = (dev_t)arg;
+ instance = getminor(dev);
+
+ switch (infocmd) {
+ case DDI_INFO_DEVT2DEVINFO:
+ si_ctlp = ddi_get_soft_state(si_statep, instance);
+ if (si_ctlp != NULL) {
+ *result = si_ctlp->sictl_devinfop;
+ return (DDI_SUCCESS);
+ } else {
+ *result = NULL;
+ return (DDI_FAILURE);
+ }
+ case DDI_INFO_DEVT2INSTANCE:
+ *(int *)result = instance;
+ break;
+ default:
+ break;
+ }
+ return (DDI_SUCCESS);
+}
+
+
+
+/*
+ * Registers the si3124 with sata framework.
+ */
+static int
+si_register_sata_hba_tran(si_ctl_state_t *si_ctlp)
+{
+ struct sata_hba_tran *sata_hba_tran;
+
+ SIDBG0(SIDBG_INIT|SIDBG_ENTRY, si_ctlp,
+ "si_register_sata_hba_tran entry");
+
+ mutex_enter(&si_ctlp->sictl_mutex);
+
+ /* Allocate memory for the sata_hba_tran */
+ sata_hba_tran = kmem_zalloc(sizeof (sata_hba_tran_t), KM_SLEEP);
+
+ sata_hba_tran->sata_tran_hba_rev = SATA_TRAN_HBA_REV;
+ sata_hba_tran->sata_tran_hba_dip = si_ctlp->sictl_devinfop;
+ sata_hba_tran->sata_tran_hba_dma_attr = &buffer_dma_attr;
+
+ sata_hba_tran->sata_tran_hba_num_cports = si_ctlp->sictl_num_ports;
+ sata_hba_tran->sata_tran_hba_features_support = 0;
+ sata_hba_tran->sata_tran_hba_qdepth = SI_NUM_SLOTS;
+
+ sata_hba_tran->sata_tran_probe_port = si_tran_probe_port;
+ sata_hba_tran->sata_tran_start = si_tran_start;
+ sata_hba_tran->sata_tran_abort = si_tran_abort;
+ sata_hba_tran->sata_tran_reset_dport = si_tran_reset_dport;
+ sata_hba_tran->sata_tran_selftest = NULL;
+ sata_hba_tran->sata_tran_hotplug_ops = &si_tran_hotplug_ops;
+ sata_hba_tran->sata_tran_pwrmgt_ops = NULL;
+ sata_hba_tran->sata_tran_ioctl = NULL;
+ mutex_exit(&si_ctlp->sictl_mutex);
+
+ /* Attach it to SATA framework */
+ if (sata_hba_attach(si_ctlp->sictl_devinfop, sata_hba_tran, DDI_ATTACH)
+ != DDI_SUCCESS) {
+ kmem_free((void *)sata_hba_tran, sizeof (sata_hba_tran_t));
+ return (SI_FAILURE);
+ }
+
+ mutex_enter(&si_ctlp->sictl_mutex);
+ si_ctlp->sictl_sata_hba_tran = sata_hba_tran;
+ mutex_exit(&si_ctlp->sictl_mutex);
+
+ return (SI_SUCCESS);
+}
+
+
+/*
+ * Unregisters the si3124 with sata framework.
+ */
+static int
+si_unregister_sata_hba_tran(si_ctl_state_t *si_ctlp)
+{
+
+ /* Detach from the SATA framework. */
+ if (sata_hba_detach(si_ctlp->sictl_devinfop, DDI_DETACH) !=
+ DDI_SUCCESS) {
+ return (SI_FAILURE);
+ }
+
+ /* Deallocate sata_hba_tran. */
+ kmem_free((void *)si_ctlp->sictl_sata_hba_tran,
+ sizeof (sata_hba_tran_t));
+
+ si_ctlp->sictl_sata_hba_tran = NULL;
+
+ return (SI_SUCCESS);
+}
+
+/*
+ * Called by sata framework to probe a port. We return the
+ * cached information from a previous hardware probe.
+ *
+ * The actual hardware probing itself was done either from within
+ * si_initialize_controller() during the driver attach or
+ * from a phy ready change interrupt handler.
+ */
+static int
+si_tran_probe_port(dev_info_t *dip, sata_device_t *sd)
+{
+
+ si_ctl_state_t *si_ctlp;
+ uint8_t cport = sd->satadev_addr.cport;
+ uint8_t pmport = sd->satadev_addr.pmport;
+ uint8_t qual = sd->satadev_addr.qual;
+ uint8_t port_type;
+ si_port_state_t *si_portp;
+ si_portmult_state_t *si_portmultp;
+
+ si_ctlp = ddi_get_soft_state(si_statep, ddi_get_instance(dip));
+
+ SIDBG3(SIDBG_ENTRY, si_ctlp,
+ "si_tran_probe_port: cport: 0x%x, pmport: 0x%x, qual: 0x%x",
+ cport, pmport, qual);
+
+ if (cport >= SI_MAX_PORTS) {
+ sd->satadev_type = SATA_DTYPE_NONE;
+ sd->satadev_state = SATA_STATE_PROBED;
+ return (SATA_FAILURE);
+ }
+
+ mutex_enter(&si_ctlp->sictl_mutex);
+ si_portp = si_ctlp->sictl_ports[cport];
+ mutex_exit(&si_ctlp->sictl_mutex);
+ if (si_portp == NULL) {
+ sd->satadev_type = SATA_DTYPE_NONE;
+ sd->satadev_state = SATA_STATE_PROBED;
+ return (SATA_FAILURE);
+ }
+
+ mutex_enter(&si_portp->siport_mutex);
+
+ if (qual == SATA_ADDR_PMPORT) {
+ if (pmport >= si_portp->siport_portmult_state.sipm_num_ports) {
+ sd->satadev_type = SATA_DTYPE_NONE;
+ sd->satadev_state = SATA_STATE_PROBED;
+ mutex_exit(&si_portp->siport_mutex);
+ return (SATA_FAILURE);
+ } else {
+ si_portmultp = &si_portp->siport_portmult_state;
+ port_type = si_portmultp->sipm_port_type[pmport];
+ }
+ } else {
+ port_type = si_portp->siport_port_type;
+ }
+
+ switch (port_type) {
+
+ case PORT_TYPE_DISK:
+ sd->satadev_type = SATA_DTYPE_ATADISK;
+ sd->satadev_state = SATA_STATE_PROBED;
+ break;
+
+ case PORT_TYPE_ATAPI:
+ sd->satadev_type = SATA_DTYPE_ATAPICD;
+ sd->satadev_state = SATA_STATE_PROBED;
+ break;
+
+ case PORT_TYPE_MULTIPLIER:
+ sd->satadev_type = SATA_DTYPE_PMULT;
+ sd->satadev_add_info =
+ si_portp->siport_portmult_state.sipm_num_ports;
+ sd->satadev_state = SATA_STATE_PROBED;
+ break;
+
+ case PORT_TYPE_UNKNOWN:
+ sd->satadev_type = SATA_DTYPE_UNKNOWN;
+ sd->satadev_state = SATA_STATE_PROBED;
+
+ default:
+ /* we don't support any other device types. */
+ sd->satadev_type = SATA_DTYPE_NONE;
+ sd->satadev_state = SATA_STATE_PROBED;
+ break;
+ }
+
+ if (qual == SATA_ADDR_PMPORT) {
+ (void) si_read_portmult_reg(si_ctlp, si_portp, cport,
+ pmport, PSCR_REG0, &sd->satadev_scr.sstatus);
+ (void) si_read_portmult_reg(si_ctlp, si_portp, cport,
+ pmport, PSCR_REG1, &sd->satadev_scr.serror);
+ (void) si_read_portmult_reg(si_ctlp, si_portp, cport,
+ pmport, PSCR_REG2, &sd->satadev_scr.scontrol);
+ (void) si_read_portmult_reg(si_ctlp, si_portp, cport,
+ pmport, PSCR_REG3, &sd->satadev_scr.sactive);
+ } else {
+ fill_dev_sregisters(si_ctlp, cport, sd);
+ if (!(si_portp->siport_active)) {
+ /*
+ * Since we are implementing the port deactivation
+ * in software only, we need to fake a valid value
+ * for sstatus when the device is in deactivated state.
+ */
+ SSTATUS_SET_DET(sd->satadev_scr.sstatus,
+ SSTATUS_DET_PHYOFFLINE);
+ SSTATUS_SET_IPM(sd->satadev_scr.sstatus,
+ SSTATUS_IPM_NODEV_NOPHY);
+ }
+ }
+
+ mutex_exit(&si_portp->siport_mutex);
+ return (SATA_SUCCESS);
+}
+
+/*
+ * Called by sata framework to transport a sata packet down stream.
+ *
+ * The actual work of building the FIS & transporting it to the hardware
+ * is done out of the subroutine si_deliver_satapkt().
+ */
+static int
+si_tran_start(dev_info_t *dip, sata_pkt_t *spkt)
+{
+ si_ctl_state_t *si_ctlp;
+ uint8_t cport;
+ si_port_state_t *si_portp;
+ int slot;
+
+ cport = spkt->satapkt_device.satadev_addr.cport;
+ si_ctlp = ddi_get_soft_state(si_statep, ddi_get_instance(dip));
+ mutex_enter(&si_ctlp->sictl_mutex);
+ si_portp = si_ctlp->sictl_ports[cport];
+ mutex_exit(&si_ctlp->sictl_mutex);
+
+ SIDBG1(SIDBG_ENTRY, si_ctlp,
+ "si_tran_start entry: port: 0x%x", cport);
+
+ mutex_enter(&si_portp->siport_mutex);
+
+ if ((si_portp->siport_port_type == PORT_TYPE_NODEV) ||
+ !si_portp->siport_active) {
+ /*
+ * si_intr_phy_ready_change() may have rendered it to
+ * PORT_TYPE_NODEV. cfgadm operation may have rendered
+ * it inactive.
+ */
+ spkt->satapkt_reason = SATA_PKT_PORT_ERROR;
+ fill_dev_sregisters(si_ctlp, cport, &spkt->satapkt_device);
+ mutex_exit(&si_portp->siport_mutex);
+ return (SATA_TRAN_PORT_ERROR);
+ }
+
+ if (spkt->satapkt_cmd.satacmd_flags & SATA_CLEAR_DEV_RESET_STATE) {
+ si_portp->siport_reset_in_progress = 0;
+ SIDBG1(SIDBG_ENTRY, si_ctlp,
+ "si_tran_start clearing the "
+ "reset_in_progress for port: 0x%x", cport);
+ }
+
+ if (si_portp->siport_reset_in_progress &&
+ !(spkt->satapkt_cmd.satacmd_flags &
+ SATA_IGNORE_DEV_RESET_STATE)) {
+
+ spkt->satapkt_reason = SATA_PKT_BUSY;
+ SIDBG1(SIDBG_ERRS, si_ctlp,
+ "si_tran_start returning BUSY while "
+ "reset in progress: port: 0x%x", cport);
+ mutex_exit(&si_portp->siport_mutex);
+ return (SATA_TRAN_BUSY);
+ }
+
+ if (si_portp->mopping_in_progress) {
+ spkt->satapkt_reason = SATA_PKT_BUSY;
+ SIDBG1(SIDBG_ERRS, si_ctlp,
+ "si_tran_start returning BUSY while "
+ "mopping in progress: port: 0x%x", cport);
+ mutex_exit(&si_portp->siport_mutex);
+ return (SATA_TRAN_BUSY);
+ }
+
+ if ((slot = si_deliver_satapkt(si_ctlp, si_portp, cport, spkt))
+ == SI_FAILURE) {
+ spkt->satapkt_reason = SATA_PKT_QUEUE_FULL;
+ SIDBG1(SIDBG_ERRS, si_ctlp,
+ "si_tran_start returning QUEUE_FULL: port: 0x%x",
+ cport);
+ mutex_exit(&si_portp->siport_mutex);
+ return (SATA_TRAN_QUEUE_FULL);
+ }
+
+ if (spkt->satapkt_op_mode & (SATA_OPMODE_POLLING|SATA_OPMODE_SYNCH)) {
+ /* we need to poll now */
+ mutex_exit(&si_portp->siport_mutex);
+ si_poll_cmd(si_ctlp, si_portp, cport, slot, spkt);
+ mutex_enter(&si_portp->siport_mutex);
+ }
+
+ mutex_exit(&si_portp->siport_mutex);
+ return (SATA_TRAN_ACCEPTED);
+}
+
+#define SENDUP_PACKET(si_portp, satapkt, reason) \
+ if ((satapkt->satapkt_cmd.satacmd_cmd_reg == \
+ SATAC_WRITE_FPDMA_QUEUED) || \
+ (satapkt->satapkt_cmd.satacmd_cmd_reg == \
+ SATAC_READ_FPDMA_QUEUED)) { \
+ si_portp->siport_pending_ncq_count--; \
+ } \
+ if (satapkt) { \
+ satapkt->satapkt_reason = reason; \
+ /* \
+ * We set the satapkt_reason in both synch and \
+ * non-synch cases. \
+ */ \
+ } \
+ if (satapkt && \
+ !(satapkt->satapkt_op_mode & SATA_OPMODE_SYNCH) && \
+ satapkt->satapkt_comp) { \
+ mutex_exit(&si_portp->siport_mutex); \
+ (*satapkt->satapkt_comp)(satapkt); \
+ mutex_enter(&si_portp->siport_mutex); \
+ }
+
+/*
+ * Mopping is necessitated because of the si3124 hardware limitation.
+ * The only way to recover from errors or to abort a command is to
+ * reset the port/device but such a reset also results in throwing
+ * away all the unfinished pending commands.
+ *
+ * A port or device is reset in four scenarios:
+ * a) some commands failed with errors
+ * b) or we need to timeout some commands
+ * c) or we need to abort some commands
+ * d) or we need reset the port at the request of sata framework
+ *
+ * In all these scenarios, we need to send any pending unfinished
+ * commands up to sata framework.
+ *
+ * Only one mopping process at a time is allowed; this is achieved
+ * by using siport_mop_mutex.
+ */
+static void
+si_mop_commands(si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ uint8_t port,
+
+ uint32_t slot_status,
+ uint32_t failed_tags,
+ uint32_t timedout_tags,
+ uint32_t aborting_tags,
+ uint32_t reset_tags)
+{
+ uint32_t finished_tags, unfinished_tags;
+ int tmpslot;
+ sata_pkt_t *satapkt;
+ si_prb_t *prb;
+ uint32_t *prb_word_ptr;
+ int i;
+
+ SIDBG1(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
+ "si_mop_commands entered: slot_status: 0x%x",
+ slot_status);
+
+ SIDBG4(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
+ "si_mop_commands: failed_tags: 0x%x, timedout_tags: 0x%x"
+ "aborting_tags: 0x%x, reset_tags: 0x%x",
+ failed_tags,
+ timedout_tags,
+ aborting_tags,
+ reset_tags);
+ /*
+ * We could be here for four reasons: abort, reset,
+ * timeout or error handling. Only one such mopping
+ * is allowed at a time.
+ *
+ * Note that we are already holding the main per port
+ * mutex; all we need now is siport_mop_mutex.
+ */
+ mutex_enter(&si_portp->siport_mop_mutex);
+ mutex_enter(&si_portp->siport_mutex);
+
+ si_portp->mopping_in_progress = 1;
+
+ finished_tags = si_portp->siport_pending_tags &
+ ~slot_status & SI_SLOT_MASK;
+
+ unfinished_tags = slot_status & SI_SLOT_MASK &
+ ~failed_tags &
+ ~aborting_tags &
+ ~reset_tags &
+ ~timedout_tags;
+
+ /* Send up the finished_tags with SATA_PKT_COMPLETED. */
+ while (finished_tags) {
+ tmpslot = ddi_ffs(finished_tags) - 1;
+ if (tmpslot == -1) {
+ break;
+ }
+
+ satapkt = si_portp->siport_slot_pkts[tmpslot];
+ ASSERT(satapkt != NULL);
+ SIDBG1(SIDBG_ERRS, si_ctlp,
+ "si_mop_commands sending up completed satapkt: %x",
+ satapkt);
+ SENDUP_PACKET(si_portp, satapkt, SATA_PKT_COMPLETED);
+
+ CLEAR_BIT(si_portp->siport_pending_tags, tmpslot);
+ CLEAR_BIT(finished_tags, tmpslot);
+ }
+
+ ASSERT(finished_tags == 0);
+
+ /* Send up failed_tags with SATA_PKT_DEV_ERROR. */
+ while (failed_tags) {
+ tmpslot = ddi_ffs(failed_tags) - 1;
+ if (tmpslot == -1) {
+ break;
+ }
+ SIDBG1(SIDBG_ERRS, si_ctlp, "si3124: si_mop_commands: "
+ "handling failed slot: 0x%x", tmpslot);
+
+ satapkt = si_portp->siport_slot_pkts[tmpslot];
+ ASSERT(satapkt != NULL);
+ if (satapkt->satapkt_device.satadev_type ==
+ SATA_DTYPE_ATAPICD) {
+ si_set_sense_data(satapkt, SATA_PKT_DEV_ERROR);
+ }
+
+ /*
+ * The LRAM contains the the modified FIS.
+ * Read the modified FIS to obtain the Error & Status.
+ */
+ prb = &(si_portp->siport_prbpool[tmpslot]);
+ prb_word_ptr = (uint32_t *)prb;
+ for (i = 0; i < (sizeof (si_prb_t)/4); i++) {
+ prb_word_ptr[i] = ddi_get32(
+ si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_LRAM(si_ctlp, port,
+ tmpslot)+i*4));
+ }
+
+ satapkt->satapkt_cmd.satacmd_status_reg =
+ GET_FIS_COMMAND(prb->prb_fis);
+ satapkt->satapkt_cmd.satacmd_error_reg =
+ GET_FIS_FEATURES(prb->prb_fis);
+
+ /*
+ * In the case of NCQ command failures, the error is
+ * overwritten by the one obtained from issuing of a
+ * READ LOG EXTENDED command.
+ */
+ if (si_portp->siport_err_tags_SDBERROR & (1 << tmpslot)) {
+ satapkt->satapkt_cmd.satacmd_error_reg =
+ si_read_log_ext(si_ctlp, si_portp, port);
+ }
+
+ SENDUP_PACKET(si_portp, satapkt, SATA_PKT_DEV_ERROR);
+
+ CLEAR_BIT(failed_tags, tmpslot);
+ CLEAR_BIT(si_portp->siport_pending_tags, tmpslot);
+ }
+
+ ASSERT(failed_tags == 0);
+
+ /* Send up timedout_tags with SATA_PKT_TIMEOUT. */
+ while (timedout_tags) {
+ tmpslot = ddi_ffs(timedout_tags) - 1;
+ if (tmpslot == -1) {
+ break;
+ }
+
+ satapkt = si_portp->siport_slot_pkts[tmpslot];
+ ASSERT(satapkt != NULL);
+ SIDBG1(SIDBG_ERRS, si_ctlp,
+ "si_mop_commands sending "
+ "spkt up with PKT_TIMEOUT: %x",
+ satapkt);
+
+ SENDUP_PACKET(si_portp, satapkt, SATA_PKT_TIMEOUT);
+
+ CLEAR_BIT(si_portp->siport_pending_tags, tmpslot);
+ CLEAR_BIT(timedout_tags, tmpslot);
+ }
+
+ ASSERT(timedout_tags == 0);
+
+ /* Send up aborting packets with SATA_PKT_ABORTED. */
+ while (aborting_tags) {
+ tmpslot = ddi_ffs(unfinished_tags) - 1;
+ if (tmpslot == -1) {
+ break;
+ }
+
+ satapkt = si_portp->siport_slot_pkts[tmpslot];
+ ASSERT(satapkt != NULL);
+ SIDBG1(SIDBG_ERRS, si_ctlp,
+ "si_mop_commands aborting spkt: %x",
+ satapkt);
+ if (satapkt->satapkt_device.satadev_type ==
+ SATA_DTYPE_ATAPICD) {
+ si_set_sense_data(satapkt, SATA_PKT_ABORTED);
+ }
+ SENDUP_PACKET(si_portp, satapkt, SATA_PKT_ABORTED);
+
+ CLEAR_BIT(si_portp->siport_pending_tags, tmpslot);
+ CLEAR_BIT(aborting_tags, tmpslot);
+
+ }
+
+ ASSERT(aborting_tags == 0);
+
+ /* Reset tags are sent up to framework with SATA_PKT_RESET. */
+ while (reset_tags) {
+ tmpslot = ddi_ffs(reset_tags) - 1;
+ if (tmpslot == -1) {
+ break;
+ }
+ satapkt = si_portp->siport_slot_pkts[tmpslot];
+ ASSERT(satapkt != NULL);
+ SIDBG1(SIDBG_ERRS, si_ctlp,
+ "si_mop_commands sending PKT_RESET for "
+ "reset spkt: %x",
+ satapkt);
+ SENDUP_PACKET(si_portp, satapkt, SATA_PKT_RESET);
+
+ CLEAR_BIT(reset_tags, tmpslot);
+ CLEAR_BIT(si_portp->siport_pending_tags, tmpslot);
+ }
+
+ ASSERT(reset_tags == 0);
+
+ /* Send up the unfinished_tags with SATA_PKT_BUSY. */
+ while (unfinished_tags) {
+ tmpslot = ddi_ffs(unfinished_tags) - 1;
+ if (tmpslot == -1) {
+ break;
+ }
+ satapkt = si_portp->siport_slot_pkts[tmpslot];
+ ASSERT(satapkt != NULL);
+ SIDBG1(SIDBG_ERRS, si_ctlp,
+ "si_mop_commands sending PKT_BUSY for "
+ "retry spkt: %x",
+ satapkt);
+ SENDUP_PACKET(si_portp, satapkt, SATA_PKT_BUSY);
+
+ CLEAR_BIT(unfinished_tags, tmpslot);
+ CLEAR_BIT(si_portp->siport_pending_tags, tmpslot);
+ }
+
+ ASSERT(unfinished_tags == 0);
+
+ si_portp->mopping_in_progress = 0;
+
+ mutex_exit(&si_portp->siport_mutex);
+ mutex_exit(&si_portp->siport_mop_mutex);
+
+}
+
+/*
+ * Called by the sata framework to abort the previously sent packet(s).
+ *
+ * We reset the device and mop the commands on the port.
+ */
+static int
+si_tran_abort(dev_info_t *dip, sata_pkt_t *spkt, int flag)
+{
+ uint32_t slot_status;
+ uint8_t port;
+ int tmpslot;
+ uint32_t aborting_tags;
+ uint32_t finished_tags;
+ si_port_state_t *si_portp;
+ si_ctl_state_t *si_ctlp;
+
+ port = spkt->satapkt_device.satadev_addr.cport;
+ si_ctlp = ddi_get_soft_state(si_statep, ddi_get_instance(dip));
+ mutex_enter(&si_ctlp->sictl_mutex);
+ si_portp = si_ctlp->sictl_ports[port];
+ mutex_exit(&si_ctlp->sictl_mutex);
+
+ SIDBG1(SIDBG_ENTRY, si_ctlp, "si_tran_abort on port: %x", port);
+
+ mutex_enter(&si_portp->siport_mutex);
+
+ if ((si_portp->siport_port_type == PORT_TYPE_NODEV) ||
+ !si_portp->siport_active) {
+ /*
+ * si_intr_phy_ready_change() may have rendered it to
+ * PORT_TYPE_NODEV. cfgadm operation may have rendered
+ * it inactive.
+ */
+ spkt->satapkt_reason = SATA_PKT_PORT_ERROR;
+ fill_dev_sregisters(si_ctlp, port, &spkt->satapkt_device);
+ mutex_exit(&si_portp->siport_mutex);
+ return (SATA_FAILURE);
+ }
+
+ if (flag == SATA_ABORT_ALL_PACKETS) {
+ aborting_tags = si_portp->siport_pending_tags;
+ } else {
+ /*
+ * Need to abort a single packet.
+ * Search our siport_slot_pkts[] list for matching spkt.
+ */
+ aborting_tags = 0xffffffff; /* 0xffffffff is impossible tag */
+ for (tmpslot = 0; tmpslot < SI_NUM_SLOTS; tmpslot++) {
+ if (si_portp->siport_slot_pkts[tmpslot] == spkt) {
+ aborting_tags = (0x1 << tmpslot);
+ break;
+ }
+ }
+
+ if (aborting_tags == 0xffffffff) {
+ /* requested packet is not on pending list. */
+ fill_dev_sregisters(si_ctlp, port,
+ &spkt->satapkt_device);
+ mutex_exit(&si_portp->siport_mutex);
+ return (SATA_FAILURE);
+ }
+ }
+
+
+ slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
+ (void) si_reset_dport_wait_till_ready(si_ctlp, si_portp,
+ port, SI_DEVICE_RESET);
+
+ /*
+ * Compute which have finished and which need to be retried.
+ *
+ * The finished tags are siport_pending_tags minus the slot_status.
+ * The aborting_tags have to be reduced by finished_tags since we
+ * can't possibly abort a tag which had finished already.
+ */
+ finished_tags = si_portp->siport_pending_tags &
+ ~slot_status & SI_SLOT_MASK;
+ aborting_tags &= ~finished_tags;
+
+ mutex_exit(&si_portp->siport_mutex);
+ si_mop_commands(si_ctlp,
+ si_portp,
+ port,
+ slot_status,
+ 0, /* failed_tags */
+ 0, /* timedout_tags */
+ aborting_tags,
+ 0); /* reset_tags */
+ mutex_enter(&si_portp->siport_mutex);
+
+ fill_dev_sregisters(si_ctlp, port, &spkt->satapkt_device);
+ mutex_exit(&si_portp->siport_mutex);
+ return (SATA_SUCCESS);
+}
+
+
+/*
+ * Used to reject all the pending packets on a port during a reset
+ * operation.
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+static void
+si_reject_all_reset_pkts(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ uint32_t slot_status;
+ uint32_t reset_tags;
+
+ _NOTE(ASSUMING_PROTECTED(si_portp))
+
+ SIDBG1(SIDBG_ENTRY, si_ctlp,
+ "si_reject_all_reset_pkts on port: %x",
+ port);
+
+ slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
+
+ /* Compute which tags need to be sent up. */
+ reset_tags = slot_status & SI_SLOT_MASK;
+
+ mutex_exit(&si_portp->siport_mutex);
+ si_mop_commands(si_ctlp,
+ si_portp,
+ port,
+ slot_status,
+ 0, /* failed_tags */
+ 0, /* timedout_tags */
+ 0, /* aborting_tags */
+ reset_tags);
+ mutex_enter(&si_portp->siport_mutex);
+
+}
+
+
+/*
+ * Called by sata framework to reset a port(s) or device.
+ *
+ */
+static int
+si_tran_reset_dport(dev_info_t *dip, sata_device_t *sd)
+{
+ si_ctl_state_t *si_ctlp;
+ uint8_t port = sd->satadev_addr.cport;
+ int i;
+ si_port_state_t *si_portp;
+ int retval = SI_SUCCESS;
+
+ si_ctlp = ddi_get_soft_state(si_statep, ddi_get_instance(dip));
+ SIDBG1(SIDBG_ENTRY, si_ctlp,
+ "si_tran_reset_port entry: port: 0x%x",
+ port);
+
+ switch (sd->satadev_addr.qual) {
+ case SATA_ADDR_CPORT:
+ mutex_enter(&si_ctlp->sictl_mutex);
+ si_portp = si_ctlp->sictl_ports[port];
+ mutex_exit(&si_ctlp->sictl_mutex);
+
+ mutex_enter(&si_portp->siport_mutex);
+ retval = si_reset_dport_wait_till_ready(si_ctlp, si_portp, port,
+ SI_PORT_RESET);
+ si_reject_all_reset_pkts(si_ctlp, si_portp, port);
+ mutex_exit(&si_portp->siport_mutex);
+
+ break;
+
+ case SATA_ADDR_DCPORT:
+ mutex_enter(&si_ctlp->sictl_mutex);
+ si_portp = si_ctlp->sictl_ports[port];
+ mutex_exit(&si_ctlp->sictl_mutex);
+
+ mutex_enter(&si_portp->siport_mutex);
+
+ if ((si_portp->siport_port_type == PORT_TYPE_NODEV) ||
+ !si_portp->siport_active) {
+ mutex_exit(&si_portp->siport_mutex);
+ retval = SI_FAILURE;
+ break;
+ }
+
+ retval = si_reset_dport_wait_till_ready(si_ctlp, si_portp, port,
+ SI_DEVICE_RESET);
+ si_reject_all_reset_pkts(si_ctlp, si_portp, port);
+ mutex_exit(&si_portp->siport_mutex);
+
+ break;
+
+ case SATA_ADDR_CNTRL:
+ for (i = 0; i < si_ctlp->sictl_num_ports; i++) {
+ mutex_enter(&si_ctlp->sictl_mutex);
+ si_portp = si_ctlp->sictl_ports[port];
+ mutex_exit(&si_ctlp->sictl_mutex);
+
+ mutex_enter(&si_portp->siport_mutex);
+ retval = si_reset_dport_wait_till_ready(si_ctlp,
+ si_portp, i, SI_PORT_RESET);
+ if (retval) {
+ mutex_exit(&si_portp->siport_mutex);
+ break;
+ }
+ si_reject_all_reset_pkts(si_ctlp, si_portp, port);
+ mutex_exit(&si_portp->siport_mutex);
+ }
+ break;
+
+ case SATA_ADDR_PMPORT:
+ case SATA_ADDR_DPMPORT:
+ SIDBG0(SIDBG_VERBOSE, si_ctlp,
+ "port mult reset not implemented yet");
+ /* FALLSTHROUGH */
+
+ default:
+ retval = SI_FAILURE;
+
+ }
+
+ return (retval);
+}
+
+
+/*
+ * Called by sata framework to activate a port as part of hotplug.
+ *
+ * Note: Not port-mult aware.
+ */
+static int
+si_tran_hotplug_port_activate(dev_info_t *dip, sata_device_t *satadev)
+{
+ si_ctl_state_t *si_ctlp;
+ si_port_state_t *si_portp;
+ uint8_t port;
+
+ si_ctlp = ddi_get_soft_state(si_statep, ddi_get_instance(dip));
+ port = satadev->satadev_addr.cport;
+ mutex_enter(&si_ctlp->sictl_mutex);
+ si_portp = si_ctlp->sictl_ports[port];
+ mutex_exit(&si_ctlp->sictl_mutex);
+
+ SIDBG0(SIDBG_ENTRY, si_ctlp, "si_tran_hotplug_port_activate entry");
+
+ mutex_enter(&si_portp->siport_mutex);
+ si_enable_port_interrupts(si_ctlp, port);
+
+ /*
+ * Reset the device so that a si_find_dev_signature() would trigger.
+ * But this reset is an internal operation; the sata framework does
+ * not need to know about it.
+ */
+ (void) si_reset_dport_wait_till_ready(si_ctlp, si_portp, port,
+ SI_DEVICE_RESET|SI_RESET_NO_EVENTS_UP);
+
+ satadev->satadev_state = SATA_STATE_READY;
+
+ si_portp->siport_active = PORT_ACTIVE;
+
+ fill_dev_sregisters(si_ctlp, port, satadev);
+
+ mutex_exit(&si_portp->siport_mutex);
+ return (SATA_SUCCESS);
+}
+
+/*
+ * Called by sata framework to deactivate a port as part of hotplug.
+ *
+ * Note: Not port-mult aware.
+ */
+static int
+si_tran_hotplug_port_deactivate(dev_info_t *dip, sata_device_t *satadev)
+{
+ si_ctl_state_t *si_ctlp;
+ si_port_state_t *si_portp;
+ uint8_t port;
+
+ si_ctlp = ddi_get_soft_state(si_statep, ddi_get_instance(dip));
+ port = satadev->satadev_addr.cport;
+ mutex_enter(&si_ctlp->sictl_mutex);
+ si_portp = si_ctlp->sictl_ports[port];
+ mutex_exit(&si_ctlp->sictl_mutex);
+
+ SIDBG0(SIDBG_ENTRY, NULL, "si_tran_hotplug_port_deactivate entry");
+
+ mutex_enter(&si_portp->siport_mutex);
+ if (si_portp->siport_pending_tags & SI_SLOT_MASK) {
+ /*
+ * There are pending commands on this port.
+ * Fail the deactivate request.
+ */
+ satadev->satadev_state = SATA_STATE_READY;
+ mutex_exit(&si_portp->siport_mutex);
+ return (SATA_FAILURE);
+ }
+
+ /* mark the device as not accessible any more. */
+ si_portp->siport_active = PORT_INACTIVE;
+
+ /* disable the interrupts on the port. */
+ si_disable_port_interrupts(si_ctlp, port);
+
+ satadev->satadev_state = SATA_PSTATE_SHUTDOWN;
+
+ fill_dev_sregisters(si_ctlp, port, satadev);
+ /*
+ * Since we are implementing the port deactivation in software only,
+ * we need to fake a valid value for sstatus.
+ */
+ SSTATUS_SET_DET(satadev->satadev_scr.sstatus, SSTATUS_DET_PHYOFFLINE);
+ SSTATUS_SET_IPM(satadev->satadev_scr.sstatus, SSTATUS_IPM_NODEV_NOPHY);
+
+ mutex_exit(&si_portp->siport_mutex);
+ return (SATA_SUCCESS);
+}
+
+
+/*
+ * Allocates the si_port_state_t.
+ */
+static int
+si_alloc_port_state(si_ctl_state_t *si_ctlp, int port)
+{
+ si_port_state_t *si_portp;
+
+ si_ctlp->sictl_ports[port] = (si_port_state_t *)kmem_zalloc(
+ sizeof (si_port_state_t), KM_SLEEP);
+
+ si_portp = si_ctlp->sictl_ports[port];
+ mutex_init(&si_portp->siport_mutex, NULL, MUTEX_DRIVER,
+ (void *)(uint64_t)si_ctlp->sictl_intr_pri);
+ mutex_init(&si_portp->siport_mop_mutex, NULL, MUTEX_DRIVER,
+ (void *)(uint64_t)si_ctlp->sictl_intr_pri);
+ mutex_enter(&si_portp->siport_mutex);
+
+ /* allocate prb & sgt pkts for this port. */
+ if (si_alloc_prbpool(si_ctlp, port)) {
+ mutex_exit(&si_portp->siport_mutex);
+ kmem_free(si_ctlp->sictl_ports[port], sizeof (si_port_state_t));
+ return (SI_FAILURE);
+ }
+ if (si_alloc_sgbpool(si_ctlp, port)) {
+ si_dealloc_prbpool(si_ctlp, port);
+ mutex_exit(&si_portp->siport_mutex);
+ kmem_free(si_ctlp->sictl_ports[port], sizeof (si_port_state_t));
+ return (SI_FAILURE);
+ }
+
+ si_portp->siport_active = PORT_ACTIVE;
+ mutex_exit(&si_portp->siport_mutex);
+
+ return (SI_SUCCESS);
+
+}
+
+/*
+ * Deallocates the si_port_state_t.
+ */
+static void
+si_dealloc_port_state(si_ctl_state_t *si_ctlp, int port)
+{
+ si_port_state_t *si_portp;
+ si_portp = si_ctlp->sictl_ports[port];
+
+ mutex_enter(&si_portp->siport_mutex);
+ si_dealloc_sgbpool(si_ctlp, port);
+ si_dealloc_prbpool(si_ctlp, port);
+ mutex_exit(&si_portp->siport_mutex);
+
+ mutex_destroy(&si_portp->siport_mutex);
+ mutex_destroy(&si_portp->siport_mop_mutex);
+
+ kmem_free(si_ctlp->sictl_ports[port], sizeof (si_port_state_t));
+
+}
+
+/*
+ * Allocates the SGB (Scatter Gather Block) incore buffer.
+ */
+static int
+si_alloc_sgbpool(si_ctl_state_t *si_ctlp, int port)
+{
+ si_port_state_t *si_portp;
+ uint_t cookie_count;
+ size_t incore_sgbpool_size = SI_NUM_SLOTS * sizeof (si_sgblock_t);
+ size_t ret_len;
+ ddi_dma_cookie_t sgbpool_dma_cookie;
+
+ si_portp = si_ctlp->sictl_ports[port];
+
+ /* allocate sgbpool dma handle. */
+ if (ddi_dma_alloc_handle(si_ctlp->sictl_devinfop,
+ &prb_sgt_dma_attr,
+ DDI_DMA_SLEEP,
+ NULL,
+ &si_portp->siport_sgbpool_dma_handle) !=
+ DDI_SUCCESS) {
+
+ return (SI_FAILURE);
+ }
+
+ /* allocate the memory for sgbpool. */
+ if (ddi_dma_mem_alloc(si_portp->siport_sgbpool_dma_handle,
+ incore_sgbpool_size,
+ &accattr,
+ DDI_DMA_RDWR | DDI_DMA_CONSISTENT,
+ DDI_DMA_SLEEP,
+ NULL,
+ (caddr_t *)&si_portp->siport_sgbpool,
+ &ret_len,
+ &si_portp->siport_sgbpool_acc_handle) != NULL) {
+
+ /* error.. free the dma handle. */
+ ddi_dma_free_handle(&si_portp->siport_sgbpool_dma_handle);
+ return (SI_FAILURE);
+ }
+
+ /* now bind it */
+ if (ddi_dma_addr_bind_handle(si_portp->siport_sgbpool_dma_handle,
+ NULL,
+ (caddr_t)si_portp->siport_sgbpool,
+ incore_sgbpool_size,
+ DDI_DMA_CONSISTENT,
+ DDI_DMA_SLEEP,
+ NULL,
+ &sgbpool_dma_cookie,
+ &cookie_count) != DDI_DMA_MAPPED) {
+ /* error.. free the dma handle & free the memory. */
+ ddi_dma_mem_free(&si_portp->siport_sgbpool_acc_handle);
+ ddi_dma_free_handle(&si_portp->siport_sgbpool_dma_handle);
+ return (SI_FAILURE);
+ }
+
+ si_portp->siport_sgbpool_physaddr = sgbpool_dma_cookie.dmac_laddress;
+ return (SI_SUCCESS);
+}
+
+/*
+ * Deallocates the SGB (Scatter Gather Block) incore buffer.
+ */
+static void
+si_dealloc_sgbpool(si_ctl_state_t *si_ctlp, int port)
+{
+ si_port_state_t *si_portp = si_ctlp->sictl_ports[port];
+
+ /* Unbind the dma handle first. */
+ (void) ddi_dma_unbind_handle(si_portp->siport_sgbpool_dma_handle);
+
+ /* Then free the underlying memory. */
+ ddi_dma_mem_free(&si_portp->siport_sgbpool_acc_handle);
+
+ /* Now free the handle itself. */
+ ddi_dma_free_handle(&si_portp->siport_sgbpool_dma_handle);
+
+}
+
+/*
+ * Allocates the PRB (Port Request Block) incore packets.
+ */
+static int
+si_alloc_prbpool(si_ctl_state_t *si_ctlp, int port)
+{
+ si_port_state_t *si_portp;
+ uint_t cookie_count;
+ size_t incore_pkt_size = SI_NUM_SLOTS * sizeof (si_prb_t);
+ size_t ret_len;
+ ddi_dma_cookie_t prbpool_dma_cookie;
+
+ si_portp = si_ctlp->sictl_ports[port];
+
+ /* allocate prb pkts. */
+ if (ddi_dma_alloc_handle(si_ctlp->sictl_devinfop,
+ &prb_sgt_dma_attr,
+ DDI_DMA_SLEEP,
+ NULL,
+ &si_portp->siport_prbpool_dma_handle) !=
+ DDI_SUCCESS) {
+
+ return (SI_FAILURE);
+ }
+
+ if (ddi_dma_mem_alloc(si_portp->siport_prbpool_dma_handle,
+ incore_pkt_size,
+ &accattr,
+ DDI_DMA_RDWR | DDI_DMA_CONSISTENT,
+ DDI_DMA_SLEEP,
+ NULL,
+ (caddr_t *)&si_portp->siport_prbpool,
+ &ret_len,
+ &si_portp->siport_prbpool_acc_handle) != NULL) {
+
+ /* error.. free the dma handle. */
+ ddi_dma_free_handle(&si_portp->siport_prbpool_dma_handle);
+ return (SI_FAILURE);
+ }
+
+ if (ddi_dma_addr_bind_handle(si_portp->siport_prbpool_dma_handle,
+ NULL,
+ (caddr_t)si_portp->siport_prbpool,
+ incore_pkt_size,
+ DDI_DMA_CONSISTENT,
+ DDI_DMA_SLEEP,
+ NULL,
+ &prbpool_dma_cookie,
+ &cookie_count) != DDI_DMA_MAPPED) {
+ /* error.. free the dma handle & free the memory. */
+ ddi_dma_mem_free(&si_portp->siport_prbpool_acc_handle);
+ ddi_dma_free_handle(&si_portp->siport_prbpool_dma_handle);
+ return (SI_FAILURE);
+ }
+
+ si_portp->siport_prbpool_physaddr =
+ prbpool_dma_cookie.dmac_laddress;
+ return (SI_SUCCESS);
+}
+
+/*
+ * Deallocates the PRB (Port Request Block) incore packets.
+ */
+static void
+si_dealloc_prbpool(si_ctl_state_t *si_ctlp, int port)
+{
+ si_port_state_t *si_portp = si_ctlp->sictl_ports[port];
+
+ /* Unbind the prb dma handle first. */
+ (void) ddi_dma_unbind_handle(si_portp->siport_prbpool_dma_handle);
+
+ /* Then free the underlying memory. */
+ ddi_dma_mem_free(&si_portp->siport_prbpool_acc_handle);
+
+ /* Now free the handle itself. */
+ ddi_dma_free_handle(&si_portp->siport_prbpool_dma_handle);
+
+}
+
+
+
+/*
+ * Soft-reset the port to find the signature of the device connected to
+ * the port.
+ */
+static void
+si_find_dev_signature(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port,
+ int pmp)
+{
+ si_prb_t *prb;
+ uint32_t slot_status, signature;
+ int slot, loop_count;
+
+ SIDBG2(SIDBG_ENTRY|SIDBG_INIT, si_ctlp,
+ "si_find_dev_signature enter: port: %x, pmp: %x",
+ port, pmp);
+
+ /* Build a Soft Reset PRB in host memory. */
+ mutex_enter(&si_portp->siport_mutex);
+
+ slot = si_claim_free_slot(si_ctlp, si_portp, port);
+ if (slot == -1) {
+ /* Empty slot could not be found. */
+ if (pmp != PORTMULT_CONTROL_PORT) {
+ /* We are behind port multiplier. */
+ si_portp->siport_portmult_state.sipm_port_type[pmp] =
+ PORT_TYPE_NODEV;
+ } else {
+ si_portp->siport_port_type = PORT_TYPE_NODEV;
+ }
+
+ mutex_exit(&si_portp->siport_mutex);
+ return;
+ }
+ prb = &si_portp->siport_prbpool[slot];
+ bzero((void *)prb, sizeof (si_prb_t));
+
+ SET_FIS_PMP(prb->prb_fis, pmp);
+ SET_PRB_CONTROL_SOFT_RESET(prb);
+
+#if SI_DEBUG
+ if (si_debug_flags & SIDBG_DUMP_PRB) {
+ char *ptr;
+ int j;
+
+ ptr = (char *)prb;
+ cmn_err(CE_WARN, "si_find_dev_signature, prb: ");
+ for (j = 0; j < (sizeof (si_prb_t)); j++) {
+ if (j%4 == 0) {
+ cmn_err(CE_WARN, "----");
+ }
+ cmn_err(CE_WARN, "%x ", ptr[j]);
+ }
+
+ }
+#endif /* SI_DEBUG */
+
+ /* deliver soft reset prb to empty slot. */
+ POST_PRB_ADDR(si_ctlp, si_portp, port, slot);
+
+ loop_count = 0;
+ /* Loop till the soft reset is finished. */
+ do {
+ slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
+
+ if (loop_count++ > SI_POLLRATE_SOFT_RESET) {
+ /* We are effectively timing out after 10 sec. */
+ break;
+ }
+
+ /* Wait for 10 millisec */
+#ifndef __lock_lint
+ delay(SI_10MS_TICKS);
+#endif /* __lock_lint */
+
+ } while (slot_status & SI_SLOT_MASK & (0x1 << slot));
+
+ SIDBG2(SIDBG_POLL_LOOP, si_ctlp,
+ "si_find_dev_signature: loop count: %d, slot_status: 0x%x",
+ loop_count, slot_status);
+
+ CLEAR_BIT(si_portp->siport_pending_tags, slot);
+
+ /* Read device signature from command slot. */
+ signature = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SIGNATURE_MSB(si_ctlp, port, slot)));
+ signature <<= 8;
+ signature |= (0xff & ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SIGNATURE_LSB(si_ctlp,
+ port, slot))));
+
+ SIDBG1(SIDBG_INIT, si_ctlp, "Device signature: 0x%x", signature);
+
+ if (signature == SI_SIGNATURE_PORT_MULTIPLIER) {
+
+ SIDBG2(SIDBG_INIT, si_ctlp,
+ "Found multiplier at cport: 0x%d, pmport: 0x%x",
+ port, pmp);
+
+ if (pmp != PORTMULT_CONTROL_PORT) {
+ /*
+ * It is wrong to chain a port multiplier behind
+ * another port multiplier.
+ */
+ si_portp->siport_portmult_state.sipm_port_type[pmp] =
+ PORT_TYPE_NODEV;
+ } else {
+ si_portp->siport_port_type = PORT_TYPE_MULTIPLIER;
+ mutex_exit(&si_portp->siport_mutex);
+ (void) si_enumerate_port_multiplier(si_ctlp,
+ si_portp, port);
+ mutex_enter(&si_portp->siport_mutex);
+ }
+ si_init_port(si_ctlp, port);
+
+ } else if (signature == SI_SIGNATURE_ATAPI) {
+ if (pmp != PORTMULT_CONTROL_PORT) {
+ /* We are behind port multiplier. */
+ si_portp->siport_portmult_state.sipm_port_type[pmp] =
+ PORT_TYPE_ATAPI;
+ } else {
+ si_portp->siport_port_type = PORT_TYPE_ATAPI;
+ si_init_port(si_ctlp, port);
+ }
+ SIDBG2(SIDBG_INIT, si_ctlp,
+ "Found atapi at : cport: %x, pmport: %x",
+ port, pmp);
+
+ } else if (signature == SI_SIGNATURE_DISK) {
+
+ if (pmp != PORTMULT_CONTROL_PORT) {
+ /* We are behind port multiplier. */
+ si_portp->siport_portmult_state.sipm_port_type[pmp] =
+ PORT_TYPE_DISK;
+ } else {
+ si_portp->siport_port_type = PORT_TYPE_DISK;
+ si_init_port(si_ctlp, port);
+ }
+ SIDBG2(SIDBG_INIT, si_ctlp,
+ "found disk at : cport: %x, pmport: %x",
+ port, pmp);
+
+ } else {
+ if (pmp != PORTMULT_CONTROL_PORT) {
+ /* We are behind port multiplier. */
+ si_portp->siport_portmult_state.sipm_port_type[pmp] =
+ PORT_TYPE_UNKNOWN;
+ } else {
+ si_portp->siport_port_type = PORT_TYPE_UNKNOWN;
+ }
+ SIDBG3(SIDBG_INIT, si_ctlp,
+ "Found unknown signature 0x%x at: port: %x, pmp: %x",
+ signature, port, pmp);
+ }
+
+ mutex_exit(&si_portp->siport_mutex);
+}
+
+
+/*
+ * Polls for the completion of the command. This is safe with both
+ * interrupts enabled or disabled.
+ */
+static void
+si_poll_cmd(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port,
+ int slot,
+ sata_pkt_t *satapkt)
+{
+ uint32_t slot_status;
+ int pkt_timeout_ticks;
+ uint32_t port_intr_status;
+ int in_panic = ddi_in_panic();
+
+ SIDBG1(SIDBG_ENTRY, si_ctlp, "si_poll_cmd entered: port: 0x%x", port);
+
+ pkt_timeout_ticks = drv_usectohz((clock_t)satapkt->satapkt_time *
+ 1000000);
+
+ mutex_enter(&si_portp->siport_mutex);
+
+ /* we start out with SATA_PKT_COMPLETED as the satapkt_reason */
+ satapkt->satapkt_reason = SATA_PKT_COMPLETED;
+
+ do {
+ slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
+
+ if (slot_status & SI_SLOT_MASK & (0x1 << slot)) {
+ if (in_panic) {
+ /*
+ * If we are in panic, we can't rely on
+ * timers; so, busy wait instead of delay().
+ */
+ mutex_exit(&si_portp->siport_mutex);
+ drv_usecwait(SI_1MS_USECS);
+ mutex_enter(&si_portp->siport_mutex);
+ } else {
+ mutex_exit(&si_portp->siport_mutex);
+#ifndef __lock_lint
+ delay(SI_1MS_TICKS);
+#endif /* __lock_lint */
+ mutex_enter(&si_portp->siport_mutex);
+ }
+ } else {
+ break;
+ }
+
+ pkt_timeout_ticks -= SI_1MS_TICKS;
+
+ } while (pkt_timeout_ticks > 0);
+
+ if (satapkt->satapkt_reason != SATA_PKT_COMPLETED) {
+ /* The si_mop_command() got to our packet before us */
+ goto poll_done;
+ }
+
+ /*
+ * Interrupts and timers may not be working properly in a crash dump
+ * situation; we may need to handle all the three conditions here:
+ * successful completion, packet failure and packet timeout.
+ */
+ if (IS_ATTENTION_RAISED(slot_status)) { /* error seen on port */
+
+ port_intr_status = ddi_get32(si_ctlp->sictl_global_acc_handle,
+ (uint32_t *)PORT_INTERRUPT_STATUS(si_ctlp, port));
+
+ SIDBG2(SIDBG_VERBOSE, si_ctlp,
+ "si_poll_cmd: port_intr_status: 0x%x, port: %x",
+ port_intr_status, port);
+
+ if (port_intr_status & INTR_COMMAND_ERROR) {
+ mutex_exit(&si_portp->siport_mutex);
+ (void) si_intr_command_error(si_ctlp, si_portp, port);
+ mutex_enter(&si_portp->siport_mutex);
+
+ goto poll_done;
+
+ /*
+ * Why do we need to call si_intr_command_error() ?
+ *
+ * Answer: Even if the current packet is not the
+ * offending command, we need to restart the stalled
+ * port; (may be, the interrupts are not working well
+ * in panic condition). The call to routine
+ * si_intr_command_error() will achieve that.
+ *
+ * What if the interrupts are working fine and the
+ * si_intr_command_error() gets called once more from
+ * interrupt context ?
+ *
+ * Answer: The second instance of routine
+ * si_intr_command_error() will not mop anything
+ * since the first error handler has already blown
+ * away the hardware pending queues through reset.
+ *
+ * Will the si_intr_command_error() hurt current
+ * packet ?
+ *
+ * Answer: No.
+ */
+ } else {
+ /* Ignore any non-error interrupts at this stage */
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_INTERRUPT_STATUS(si_ctlp,
+ port)),
+ port_intr_status & INTR_MASK);
+ }
+
+
+ } else if (slot_status & SI_SLOT_MASK & (0x1 << slot)) {
+ satapkt->satapkt_reason = SATA_PKT_TIMEOUT;
+ } /* else: the command completed successfully */
+
+ if ((satapkt->satapkt_cmd.satacmd_cmd_reg ==
+ SATAC_WRITE_FPDMA_QUEUED) ||
+ (satapkt->satapkt_cmd.satacmd_cmd_reg ==
+ SATAC_READ_FPDMA_QUEUED)) {
+ si_portp->siport_pending_ncq_count--;
+ }
+
+ CLEAR_BIT(si_portp->siport_pending_tags, slot);
+
+poll_done:
+ mutex_exit(&si_portp->siport_mutex);
+
+ /*
+ * tidbit: What is the interaction of abort with polling ?
+ * What happens if the current polled pkt is aborted in parallel ?
+ *
+ * Answer: Assuming that the si_mop_commands() completes ahead
+ * of polling, all it does is to set the satapkt_reason to
+ * SPKT_PKT_ABORTED. That would be fine with us.
+ *
+ * The same logic applies to reset interacting with polling.
+ */
+}
+
+
+/*
+ * Searches for and claims a free slot.
+ *
+ * Returns: SI_FAILURE if no slots found
+ * claimed slot number if successful
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+/*ARGSUSED*/
+static int
+si_claim_free_slot(si_ctl_state_t *si_ctlp, si_port_state_t *si_portp, int port)
+{
+ uint32_t free_slots;
+ int slot;
+
+ _NOTE(ASSUMING_PROTECTED(si_portp))
+
+ SIDBG1(SIDBG_ENTRY, si_ctlp,
+ "si_claim_free_slot entry: siport_pending_tags: %x",
+ si_portp->siport_pending_tags);
+
+ free_slots = (~si_portp->siport_pending_tags) & SI_SLOT_MASK;
+ slot = ddi_ffs(free_slots) - 1;
+ if (slot == -1) {
+ SIDBG0(SIDBG_VERBOSE, si_ctlp,
+ "si_claim_free_slot: no empty slots");
+ return (SI_FAILURE);
+ }
+
+ si_portp->siport_pending_tags |= (0x1 << slot);
+ SIDBG1(SIDBG_VERBOSE, si_ctlp, "si_claim_free_slot: found slot: 0x%x",
+ slot);
+ return (slot);
+}
+
+/*
+ * Builds the PRB for the sata packet and delivers it to controller.
+ *
+ * Returns:
+ * slot number if we can obtain a slot successfully
+ * otherwise, return SI_FAILURE
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+static int
+si_deliver_satapkt(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port,
+ sata_pkt_t *spkt)
+{
+ int slot;
+ si_prb_t *prb;
+ sata_cmd_t *cmd;
+ si_sge_t *sgep; /* scatter gather entry pointer */
+ si_sgt_t *sgtp; /* scatter gather table pointer */
+ si_sgblock_t *sgbp; /* scatter gather block pointer */
+ int i, j, cookie_index;
+ int ncookies;
+ int is_atapi = 0;
+ ddi_dma_cookie_t cookie;
+
+ _NOTE(ASSUMING_PROTECTED(si_portp))
+
+ slot = si_claim_free_slot(si_ctlp, si_portp, port);
+ if (slot == -1) {
+ return (SI_FAILURE);
+ }
+
+ if (spkt->satapkt_device.satadev_type == SATA_DTYPE_ATAPICD) {
+ is_atapi = 1;
+ }
+
+ if ((si_portp->siport_port_type == PORT_TYPE_NODEV) ||
+ !si_portp->siport_active) {
+ /*
+ * si_intr_phy_ready_change() may have rendered it to
+ * PORT_TYPE_NODEV. cfgadm operation may have rendered
+ * it inactive.
+ */
+ spkt->satapkt_reason = SATA_PKT_PORT_ERROR;
+ fill_dev_sregisters(si_ctlp, port, &spkt->satapkt_device);
+
+ return (SI_FAILURE);
+ }
+
+
+ prb = &(si_portp->siport_prbpool[slot]);
+ bzero((void *)prb, sizeof (si_prb_t));
+
+ cmd = &spkt->satapkt_cmd;
+
+ SIDBG4(SIDBG_ENTRY, si_ctlp,
+ "si_deliver_satpkt entry: cmd_reg: 0x%x, slot: 0x%x, \
+ port: %x, satapkt: %x",
+ cmd->satacmd_cmd_reg, slot, port, (uint32_t)(intptr_t)spkt);
+
+ /* Now fill the prb. */
+ if (is_atapi) {
+ if (spkt->satapkt_cmd.satacmd_flags == SATA_DIR_READ) {
+ SET_PRB_CONTROL_PKT_READ(prb);
+ } else if (spkt->satapkt_cmd.satacmd_flags == SATA_DIR_WRITE) {
+ SET_PRB_CONTROL_PKT_WRITE(prb);
+ }
+ }
+
+ SET_FIS_TYPE(prb->prb_fis, REGISTER_FIS_H2D);
+ if ((spkt->satapkt_device.satadev_addr.qual == SATA_ADDR_PMPORT) ||
+ (spkt->satapkt_device.satadev_addr.qual == SATA_ADDR_DPMPORT)) {
+ SET_FIS_PMP(prb->prb_fis,
+ spkt->satapkt_device.satadev_addr.pmport);
+ }
+ SET_FIS_CDMDEVCTL(prb->prb_fis, 1);
+ SET_FIS_COMMAND(prb->prb_fis, cmd->satacmd_cmd_reg);
+ SET_FIS_FEATURES(prb->prb_fis, cmd->satacmd_features_reg);
+ SET_FIS_SECTOR_COUNT(prb->prb_fis, cmd->satacmd_sec_count_lsb);
+
+ switch (cmd->satacmd_addr_type) {
+
+ case ATA_ADDR_LBA:
+ /* fallthru */
+
+ case ATA_ADDR_LBA28:
+ /* LBA[7:0] */
+ SET_FIS_SECTOR(prb->prb_fis, cmd->satacmd_lba_low_lsb);
+
+ /* LBA[15:8] */
+ SET_FIS_CYL_LOW(prb->prb_fis, cmd->satacmd_lba_mid_lsb);
+
+ /* LBA[23:16] */
+ SET_FIS_CYL_HI(prb->prb_fis, cmd->satacmd_lba_high_lsb);
+
+ /* LBA [27:24] (also called dev_head) */
+ SET_FIS_DEV_HEAD(prb->prb_fis, cmd->satacmd_device_reg);
+
+ break;
+
+ case ATA_ADDR_LBA48:
+ /* LBA[7:0] */
+ SET_FIS_SECTOR(prb->prb_fis, cmd->satacmd_lba_low_lsb);
+
+ /* LBA[15:8] */
+ SET_FIS_CYL_LOW(prb->prb_fis, cmd->satacmd_lba_mid_lsb);
+
+ /* LBA[23:16] */
+ SET_FIS_CYL_HI(prb->prb_fis, cmd->satacmd_lba_high_lsb);
+
+ /* LBA [31:24] */
+ SET_FIS_SECTOR_EXP(prb->prb_fis, cmd->satacmd_lba_low_msb);
+
+ /* LBA [39:32] */
+ SET_FIS_CYL_LOW_EXP(prb->prb_fis, cmd->satacmd_lba_mid_msb);
+
+ /* LBA [47:40] */
+ SET_FIS_CYL_HI_EXP(prb->prb_fis, cmd->satacmd_lba_high_msb);
+
+ /* Set dev_head */
+ SET_FIS_DEV_HEAD(prb->prb_fis, cmd->satacmd_device_reg);
+
+ /* Set the extended sector count and features */
+ SET_FIS_SECTOR_COUNT_EXP(prb->prb_fis,
+ cmd->satacmd_sec_count_msb);
+ SET_FIS_FEATURES_EXP(prb->prb_fis,
+ cmd->satacmd_features_reg_ext);
+
+ break;
+
+ }
+
+ if (cmd->satacmd_flags & SATA_QUEUED_CMD) {
+ /*
+ * For queued commands, the TAG for the sector count lsb is
+ * generated from current slot number.
+ */
+ SET_FIS_SECTOR_COUNT(prb->prb_fis, slot << 3);
+ }
+
+ if ((cmd->satacmd_cmd_reg == SATAC_WRITE_FPDMA_QUEUED) ||
+ (cmd->satacmd_cmd_reg == SATAC_READ_FPDMA_QUEUED)) {
+ si_portp->siport_pending_ncq_count++;
+ }
+
+ /* *** now fill the scatter gather list ******* */
+
+ if (is_atapi) { /* It is an ATAPI drive */
+ /* atapi command goes into sge0 */
+ bcopy(cmd->satacmd_acdb, &prb->prb_sge0, sizeof (si_sge_t));
+
+ /* Now fill sge1 with pointer to external SGT. */
+ if (spkt->satapkt_cmd.satacmd_num_dma_cookies) {
+ prb->prb_sge1.sge_addr =
+ si_portp->siport_sgbpool_physaddr +
+ slot*sizeof (si_sgblock_t);
+ SET_SGE_LNK(prb->prb_sge1);
+ } else {
+ SET_SGE_TRM(prb->prb_sge1);
+ }
+ } else {
+ /* Fill the sge0 */
+ if (spkt->satapkt_cmd.satacmd_num_dma_cookies) {
+ prb->prb_sge0.sge_addr =
+ si_portp->siport_sgbpool_physaddr +
+ slot*sizeof (si_sgblock_t);
+ SET_SGE_LNK(prb->prb_sge0);
+
+ } else {
+ SET_SGE_TRM(prb->prb_sge0);
+ }
+
+ /* sge1 is left empty in non-ATAPI case */
+ }
+
+ bzero(&si_portp->siport_sgbpool[slot], sizeof (si_sgblock_t));
+
+ ncookies = spkt->satapkt_cmd.satacmd_num_dma_cookies;
+ ASSERT(ncookies <= SI_MAX_SGL_LENGTH);
+
+ SIDBG1(SIDBG_COOKIES, si_ctlp, "total ncookies: %d", ncookies);
+ if (ncookies == 0) {
+ sgbp = &si_portp->siport_sgbpool[slot];
+ sgtp = &sgbp->sgb_sgt[0];
+ sgep = &sgtp->sgt_sge[0];
+
+ /* No cookies. Terminate the chain. */
+ SIDBG0(SIDBG_COOKIES, si_ctlp, "empty cookies: terminating.");
+
+ sgep->sge_addr_low = 0;
+ sgep->sge_addr_high = 0;
+ sgep->sge_data_count = 0;
+ SET_SGE_TRM((*sgep));
+
+ goto sgl_fill_done;
+ }
+
+ for (i = 0, cookie_index = 0, sgbp = &si_portp->siport_sgbpool[slot];
+ i < SI_MAX_SGT_TABLES_PER_PRB; i++) {
+
+ sgtp = &sgbp->sgb_sgt[i];
+
+ /* Now fill the first 3 entries of SGT in the loop below. */
+ for (j = 0, sgep = &sgtp->sgt_sge[0];
+ ((j < 3) && (cookie_index < ncookies-1));
+ j++, cookie_index++, sgep++) {
+ ASSERT(cookie_index < ncookies);
+ SIDBG2(SIDBG_COOKIES, si_ctlp,
+ "inner loop: cookie_index: %d, ncookies: %d",
+ cookie_index,
+ ncookies);
+ cookie = spkt->satapkt_cmd.
+ satacmd_dma_cookie_list[cookie_index];
+
+ sgep->sge_addr_low = cookie._dmu._dmac_la[0];
+ sgep->sge_addr_high = cookie._dmu._dmac_la[1];
+ sgep->sge_data_count = cookie.dmac_size;
+ }
+
+ /*
+ * If this happens to be the last cookie, we terminate it here.
+ * Otherwise, we link to next SGT.
+ */
+
+ if (cookie_index == ncookies-1) {
+ /* This is the last cookie. Terminate the chain. */
+ SIDBG2(SIDBG_COOKIES, si_ctlp,
+ "filling the last: cookie_index: %d, "
+ "ncookies: %d",
+ cookie_index,
+ ncookies);
+ cookie = spkt->satapkt_cmd.
+ satacmd_dma_cookie_list[cookie_index];
+
+ sgep->sge_addr_low = cookie._dmu._dmac_la[0];
+ sgep->sge_addr_high = cookie._dmu._dmac_la[1];
+ sgep->sge_data_count = cookie.dmac_size;
+ SET_SGE_TRM((*sgep));
+
+ break; /* we break the loop */
+
+ } else {
+ /* This is not the last one. So link it. */
+ SIDBG2(SIDBG_COOKIES, si_ctlp,
+ "linking SGT: cookie_index: %d, ncookies: %d",
+ cookie_index,
+ ncookies);
+ sgep->sge_addr = si_portp->siport_sgbpool_physaddr +
+ slot * sizeof (si_sgblock_t) +
+ (i+1) * sizeof (si_sgt_t);
+
+ SET_SGE_LNK((*sgep));
+ }
+
+ }
+
+ /* *** finished filling the scatter gather list ******* */
+
+sgl_fill_done:
+ /* Now remember the sata packet in siport_slot_pkts[]. */
+ si_portp->siport_slot_pkts[slot] = spkt;
+
+ /*
+ * We are overloading satapkt_hba_driver_private with
+ * watched_cycle count.
+ */
+ spkt->satapkt_hba_driver_private = (void *)(intptr_t)0;
+
+ if (is_atapi) {
+ /* program the packet_lenth if it is atapi device. */
+
+
+#ifdef ATAPI_2nd_PHASE
+ /*
+ * Framework needs to calculate the acdb_len based on
+ * identify packet data. This needs to be accomplished
+ * in second phase of the project.
+ */
+ ASSERT((cmd->satacmd_acdb_len == 12) ||
+ (cmd->satacmd_acdb_len == 16));
+ SIDBG1(SIDBG_VERBOSE, si_ctlp, "deliver: acdb_len: %d",
+ cmd->satacmd_acdb_len);
+
+ if (cmd->satacmd_acdb_len == 16) {
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
+ PORT_CONTROL_SET_BITS_PACKET_LEN);
+ } else {
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_CONTROL_CLEAR(si_ctlp, port),
+ PORT_CONTROL_CLEAR_BITS_PACKET_LEN);
+ }
+
+#else /* ATAPI_2nd_PHASE */
+ /* hard coding for now to 12 bytes */
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_CONTROL_CLEAR(si_ctlp, port),
+ PORT_CONTROL_CLEAR_BITS_PACKET_LEN);
+#endif /* ATAPI_2nd_PHASE */
+ }
+
+
+#if SI_DEBUG
+ if (si_debug_flags & SIDBG_DUMP_PRB) {
+ if (!(is_atapi && (prb->prb_sge0.sge_addr_low == 0))) {
+ /*
+ * Do not dump the atapi Test-Unit-Ready commands.
+ * The sd_media_watch spews too many of these.
+ */
+ int *ptr;
+ si_sge_t *tmpsgep;
+ int j;
+
+ ptr = (int *)prb;
+ cmn_err(CE_WARN, "si_deliver_satpkt prb: ");
+ for (j = 0; j < (sizeof (si_prb_t)/4); j++) {
+ cmn_err(CE_WARN, "%x ", ptr[j]);
+ }
+
+ cmn_err(CE_WARN,
+ "si_deliver_satpkt sgt: low, high, count link");
+ for (j = 0,
+ tmpsgep = (si_sge_t *)
+ &si_portp->siport_sgbpool[slot];
+ j < (sizeof (si_sgblock_t)/ sizeof (si_sge_t));
+ j++, tmpsgep++) {
+ ptr = (int *)tmpsgep;
+ cmn_err(CE_WARN, "%x %x %x %x",
+ ptr[0],
+ ptr[1],
+ ptr[2],
+ ptr[3]);
+ if (IS_SGE_TRM_SET((*tmpsgep))) {
+ break;
+ }
+
+ }
+ }
+
+ }
+#endif /* SI_DEBUG */
+
+ /* Deliver PRB */
+ POST_PRB_ADDR(si_ctlp, si_portp, port, slot);
+
+ return (slot);
+}
+
+/*
+ * Initialize the controller and set up driver data structures.
+ *
+ * This routine can be called from three separate cases: DDI_ATTACH, PM_LEVEL_D0
+ * and DDI_RESUME. The DDI_ATTACH case is different from other two cases; the
+ * memory allocation & device signature probing are attempted only during
+ * DDI_ATTACH case. In the case of PM_LEVEL_D0 & DDI_RESUME, we are starting
+ * from a previously initialized state; so there is no need to allocate memory
+ * or to attempt probing the device signatures.
+ */
+static int
+si_initialize_controller(si_ctl_state_t *si_ctlp)
+{
+ uint32_t port_status;
+ uint32_t SStatus;
+ uint32_t SControl;
+ int port;
+ int loop_count = 0;
+ si_port_state_t *si_portp;
+
+ SIDBG0(SIDBG_INIT|SIDBG_ENTRY, si_ctlp,
+ "si3124: si_initialize_controller entered");
+
+ mutex_enter(&si_ctlp->sictl_mutex);
+
+ /* Remove the Global Reset. */
+ ddi_put32(si_ctlp->sictl_global_acc_handle,
+ (uint32_t *)GLOBAL_CONTROL_REG(si_ctlp),
+ GLOBAL_CONTROL_REG_BITS_CLEAR);
+
+ for (port = 0; port < si_ctlp->sictl_num_ports; port++) {
+
+ if (si_ctlp->sictl_flags & SI_ATTACH) {
+ /*
+ * We allocate the port state only during attach
+ * sequence. We don't want to do it during
+ * suspend/resume sequence.
+ */
+ if (si_alloc_port_state(si_ctlp, port)) {
+ mutex_exit(&si_ctlp->sictl_mutex);
+ return (SI_FAILURE);
+ }
+ }
+
+ si_portp = si_ctlp->sictl_ports[port];
+ mutex_enter(&si_portp->siport_mutex);
+
+ /* Clear Port Reset. */
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_CONTROL_CLEAR(si_ctlp, port),
+ PORT_CONTROL_CLEAR_BITS_PORT_RESET);
+
+ /*
+ * Arm the interrupts for: Cmd completion, Cmd error,
+ * Port Ready, PM Change, PhyRdyChange, Commwake,
+ * UnrecFIS, Devxchanged, SDBNotify.
+ */
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_INTERRUPT_ENABLE_SET(si_ctlp, port),
+ (INTR_COMMAND_COMPLETE |
+ INTR_COMMAND_ERROR |
+ INTR_PORT_READY |
+ INTR_POWER_CHANGE |
+ INTR_PHYRDY_CHANGE |
+ INTR_COMWAKE_RECEIVED |
+ INTR_UNRECOG_FIS |
+ INTR_DEV_XCHANGED |
+ INTR_SETDEVBITS_NOTIFY));
+
+ /* Now enable the interrupts. */
+ si_enable_port_interrupts(si_ctlp, port);
+
+ /*
+ * The following PHY initialization is redundant in
+ * in x86 since the BIOS anyway does this as part of
+ * device enumeration during the power up. But this
+ * is a required step in sparc since there is no BIOS.
+ *
+ * The way to initialize the PHY is to write a 1 and then
+ * a 0 to DET field of SControl register.
+ */
+
+ /*
+ * Fetch the current SControl before writing the
+ * DET part with 1
+ */
+ SControl = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_SCONTROL(si_ctlp, port));
+ SCONTROL_SET_DET(SControl, SCONTROL_DET_COMRESET);
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SCONTROL(si_ctlp, port)),
+ SControl);
+#ifndef __lock_lint
+ delay(SI_10MS_TICKS); /* give time for COMRESET to percolate */
+#endif /* __lock_lint */
+
+ /*
+ * Now fetch the SControl again and rewrite the
+ * DET part with 0
+ */
+ SControl = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_SCONTROL(si_ctlp, port));
+ SCONTROL_SET_DET(SControl, SCONTROL_DET_NOACTION);
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SCONTROL(si_ctlp, port)),
+ SControl);
+
+ /*
+ * PHY may be initialized by now. Check the DET field of
+ * SStatus to determine if there is a device present.
+ *
+ * The DET field is valid only if IPM field indicates that
+ * the interface is in active state.
+ */
+
+ loop_count = 0;
+ do {
+ SStatus = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_SSTATUS(si_ctlp, port));
+
+ if (SSTATUS_GET_IPM(SStatus) !=
+ SSTATUS_IPM_INTERFACE_ACTIVE) {
+ /*
+ * If the interface is not active, the DET field
+ * is considered not accurate. So we want to
+ * continue looping.
+ */
+ SSTATUS_SET_DET(SStatus,
+ SSTATUS_DET_NODEV_NOPHY);
+ }
+
+ if (loop_count++ > SI_POLLRATE_SSTATUS) {
+ /*
+ * We are effectively timing out after 0.1 sec.
+ */
+ break;
+ }
+
+ /* Wait for 10 millisec */
+#ifndef __lock_lint
+ delay(SI_10MS_TICKS);
+#endif /* __lock_lint */
+
+ } while (SSTATUS_GET_DET(SStatus) !=
+ SSTATUS_DET_DEVPRESENT_PHYONLINE);
+
+ SIDBG2(SIDBG_POLL_LOOP|SIDBG_INIT, si_ctlp,
+ "si_initialize_controller: 1st loop count: %d, "
+ "SStatus: 0x%x",
+ loop_count,
+ SStatus);
+
+ if ((SSTATUS_GET_IPM(SStatus) !=
+ SSTATUS_IPM_INTERFACE_ACTIVE) ||
+ (SSTATUS_GET_DET(SStatus) !=
+ SSTATUS_DET_DEVPRESENT_PHYONLINE)) {
+ /*
+ * Either the port is not active or there
+ * is no device present.
+ */
+ si_ctlp->sictl_ports[port]->siport_port_type =
+ PORT_TYPE_NODEV;
+ mutex_exit(&si_portp->siport_mutex);
+ continue;
+ }
+
+ /* Wait until Port Ready */
+ loop_count = 0;
+ do {
+ port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_STATUS(si_ctlp, port));
+
+ if (loop_count++ > SI_POLLRATE_PORTREADY) {
+ /*
+ * We are effectively timing out after 0.5 sec.
+ */
+ break;
+ }
+
+ /* Wait for 10 millisec */
+#ifndef __lock_lint
+ delay(SI_10MS_TICKS);
+#endif /* __lock_lint */
+
+ } while (!(port_status & PORT_STATUS_BITS_PORT_READY));
+
+ SIDBG1(SIDBG_POLL_LOOP|SIDBG_INIT, si_ctlp,
+ "si_initialize_controller: 2nd loop count: %d",
+ loop_count);
+
+ if (si_ctlp->sictl_flags & SI_ATTACH) {
+ /*
+ * We want to probe for dev signature only during attach
+ * case. Don't do it during suspend/resume sequence.
+ */
+ if (port_status & PORT_STATUS_BITS_PORT_READY) {
+ mutex_exit(&si_portp->siport_mutex);
+ si_find_dev_signature(si_ctlp, si_portp, port,
+ PORTMULT_CONTROL_PORT);
+ mutex_enter(&si_portp->siport_mutex);
+ } else {
+ si_ctlp->sictl_ports[port]->siport_port_type =
+ PORT_TYPE_NODEV;
+ }
+ }
+
+ mutex_exit(&si_portp->siport_mutex);
+ }
+
+ mutex_exit(&si_ctlp->sictl_mutex);
+ return (SI_SUCCESS);
+}
+
+/*
+ * Reverse of si_initialize_controller().
+ *
+ * WARNING, WARNING: The caller is expected to obtain the sictl_mutex
+ * before calling us.
+ */
+static void
+si_deinititalize_controller(si_ctl_state_t *si_ctlp)
+{
+ int port;
+
+ _NOTE(ASSUMING_PROTECTED(si_ctlp))
+
+ SIDBG0(SIDBG_INIT|SIDBG_ENTRY, si_ctlp,
+ "si3124: si_deinititalize_controller entered");
+
+ /* disable all the interrupts. */
+ si_disable_all_interrupts(si_ctlp);
+
+ if (si_ctlp->sictl_flags & SI_DETACH) {
+ /*
+ * We want to dealloc all the memory in detach case.
+ */
+ for (port = 0; port < si_ctlp->sictl_num_ports; port++) {
+ si_dealloc_port_state(si_ctlp, port);
+ }
+ }
+
+}
+
+/*
+ * Prepare the port ready for usage.
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+static void
+si_init_port(si_ctl_state_t *si_ctlp, int port)
+{
+
+ SIDBG1(SIDBG_ENTRY|SIDBG_INIT, si_ctlp,
+ "si_init_port entered: port: 0x%x",
+ port);
+
+ /* Initialize the port. */
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
+ PORT_CONTROL_SET_BITS_PORT_INITIALIZE);
+
+ /*
+ * Clear the InterruptNCOR (Interupt No Clear on Read).
+ * This step ensures that a mere reading of slot_status will clear
+ * the interrupt; no explicit clearing of interrupt condition
+ * will be needed for successful completion of commands.
+ */
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_CONTROL_CLEAR(si_ctlp, port),
+ PORT_CONTROL_CLEAR_BITS_INTR_NCoR);
+
+ /* clear any pending interrupts at this point */
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_INTERRUPT_STATUS(si_ctlp, port)),
+ INTR_MASK);
+
+}
+
+
+/*
+ * Enumerate the devices connected to the port multiplier.
+ * Once a device is detected, we call si_find_dev_signature()
+ * to find the type of device connected. Even though we are
+ * called from within si_find_dev_signature(), there is no
+ * recursion possible.
+ */
+static int
+si_enumerate_port_multiplier(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ uint32_t num_dev_ports = 0;
+ int pmport;
+ uint32_t SControl = 0;
+ uint32_t SStatus = 0;
+ uint32_t SError = 0;
+ int loop_count = 0;
+
+ SIDBG1(SIDBG_ENTRY|SIDBG_INIT, si_ctlp,
+ "si_enumerate_port_multiplier entered: port: %d",
+ port);
+
+ mutex_enter(&si_portp->siport_mutex);
+
+ /* Enable Port Multiplier context switching. */
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
+ PORT_CONTROL_SET_BITS_PM_ENABLE);
+
+ /*
+ * Read the num dev ports connected.
+ * GSCR[2] contains the number of device ports.
+ */
+ if (si_read_portmult_reg(si_ctlp, si_portp, port, PORTMULT_CONTROL_PORT,
+ PSCR_REG2, &num_dev_ports)) {
+ mutex_exit(&si_portp->siport_mutex);
+ return (SI_FAILURE);
+ }
+ si_portp->siport_portmult_state.sipm_num_ports = num_dev_ports;
+
+ SIDBG1(SIDBG_INIT, si_ctlp,
+ "si_enumerate_port_multiplier: ports found: %d",
+ num_dev_ports);
+
+ for (pmport = 0; pmport < num_dev_ports-1; pmport++) {
+ /*
+ * Enable PHY by writing a 1, then a 0 to SControl
+ * (i.e. PSCR[2]) DET field.
+ */
+ if (si_read_portmult_reg(si_ctlp, si_portp, port, pmport,
+ PSCR_REG2, &SControl)) {
+ continue;
+ }
+
+ /* First write a 1 to DET field of SControl. */
+ SCONTROL_SET_DET(SControl, SCONTROL_DET_COMRESET);
+ if (si_write_portmult_reg(si_ctlp, si_portp, port, pmport,
+ PSCR_REG2, SControl)) {
+ continue;
+ }
+#ifndef __lock_lint
+ delay(SI_10MS_TICKS); /* give time for COMRESET to percolate */
+#endif /* __lock_lint */
+
+ /* Then write a 0 to the DET field of SControl. */
+ SCONTROL_SET_DET(SControl, SCONTROL_DET_NOACTION);
+ if (si_write_portmult_reg(si_ctlp, si_portp, port, pmport,
+ PSCR_REG2, SControl)) {
+ continue;
+ }
+
+ /* Wait for PHYRDY by polling SStatus (i.e. PSCR[0]). */
+ loop_count = 0;
+ do {
+ if (si_read_portmult_reg(si_ctlp, si_portp, port,
+ pmport, PSCR_REG0, &SStatus)) {
+ break;
+ }
+ SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
+ "looping for PHYRDY: SStatus: %x",
+ SStatus);
+
+ if (SSTATUS_GET_IPM(SStatus) !=
+ SSTATUS_IPM_INTERFACE_ACTIVE) {
+ /*
+ * If the interface is not active, the DET field
+ * is considered not accurate. So we want to
+ * continue looping.
+ */
+ SSTATUS_SET_DET(SStatus,
+ SSTATUS_DET_NODEV_NOPHY);
+ }
+
+ if (loop_count++ > SI_POLLRATE_SSTATUS) {
+ /*
+ * We are effectively timing out after 0.1 sec.
+ */
+ break;
+ }
+
+ /* Wait for 10 millisec */
+#ifndef __lock_lint
+ delay(SI_10MS_TICKS);
+#endif /* __lock_lint */
+
+ } while (SSTATUS_GET_DET(SStatus) !=
+ SSTATUS_DET_DEVPRESENT_PHYONLINE);
+
+ SIDBG2(SIDBG_POLL_LOOP, si_ctlp,
+ "si_enumerate_port_multiplier: "
+ "loop count: %d, SStatus: 0x%x",
+ loop_count,
+ SStatus);
+
+ if ((SSTATUS_GET_IPM(SStatus) ==
+ SSTATUS_IPM_INTERFACE_ACTIVE) &&
+ (SSTATUS_GET_DET(SStatus) ==
+ SSTATUS_DET_DEVPRESENT_PHYONLINE)) {
+ /* The interface is active and the device is present */
+ SIDBG1(SIDBG_INIT, si_ctlp,
+ "Status: %x, device exists",
+ SStatus);
+ /*
+ * Clear error bits in SError register (i.e. PSCR[1]
+ * by writing back error bits.
+ */
+ if (si_read_portmult_reg(si_ctlp, si_portp, port,
+ pmport, PSCR_REG1, &SError)) {
+ continue;
+ }
+ SIDBG1(SIDBG_INIT, si_ctlp,
+ "SError bits are: %x", SError);
+ if (si_write_portmult_reg(si_ctlp, si_portp, port,
+ pmport, PSCR_REG1, SError)) {
+ continue;
+ }
+
+ /* There exists a device. */
+ mutex_exit(&si_portp->siport_mutex);
+ si_find_dev_signature(si_ctlp, si_portp, port, pmport);
+ mutex_enter(&si_portp->siport_mutex);
+ }
+ }
+
+ mutex_exit(&si_portp->siport_mutex);
+
+ return (SI_SUCCESS);
+}
+
+
+/*
+ * Read a port multiplier register.
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+static int
+si_read_portmult_reg(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port,
+ int pmport,
+ int regnum,
+ uint32_t *regval)
+{
+ int slot;
+ si_prb_t *prb;
+ uint32_t *prb_word_ptr;
+ int i;
+ uint32_t slot_status;
+ int loop_count = 0;
+
+ _NOTE(ASSUMING_PROTECTED(si_portp))
+
+ SIDBG3(SIDBG_ENTRY, si_ctlp, "si_read_portmult_reg: port: %x,"
+ "pmport: %x, regnum: %x",
+ port, pmport, regnum);
+
+ slot = si_claim_free_slot(si_ctlp, si_portp, port);
+ if (slot == -1) {
+ return (SI_FAILURE);
+ }
+
+ prb = &(si_portp->siport_prbpool[slot]);
+ bzero((void *)prb, sizeof (si_prb_t));
+
+ /* Now fill the prb. */
+ SET_FIS_TYPE(prb->prb_fis, REGISTER_FIS_H2D);
+ SET_FIS_PMP(prb->prb_fis, PORTMULT_CONTROL_PORT);
+ SET_FIS_CDMDEVCTL(prb->prb_fis, 1);
+ SET_FIS_COMMAND(prb->prb_fis, SATAC_READ_PM_REG);
+
+ SET_FIS_DEV_HEAD(prb->prb_fis, pmport);
+ SET_FIS_FEATURES(prb->prb_fis, regnum);
+
+ /* no real data transfer is involved. */
+ SET_SGE_TRM(prb->prb_sge0);
+
+#if SI_DEBUG
+ if (si_debug_flags & SIDBG_DUMP_PRB) {
+ int *ptr;
+ int j;
+
+ ptr = (int *)prb;
+ cmn_err(CE_WARN, "read_port_mult_reg, prb: ");
+ for (j = 0; j < (sizeof (si_prb_t)/4); j++) {
+ cmn_err(CE_WARN, "%x ", ptr[j]);
+ }
+
+ }
+#endif /* SI_DEBUG */
+
+ /* Deliver PRB */
+ POST_PRB_ADDR(si_ctlp, si_portp, port, slot);
+
+ /* Loop till the command is finished. */
+ do {
+ slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
+
+ SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
+ "looping read_pm slot_status: 0x%x",
+ slot_status);
+
+ if (loop_count++ > SI_POLLRATE_SLOTSTATUS) {
+ /* We are effectively timing out after 0.5 sec. */
+ break;
+ }
+
+ /* Wait for 10 millisec */
+#ifndef __lock_lint
+ delay(SI_10MS_TICKS);
+#endif /* __lock_lint */
+
+ } while (slot_status & SI_SLOT_MASK & (0x1 << slot));
+
+ SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
+ "read_portmult_reg: loop count: %d",
+ loop_count);
+
+ CLEAR_BIT(si_portp->siport_pending_tags, slot);
+
+ /* Now inspect the port LRAM for the modified FIS. */
+ prb_word_ptr = (uint32_t *)prb;
+ for (i = 0; i < (sizeof (si_prb_t)/4); i++) {
+ prb_word_ptr[i] = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_LRAM(si_ctlp, port, slot)+i*4));
+ }
+
+ if (((GET_FIS_COMMAND(prb->prb_fis) & 0x1) != 0) ||
+ (GET_FIS_FEATURES(prb->prb_fis) != 0)) {
+ /* command failed. */
+ return (SI_FAILURE);
+ }
+
+ /* command succeeded. */
+ *regval = (GET_FIS_SECTOR_COUNT(prb->prb_fis) & 0xff) |
+ ((GET_FIS_SECTOR(prb->prb_fis) << 8) & 0xff00) |
+ ((GET_FIS_CYL_LOW(prb->prb_fis) << 16) & 0xff0000) |
+ ((GET_FIS_CYL_HI(prb->prb_fis) << 24) & 0xff000000);
+
+ return (SI_SUCCESS);
+}
+
+/*
+ * Write a port multiplier register.
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+static int
+si_write_portmult_reg(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port,
+ int pmport,
+ int regnum,
+ uint32_t regval)
+{
+ int slot;
+ si_prb_t *prb;
+ uint32_t *prb_word_ptr;
+ uint32_t slot_status;
+ int i;
+ int loop_count = 0;
+
+ _NOTE(ASSUMING_PROTECTED(si_portp))
+
+ SIDBG4(SIDBG_ENTRY, si_ctlp,
+ "si_write_portmult_reg: port: %x, pmport: %x,"
+ "regnum: %x, regval: %x",
+ port, pmport, regnum, regval);
+
+ slot = si_claim_free_slot(si_ctlp, si_portp, port);
+ if (slot == -1) {
+ return (SI_FAILURE);
+ }
+
+ prb = &(si_portp->siport_prbpool[slot]);
+ bzero((void *)prb, sizeof (si_prb_t));
+
+ /* Now fill the prb. */
+ SET_FIS_TYPE(prb->prb_fis, REGISTER_FIS_H2D);
+ SET_FIS_PMP(prb->prb_fis, PORTMULT_CONTROL_PORT);
+ SET_FIS_CDMDEVCTL(prb->prb_fis, 1);
+
+ SET_FIS_COMMAND(prb->prb_fis, SATAC_WRITE_PM_REG);
+ SET_FIS_DEV_HEAD(prb->prb_fis, pmport);
+ SET_FIS_FEATURES(prb->prb_fis, regnum);
+
+ SET_FIS_SECTOR_COUNT(prb->prb_fis, regval & 0xff);
+ SET_FIS_SECTOR(prb->prb_fis, (regval >> 8) & 0xff);
+ SET_FIS_CYL_LOW(prb->prb_fis, (regval >> 16) & 0xff);
+ SET_FIS_CYL_HI(prb->prb_fis, (regval >> 24) & 0xff);
+
+ /* no real data transfer is involved. */
+ SET_SGE_TRM(prb->prb_sge0);
+
+#if SI_DEBUG
+ if (si_debug_flags & SIDBG_DUMP_PRB) {
+ int *ptr;
+ int j;
+
+ ptr = (int *)prb;
+ cmn_err(CE_WARN, "read_port_mult_reg, prb: ");
+ for (j = 0; j < (sizeof (si_prb_t)/4); j++) {
+ cmn_err(CE_WARN, "%x ", ptr[j]);
+ }
+
+ }
+#endif /* SI_DEBUG */
+
+ /* Deliver PRB */
+ POST_PRB_ADDR(si_ctlp, si_portp, port, slot);
+
+ /* Loop till the command is finished. */
+ do {
+ slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
+
+ SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
+ "looping write_pmp slot_status: 0x%x",
+ slot_status);
+
+ if (loop_count++ > SI_POLLRATE_SLOTSTATUS) {
+ /* We are effectively timing out after 0.5 sec. */
+ break;
+ }
+
+ /* Wait for 10 millisec */
+#ifndef __lock_lint
+ delay(SI_10MS_TICKS);
+#endif /* __lock_lint */
+
+ } while (slot_status & SI_SLOT_MASK & (0x1 << slot));
+
+ SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
+ "write_portmult_reg: loop count: %d",
+ loop_count);
+
+ CLEAR_BIT(si_portp->siport_pending_tags, slot);
+
+ /* Now inspect the port LRAM for the modified FIS. */
+ prb_word_ptr = (uint32_t *)prb;
+ for (i = 0; i < (sizeof (si_prb_t)/4); i++) {
+ prb_word_ptr[i] = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_LRAM(si_ctlp, port, slot)+i*4));
+ }
+
+ if (((GET_FIS_COMMAND(prb->prb_fis) & 0x1) != 0) ||
+ (GET_FIS_FEATURES(prb->prb_fis) != 0)) {
+ /* command failed */
+ return (SI_FAILURE);
+ }
+
+ /* command succeeded */
+ return (SI_SUCCESS);
+}
+
+
+/*
+ * Set the auto sense data for ATAPI devices.
+ *
+ * Note: Currently the sense data is simulated; this code will be enhanced
+ * in second phase to fetch the real sense data from the atapi device.
+ */
+static void
+si_set_sense_data(sata_pkt_t *satapkt, int reason)
+{
+ struct scsi_extended_sense *sense;
+
+ sense = (struct scsi_extended_sense *)
+ satapkt->satapkt_cmd.satacmd_rqsense;
+ bzero(sense, sizeof (struct scsi_extended_sense));
+ sense->es_valid = 1; /* Valid sense */
+ sense->es_class = 7; /* Response code 0x70 - current err */
+ sense->es_key = 0;
+ sense->es_info_1 = 0;
+ sense->es_info_2 = 0;
+ sense->es_info_3 = 0;
+ sense->es_info_4 = 0;
+ sense->es_add_len = 6; /* Additional length */
+ sense->es_cmd_info[0] = 0;
+ sense->es_cmd_info[1] = 0;
+ sense->es_cmd_info[2] = 0;
+ sense->es_cmd_info[3] = 0;
+ sense->es_add_code = 0;
+ sense->es_qual_code = 0;
+
+ if ((reason == SATA_PKT_DEV_ERROR) || (reason == SATA_PKT_TIMEOUT)) {
+ sense->es_key = KEY_HARDWARE_ERROR;
+ }
+}
+
+
+/*
+ * Interrupt service handler. We loop through each of the ports to find
+ * if the interrupt belongs to any of them.
+ *
+ * Bulk of the interrupt handling is actually done out of subroutines
+ * like si_intr_command_complete() etc.
+ */
+/*ARGSUSED*/
+static uint_t
+si_intr(caddr_t arg1, caddr_t arg2)
+{
+
+ si_ctl_state_t *si_ctlp = (si_ctl_state_t *)arg1;
+ si_port_state_t *si_portp;
+ uint32_t global_intr_status;
+ uint32_t mask, port_intr_status;
+ int port;
+
+ global_intr_status = ddi_get32(si_ctlp->sictl_global_acc_handle,
+ (uint32_t *)GLOBAL_INTERRUPT_STATUS(si_ctlp));
+
+ SIDBG1(SIDBG_INTR|SIDBG_ENTRY, si_ctlp,
+ "si_intr: global_int_status: 0x%x",
+ global_intr_status);
+
+ if (!(global_intr_status & SI31xx_INTR_PORT_MASK)) {
+ /* Sorry, the interrupt is not ours. */
+ return (DDI_INTR_UNCLAIMED);
+ }
+
+ /* Loop for all the ports. */
+ for (port = 0; port < si_ctlp->sictl_num_ports; port++) {
+
+ mask = 0x1 << port;
+ if (!(global_intr_status & mask)) {
+ continue;
+ }
+
+ mutex_enter(&si_ctlp->sictl_mutex);
+ si_portp = si_ctlp->sictl_ports[port];
+ mutex_exit(&si_ctlp->sictl_mutex);
+
+ port_intr_status = ddi_get32(si_ctlp->sictl_global_acc_handle,
+ (uint32_t *)PORT_INTERRUPT_STATUS(si_ctlp, port));
+
+ SIDBG2(SIDBG_VERBOSE, si_ctlp,
+ "s_intr: port_intr_status: 0x%x, port: %x",
+ port_intr_status,
+ port);
+
+ if (port_intr_status & INTR_COMMAND_COMPLETE) {
+ (void) si_intr_command_complete(si_ctlp, si_portp,
+ port);
+ }
+
+ /* Clear the interrupts */
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_INTERRUPT_STATUS(si_ctlp, port)),
+ port_intr_status & INTR_MASK);
+
+ /*
+ * Note that we did not clear the interrupt for command
+ * completion interrupt. Reading of slot_status takes care
+ * of clearing the interrupt for command completion case.
+ */
+
+ if (port_intr_status & INTR_COMMAND_ERROR) {
+ (void) si_intr_command_error(si_ctlp, si_portp, port);
+ }
+
+ if (port_intr_status & INTR_PORT_READY) {
+ (void) si_intr_port_ready(si_ctlp, si_portp, port);
+ }
+
+ if (port_intr_status & INTR_POWER_CHANGE) {
+ (void) si_intr_pwr_change(si_ctlp, si_portp, port);
+ }
+
+ if (port_intr_status & INTR_PHYRDY_CHANGE) {
+ (void) si_intr_phy_ready_change(si_ctlp, si_portp,
+ port);
+ }
+
+ if (port_intr_status & INTR_COMWAKE_RECEIVED) {
+ (void) si_intr_comwake_rcvd(si_ctlp, si_portp,
+ port);
+ }
+
+ if (port_intr_status & INTR_UNRECOG_FIS) {
+ (void) si_intr_unrecognised_fis(si_ctlp, si_portp,
+ port);
+ }
+
+ if (port_intr_status & INTR_DEV_XCHANGED) {
+ (void) si_intr_dev_xchanged(si_ctlp, si_portp, port);
+ }
+
+ if (port_intr_status & INTR_8B10B_DECODE_ERROR) {
+ (void) si_intr_decode_err_threshold(si_ctlp, si_portp,
+ port);
+ }
+
+ if (port_intr_status & INTR_CRC_ERROR) {
+ (void) si_intr_crc_err_threshold(si_ctlp, si_portp,
+ port);
+ }
+
+ if (port_intr_status & INTR_HANDSHAKE_ERROR) {
+ (void) si_intr_handshake_err_threshold(si_ctlp,
+ si_portp, port);
+ }
+
+ if (port_intr_status & INTR_SETDEVBITS_NOTIFY) {
+ (void) si_intr_set_devbits_notify(si_ctlp, si_portp,
+ port);
+ }
+ }
+
+ return (DDI_INTR_CLAIMED);
+}
+
+/*
+ * Interrupt which indicates that one or more commands have successfully
+ * completed.
+ *
+ * Since we disabled W1C (write-one-to-clear) previously, mere reading
+ * of slot_status register clears the interrupt. There is no need to
+ * explicitly clear the interrupt.
+ */
+static int
+si_intr_command_complete(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+
+ uint32_t slot_status;
+ uint32_t finished_tags;
+ int finished_slot;
+ sata_pkt_t *satapkt;
+
+ SIDBG0(SIDBG_ENTRY|SIDBG_INTR, si_ctlp,
+ "si_intr_command_complete enter");
+
+ mutex_enter(&si_portp->siport_mutex);
+
+ slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
+
+ if (!si_portp->siport_pending_tags) {
+ /*
+ * Spurious interrupt. Nothing to be done.
+ * Do read the slot_status to clear the interrupt.
+ */
+ mutex_exit(&si_portp->siport_mutex);
+ return (SI_SUCCESS);
+ }
+
+ SIDBG2(SIDBG_VERBOSE, si_ctlp, "si3124: si_intr_command_complete: "
+ "pending_tags: %x, slot_status: %x",
+ si_portp->siport_pending_tags,
+ slot_status);
+
+ finished_tags = si_portp->siport_pending_tags &
+ ~slot_status & SI_SLOT_MASK;
+ while (finished_tags) {
+ finished_slot = ddi_ffs(finished_tags) - 1;
+ if (finished_slot == -1) {
+ break;
+ }
+
+ satapkt = si_portp->siport_slot_pkts[finished_slot];
+
+ SENDUP_PACKET(si_portp, satapkt, SATA_PKT_COMPLETED);
+
+ CLEAR_BIT(si_portp->siport_pending_tags, finished_slot);
+ CLEAR_BIT(finished_tags, finished_slot);
+ }
+
+ SIDBG2(SIDBG_PKTCOMP, si_ctlp,
+ "command_complete done: pend_tags: 0x%x, slot_status: 0x%x",
+ si_portp->siport_pending_tags,
+ slot_status);
+
+ /*
+ * tidbit: no need to clear the interrupt since reading of
+ * slot_status automatically clears the interrupt in the case
+ * of a successful command completion.
+ */
+
+ mutex_exit(&si_portp->siport_mutex);
+
+ return (SI_SUCCESS);
+}
+
+/*
+ * Interrupt which indicates that a command did not complete successfully.
+ *
+ * The port halts whenever a command error interrupt is received.
+ * The only way to restart it is to reset or reinitialize the port
+ * but such an operation throws away all the pending commands on
+ * the port.
+ *
+ * We reset the device and mop the commands on the port.
+ */
+static int
+si_intr_command_error(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ uint32_t command_error, slot_status;
+ uint32_t failed_tags;
+
+ command_error = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_COMMAND_ERROR(si_ctlp, port)));
+
+ SIDBG1(SIDBG_INTR|SIDBG_ENTRY, si_ctlp,
+ "si_intr_command_error: command_error: 0x%x",
+ command_error);
+
+ mutex_enter(&si_portp->siport_mutex);
+
+ /*
+ * Remember the slot_status since any of the recovery handler
+ * can blow it away with reset operation.
+ */
+ slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
+
+ si_log_error_message(si_ctlp, port, command_error);
+
+ switch (command_error) {
+
+ case CMD_ERR_DEVICEERRROR:
+ si_error_recovery_DEVICEERROR(si_ctlp, si_portp, port);
+ break;
+
+ case CMD_ERR_SDBERROR:
+ si_error_recovery_SDBERROR(si_ctlp, si_portp, port);
+ break;
+
+ case CMD_ERR_DATAFISERROR:
+ si_error_recovery_DATAFISERROR(si_ctlp, si_portp, port);
+ break;
+
+ case CMD_ERR_SENDFISERROR:
+ si_error_recovery_SENDFISERROR(si_ctlp, si_portp, port);
+ break;
+
+ default:
+ si_error_recovery_default(si_ctlp, si_portp, port);
+ break;
+
+ }
+
+ /*
+ * Compute the failed_tags by adding up the error tags.
+ *
+ * The siport_err_tags_SDBERROR and siport_err_tags_nonSDBERROR
+ * were filled in by the si_error_recovery_* routines.
+ */
+ failed_tags = si_portp->siport_pending_tags &
+ (si_portp->siport_err_tags_SDBERROR |
+ si_portp->siport_err_tags_nonSDBERROR);
+
+ SIDBG3(SIDBG_ERRS|SIDBG_INTR, si_ctlp, "si_intr_command_error: "
+ "err_tags_SDBERROR: 0x%x, "
+ "err_tags_nonSDBERRROR: 0x%x, "
+ "failed_tags: 0x%x",
+ si_portp->siport_err_tags_SDBERROR,
+ si_portp->siport_err_tags_nonSDBERROR,
+ failed_tags);
+
+ SIDBG2(SIDBG_ERRS|SIDBG_INTR, si_ctlp, "si3124: si_intr_command_error: "
+ "slot_status:0x%x, pending_tags: 0x%x",
+ slot_status,
+ si_portp->siport_pending_tags);
+
+ mutex_exit(&si_portp->siport_mutex);
+ si_mop_commands(si_ctlp,
+ si_portp,
+ port,
+ slot_status,
+ failed_tags,
+ 0, /* timedout_tags */
+ 0, /* aborting_tags */
+ 0); /* reset_tags */
+ mutex_enter(&si_portp->siport_mutex);
+
+ ASSERT(si_portp->siport_pending_tags == 0);
+
+ si_portp->siport_err_tags_SDBERROR = 0;
+ si_portp->siport_err_tags_nonSDBERROR = 0;
+
+ mutex_exit(&si_portp->siport_mutex);
+
+ return (SI_SUCCESS);
+}
+
+/*
+ * There is a subtle difference between errors on a normal port and
+ * a port-mult port. When an error happens on a normal port, the port
+ * is halted effectively until the port is reset or initialized.
+ * However, in port-mult port errors, port does not get halted since
+ * other non-error devices behind the port multiplier can still
+ * continue to operate. So we wait till all the commands are drained
+ * instead of resetting it right away.
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+static void
+si_recover_portmult_errors(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ uint32_t command_error, slot_status, port_status;
+ int failed_slot;
+ int loop_count = 0;
+
+ _NOTE(ASSUMING_PROTECTED(si_portp))
+
+ SIDBG1(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
+ "si_recover_portmult_errors: port: 0x%x",
+ port);
+
+ /* Resume the port */
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
+ PORT_CONTROL_SET_BITS_RESUME);
+
+ port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_STATUS(si_ctlp, port));
+
+ failed_slot = (port_status >> 16) & SI_NUM_SLOTS;
+ command_error = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_COMMAND_ERROR(si_ctlp, port)));
+
+ if (command_error == CMD_ERR_SDBERROR) {
+ si_portp->siport_err_tags_SDBERROR |= (0x1 << failed_slot);
+ } else {
+ si_portp->siport_err_tags_nonSDBERROR |= (0x1 << failed_slot);
+ }
+
+ /* Now we drain the pending commands. */
+ do {
+ slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
+
+ /*
+ * Since we have not yet returned DDI_INTR_CLAIMED,
+ * our interrupt handler is guaranteed not to be called again.
+ * So we need to check IS_ATTENTION_RAISED() for further
+ * decisions.
+ *
+ * This is a too big a delay for an interrupt context.
+ * But this is supposed to be a rare condition.
+ */
+
+ if (IS_ATTENTION_RAISED(slot_status)) {
+ /* Resume again */
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
+ PORT_CONTROL_SET_BITS_RESUME);
+
+ port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_STATUS(si_ctlp, port));
+ failed_slot = (port_status >> 16) & SI_NUM_SLOTS;
+ command_error = ddi_get32(
+ si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_COMMAND_ERROR(si_ctlp,
+ port)));
+ if (command_error == CMD_ERR_SDBERROR) {
+ si_portp->siport_err_tags_SDBERROR |=
+ (0x1 << failed_slot);
+ } else {
+ si_portp->siport_err_tags_nonSDBERROR |=
+ (0x1 << failed_slot);
+ }
+ }
+
+ if (loop_count++ > SI_POLLRATE_RECOVERPORTMULT) {
+ /* We are effectively timing out after 10 sec. */
+ break;
+ }
+
+ /* Wait for 10 millisec */
+#ifndef __lock_lint
+ delay(SI_10MS_TICKS);
+#endif /* __lock_lint */
+
+ } while (slot_status & SI_SLOT_MASK);
+
+ /*
+ * The above loop can be improved for 3132 since we could obtain the
+ * Port Multiplier Context of the device in error. Then we could
+ * do a better job in filtering out commands for the device in error.
+ * The loop could finish much earlier with such a logic.
+ */
+
+ /* Clear the RESUME bit. */
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_CONTROL_CLEAR(si_ctlp, port),
+ PORT_CONTROL_CLEAR_BITS_RESUME);
+
+}
+
+/*
+ * If we are connected to port multiplier, drain the non-failed devices.
+ * Otherwise, we initialize the port (which effectively fails all the
+ * pending commands in the hope that sd would retry them later).
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+static void
+si_error_recovery_DEVICEERROR(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ uint32_t port_status;
+ int failed_slot;
+
+ _NOTE(ASSUMING_PROTECTED(si_portp))
+
+ SIDBG1(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
+ "si_error_recovery_DEVICEERROR: port: 0x%x",
+ port);
+
+ if (si_portp->siport_port_type == PORT_TYPE_MULTIPLIER) {
+ si_recover_portmult_errors(si_ctlp, si_portp, port);
+ } else {
+ port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_STATUS(si_ctlp, port));
+ failed_slot = (port_status >> 16) & SI_NUM_SLOTS;
+ si_portp->siport_err_tags_nonSDBERROR |= (0x1 << failed_slot);
+ }
+
+ /* In either case (port-mult or not), we reinitialize the port. */
+ (void) si_initialize_port_wait_till_ready(si_ctlp, port);
+}
+
+/*
+ * Handle exactly like DEVICEERROR. Remember the tags with SDBERROR
+ * to perform read_log_ext on them later. SDBERROR means that the
+ * error was for an NCQ command.
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+static void
+si_error_recovery_SDBERROR(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ uint32_t port_status;
+ int failed_slot;
+
+ _NOTE(ASSUMING_PROTECTED(si_portp))
+
+ SIDBG1(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
+ "si3124: si_error_recovery_SDBERROR: port: 0x%x",
+ port);
+
+ if (si_portp->siport_port_type == PORT_TYPE_MULTIPLIER) {
+ si_recover_portmult_errors(si_ctlp, si_portp, port);
+ } else {
+ port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_STATUS(si_ctlp, port));
+ failed_slot = (port_status >> 16) & SI_NUM_SLOTS;
+ si_portp->siport_err_tags_SDBERROR |= (0x1 << failed_slot);
+ }
+
+ /* In either case (port-mult or not), we reinitialize the port. */
+ (void) si_initialize_port_wait_till_ready(si_ctlp, port);
+}
+
+/*
+ * Handle exactly like DEVICEERROR except resetting the port if there was
+ * an NCQ command on the port.
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+static void
+si_error_recovery_DATAFISERROR(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ uint32_t port_status;
+ int failed_slot;
+
+ _NOTE(ASSUMING_PROTECTED(si_portp))
+
+ SIDBG1(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
+ "si3124: si_error_recovery_DATAFISERROR: port: 0x%x",
+ port);
+
+ /* reset device if we were waiting for any ncq commands. */
+ if (si_portp->siport_pending_ncq_count) {
+ port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_STATUS(si_ctlp, port));
+ failed_slot = (port_status >> 16) & SI_NUM_SLOTS;
+ si_portp->siport_err_tags_nonSDBERROR |= (0x1 << failed_slot);
+ (void) si_reset_dport_wait_till_ready(si_ctlp, si_portp, port,
+ SI_DEVICE_RESET);
+ return;
+ }
+
+ /*
+ * If we don't have any ncq commands pending, the rest of
+ * the process is similar to the one for DEVICEERROR.
+ */
+ si_error_recovery_DEVICEERROR(si_ctlp, si_portp, port);
+}
+
+/*
+ * We handle just like DEVICERROR except that we reset the device instead
+ * of initializing the port.
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+static void
+si_error_recovery_SENDFISERROR(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ uint32_t port_status;
+ int failed_slot;
+
+ _NOTE(ASSUMING_PROTECTED(si_portp))
+
+ SIDBG1(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
+ "si3124: si_error_recovery_SENDFISERROR: port: 0x%x",
+ port);
+
+ if (si_portp->siport_port_type == PORT_TYPE_MULTIPLIER) {
+ si_recover_portmult_errors(si_ctlp, si_portp, port);
+ } else {
+ port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_STATUS(si_ctlp, port));
+ failed_slot = (port_status >> 16) & SI_NUM_SLOTS;
+ si_portp->siport_err_tags_nonSDBERROR |= (0x1 << failed_slot);
+ (void) si_reset_dport_wait_till_ready(si_ctlp, si_portp, port,
+ SI_DEVICE_RESET);
+ }
+}
+
+/*
+ * The default behavior for all other errors is to reset the device.
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+static void
+si_error_recovery_default(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ uint32_t port_status;
+ int failed_slot;
+
+ _NOTE(ASSUMING_PROTECTED(si_portp))
+
+ SIDBG1(SIDBG_ERRS|SIDBG_ENTRY, si_ctlp,
+ "si3124: si_error_recovery_default: port: 0x%x",
+ port);
+
+ port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_STATUS(si_ctlp, port));
+ failed_slot = (port_status >> 16) & SI_NUM_SLOTS;
+ si_portp->siport_err_tags_nonSDBERROR |= (0x1 << failed_slot);
+
+ (void) si_reset_dport_wait_till_ready(si_ctlp, si_portp, port,
+ SI_DEVICE_RESET);
+}
+
+/*
+ * Read Log Ext with PAGE 10 to retrieve the error for an NCQ command.
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+static uint8_t
+si_read_log_ext(si_ctl_state_t *si_ctlp, si_port_state_t *si_portp, int port)
+{
+ int slot;
+ si_prb_t *prb;
+ int i;
+ uint32_t slot_status;
+ int loop_count = 0;
+ uint32_t *prb_word_ptr;
+ uint8_t error;
+
+ _NOTE(ASSUMING_PROTECTED(si_portp))
+
+ SIDBG1(SIDBG_ENTRY|SIDBG_ERRS, si_ctlp,
+ "si_read_log_ext: port: %x", port);
+
+ slot = si_claim_free_slot(si_ctlp, si_portp, port);
+ if (slot == -1) {
+ return (0);
+ }
+
+ prb = &(si_portp->siport_prbpool[slot]);
+ bzero((void *)prb, sizeof (si_prb_t));
+
+ /* Now fill the prb */
+ SET_FIS_TYPE(prb->prb_fis, REGISTER_FIS_H2D);
+ SET_FIS_PMP(prb->prb_fis, PORTMULT_CONTROL_PORT);
+ SET_FIS_CDMDEVCTL(prb->prb_fis, 1);
+ SET_FIS_COMMAND(prb->prb_fis, SATAC_READ_LOG_EXT);
+ SET_FIS_SECTOR(prb->prb_fis, SATA_LOG_PAGE_10);
+
+ /* no real data transfer is involved */
+ SET_SGE_TRM(prb->prb_sge0);
+
+#if SI_DEBUG
+ if (si_debug_flags & SIDBG_DUMP_PRB) {
+ int *ptr;
+ int j;
+
+ ptr = (int *)prb;
+ cmn_err(CE_WARN, "read_port_mult_reg, prb: ");
+ for (j = 0; j < (sizeof (si_prb_t)/4); j++) {
+ cmn_err(CE_WARN, "%x ", ptr[j]);
+ }
+
+ }
+#endif /* SI_DEBUG */
+
+ /* Deliver PRB */
+ POST_PRB_ADDR(si_ctlp, si_portp, port, slot);
+
+ /* Loop till the command is finished. */
+ do {
+ slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
+
+ SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
+ "looping read_log_ext slot_status: 0x%x",
+ slot_status);
+
+ if (loop_count++ > SI_POLLRATE_SLOTSTATUS) {
+ /* We are effectively timing out after 0.5 sec. */
+ break;
+ }
+
+ /* Wait for 10 millisec */
+#ifndef __lock_lint
+ delay(SI_10MS_TICKS);
+#endif /* __lock_lint */
+
+ } while (slot_status & SI_SLOT_MASK & (0x1 << slot));
+
+ if (slot_status & SI_SLOT_MASK & (0x1 << slot)) {
+ /*
+ * If we fail with the READ LOG EXT command, we need to
+ * initialize the port to clear the slot_status register.
+ * We don't need to worry about any other valid commands
+ * being thrown away because we are already in recovery
+ * mode and READ LOG EXT is the only pending command.
+ */
+ (void) si_initialize_port_wait_till_ready(si_ctlp, port);
+ }
+
+ SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
+ "read_portmult_reg: loop count: %d",
+ loop_count);
+
+ /*
+ * The LRAM contains the the modified FIS.
+ * Read the modified FIS to obtain the Error.
+ */
+ prb_word_ptr = (uint32_t *)prb;
+ for (i = 0; i < (sizeof (si_prb_t)/4); i++) {
+ prb_word_ptr[i] = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_LRAM(si_ctlp, port, slot)+i*4));
+ }
+ error = GET_FIS_FEATURES(prb->prb_fis);
+
+ CLEAR_BIT(si_portp->siport_pending_tags, slot);
+
+ return (error);
+
+}
+
+/*
+ * Dump the error message to the log.
+ */
+static void
+si_log_error_message(si_ctl_state_t *si_ctlp, int port, uint32_t command_error)
+{
+ char *errstr;
+
+ switch (command_error) {
+
+ case CMD_ERR_DEVICEERRROR:
+ errstr = "Standard Error: Error bit set in register - device"
+ " to host FIS";
+ break;
+
+ case CMD_ERR_SDBERROR:
+ errstr = "NCQ Error: Error bit set in register - device"
+ " to host FIS";
+ break;
+
+ case CMD_ERR_DATAFISERROR:
+ errstr = "Error in data FIS not detected by device";
+ break;
+
+ case CMD_ERR_SENDFISERROR:
+ errstr = "Initial command FIS transmission failed";
+ break;
+
+ case CMD_ERR_INCONSISTENTSTATE:
+ errstr = "Inconsistency in protocol";
+ break;
+
+ case CMD_ERR_DIRECTIONERROR:
+ errstr = "DMA direction flag does not match the command";
+ break;
+
+ case CMD_ERR_UNDERRUNERROR:
+ errstr = "Run out of scatter gather entries while writing data";
+ break;
+
+ case CMD_ERR_OVERRUNERROR:
+ errstr = "Run out of scatter gather entries while reading data";
+ break;
+
+ case CMD_ERR_PACKETPROTOCOLERROR:
+ errstr = "Packet protocol error";
+ break;
+
+ case CMD_ERR_PLDSGTERRORBOUNDARY:
+ errstr = "Scatter/gather table not on quadword boundary";
+ break;
+
+ case CMD_ERR_PLDSGTERRORTARETABORT:
+ errstr = "PCI(X) Target abort while fetching scatter/gather"
+ " table";
+ break;
+
+ case CMD_ERR_PLDSGTERRORMASTERABORT:
+ errstr = "PCI(X) Master abort while fetching scatter/gather"
+ " table";
+ break;
+
+ case CMD_ERR_PLDSGTERRORPCIERR:
+ errstr = "PCI(X) parity error while fetching scatter/gather"
+ " table";
+ break;
+
+ case CMD_ERR_PLDCMDERRORBOUNDARY:
+ errstr = "PRB not on quadword boundary";
+ break;
+
+ case CMD_ERR_PLDCMDERRORTARGETABORT:
+ errstr = "PCI(X) Target abort while fetching PRB";
+ break;
+
+ case CMD_ERR_PLDCMDERRORMASTERABORT:
+ errstr = "PCI(X) Master abort while fetching PRB";
+ break;
+
+ case CMD_ERR_PLDCMDERORPCIERR:
+ errstr = "PCI(X) parity error while fetching PRB";
+ break;
+
+ case CMD_ERR_PSDERRORTARGETABORT:
+ errstr = "PCI(X) Target abort during data transfer";
+ break;
+
+ case CMD_ERR_PSDERRORMASTERABORT:
+ errstr = "PCI(X) Master abort during data transfer";
+ break;
+
+ case CMD_ERR_PSDERRORPCIERR:
+ errstr = "PCI(X) parity error during data transfer";
+ break;
+
+ case CMD_ERR_SENDSERVICEERROR:
+ errstr = "FIS received while sending service FIS in"
+ " legacy queuing operation";
+ break;
+
+ default:
+ errstr = "Unknown Error";
+ break;
+
+ }
+
+ SIDBG2(SIDBG_ERRS, si_ctlp,
+ "command error: port: 0x%x, error: %s",
+ port,
+ errstr);
+
+}
+
+
+/*
+ * Interrupt which indicates that the Port Ready state has changed
+ * from zero to one.
+ *
+ * We are not interested in this interrupt; we just log a debug message.
+ */
+/*ARGSUSED*/
+static int
+si_intr_port_ready(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_ready");
+ return (SI_SUCCESS);
+}
+
+/*
+ * Interrupt which indicates that the port power management state
+ * has been modified.
+ *
+ * We are not interested in this interrupt; we just log a debug message.
+ */
+/*ARGSUSED*/
+static int
+si_intr_pwr_change(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_pwr_change");
+ return (SI_SUCCESS);
+}
+
+/*
+ * Interrupt which indicates that the PHY sate has changed either from
+ * Not-Ready to Ready or from Ready to Not-Ready.
+ */
+static int
+si_intr_phy_ready_change(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ sata_device_t sdevice;
+ uint32_t SStatus = 0; /* No dev present & PHY not established. */
+ int dev_exists_now = 0;
+ int dev_existed_previously = 0;
+
+ SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_phy_rdy_change");
+
+ mutex_enter(&si_ctlp->sictl_mutex);
+ if ((si_ctlp->sictl_sata_hba_tran == NULL) || (si_portp == NULL)) {
+ /* the whole controller setup is not yet done. */
+ mutex_exit(&si_ctlp->sictl_mutex);
+ return (SI_SUCCESS);
+ }
+
+ mutex_exit(&si_ctlp->sictl_mutex);
+
+ mutex_enter(&si_portp->siport_mutex);
+
+ /* SStatus tells the presence of device. */
+ SStatus = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_SSTATUS(si_ctlp, port));
+ dev_exists_now =
+ (SSTATUS_GET_DET(SStatus) == SSTATUS_DET_DEVPRESENT_PHYONLINE);
+
+ if (si_portp->siport_port_type != PORT_TYPE_NODEV) {
+ dev_existed_previously = 1;
+ }
+
+ bzero((void *)&sdevice, sizeof (sata_device_t));
+ sdevice.satadev_addr.cport = port;
+ sdevice.satadev_addr.pmport = PORTMULT_CONTROL_PORT;
+
+ /* we don't have a way of determining the exact port-mult port. */
+ if (si_portp->siport_port_type == PORT_TYPE_MULTIPLIER) {
+ sdevice.satadev_addr.qual = SATA_ADDR_PMPORT;
+ } else {
+ sdevice.satadev_addr.qual = SATA_ADDR_CPORT;
+ }
+
+ sdevice.satadev_state = SATA_PSTATE_PWRON;
+
+ if (dev_exists_now) {
+ if (dev_existed_previously) {
+
+ /* Things are fine now. The loss was temporary. */
+ SIDBG0(SIDBG_INTR, NULL,
+ "phyrdy: doing BOTH EVENTS TOGETHER");
+ if (si_portp->siport_active) {
+ SIDBG0(SIDBG_EVENT, si_ctlp,
+ "sending event: LINK_LOST & "
+ "LINK_ESTABLISHED");
+
+ sata_hba_event_notify(
+ si_ctlp->sictl_sata_hba_tran->\
+ sata_tran_hba_dip,
+ &sdevice,
+ SATA_EVNT_LINK_LOST|
+ SATA_EVNT_LINK_ESTABLISHED);
+ }
+
+ } else {
+
+ /* A new device has been detected. */
+ mutex_exit(&si_portp->siport_mutex);
+ si_find_dev_signature(si_ctlp, si_portp, port,
+ PORTMULT_CONTROL_PORT);
+ mutex_enter(&si_portp->siport_mutex);
+ SIDBG0(SIDBG_INTR, NULL, "phyrdy: doing ATTACH event");
+ if (si_portp->siport_active) {
+ SIDBG0(SIDBG_EVENT, si_ctlp,
+ "sending event up: LINK_ESTABLISHED");
+
+ sata_hba_event_notify(
+ si_ctlp->sictl_sata_hba_tran->\
+ sata_tran_hba_dip,
+ &sdevice,
+ SATA_EVNT_LINK_ESTABLISHED);
+ }
+
+ }
+ } else { /* No device exists now */
+
+ if (dev_existed_previously) {
+
+ /* An existing device is lost. */
+ if (si_portp->siport_active) {
+ SIDBG0(SIDBG_EVENT, si_ctlp,
+ "sending event up: LINK_LOST");
+
+ sata_hba_event_notify(
+ si_ctlp->sictl_sata_hba_tran->
+ sata_tran_hba_dip,
+ &sdevice,
+ SATA_EVNT_LINK_LOST);
+ }
+ si_portp->siport_port_type = PORT_TYPE_NODEV;
+
+ } else {
+
+ /* spurious interrupt */
+ SIDBG0(SIDBG_INTR, NULL,
+ "spurious phy ready interrupt");
+ }
+ }
+
+ mutex_exit(&si_portp->siport_mutex);
+ return (SI_SUCCESS);
+}
+
+
+/*
+ * Interrupt which indicates that a COMWAKE OOB signal has been decoded
+ * on the receiver.
+ *
+ * We are not interested in this interrupt; we just log a debug message.
+ */
+/*ARGSUSED*/
+static int
+si_intr_comwake_rcvd(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_commwake_rcvd");
+ return (SI_SUCCESS);
+}
+
+/*
+ * Interrupt which indicates that the F-bit has been set in SError
+ * Diag field.
+ *
+ * We are not interested in this interrupt; we just log a debug message.
+ */
+/*ARGSUSED*/
+static int
+si_intr_unrecognised_fis(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_unrecognised_fis");
+ return (SI_SUCCESS);
+}
+
+/*
+ * Interrupt which indicates that the X-bit has been set in SError
+ * Diag field.
+ *
+ * We are not interested in this interrupt; we just log a debug message.
+ */
+/*ARGSUSED*/
+static int
+si_intr_dev_xchanged(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+
+ SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_dev_xchanged");
+ return (SI_SUCCESS);
+}
+
+/*
+ * Interrupt which indicates that the 8b/10 Decode Error counter has
+ * exceeded the programmed non-zero threshold value.
+ *
+ * We are not interested in this interrupt; we just log a debug message.
+ */
+/*ARGSUSED*/
+static int
+si_intr_decode_err_threshold(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_err_threshold");
+ return (SI_SUCCESS);
+}
+
+/*
+ * Interrupt which indicates that the CRC Error counter has exceeded the
+ * programmed non-zero threshold value.
+ *
+ * We are not interested in this interrupt; we just log a debug message.
+ */
+/*ARGSUSED*/
+static int
+si_intr_crc_err_threshold(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_crc_threshold");
+ return (SI_SUCCESS);
+}
+
+/*
+ * Interrupt which indicates that the Handshake Error counter has
+ * exceeded the programmed non-zero threshold value.
+ *
+ * We are not interested in this interrupt; we just log a debug message.
+ */
+/*ARGSUSED*/
+static int
+si_intr_handshake_err_threshold(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp,
+ "si_intr_handshake_err_threshold");
+ return (SI_SUCCESS);
+}
+
+/*
+ * Interrupt which indicates that a "Set Device Bits" FIS has been
+ * received with N-bit set in the control field.
+ *
+ * We are not interested in this interrupt; we just log a debug message.
+ */
+/*ARGSUSED*/
+static int
+si_intr_set_devbits_notify(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port)
+{
+ SIDBG0(SIDBG_INTR|SIDBG_ENTRY, si_ctlp, "si_intr_set_devbits_notify");
+ return (SI_SUCCESS);
+}
+
+
+/*
+ * Enable the interrupts for a particular port.
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+static void
+si_enable_port_interrupts(si_ctl_state_t *si_ctlp, int port)
+{
+ uint32_t mask;
+
+ /* get the current settings first. */
+ mask = ddi_get32(si_ctlp->sictl_global_acc_handle,
+ (uint32_t *)GLOBAL_CONTROL_REG(si_ctlp));
+
+ SIDBG1(SIDBG_INIT|SIDBG_ENTRY, si_ctlp,
+ "si_enable_port_interrupts: current mask: 0x%x",
+ mask);
+
+ /* enable the bit for current port. */
+ SET_BIT(mask, port);
+
+ /* now use this mask to enable the interrupt. */
+ ddi_put32(si_ctlp->sictl_global_acc_handle,
+ (uint32_t *)GLOBAL_CONTROL_REG(si_ctlp),
+ mask);
+}
+
+/*
+ * Enable interrupts for all the ports.
+ */
+static void
+si_enable_all_interrupts(si_ctl_state_t *si_ctlp)
+{
+ int port;
+
+ for (port = 0; port < si_ctlp->sictl_num_ports; port++) {
+ si_enable_port_interrupts(si_ctlp, port);
+ }
+}
+
+/*
+ * Disable interrupts for a particular port.
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+static void
+si_disable_port_interrupts(si_ctl_state_t *si_ctlp, int port)
+{
+ uint32_t mask;
+
+ /* get the current settings first. */
+ mask = ddi_get32(si_ctlp->sictl_global_acc_handle,
+ (uint32_t *)GLOBAL_CONTROL_REG(si_ctlp));
+
+ /* clear the bit for current port. */
+ CLEAR_BIT(mask, port);
+
+ /* now use this mask to disable the interrupt. */
+ ddi_put32(si_ctlp->sictl_global_acc_handle,
+ (uint32_t *)GLOBAL_CONTROL_REG(si_ctlp),
+ mask);
+
+}
+
+/*
+ * Disable interrupts for all the ports.
+ */
+static void
+si_disable_all_interrupts(si_ctl_state_t *si_ctlp)
+{
+ int port;
+
+ for (port = 0; port < si_ctlp->sictl_num_ports; port++) {
+ si_disable_port_interrupts(si_ctlp, port);
+ }
+}
+
+/*
+ * Fetches the latest sstatus, scontrol, serror, sactive registers
+ * and stuffs them into sata_device_t structure.
+ */
+static void
+fill_dev_sregisters(si_ctl_state_t *si_ctlp, int port, sata_device_t *satadev)
+{
+ satadev->satadev_scr.sstatus = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SSTATUS(si_ctlp, port)));
+ satadev->satadev_scr.serror = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SERROR(si_ctlp, port)));
+ satadev->satadev_scr.sactive = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SACTIVE(si_ctlp, port)));
+ satadev->satadev_scr.scontrol =
+ ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SCONTROL(si_ctlp, port)));
+
+}
+
+/*
+ * si_add_legacy_intrs() handles INTx and legacy interrupts.
+ */
+static int
+si_add_legacy_intrs(si_ctl_state_t *si_ctlp)
+{
+ dev_info_t *devinfo = si_ctlp->sictl_devinfop;
+ int actual, count = 0;
+ int x, y, rc, inum = 0;
+
+ SIDBG0(SIDBG_ENTRY, si_ctlp, "si_add_legacy_intrs");
+
+ /* get number of interrupts. */
+ rc = ddi_intr_get_nintrs(devinfo, DDI_INTR_TYPE_FIXED, &count);
+ if ((rc != DDI_SUCCESS) || (count == 0)) {
+ SIDBG2(SIDBG_INTR|SIDBG_INIT, si_ctlp,
+ "ddi_intr_get_nintrs() failed, "
+ "rc %d count %d\n", rc, count);
+ return (DDI_FAILURE);
+ }
+
+ /* Allocate an array of interrupt handles. */
+ si_ctlp->sictl_intr_size = count * sizeof (ddi_intr_handle_t);
+ si_ctlp->sictl_htable = kmem_zalloc(si_ctlp->sictl_intr_size, KM_SLEEP);
+
+ /* call ddi_intr_alloc(). */
+ rc = ddi_intr_alloc(devinfo, si_ctlp->sictl_htable, DDI_INTR_TYPE_FIXED,
+ inum, count, &actual, DDI_INTR_ALLOC_STRICT);
+
+ if ((rc != DDI_SUCCESS) || (actual == 0)) {
+ SIDBG1(SIDBG_INTR|SIDBG_INIT, si_ctlp,
+ "ddi_intr_alloc() failed, rc %d\n", rc);
+ kmem_free(si_ctlp->sictl_htable, si_ctlp->sictl_intr_size);
+ return (DDI_FAILURE);
+ }
+
+ if (actual < count) {
+ SIDBG2(SIDBG_INTR|SIDBG_INIT, si_ctlp,
+ "Requested: %d, Received: %d", count, actual);
+
+ for (x = 0; x < actual; x++) {
+ (void) ddi_intr_free(si_ctlp->sictl_htable[x]);
+ }
+
+ kmem_free(si_ctlp->sictl_htable, si_ctlp->sictl_intr_size);
+ return (DDI_FAILURE);
+ }
+
+ si_ctlp->sictl_intr_cnt = actual;
+
+ /* Get intr priority. */
+ if (ddi_intr_get_pri(si_ctlp->sictl_htable[0],
+ &si_ctlp->sictl_intr_pri) != DDI_SUCCESS) {
+ SIDBG0(SIDBG_INTR|SIDBG_INIT, si_ctlp,
+ "ddi_intr_get_pri() failed");
+
+ for (x = 0; x < actual; x++) {
+ (void) ddi_intr_free(si_ctlp->sictl_htable[x]);
+ }
+
+ kmem_free(si_ctlp->sictl_htable, si_ctlp->sictl_intr_size);
+ return (DDI_FAILURE);
+ }
+
+ /* Test for high level mutex. */
+ if (si_ctlp->sictl_intr_pri >= ddi_intr_get_hilevel_pri()) {
+ SIDBG0(SIDBG_INTR|SIDBG_INIT, si_ctlp,
+ "si_add_legacy_intrs: Hi level intr not supported");
+
+ for (x = 0; x < actual; x++) {
+ (void) ddi_intr_free(si_ctlp->sictl_htable[x]);
+ }
+
+ kmem_free(si_ctlp->sictl_htable, sizeof (ddi_intr_handle_t));
+
+ return (DDI_FAILURE);
+ }
+
+ /* Call ddi_intr_add_handler(). */
+ for (x = 0; x < actual; x++) {
+ if (ddi_intr_add_handler(si_ctlp->sictl_htable[x], si_intr,
+ (caddr_t)si_ctlp, NULL) != DDI_SUCCESS) {
+ SIDBG0(SIDBG_INTR|SIDBG_INIT, si_ctlp,
+ "ddi_intr_add_handler() failed");
+
+ for (y = 0; y < actual; y++) {
+ (void) ddi_intr_free(si_ctlp->sictl_htable[y]);
+ }
+
+ kmem_free(si_ctlp->sictl_htable,
+ si_ctlp->sictl_intr_size);
+ return (DDI_FAILURE);
+ }
+ }
+
+ /* Call ddi_intr_enable() for legacy interrupts. */
+ for (x = 0; x < si_ctlp->sictl_intr_cnt; x++) {
+ (void) ddi_intr_enable(si_ctlp->sictl_htable[x]);
+ }
+
+ return (DDI_SUCCESS);
+}
+
+/*
+ * si_add_msictl_intrs() handles MSI interrupts.
+ */
+static int
+si_add_msi_intrs(si_ctl_state_t *si_ctlp)
+{
+ dev_info_t *devinfo = si_ctlp->sictl_devinfop;
+ int count, avail, actual;
+ int x, y, rc, inum = 0;
+
+ SIDBG0(SIDBG_ENTRY|SIDBG_INIT, si_ctlp, "si_add_msi_intrs");
+
+ /* get number of interrupts. */
+ rc = ddi_intr_get_nintrs(devinfo, DDI_INTR_TYPE_MSI, &count);
+ if ((rc != DDI_SUCCESS) || (count == 0)) {
+ SIDBG2(SIDBG_INIT, si_ctlp,
+ "ddi_intr_get_nintrs() failed, "
+ "rc %d count %d\n", rc, count);
+ return (DDI_FAILURE);
+ }
+
+ /* get number of available interrupts. */
+ rc = ddi_intr_get_navail(devinfo, DDI_INTR_TYPE_MSI, &avail);
+ if ((rc != DDI_SUCCESS) || (avail == 0)) {
+ SIDBG2(SIDBG_INIT, si_ctlp,
+ "ddi_intr_get_navail() failed, "
+ "rc %d avail %d\n", rc, avail);
+ return (DDI_FAILURE);
+ }
+
+ if (avail < count) {
+ SIDBG2(SIDBG_INIT, si_ctlp,
+ "ddi_intr_get_nvail returned %d, navail() returned %d",
+ count, avail);
+ }
+
+ /* Allocate an array of interrupt handles. */
+ si_ctlp->sictl_intr_size = count * sizeof (ddi_intr_handle_t);
+ si_ctlp->sictl_htable = kmem_alloc(si_ctlp->sictl_intr_size, KM_SLEEP);
+
+ /* call ddi_intr_alloc(). */
+ rc = ddi_intr_alloc(devinfo, si_ctlp->sictl_htable, DDI_INTR_TYPE_MSI,
+ inum, count, &actual, DDI_INTR_ALLOC_NORMAL);
+
+ if ((rc != DDI_SUCCESS) || (actual == 0)) {
+ SIDBG1(SIDBG_INIT, si_ctlp,
+ "ddi_intr_alloc() failed, rc %d\n", rc);
+ kmem_free(si_ctlp->sictl_htable, si_ctlp->sictl_intr_size);
+ return (DDI_FAILURE);
+ }
+
+ /* use interrupt count returned */
+ if (actual < count) {
+ SIDBG2(SIDBG_INIT, si_ctlp,
+ "Requested: %d, Received: %d", count, actual);
+ }
+
+ si_ctlp->sictl_intr_cnt = actual;
+
+ /*
+ * Get priority for first msi, assume remaining are all the same.
+ */
+ if (ddi_intr_get_pri(si_ctlp->sictl_htable[0],
+ &si_ctlp->sictl_intr_pri) != DDI_SUCCESS) {
+ SIDBG0(SIDBG_INIT, si_ctlp, "ddi_intr_get_pri() failed");
+
+ /* Free already allocated intr. */
+ for (y = 0; y < actual; y++) {
+ (void) ddi_intr_free(si_ctlp->sictl_htable[y]);
+ }
+
+ kmem_free(si_ctlp->sictl_htable, si_ctlp->sictl_intr_size);
+ return (DDI_FAILURE);
+ }
+
+ /* Test for high level mutex. */
+ if (si_ctlp->sictl_intr_pri >= ddi_intr_get_hilevel_pri()) {
+ SIDBG0(SIDBG_INIT, si_ctlp,
+ "si_add_msi_intrs: Hi level intr not supported");
+
+ /* Free already allocated intr. */
+ for (y = 0; y < actual; y++) {
+ (void) ddi_intr_free(si_ctlp->sictl_htable[y]);
+ }
+
+ kmem_free(si_ctlp->sictl_htable, sizeof (ddi_intr_handle_t));
+
+ return (DDI_FAILURE);
+ }
+
+ /* Call ddi_intr_add_handler(). */
+ for (x = 0; x < actual; x++) {
+ if (ddi_intr_add_handler(si_ctlp->sictl_htable[x], si_intr,
+ (caddr_t)si_ctlp, NULL) != DDI_SUCCESS) {
+ SIDBG0(SIDBG_INIT, si_ctlp,
+ "ddi_intr_add_handler() failed");
+
+ /* Free already allocated intr. */
+ for (y = 0; y < actual; y++) {
+ (void) ddi_intr_free(si_ctlp->sictl_htable[y]);
+ }
+
+ kmem_free(si_ctlp->sictl_htable,
+ si_ctlp->sictl_intr_size);
+ return (DDI_FAILURE);
+ }
+ }
+
+ (void) ddi_intr_get_cap(si_ctlp->sictl_htable[0],
+ &si_ctlp->sictl_intr_cap);
+
+ if (si_ctlp->sictl_intr_cap & DDI_INTR_FLAG_BLOCK) {
+ /* Call ddi_intr_block_enable() for MSI. */
+ (void) ddi_intr_block_enable(si_ctlp->sictl_htable,
+ si_ctlp->sictl_intr_cnt);
+ } else {
+ /* Call ddi_intr_enable() for MSI non block enable. */
+ for (x = 0; x < si_ctlp->sictl_intr_cnt; x++) {
+ (void) ddi_intr_enable(si_ctlp->sictl_htable[x]);
+ }
+ }
+
+ return (DDI_SUCCESS);
+}
+
+/*
+ * Removes the registered interrupts irrespective of whether they
+ * were legacy or MSI.
+ */
+static void
+si_rem_intrs(si_ctl_state_t *si_ctlp)
+{
+ int x;
+
+ SIDBG0(SIDBG_ENTRY, si_ctlp, "si_rem_intrs entered");
+
+ /* Disable all interrupts. */
+ if ((si_ctlp->sictl_intr_type == DDI_INTR_TYPE_MSI) &&
+ (si_ctlp->sictl_intr_cap & DDI_INTR_FLAG_BLOCK)) {
+ /* Call ddi_intr_block_disable(). */
+ (void) ddi_intr_block_disable(si_ctlp->sictl_htable,
+ si_ctlp->sictl_intr_cnt);
+ } else {
+ for (x = 0; x < si_ctlp->sictl_intr_cnt; x++) {
+ (void) ddi_intr_disable(si_ctlp->sictl_htable[x]);
+ }
+ }
+
+ /* Call ddi_intr_remove_handler(). */
+ for (x = 0; x < si_ctlp->sictl_intr_cnt; x++) {
+ (void) ddi_intr_remove_handler(si_ctlp->sictl_htable[x]);
+ (void) ddi_intr_free(si_ctlp->sictl_htable[x]);
+ }
+
+ kmem_free(si_ctlp->sictl_htable, si_ctlp->sictl_intr_size);
+}
+
+/*
+ * Resets either the port or the device connected to the port based on
+ * the flag variable.
+ *
+ * The reset effectively throws away all the pending commands. So, the caller
+ * has to make provision to handle the pending commands.
+ *
+ * After the reset, we wait till the port is ready again.
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ *
+ * Note: Not port-mult aware.
+ */
+static int
+si_reset_dport_wait_till_ready(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port,
+ int flag)
+{
+ uint32_t port_status;
+ int loop_count = 0;
+ sata_device_t sdevice;
+ uint32_t SStatus;
+ uint32_t SControl;
+
+ _NOTE(ASSUMING_PROTECTED(si_portp))
+
+ if (flag == SI_PORT_RESET) {
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
+ PORT_CONTROL_SET_BITS_PORT_RESET);
+
+ /* Port reset is not self clearing. So clear it now. */
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_CONTROL_CLEAR(si_ctlp, port),
+ PORT_CONTROL_CLEAR_BITS_PORT_RESET);
+ } else {
+ /* Reset the device. */
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
+ PORT_CONTROL_SET_BITS_DEV_RESET);
+
+ /*
+ * tidbit: this bit is self clearing; so there is no need
+ * for manual clear as we did for port reset.
+ */
+ }
+
+ /* Set the reset in progress flag */
+ if (!(flag & SI_RESET_NO_EVENTS_UP)) {
+ si_portp->siport_reset_in_progress = 1;
+ }
+
+ /*
+ * For some reason, we are losing the interrupt enablement after
+ * any reset condition. So restore them back now.
+ */
+ SIDBG1(SIDBG_INIT, si_ctlp,
+ "current interrupt enable set: 0x%x",
+ ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_INTERRUPT_ENABLE_SET(si_ctlp, port)));
+
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_INTERRUPT_ENABLE_SET(si_ctlp, port),
+ (INTR_COMMAND_COMPLETE |
+ INTR_COMMAND_ERROR |
+ INTR_PORT_READY |
+ INTR_POWER_CHANGE |
+ INTR_PHYRDY_CHANGE |
+ INTR_COMWAKE_RECEIVED |
+ INTR_UNRECOG_FIS |
+ INTR_DEV_XCHANGED |
+ INTR_SETDEVBITS_NOTIFY));
+
+ si_enable_port_interrupts(si_ctlp, port);
+
+ /*
+ * Every reset needs a PHY initialization.
+ *
+ * The way to initialize the PHY is to write a 1 and then
+ * a 0 to DET field of SControl register.
+ */
+
+ /* Fetch the current SControl before writing the DET part with 1. */
+ SControl = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_SCONTROL(si_ctlp, port));
+ SCONTROL_SET_DET(SControl, SCONTROL_DET_COMRESET);
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SCONTROL(si_ctlp, port)),
+ SControl);
+#ifndef __lock_lint
+ delay(SI_10MS_TICKS); /* give time for COMRESET to percolate */
+#endif /* __lock_lint */
+
+ /* Now fetch the SControl again and rewrite the DET part with 0 */
+ SControl = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_SCONTROL(si_ctlp, port));
+ SCONTROL_SET_DET(SControl, SCONTROL_DET_NOACTION);
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SCONTROL(si_ctlp, port)),
+ SControl);
+
+ /*
+ * PHY may be initialized by now. Check the DET field of SStatus
+ * to determine if there is a device present.
+ *
+ * The DET field is valid only if IPM field indicates that
+ * the interface is in active state.
+ */
+
+ loop_count = 0;
+ do {
+ SStatus = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_SSTATUS(si_ctlp, port));
+
+ if (SSTATUS_GET_IPM(SStatus) !=
+ SSTATUS_IPM_INTERFACE_ACTIVE) {
+ /*
+ * If the interface is not active, the DET field
+ * is considered not accurate. So we want to
+ * continue looping.
+ */
+ SSTATUS_SET_DET(SStatus, SSTATUS_DET_NODEV_NOPHY);
+ }
+
+ if (loop_count++ > SI_POLLRATE_SSTATUS) {
+ /* We are effectively timing out after 0.1 sec. */
+ break;
+ }
+
+ /* Wait for 10 millisec */
+#ifndef __lock_lint
+ delay(SI_10MS_TICKS);
+#endif /* __lock_lint */
+
+ } while (SSTATUS_GET_DET(SStatus) != SSTATUS_DET_DEVPRESENT_PHYONLINE);
+
+ SIDBG2(SIDBG_POLL_LOOP, si_ctlp,
+ "si_reset_dport_wait_till_ready: loop count: %d, \
+ SStatus: 0x%x",
+ loop_count,
+ SStatus);
+
+ /* Now check for port readiness. */
+ loop_count = 0;
+ do {
+ port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_STATUS(si_ctlp, port));
+
+ if (loop_count++ > SI_POLLRATE_PORTREADY) {
+ /* We are effectively timing out after 0.5 sec. */
+ break;
+ }
+
+ /* Wait for 10 millisec */
+#ifndef __lock_lint
+ delay(SI_10MS_TICKS);
+#endif /* __lock_lint */
+
+ } while (!(port_status & PORT_STATUS_BITS_PORT_READY));
+
+ SIDBG3(SIDBG_POLL_LOOP, si_ctlp,
+ "si_reset_dport_wait_till_ready: loop count: %d, \
+ port_status: 0x%x, SStatus: 0x%x",
+ loop_count,
+ port_status,
+ SStatus);
+
+ /* Indicate to the framework that a reset has happened. */
+ if (!(flag & SI_RESET_NO_EVENTS_UP)) {
+
+ bzero((void *)&sdevice, sizeof (sata_device_t));
+ sdevice.satadev_addr.cport = port;
+ sdevice.satadev_addr.pmport = PORTMULT_CONTROL_PORT;
+
+ if (si_portp->siport_port_type == PORT_TYPE_MULTIPLIER) {
+ sdevice.satadev_addr.qual = SATA_ADDR_DPMPORT;
+ } else {
+ sdevice.satadev_addr.qual = SATA_ADDR_DCPORT;
+ }
+ sdevice.satadev_state = SATA_DSTATE_RESET |
+ SATA_DSTATE_PWR_ACTIVE;
+ if (si_ctlp->sictl_sata_hba_tran) {
+ sata_hba_event_notify(
+ si_ctlp->sictl_sata_hba_tran->sata_tran_hba_dip,
+ &sdevice,
+ SATA_EVNT_DEVICE_RESET);
+ }
+
+ SIDBG0(SIDBG_EVENT, si_ctlp,
+ "sending event up: SATA_EVNT_RESET");
+ }
+
+ if ((SSTATUS_GET_IPM(SStatus) == SSTATUS_IPM_INTERFACE_ACTIVE) &&
+ (SSTATUS_GET_DET(SStatus) ==
+ SSTATUS_DET_DEVPRESENT_PHYONLINE)) {
+ /* The interface is active and the device is present */
+ if (!(port_status & PORT_STATUS_BITS_PORT_READY)) {
+ /* But the port is is not ready for some reason */
+ SIDBG0(SIDBG_POLL_LOOP, si_ctlp,
+ "si_reset_dport_wait_till_ready failed");
+ return (SI_FAILURE);
+ }
+ }
+
+ SIDBG0(SIDBG_POLL_LOOP, si_ctlp,
+ "si_reset_dport_wait_till_ready returning success");
+
+ return (SI_SUCCESS);
+}
+
+/*
+ * Initializes the port.
+ *
+ * Initialization effectively throws away all the pending commands on
+ * the port. So, the caller has to make provision to handle the pending
+ * commands.
+ *
+ * After the port initialization, we wait till the port is ready again.
+ *
+ * WARNING, WARNING: The caller is expected to obtain the siport_mutex
+ * before calling us.
+ */
+static int
+si_initialize_port_wait_till_ready(si_ctl_state_t *si_ctlp, int port)
+{
+ uint32_t port_status;
+ int loop_count = 0;
+ uint32_t SStatus;
+
+ /* Initialize the port. */
+ ddi_put32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_CONTROL_SET(si_ctlp, port),
+ PORT_CONTROL_SET_BITS_PORT_INITIALIZE);
+
+ /* Wait until Port Ready */
+ loop_count = 0;
+ do {
+ port_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_STATUS(si_ctlp, port));
+
+ if (loop_count++ > SI_POLLRATE_PORTREADY) {
+ SIDBG1(SIDBG_INTR, si_ctlp,
+ "si_initialize_port_wait is timing out: "
+ "port_status: %x",
+ port_status);
+ /* We are effectively timing out after 0.5 sec. */
+ break;
+ }
+
+ /* Wait for 10 millisec */
+#ifndef __lock_lint
+ delay(SI_10MS_TICKS);
+#endif /* __lock_lint */
+
+ } while (!(port_status & PORT_STATUS_BITS_PORT_READY));
+
+ SIDBG1(SIDBG_POLL_LOOP, si_ctlp,
+ "si_initialize_port_wait_till_ready: loop count: %d",
+ loop_count);
+
+ SStatus = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)PORT_SSTATUS(si_ctlp, port));
+
+ if ((SSTATUS_GET_IPM(SStatus) == SSTATUS_IPM_INTERFACE_ACTIVE) &&
+ (SSTATUS_GET_DET(SStatus) ==
+ SSTATUS_DET_DEVPRESENT_PHYONLINE)) {
+ /* The interface is active and the device is present */
+ if (!(port_status & PORT_STATUS_BITS_PORT_READY)) {
+ /* But the port is is not ready for some reason */
+ return (SI_FAILURE);
+ }
+ }
+
+ return (SI_SUCCESS);
+}
+
+
+/*
+ * si_watchdog_handler() calls us if it detects that there are some
+ * commands which timed out. We recalculate the timed out commands once
+ * again since some of them may have finished recently.
+ */
+static void
+si_timeout_pkts(
+ si_ctl_state_t *si_ctlp,
+ si_port_state_t *si_portp,
+ int port,
+ uint32_t timedout_tags)
+{
+ uint32_t slot_status;
+ uint32_t finished_tags;
+
+ SIDBG0(SIDBG_TIMEOUT|SIDBG_ENTRY, si_ctlp, "si_timeout_pkts entry");
+
+ mutex_enter(&si_portp->siport_mutex);
+ slot_status = ddi_get32(si_ctlp->sictl_port_acc_handle,
+ (uint32_t *)(PORT_SLOT_STATUS(si_ctlp, port)));
+
+ /*
+ * Initialize the controller. The only way to timeout the commands
+ * is to reset or initialize the controller. We mop commands after
+ * the initialization.
+ */
+ (void) si_initialize_port_wait_till_ready(si_ctlp, port);
+
+ /*
+ * Recompute the timedout tags since some of them may have finished
+ * meanwhile.
+ */
+ finished_tags = si_portp->siport_pending_tags &
+ ~slot_status & SI_SLOT_MASK;
+ timedout_tags &= ~finished_tags;
+
+ SIDBG2(SIDBG_TIMEOUT, si_ctlp,
+ "si_timeout_pkts: finished: %x, timeout: %x",
+ finished_tags,
+ timedout_tags);
+
+ mutex_exit(&si_portp->siport_mutex);
+ si_mop_commands(si_ctlp,
+ si_portp,
+ port,
+ slot_status,
+ 0, /* failed_tags */
+ timedout_tags,
+ 0, /* aborting_tags */
+ 0); /* reset_tags */
+
+}
+
+
+
+/*
+ * Watchdog handler kicks in every 5 seconds to timeout any commands pending
+ * for long time.
+ */
+static void
+si_watchdog_handler(si_ctl_state_t *si_ctlp)
+{
+ uint32_t pending_tags = 0;
+ uint32_t timedout_tags = 0;
+ si_port_state_t *si_portp;
+ int port;
+ int tmpslot;
+ sata_pkt_t *satapkt;
+
+ /* max number of cycles this packet should survive */
+ int max_life_cycles;
+
+ /* how many cycles this packet survived so far */
+ int watched_cycles;
+
+ mutex_enter(&si_ctlp->sictl_mutex);
+ SIDBG0(SIDBG_TIMEOUT|SIDBG_ENTRY, si_ctlp,
+ "si_watchdog_handler entered");
+
+ for (port = 0; port < si_ctlp->sictl_num_ports; port++) {
+
+ si_portp = si_ctlp->sictl_ports[port];
+ if (si_portp == NULL) {
+ continue;
+ }
+
+ mutex_enter(&si_portp->siport_mutex);
+
+ if (si_portp->siport_port_type == PORT_TYPE_NODEV) {
+ mutex_exit(&si_portp->siport_mutex);
+ continue;
+ }
+
+ pending_tags = si_portp->siport_pending_tags;
+ timedout_tags = 0;
+ while (pending_tags) {
+ tmpslot = ddi_ffs(pending_tags) - 1;
+ if (tmpslot == -1) {
+ break;
+ }
+ satapkt = si_portp->siport_slot_pkts[tmpslot];
+
+ if ((satapkt != NULL) && satapkt->satapkt_time) {
+
+ /*
+ * We are overloading satapkt_hba_driver_private
+ * with watched_cycle count.
+ *
+ * If a packet has survived for more than it's
+ * max life cycles, it is a candidate for time
+ * out.
+ */
+ watched_cycles = (int)(intptr_t)
+ satapkt->satapkt_hba_driver_private;
+ watched_cycles++;
+ max_life_cycles = (satapkt->satapkt_time +
+ si_watchdog_timeout - 1) /
+ si_watchdog_timeout;
+ if (watched_cycles > max_life_cycles) {
+ timedout_tags |= (0x1 << tmpslot);
+ SIDBG1(SIDBG_TIMEOUT|SIDBG_VERBOSE,
+ si_ctlp,
+ "watchdog: timedout_tags: 0x%x",
+ timedout_tags);
+ }
+ satapkt->satapkt_hba_driver_private =
+ (void *)(intptr_t)watched_cycles;
+ }
+
+ CLEAR_BIT(pending_tags, tmpslot);
+ }
+
+ if (timedout_tags) {
+ mutex_exit(&si_portp->siport_mutex);
+ mutex_exit(&si_ctlp->sictl_mutex);
+ si_timeout_pkts(si_ctlp, si_portp, port, timedout_tags);
+ mutex_enter(&si_ctlp->sictl_mutex);
+ mutex_enter(&si_portp->siport_mutex);
+ }
+
+ mutex_exit(&si_portp->siport_mutex);
+ }
+
+ /* Reinstall the watchdog timeout handler. */
+ if (!(si_ctlp->sictl_flags & SI_NO_TIMEOUTS)) {
+ si_ctlp->sictl_timeout_id =
+ timeout((void (*)(void *))si_watchdog_handler,
+ (caddr_t)si_ctlp, si_watchdog_tick);
+ }
+ mutex_exit(&si_ctlp->sictl_mutex);
+}
+
+
+/*
+ * Logs the message.
+ */
+static void
+si_log(si_ctl_state_t *si_ctlp, uint_t level, char *fmt, ...)
+{
+ va_list ap;
+
+ mutex_enter(&si_log_mutex);
+
+ va_start(ap, fmt);
+ if (si_ctlp) {
+ (void) sprintf(si_log_buf, "%s-[%d]:",
+ ddi_get_name(si_ctlp->sictl_devinfop),
+ ddi_get_instance(si_ctlp->sictl_devinfop));
+ } else {
+ (void) sprintf(si_log_buf, "si3124:");
+ }
+ (void) vsprintf(si_log_buf, fmt, ap);
+ va_end(ap);
+
+ cmn_err(level, "%s", si_log_buf);
+
+ mutex_exit(&si_log_mutex);
+
+}
diff --git a/usr/src/uts/common/io/sata/impl/sata.c b/usr/src/uts/common/io/sata/impl/sata.c
new file mode 100644
index 0000000000..96d504ea09
--- /dev/null
+++ b/usr/src/uts/common/io/sata/impl/sata.c
@@ -0,0 +1,9747 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * SATA Framework
+ * Generic SATA Host Adapter Implementation
+ *
+ * NOTE: THIS VERSION DOES NOT SUPPORT ATAPI DEVICES,
+ * although there is some code related to these devices.
+ *
+ */
+#include <sys/conf.h>
+#include <sys/file.h>
+#include <sys/ddi.h>
+#include <sys/sunddi.h>
+#include <sys/modctl.h>
+#include <sys/cmn_err.h>
+#include <sys/errno.h>
+#include <sys/thread.h>
+#include <sys/kstat.h>
+#include <sys/note.h>
+
+#include <sys/sata/impl/sata.h>
+#include <sys/sata/sata_hba.h>
+#include <sys/sata/sata_defs.h>
+#include <sys/sata/sata_cfgadm.h>
+
+
+
+/* Debug flags - defined in sata.h */
+int sata_debug_flags = 0;
+/*
+ * Flags enabling selected SATA HBA framework functionality
+ */
+#define SATA_ENABLE_QUEUING 1
+#define SATA_ENABLE_NCQ 2
+#define SATA_ENABLE_PROCESS_EVENTS 4
+static int sata_func_enable = SATA_ENABLE_PROCESS_EVENTS;
+
+#ifdef SATA_DEBUG
+#define SATA_LOG_D(args) sata_log args
+#else
+#define SATA_LOG_D(arg)
+#endif
+
+
+/*
+ * SATA cb_ops functions
+ */
+static int sata_hba_open(dev_t *, int, int, cred_t *);
+static int sata_hba_close(dev_t, int, int, cred_t *);
+static int sata_hba_ioctl(dev_t, int, intptr_t, int, cred_t *, int *);
+
+/*
+ * SCSA required entry points
+ */
+static int sata_scsi_tgt_init(dev_info_t *, dev_info_t *,
+ scsi_hba_tran_t *, struct scsi_device *);
+static int sata_scsi_tgt_probe(struct scsi_device *,
+ int (*callback)(void));
+static void sata_scsi_tgt_free(dev_info_t *, dev_info_t *,
+ scsi_hba_tran_t *, struct scsi_device *);
+static int sata_scsi_start(struct scsi_address *, struct scsi_pkt *);
+static int sata_scsi_abort(struct scsi_address *, struct scsi_pkt *);
+static int sata_scsi_reset(struct scsi_address *, int);
+static int sata_scsi_getcap(struct scsi_address *, char *, int);
+static int sata_scsi_setcap(struct scsi_address *, char *, int, int);
+static struct scsi_pkt *sata_scsi_init_pkt(struct scsi_address *,
+ struct scsi_pkt *, struct buf *, int, int, int, int, int (*)(caddr_t),
+ caddr_t);
+static void sata_scsi_destroy_pkt(struct scsi_address *, struct scsi_pkt *);
+static void sata_scsi_dmafree(struct scsi_address *, struct scsi_pkt *);
+static void sata_scsi_sync_pkt(struct scsi_address *, struct scsi_pkt *);
+static int sata_scsi_get_name(struct scsi_device *, char *, int);
+
+
+/*
+ * Local functions
+ */
+static void sata_remove_hba_instance(dev_info_t *);
+static int sata_validate_sata_hba_tran(dev_info_t *, sata_hba_tran_t *);
+static void sata_probe_ports(sata_hba_inst_t *);
+static int sata_reprobe_port(sata_hba_inst_t *, sata_device_t *);
+static void sata_make_device_nodes(dev_info_t *, sata_hba_inst_t *);
+static dev_info_t *sata_create_target_node(dev_info_t *, sata_hba_inst_t *,
+ sata_address_t *);
+static int sata_validate_scsi_address(sata_hba_inst_t *,
+ struct scsi_address *, sata_device_t *);
+static int sata_validate_sata_address(sata_hba_inst_t *, int, int, int);
+static sata_pkt_t *sata_pkt_alloc(sata_pkt_txlate_t *, int (*)(caddr_t));
+static void sata_pkt_free(sata_pkt_txlate_t *);
+static int sata_dma_buf_setup(sata_pkt_txlate_t *, int, int (*)(caddr_t),
+ caddr_t, ddi_dma_attr_t *);
+static int sata_probe_device(sata_hba_inst_t *, sata_device_t *);
+static sata_drive_info_t *sata_get_device_info(sata_hba_inst_t *,
+ sata_device_t *);
+static int sata_identify_device(sata_hba_inst_t *, sata_drive_info_t *);
+static struct buf *sata_alloc_local_buffer(sata_pkt_txlate_t *, int);
+static void sata_free_local_buffer(sata_pkt_txlate_t *);
+static uint64_t sata_check_capacity(sata_drive_info_t *);
+void sata_adjust_dma_attr(sata_drive_info_t *, ddi_dma_attr_t *,
+ ddi_dma_attr_t *);
+static int sata_fetch_device_identify_data(sata_hba_inst_t *,
+ sata_drive_info_t *);
+static void sata_update_port_info(sata_hba_inst_t *, sata_device_t *);
+static void sata_update_port_scr(sata_port_scr_t *, sata_device_t *);
+static int sata_set_udma_mode(sata_hba_inst_t *, sata_drive_info_t *);
+
+/* Event processing functions */
+static void sata_event_daemon(void *);
+static void sata_event_thread_control(int);
+static void sata_process_controller_events(sata_hba_inst_t *sata_hba_inst);
+static void sata_process_device_reset(sata_hba_inst_t *, sata_address_t *);
+static void sata_process_port_failed_event(sata_hba_inst_t *,
+ sata_address_t *);
+static void sata_process_port_link_events(sata_hba_inst_t *,
+ sata_address_t *);
+static void sata_process_device_detached(sata_hba_inst_t *, sata_address_t *);
+static void sata_process_device_attached(sata_hba_inst_t *, sata_address_t *);
+static void sata_process_port_pwr_change(sata_hba_inst_t *, sata_address_t *);
+static void sata_process_cntrl_pwr_level_change(sata_hba_inst_t *);
+static int sata_restore_drive_settings(sata_hba_inst_t *,
+ sata_drive_info_t *);
+
+/* Local functions for ioctl */
+static int32_t sata_get_port_num(sata_hba_inst_t *, struct devctl_iocdata *);
+static void sata_cfgadm_state(sata_hba_inst_t *, int32_t,
+ devctl_ap_state_t *);
+static dev_info_t *sata_get_target_dip(dev_info_t *, int32_t);
+static dev_info_t *sata_devt_to_devinfo(dev_t);
+
+/* Local translation functions */
+static int sata_txlt_inquiry(sata_pkt_txlate_t *);
+static int sata_txlt_test_unit_ready(sata_pkt_txlate_t *);
+static int sata_txlt_start_stop_unit(sata_pkt_txlate_t *);
+static int sata_txlt_read_capacity(sata_pkt_txlate_t *);
+static int sata_txlt_request_sense(sata_pkt_txlate_t *);
+static int sata_txlt_read(sata_pkt_txlate_t *);
+static int sata_txlt_write(sata_pkt_txlate_t *);
+static int sata_txlt_atapi(sata_pkt_txlate_t *);
+static int sata_txlt_log_sense(sata_pkt_txlate_t *);
+static int sata_txlt_log_select(sata_pkt_txlate_t *);
+static int sata_txlt_mode_sense(sata_pkt_txlate_t *);
+static int sata_txlt_mode_select(sata_pkt_txlate_t *);
+static int sata_txlt_synchronize_cache(sata_pkt_txlate_t *);
+static int sata_txlt_nodata_cmd_immediate(sata_pkt_txlate_t *);
+
+static int sata_hba_start(sata_pkt_txlate_t *, int *);
+static int sata_txlt_invalid_command(sata_pkt_txlate_t *);
+static int sata_txlt_lba_out_of_range(sata_pkt_txlate_t *);
+static void sata_txlt_rw_completion(sata_pkt_t *);
+static void sata_txlt_atapi_completion(sata_pkt_t *);
+static void sata_txlt_nodata_cmd_completion(sata_pkt_t *);
+
+static struct scsi_extended_sense *sata_immediate_error_response(
+ sata_pkt_txlate_t *, int);
+static struct scsi_extended_sense *sata_arq_sense(sata_pkt_txlate_t *);
+
+/* Local functions */
+static void sata_identdev_to_inquiry(sata_hba_inst_t *, sata_drive_info_t *,
+ uint8_t *);
+static int sata_build_msense_page_1(sata_drive_info_t *, int, uint8_t *);
+static int sata_build_msense_page_8(sata_drive_info_t *, int, uint8_t *);
+static int sata_build_msense_page_1a(sata_drive_info_t *, int, uint8_t *);
+static int sata_build_msense_page_1c(sata_drive_info_t *, int, uint8_t *);
+static int sata_mode_select_page_8(sata_pkt_txlate_t *,
+ struct mode_cache_scsi3 *, int, int *, int *, int *);
+static void sata_save_drive_settings(sata_drive_info_t *);
+static void sata_show_drive_info(sata_hba_inst_t *, sata_drive_info_t *);
+
+static void sata_log(sata_hba_inst_t *, uint_t, char *fmt, ...);
+
+/*
+ * SATA Framework will ignore SATA HBA driver cb_ops structure and
+ * register following one with SCSA framework.
+ * Open & close are provided, so scsi framework will not use its own
+ */
+static struct cb_ops sata_cb_ops = {
+ sata_hba_open, /* open */
+ sata_hba_close, /* close */
+ nodev, /* strategy */
+ nodev, /* print */
+ nodev, /* dump */
+ nodev, /* read */
+ nodev, /* write */
+ sata_hba_ioctl, /* ioctl */
+ nodev, /* devmap */
+ nodev, /* mmap */
+ nodev, /* segmap */
+ nochpoll, /* chpoll */
+ ddi_prop_op, /* cb_prop_op */
+ 0, /* streamtab */
+ D_NEW | D_MP, /* cb_flag */
+ CB_REV, /* rev */
+ nodev, /* aread */
+ nodev /* awrite */
+};
+
+
+extern struct mod_ops mod_miscops;
+extern uchar_t scsi_cdb_size[];
+
+static struct modlmisc modlmisc = {
+ &mod_miscops, /* Type of module */
+ "Generic SATA Driver v%I%" /* module name */
+};
+
+
+static struct modlinkage modlinkage = {
+ MODREV_1,
+ (void *)&modlmisc,
+ NULL
+};
+
+/*
+ * Default sata pkt timeout. Used when a target driver scsi_pkt time is zero,
+ * i.e. when scsi_pkt has not timeout specified.
+ */
+static int sata_default_pkt_time = 60; /* 60 seconds */
+
+/*
+ * Mutexes protecting structures in multithreaded operations.
+ * Because events are relatively rare, a single global mutex protecting
+ * data structures should be sufficient. To increase performance, add
+ * separate mutex per each sata port and use global mutex only to protect
+ * common data structures.
+ */
+static kmutex_t sata_mutex; /* protects sata_hba_list */
+static kmutex_t sata_log_mutex; /* protects log */
+
+static char sata_log_buf[256];
+
+/*
+ * Linked list of HBA instances
+ */
+static sata_hba_inst_t *sata_hba_list = NULL;
+static sata_hba_inst_t *sata_hba_list_tail = NULL;
+/*
+ * Pointer to per-instance SATA HBA soft structure is stored in sata_hba_tran
+ * structure and in sata soft state.
+ */
+
+/*
+ * Event daemon related variables
+ */
+static kmutex_t sata_event_mutex;
+static kcondvar_t sata_event_cv;
+static kthread_t *sata_event_thread = NULL;
+static int sata_event_thread_terminate = 0;
+static int sata_event_pending = 0;
+static int sata_event_thread_active = 0;
+extern pri_t minclsyspri;
+
+/* Warlock directives */
+
+_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", scsi_hba_tran))
+_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", scsi_device))
+_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", dev_ops))
+_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", scsi_extended_sense))
+_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", scsi_arq_status))
+_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", ddi_dma_attr))
+_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", ddi_dma_cookie_t))
+_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", devctl_ap_state))
+_NOTE(MUTEX_PROTECTS_DATA(sata_mutex, sata_hba_list))
+_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_hba_list))
+_NOTE(MUTEX_PROTECTS_DATA(sata_mutex, sata_hba_inst::satahba_next))
+_NOTE(MUTEX_PROTECTS_DATA(sata_mutex, sata_hba_inst::satahba_prev))
+_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", \
+ sata_hba_inst::satahba_scsi_tran))
+_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", sata_hba_inst::satahba_tran))
+_NOTE(SCHEME_PROTECTS_DATA("No Mutex Needed", sata_hba_inst::satahba_dip))
+_NOTE(SCHEME_PROTECTS_DATA("Scheme", sata_hba_inst::satahba_attached))
+_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_hba_inst::satahba_dev_port))
+_NOTE(MUTEX_PROTECTS_DATA(sata_hba_inst::satahba_mutex,
+ sata_hba_inst::satahba_event_flags))
+_NOTE(MUTEX_PROTECTS_DATA(sata_cport_info::cport_mutex, \
+ sata_cport_info::cport_devp))
+_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_cport_info::cport_devp))
+_NOTE(SCHEME_PROTECTS_DATA("Scheme", sata_cport_info::cport_addr))
+_NOTE(MUTEX_PROTECTS_DATA(sata_cport_info::cport_mutex, \
+ sata_cport_info::cport_dev_type))
+_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_cport_info::cport_dev_type))
+_NOTE(MUTEX_PROTECTS_DATA(sata_cport_info::cport_mutex, \
+ sata_cport_info::cport_state))
+_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_cport_info::cport_state))
+_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_pmport_info::pmport_dev_type))
+_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_pmport_info::pmport_sata_drive))
+_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_pmult_info::pmult_dev_port))
+_NOTE(DATA_READABLE_WITHOUT_LOCK(sata_pmult_info::pmult_num_dev_ports))
+
+/* End of warlock directives */
+
+/* ************** loadable module configuration functions ************** */
+
+int
+_init()
+{
+ int rval;
+
+ mutex_init(&sata_mutex, NULL, MUTEX_DRIVER, NULL);
+ mutex_init(&sata_event_mutex, NULL, MUTEX_DRIVER, NULL);
+ mutex_init(&sata_log_mutex, NULL, MUTEX_DRIVER, NULL);
+ cv_init(&sata_event_cv, NULL, CV_DRIVER, NULL);
+ if ((rval = mod_install(&modlinkage)) != 0) {
+#ifdef SATA_DEBUG
+ cmn_err(CE_WARN, "sata: _init: mod_install failed\n");
+#endif
+ mutex_destroy(&sata_log_mutex);
+ cv_destroy(&sata_event_cv);
+ mutex_destroy(&sata_event_mutex);
+ mutex_destroy(&sata_mutex);
+ }
+ return (rval);
+}
+
+int
+_fini()
+{
+ int rval;
+
+ if ((rval = mod_remove(&modlinkage)) != 0)
+ return (rval);
+
+ mutex_destroy(&sata_log_mutex);
+ cv_destroy(&sata_event_cv);
+ mutex_destroy(&sata_event_mutex);
+ mutex_destroy(&sata_mutex);
+ return (rval);
+}
+
+int
+_info(struct modinfo *modinfop)
+{
+ return (mod_info(&modlinkage, modinfop));
+}
+
+
+
+/* ********************* SATA HBA entry points ********************* */
+
+
+/*
+ * Called by SATA HBA from _init().
+ * Registers HBA driver instance/sata framework pair with scsi framework, by
+ * calling scsi_hba_init().
+ *
+ * SATA HBA driver cb_ops are ignored - SATA HBA framework cb_ops are used
+ * instead. SATA HBA framework cb_ops pointer overwrites SATA HBA driver
+ * cb_ops pointer in SATA HBA driver dev_ops structure.
+ * SATA HBA framework cb_ops supplies cb_open cb_close and cb_ioctl vectors.
+ *
+ * Return status of the scsi_hba_init() is returned to a calling SATA HBA
+ * driver.
+ */
+int
+sata_hba_init(struct modlinkage *modlp)
+{
+ int rval;
+ struct dev_ops *hba_ops;
+
+ SATADBG1(SATA_DBG_HBA_IF, NULL,
+ "sata_hba_init: name %s \n",
+ ((struct modldrv *)(modlp->ml_linkage[0]))->drv_linkinfo);
+ /*
+ * Fill-up cb_ops and dev_ops when necessary
+ */
+ hba_ops = ((struct modldrv *)(modlp->ml_linkage[0]))->drv_dev_ops;
+ /*
+ * Provide pointer to SATA dev_ops
+ */
+ hba_ops->devo_cb_ops = &sata_cb_ops;
+
+ /*
+ * Register SATA HBA with SCSI framework
+ */
+ if ((rval = scsi_hba_init(modlp)) != 0) {
+ SATADBG1(SATA_DBG_HBA_IF, NULL,
+ "sata_hba_init: scsi hba init failed\n", NULL);
+ return (rval);
+ }
+
+ return (0);
+}
+
+
+/* HBA attach stages */
+#define HBA_ATTACH_STAGE_SATA_HBA_INST 1
+#define HBA_ATTACH_STAGE_SCSI_ATTACHED 2
+#define HBA_ATTACH_STAGE_SETUP 4
+#define HBA_ATTACH_STAGE_LINKED 8
+
+
+/*
+ *
+ * Called from SATA HBA driver's attach routine to attach an instance of
+ * the HBA.
+ *
+ * For DDI_ATTACH command:
+ * sata_hba_inst structure is allocated here and initialized with pointers to
+ * SATA framework implementation of required scsi tran functions.
+ * The scsi_tran's tran_hba_private field is used by SATA Framework to point
+ * to the soft structure (sata_hba_inst) allocated by SATA framework for
+ * SATA HBA instance related data.
+ * The scsi_tran's tran_hba_private field is used by SATA framework to
+ * store a pointer to per-HBA-instance of sata_hba_inst structure.
+ * The sata_hba_inst structure is cross-linked to scsi tran structure.
+ * Among other info, a pointer to sata_hba_tran structure is stored in
+ * sata_hba_inst. The sata_hba_inst structures for different HBA instances are
+ * linked together into the list, pointed to by sata_hba_list.
+ * On the first HBA instance attach the sata event thread is initialized.
+ * Attachment points are created for all SATA ports of the HBA being attached.
+ * All HBA instance's SATA ports are probed and type of plugged devices is
+ * determined. For each device of a supported type, a target node is created.
+ *
+ * DDI_SUCCESS is returned when attachment process is successful,
+ * DDI_FAILURE is returned otherwise.
+ *
+ * For DDI_RESUME command:
+ * Not implemented at this time (postponed until phase 2 of the development).
+ */
+int
+sata_hba_attach(dev_info_t *dip, sata_hba_tran_t *sata_tran,
+ ddi_attach_cmd_t cmd)
+{
+ sata_hba_inst_t *sata_hba_inst;
+ scsi_hba_tran_t *scsi_tran = NULL;
+ int hba_attach_state = 0;
+
+ SATADBG3(SATA_DBG_HBA_IF, NULL,
+ "sata_hba_attach: node %s (%s%d)\n",
+ ddi_node_name(dip), ddi_driver_name(dip),
+ ddi_get_instance(dip));
+
+ if (cmd == DDI_RESUME) {
+ /*
+ * Postponed until phase 2 of the development
+ */
+ return (DDI_FAILURE);
+ }
+
+ if (cmd != DDI_ATTACH) {
+ return (DDI_FAILURE);
+ }
+
+ /* cmd == DDI_ATTACH */
+
+ if (sata_validate_sata_hba_tran(dip, sata_tran) != SATA_SUCCESS) {
+ SATA_LOG_D((NULL, CE_WARN,
+ "sata_hba_attach: invalid sata_hba_tran"));
+ return (DDI_FAILURE);
+ }
+ /*
+ * Allocate and initialize SCSI tran structure.
+ * SATA copy of tran_bus_config is provided to create port nodes.
+ */
+ scsi_tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP);
+ if (scsi_tran == NULL)
+ return (DDI_FAILURE);
+ /*
+ * Allocate soft structure for SATA HBA instance.
+ * There is a separate softstate for each HBA instance.
+ */
+ sata_hba_inst = kmem_zalloc(sizeof (struct sata_hba_inst), KM_SLEEP);
+ ASSERT(sata_hba_inst != NULL); /* this should not fail */
+ mutex_init(&sata_hba_inst->satahba_mutex, NULL, MUTEX_DRIVER, NULL);
+ hba_attach_state |= HBA_ATTACH_STAGE_SATA_HBA_INST;
+
+ /*
+ * scsi_trans's tran_hba_private is used by SATA Framework to point to
+ * soft structure allocated by SATA framework for
+ * SATA HBA instance related data.
+ */
+ scsi_tran->tran_hba_private = sata_hba_inst;
+ scsi_tran->tran_tgt_private = NULL;
+
+ scsi_tran->tran_tgt_init = sata_scsi_tgt_init;
+ scsi_tran->tran_tgt_probe = sata_scsi_tgt_probe;
+ scsi_tran->tran_tgt_free = sata_scsi_tgt_free;
+
+ scsi_tran->tran_start = sata_scsi_start;
+ scsi_tran->tran_reset = sata_scsi_reset;
+ scsi_tran->tran_abort = sata_scsi_abort;
+ scsi_tran->tran_getcap = sata_scsi_getcap;
+ scsi_tran->tran_setcap = sata_scsi_setcap;
+ scsi_tran->tran_init_pkt = sata_scsi_init_pkt;
+ scsi_tran->tran_destroy_pkt = sata_scsi_destroy_pkt;
+
+ scsi_tran->tran_dmafree = sata_scsi_dmafree;
+ scsi_tran->tran_sync_pkt = sata_scsi_sync_pkt;
+
+ scsi_tran->tran_reset_notify = NULL;
+ scsi_tran->tran_get_bus_addr = NULL;
+ scsi_tran->tran_quiesce = NULL;
+ scsi_tran->tran_unquiesce = NULL;
+ scsi_tran->tran_bus_reset = NULL;
+
+ if (scsi_hba_attach_setup(dip, sata_tran->sata_tran_hba_dma_attr,
+ scsi_tran, 0) != DDI_SUCCESS) {
+#ifdef SATA_DEBUG
+ cmn_err(CE_WARN, "?SATA: %s%d hba scsi attach failed",
+ ddi_driver_name(dip), ddi_get_instance(dip));
+#endif
+ goto fail;
+ }
+ hba_attach_state |= HBA_ATTACH_STAGE_SCSI_ATTACHED;
+
+ if (!ddi_prop_exists(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS, "sata")) {
+ if (ddi_prop_update_int(DDI_DEV_T_NONE, dip,
+ "sata", 1) != DDI_PROP_SUCCESS) {
+ SATA_LOG_D((NULL, CE_WARN, "sata_hba_attach: "
+ "failed to create hba sata prop"));
+ goto fail;
+ }
+ }
+
+ /*
+ * Save pointers in hba instance soft state.
+ */
+ sata_hba_inst->satahba_scsi_tran = scsi_tran;
+ sata_hba_inst->satahba_tran = sata_tran;
+ sata_hba_inst->satahba_dip = dip;
+
+ hba_attach_state |= HBA_ATTACH_STAGE_SETUP;
+
+ /*
+ * Create events thread if not created yet.
+ */
+ sata_event_thread_control(1);
+
+ /*
+ * Link this hba instance into the list.
+ */
+ mutex_enter(&sata_mutex);
+
+
+ sata_hba_inst->satahba_next = NULL;
+ sata_hba_inst->satahba_prev = sata_hba_list_tail;
+ if (sata_hba_list == NULL) {
+ sata_hba_list = sata_hba_inst;
+ }
+ if (sata_hba_list_tail != NULL) {
+ sata_hba_list_tail->satahba_next = sata_hba_inst;
+ }
+ sata_hba_list_tail = sata_hba_inst;
+ mutex_exit(&sata_mutex);
+ hba_attach_state |= HBA_ATTACH_STAGE_LINKED;
+
+ /*
+ * Create SATA HBA devctl minor node for sata_hba_open, close, ioctl
+ * SATA HBA driver should not use its own open/close entry points.
+ *
+ * Make sure that instance number doesn't overflow
+ * when forming minor numbers.
+ */
+ ASSERT(ddi_get_instance(dip) <= (L_MAXMIN >> INST_MINOR_SHIFT));
+ if (ddi_create_minor_node(dip, "devctl", S_IFCHR,
+ INST2DEVCTL(ddi_get_instance(dip)),
+ DDI_NT_SATA_NEXUS, 0) != DDI_SUCCESS) {
+#ifdef SATA_DEBUG
+ cmn_err(CE_WARN, "sata_hba_attach: "
+ "cannot create devctl minor node");
+#endif
+ goto fail;
+ }
+
+
+ /*
+ * Set-up kstats here, if necessary.
+ * (postponed until phase 2 of the development).
+ */
+
+
+ /*
+ * Probe controller ports. This operation will describe a current
+ * controller/port/multipliers/device configuration and will create
+ * attachment points.
+ * We may end-up with just a controller with no devices attached.
+ */
+ sata_probe_ports(sata_hba_inst);
+
+ /*
+ * Create child nodes for all possible target devices currently
+ * attached to controller's ports and port multiplier device ports.
+ */
+ sata_make_device_nodes(sata_tran->sata_tran_hba_dip, sata_hba_inst);
+
+ sata_hba_inst->satahba_attached = 1;
+ return (DDI_SUCCESS);
+
+fail:
+ if (hba_attach_state & HBA_ATTACH_STAGE_LINKED) {
+ (void) sata_remove_hba_instance(dip);
+ if (sata_hba_list == NULL)
+ sata_event_thread_control(0);
+ }
+ if (hba_attach_state & HBA_ATTACH_STAGE_SETUP)
+ (void) ddi_prop_remove(DDI_DEV_T_ANY, dip, "sata");
+
+ if (hba_attach_state & HBA_ATTACH_STAGE_SCSI_ATTACHED)
+ (void) scsi_hba_detach(dip);
+
+ if (hba_attach_state & HBA_ATTACH_STAGE_SATA_HBA_INST) {
+ mutex_destroy(&sata_hba_inst->satahba_mutex);
+ kmem_free((void *)sata_hba_inst,
+ sizeof (struct sata_hba_inst));
+ scsi_hba_tran_free(scsi_tran);
+ }
+
+ sata_log(NULL, CE_WARN, "?SATA: %s%d hba attach failed",
+ ddi_driver_name(dip), ddi_get_instance(dip));
+
+ return (DDI_FAILURE);
+}
+
+
+/*
+ * Called by SATA HBA from to detach an instance of the driver.
+ *
+ * For DDI_DETACH command:
+ * Free local structures allocated for SATA HBA instance during
+ * sata_hba_attach processing.
+ *
+ * Returns DDI_SUCCESS when HBA was detached, DDI_FAILURE otherwise.
+ *
+ * For DDI_SUSPEND command:
+ * Not implemented at this time (postponed until phase 2 of the development)
+ * Returnd DDI_SUCCESS.
+ *
+ * When the last HBA instance is detached, the event daemon is terminated.
+ *
+ * NOTE: cport support only, no port multiplier support.
+ */
+int
+sata_hba_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
+{
+ dev_info_t *tdip;
+ sata_hba_inst_t *sata_hba_inst;
+ scsi_hba_tran_t *scsi_hba_tran;
+ sata_cport_info_t *cportinfo;
+ sata_drive_info_t *sdinfo;
+ int ncport;
+
+ SATADBG3(SATA_DBG_HBA_IF, NULL, "sata_hba_detach: node %s (%s%d)\n",
+ ddi_node_name(dip), ddi_driver_name(dip), ddi_get_instance(dip));
+
+ switch (cmd) {
+ case DDI_DETACH:
+
+ if ((scsi_hba_tran = ddi_get_driver_private(dip)) == NULL)
+ return (DDI_FAILURE);
+
+ sata_hba_inst = scsi_hba_tran->tran_hba_private;
+ if (sata_hba_inst == NULL)
+ return (DDI_FAILURE);
+
+ if (scsi_hba_detach(dip) == DDI_FAILURE) {
+ sata_hba_inst->satahba_attached = 1;
+ return (DDI_FAILURE);
+ }
+
+ /*
+ * Free all target nodes - at this point
+ * devices should be at least offlined
+ * otherwise scsi_hba_detach() should not be called.
+ */
+ for (ncport = 0; ncport < SATA_NUM_CPORTS(sata_hba_inst);
+ ncport++) {
+ cportinfo = SATA_CPORT_INFO(sata_hba_inst, ncport);
+ if (cportinfo->cport_dev_type != SATA_DTYPE_PMULT) {
+ sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
+ if (sdinfo != NULL) {
+ tdip = sata_get_target_dip(dip,
+ ncport);
+ if (tdip != NULL) {
+ if (ndi_devi_offline(tdip,
+ NDI_DEVI_REMOVE) !=
+ NDI_SUCCESS) {
+ SATA_LOG_D((
+ sata_hba_inst,
+ CE_WARN,
+ "sata_hba_detach: "
+ "Target node not "
+ "removed !"));
+ return (DDI_FAILURE);
+ }
+ }
+ }
+ }
+ }
+ /*
+ * Disable sata event daemon processing for this HBA
+ */
+ sata_hba_inst->satahba_attached = 0;
+
+ /*
+ * Remove event daemon thread, if it is last HBA instance.
+ */
+
+ mutex_enter(&sata_mutex);
+ if (sata_hba_list->satahba_next == NULL) {
+ mutex_exit(&sata_mutex);
+ sata_event_thread_control(0);
+ mutex_enter(&sata_mutex);
+ }
+ mutex_exit(&sata_mutex);
+
+ /* Remove this HBA instance from the HBA list */
+ sata_remove_hba_instance(dip);
+
+ /*
+ * At this point there should be no target nodes attached.
+ * Detach and destroy device and port info structures.
+ */
+ for (ncport = 0; ncport < SATA_NUM_CPORTS(sata_hba_inst);
+ ncport++) {
+ cportinfo = SATA_CPORT_INFO(sata_hba_inst, ncport);
+ if (cportinfo->cport_dev_type != SATA_DTYPE_PMULT) {
+ sdinfo =
+ cportinfo->cport_devp.cport_sata_drive;
+ if (sdinfo != NULL) {
+ /* Release device structure */
+ kmem_free(sdinfo,
+ sizeof (sata_drive_info_t));
+ }
+ /* Release cport info */
+ mutex_destroy(&cportinfo->cport_mutex);
+ kmem_free(cportinfo,
+ sizeof (sata_cport_info_t));
+ }
+ }
+
+ scsi_hba_tran_free(sata_hba_inst->satahba_scsi_tran);
+
+ mutex_destroy(&sata_hba_inst->satahba_mutex);
+ kmem_free((void *)sata_hba_inst,
+ sizeof (struct sata_hba_inst));
+
+ return (DDI_SUCCESS);
+
+ case DDI_SUSPEND:
+ /*
+ * Postponed until phase 2
+ */
+ return (DDI_FAILURE);
+
+ default:
+ return (DDI_FAILURE);
+ }
+}
+
+
+/*
+ * Called by an HBA drive from _fini() routine.
+ * Unregisters SATA HBA instance/SATA framework pair from the scsi framework.
+ */
+void
+sata_hba_fini(struct modlinkage *modlp)
+{
+ SATADBG1(SATA_DBG_HBA_IF, NULL,
+ "sata_hba_fini: name %s\n",
+ ((struct modldrv *)(modlp->ml_linkage[0]))->drv_linkinfo);
+
+ scsi_hba_fini(modlp);
+}
+
+
+/*
+ * Default open and close routine for sata_hba framework.
+ *
+ */
+/*
+ * Open devctl node.
+ *
+ * Returns:
+ * 0 if node was open successfully, error code otherwise.
+ *
+ *
+ */
+
+static int
+sata_hba_open(dev_t *devp, int flags, int otyp, cred_t *credp)
+{
+#ifndef __lock_lint
+ _NOTE(ARGUNUSED(credp))
+#endif
+ int rv = 0;
+ dev_info_t *dip;
+ scsi_hba_tran_t *scsi_hba_tran;
+ sata_hba_inst_t *sata_hba_inst;
+
+ SATADBG1(SATA_DBG_IOCTL_IF, NULL, "sata_hba_open: entered", NULL);
+
+ if (otyp != OTYP_CHR)
+ return (EINVAL);
+
+ dip = sata_devt_to_devinfo(*devp);
+ if (dip == NULL)
+ return (ENXIO);
+
+ if ((scsi_hba_tran = ddi_get_driver_private(dip)) == NULL)
+ return (ENXIO);
+
+ sata_hba_inst = scsi_hba_tran->tran_hba_private;
+ if (sata_hba_inst == NULL || sata_hba_inst->satahba_attached == 0)
+ return (ENXIO);
+
+ mutex_enter(&sata_mutex);
+ if (flags & FEXCL) {
+ if (sata_hba_inst->satahba_open_flag != 0) {
+ rv = EBUSY;
+ } else {
+ sata_hba_inst->satahba_open_flag =
+ SATA_DEVCTL_EXOPENED;
+ }
+ } else {
+ if (sata_hba_inst->satahba_open_flag == SATA_DEVCTL_EXOPENED) {
+ rv = EBUSY;
+ } else {
+ sata_hba_inst->satahba_open_flag =
+ SATA_DEVCTL_SOPENED;
+ }
+ }
+ mutex_exit(&sata_mutex);
+
+ return (rv);
+}
+
+
+/*
+ * Close devctl node.
+ * Returns:
+ * 0 if node was closed successfully, error code otherwise.
+ *
+ */
+
+static int
+sata_hba_close(dev_t dev, int flag, int otyp, cred_t *credp)
+{
+#ifndef __lock_lint
+ _NOTE(ARGUNUSED(credp))
+ _NOTE(ARGUNUSED(flag))
+#endif
+ dev_info_t *dip;
+ scsi_hba_tran_t *scsi_hba_tran;
+ sata_hba_inst_t *sata_hba_inst;
+
+ SATADBG1(SATA_DBG_IOCTL_IF, NULL, "sata_hba_close: entered", NULL);
+
+ if (otyp != OTYP_CHR)
+ return (EINVAL);
+
+ dip = sata_devt_to_devinfo(dev);
+ if (dip == NULL)
+ return (ENXIO);
+
+ if ((scsi_hba_tran = ddi_get_driver_private(dip)) == NULL)
+ return (ENXIO);
+
+ sata_hba_inst = scsi_hba_tran->tran_hba_private;
+ if (sata_hba_inst == NULL || sata_hba_inst->satahba_attached == 0)
+ return (ENXIO);
+
+ mutex_enter(&sata_mutex);
+ sata_hba_inst->satahba_open_flag = 0;
+ mutex_exit(&sata_mutex);
+ return (0);
+}
+
+
+
+/*
+ * Standard IOCTL commands for SATA hotplugging.
+ * Implemented DEVCTL_AP commands:
+ * DEVCTL_AP_CONNECT
+ * DEVCTL_AP_DISCONNECT
+ * DEVCTL_AP_CONFIGURE
+ * DEVCTL_UNCONFIGURE
+ * DEVCTL_AP_CONTROL
+ *
+ * Commands passed to default ndi ioctl handler:
+ * DEVCTL_DEVICE_GETSTATE
+ * DEVCTL_DEVICE_ONLINE
+ * DEVCTL_DEVICE_OFFLINE
+ * DEVCTL_DEVICE_REMOVE
+ * DEVCTL_DEVICE_INSERT
+ * DEVCTL_BUS_GETSTATE
+ *
+ * All other cmds are passed to HBA if it provide ioctl handler, or failed
+ * if not.
+ *
+ * Returns:
+ * 0 if successful,
+ * error code if operation failed.
+ *
+ * NOTE: Port Multiplier is not supported.
+ *
+ */
+
+static int
+sata_hba_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
+ int *rvalp)
+{
+#ifndef __lock_lint
+ _NOTE(ARGUNUSED(credp))
+ _NOTE(ARGUNUSED(rvalp))
+#endif
+ int rv = 0;
+ int32_t comp_port = -1;
+ dev_info_t *dip, *tdip;
+ devctl_ap_state_t ap_state;
+ struct devctl_iocdata *dcp = NULL;
+ scsi_hba_tran_t *scsi_hba_tran;
+ sata_hba_inst_t *sata_hba_inst;
+ sata_device_t sata_device;
+ sata_drive_info_t *sdinfo;
+ sata_cport_info_t *cportinfo;
+ int cport, pmport, qual;
+ int rval = SATA_SUCCESS;
+
+ dip = sata_devt_to_devinfo(dev);
+ if (dip == NULL)
+ return (ENXIO);
+
+ if ((scsi_hba_tran = ddi_get_driver_private(dip)) == NULL)
+ return (ENXIO);
+
+ sata_hba_inst = scsi_hba_tran->tran_hba_private;
+ if (sata_hba_inst == NULL)
+ return (ENXIO);
+
+ if (sata_hba_inst->satahba_tran == NULL)
+ return (ENXIO);
+
+ switch (cmd) {
+
+ case DEVCTL_DEVICE_GETSTATE:
+ case DEVCTL_DEVICE_ONLINE:
+ case DEVCTL_DEVICE_OFFLINE:
+ case DEVCTL_DEVICE_REMOVE:
+ case DEVCTL_BUS_GETSTATE:
+ /*
+ * There may be more cases that we want to pass to default
+ * handler rather then fail them.
+ */
+ return (ndi_devctl_ioctl(dip, cmd, arg, mode, 0));
+ }
+
+ /* read devctl ioctl data */
+ if (cmd != DEVCTL_AP_CONTROL) {
+ if (ndi_dc_allochdl((void *)arg, &dcp) != NDI_SUCCESS)
+ return (EFAULT);
+
+ if ((comp_port = sata_get_port_num(sata_hba_inst, dcp)) ==
+ -1) {
+ if (dcp)
+ ndi_dc_freehdl(dcp);
+ return (EINVAL);
+ }
+
+ cport = SCSI_TO_SATA_CPORT(comp_port);
+ pmport = SCSI_TO_SATA_PMPORT(comp_port);
+ /* Only cport is considered now, i.e. SATA_ADDR_CPORT */
+ qual = SATA_ADDR_CPORT;
+ if (sata_validate_sata_address(sata_hba_inst, cport, pmport,
+ qual) != 0) {
+ ndi_dc_freehdl(dcp);
+ return (EINVAL);
+ }
+
+ cportinfo = SATA_CPORT_INFO(sata_hba_inst, cport);
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ if (cportinfo->cport_event_flags & SATA_EVNT_LOCK_PORT_BUSY) {
+ /*
+ * Cannot process ioctl request now. Come back later.
+ */
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ ndi_dc_freehdl(dcp);
+ return (EBUSY);
+ }
+ /* Block event processing for this port */
+ cportinfo->cport_event_flags |= SATA_APCTL_LOCK_PORT_BUSY;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
+
+ sata_device.satadev_addr.cport = cport;
+ sata_device.satadev_addr.pmport = pmport;
+ sata_device.satadev_addr.qual = SATA_ADDR_CPORT;
+ sata_device.satadev_rev = SATA_DEVICE_REV;
+ }
+
+ switch (cmd) {
+
+ case DEVCTL_AP_DISCONNECT:
+ /*
+ * Normally, cfgadm sata plugin will try to offline
+ * (unconfigure) device before this request. Nevertheless,
+ * if a device is still configured, we need to
+ * attempt to offline and unconfigure device first, and we will
+ * deactivate the port regardless of the unconfigure
+ * operation results.
+ *
+ * DEVCTL_AP_DISCONNECT invokes
+ * sata_hba_inst->satahba_tran->
+ * sata_tran_hotplug_ops->sata_tran_port_deactivate().
+ * If successful, the device structure (if any) attached
+ * to a port is removed and state of the port marked
+ * appropriately.
+ * Failure of the port_deactivate may keep port in
+ * the active state, or may fail the port.
+ */
+
+ /* Check the current state of the port */
+ if (sata_reprobe_port(sata_hba_inst, &sata_device) !=
+ SATA_SUCCESS) {
+ rv = EIO;
+ break;
+ }
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ if (cportinfo->cport_state &
+ (SATA_PSTATE_SHUTDOWN | SATA_PSTATE_FAILED)) {
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ rv = EIO;
+ break;
+ }
+ /*
+ * set port's dev_state to not ready - this will disable
+ * an access to an attached device.
+ */
+ cportinfo->cport_state &= ~SATA_STATE_READY;
+
+ if (cportinfo->cport_dev_type != SATA_DTYPE_NONE) {
+ sdinfo = cportinfo->cport_devp.cport_sata_drive;
+ ASSERT(sdinfo != NULL);
+ if ((sdinfo->satadrv_type &
+ (SATA_VALID_DEV_TYPE))) {
+ /*
+ * If a target node exists, try to offline
+ * a device and remove target node.
+ */
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ tdip = sata_get_target_dip(dip, comp_port);
+ if (tdip != NULL) {
+ /* target node exist */
+ if (ndi_devi_offline(tdip,
+ NDI_DEVI_REMOVE) != NDI_SUCCESS) {
+ /*
+ * Problem
+ * A target node remained
+ * attached. This happens when
+ * the file was open or a node
+ * was waiting for resources.
+ * Cannot do anything about it.
+ */
+ SATA_LOG_D((sata_hba_inst,
+ CE_WARN,
+ "sata_hba_ioctl: "
+ "disconnect: cannot "
+ "remove target node!!!"));
+ }
+ }
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ /*
+ * Remove and release sata_drive_info
+ * structure.
+ */
+ if (SATA_CPORTINFO_DRV_INFO(cportinfo) !=
+ NULL) {
+ SATA_CPORTINFO_DRV_INFO(cportinfo) =
+ NULL;
+ (void) kmem_free((void *)sdinfo,
+ sizeof (sata_drive_info_t));
+ cportinfo->cport_dev_type =
+ SATA_DTYPE_NONE;
+ }
+ }
+ /*
+ * Note: PMult info requires different handling.
+ * Put PMult handling code here, when PNult is
+ * supported.
+ */
+ }
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
+ /* Sanity check */
+ if (SATA_PORT_DEACTIVATE_FUNC(sata_hba_inst) == NULL) {
+ /* No physical port deactivation supported. */
+ break;
+ }
+
+ /* Just ask HBA driver to deactivate port */
+ sata_device.satadev_addr.qual = SATA_ADDR_DCPORT;
+
+ rval = (*SATA_PORT_DEACTIVATE_FUNC(sata_hba_inst))
+ (dip, &sata_device);
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ sata_update_port_info(sata_hba_inst, &sata_device);
+
+ if (rval != SATA_SUCCESS) {
+ /*
+ * Port deactivation failure - do not
+ * change port state unless the state
+ * returned by HBA indicates a port failure.
+ */
+ if (sata_device.satadev_state & SATA_PSTATE_FAILED)
+ cportinfo->cport_state = SATA_PSTATE_FAILED;
+ rv = EIO;
+ } else {
+ cportinfo->cport_state |= SATA_PSTATE_SHUTDOWN;
+ }
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
+ break;
+
+ case DEVCTL_AP_UNCONFIGURE:
+
+ /*
+ * The unconfigure operation uses generic nexus operation to
+ * offline a device. It leaves a target device node attached.
+ * and obviously sata_drive_info attached as well, because
+ * from the hardware point of view nothing has changed.
+ */
+ if ((tdip = sata_get_target_dip(dip, comp_port)) != NULL) {
+
+ if (ndi_devi_offline(tdip, NDI_UNCONFIG) !=
+ NDI_SUCCESS) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_ioctl: unconfigure: "
+ "failed to unconfigure "
+ "device at cport %d", cport));
+ rv = EIO;
+ }
+ /*
+ * The target node devi_state should be marked with
+ * DEVI_DEVICE_OFFLINE by ndi_devi_offline().
+ * This would be the indication for cfgadm that
+ * the AP node occupant state is 'unconfigured'.
+ */
+
+ } else {
+ /*
+ * This would indicate a failure on the part of cfgadm
+ * to detect correct state of the node prior to this
+ * call - one cannot unconfigure non-existing device.
+ */
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_ioctl: unconfigure: "
+ "attempt to unconfigure non-existing device "
+ "at cport %d", cport));
+ rv = ENXIO;
+ }
+
+ break;
+
+ case DEVCTL_AP_CONNECT:
+ {
+ /*
+ * The sata cfgadm pluging will invoke this operation only if
+ * port was found in the disconnect state (failed state
+ * is also treated as the disconnected state).
+ * DEVCTL_AP_CONNECT would invoke
+ * sata_hba_inst->satahba_tran->
+ * sata_tran_hotplug_ops->sata_tran_port_activate().
+ * If successful and a device is found attached to the port,
+ * the initialization sequence is executed to attach
+ * a device structure to a port structure. The device is not
+ * set in configured state (system-wise) by this operation.
+ * The state of the port and a device would be set
+ * appropriately.
+ *
+ * Note, that activating the port may generate link events,
+ * so is is important that following processing and the
+ * event processing does not interfere with each other!
+ *
+ * This operation may remove port failed state and will
+ * try to make port active and in good standing.
+ */
+
+ /* We only care about host sata cport for now */
+
+ if (SATA_PORT_ACTIVATE_FUNC(sata_hba_inst) != NULL) {
+ /* Just let HBA driver to activate port */
+
+ if ((*SATA_PORT_ACTIVATE_FUNC(sata_hba_inst))
+ (dip, &sata_device) != SATA_SUCCESS) {
+ /*
+ * Port activation failure.
+ */
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ sata_update_port_info(sata_hba_inst,
+ &sata_device);
+ if (sata_device.satadev_state &
+ SATA_PSTATE_FAILED) {
+ cportinfo->cport_state =
+ SATA_PSTATE_FAILED;
+ }
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_ioctl: connect: "
+ "failed to activate SATA cport %d",
+ cport));
+ rv = EIO;
+ break;
+ }
+ }
+ /* Virgin port state - will be updated by the port re-probe. */
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ cportinfo->cport_state = 0;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+
+ if (sata_reprobe_port(sata_hba_inst, &sata_device) ==
+ SATA_FAILURE)
+ rv = EIO;
+
+ /*
+ * If there is a device attached to the port, emit
+ * a message.
+ */
+ if (cportinfo->cport_dev_type != SATA_DTYPE_NONE) {
+ sata_log(sata_hba_inst, CE_WARN,
+ "SATA device attached at port %d", cport);
+ }
+ break;
+ }
+
+ case DEVCTL_AP_CONFIGURE:
+ {
+ boolean_t target = TRUE;
+
+ /*
+ * A port may be in an active or shutdown state.
+ * If port is in a failed state, operation is aborted - one
+ * has to use explicit connect or port activate request
+ * to try to get a port into non-failed mode.
+ *
+ * If a port is in a shutdown state, arbitrarily invoke
+ * sata_tran_port_activate() prior to any other operation.
+ *
+ * Verify that port state is READY and there is a device
+ * of a supported type attached to this port.
+ * If target node exists, a device was most likely offlined.
+ * If target node does not exist, create a target node an
+ * attempt to online it.
+ * *
+ * NO PMult or devices beyond PMult are supported yet.
+ */
+
+ /* We only care about host controller's sata cport for now. */
+ if (cportinfo->cport_state & SATA_PSTATE_FAILED) {
+ rv = ENXIO;
+ break;
+ }
+ /* Check the current state of the port */
+ sata_device.satadev_addr.qual = SATA_ADDR_CPORT;
+
+ rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
+ (dip, &sata_device);
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ sata_update_port_info(sata_hba_inst, &sata_device);
+ if (rval != SATA_SUCCESS ||
+ (sata_device.satadev_state & SATA_PSTATE_FAILED)) {
+ cportinfo->cport_state = SATA_PSTATE_FAILED;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ rv = EIO;
+ break;
+ }
+ if (cportinfo->cport_state & SATA_PSTATE_SHUTDOWN) {
+ target = TRUE;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+
+ if (SATA_PORT_ACTIVATE_FUNC(sata_hba_inst) != NULL) {
+ /* Just let HBA driver to activate port */
+ if ((*SATA_PORT_ACTIVATE_FUNC(sata_hba_inst))
+ (dip, &sata_device) != SATA_SUCCESS) {
+ /*
+ * Port activation failure - do not
+ * change port state unless the state
+ * returned by HBA indicates a port
+ * failure.
+ */
+ mutex_enter(&SATA_CPORT_INFO(
+ sata_hba_inst, cport)->cport_mutex);
+ sata_update_port_info(sata_hba_inst,
+ &sata_device);
+ if (sata_device.satadev_state &
+ SATA_PSTATE_FAILED) {
+ cportinfo->cport_state =
+ SATA_PSTATE_FAILED;
+ }
+ mutex_exit(&SATA_CPORT_INFO(
+ sata_hba_inst, cport)->cport_mutex);
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_ioctl: configure: "
+ "failed to activate SATA cport %d",
+ cport));
+ rv = EIO;
+ break;
+ }
+ }
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ /* Virgin port state */
+ cportinfo->cport_state = 0;
+ }
+ /*
+ * Always reprobe port, to get current device info.
+ */
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
+ if (sata_reprobe_port(sata_hba_inst, &sata_device) !=
+ SATA_SUCCESS) {
+ rv = EIO;
+ break;
+ }
+ if (target == FALSE &&
+ cportinfo->cport_dev_type != SATA_DTYPE_NONE) {
+ /*
+ * That's the transition from "inactive" port to
+ * active with device attached.
+ */
+ sata_log(sata_hba_inst, CE_WARN,
+ "SATA device attached at port %d",
+ cport);
+ }
+
+ /*
+ * This is where real configure starts.
+ * Change following check for PMult support.
+ */
+ if (!(sata_device.satadev_type & SATA_VALID_DEV_TYPE)) {
+ /* No device to configure */
+ rv = ENXIO; /* No device to configure */
+ break;
+ }
+
+ /*
+ * Here we may have a device in reset condition,
+ * but because we are just configuring it, there is
+ * no need to process the reset other than just
+ * to clear device reset condition in the HBA driver.
+ * Setting the flag SATA_EVNT_CLEAR_DEVICE_RESET will
+ * cause a first command sent the HBA driver with the request
+ * to clear device reset condition.
+ */
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ sdinfo = sata_get_device_info(sata_hba_inst, &sata_device);
+ if (sdinfo == NULL) {
+ rv = ENXIO;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ break;
+ }
+ if (sdinfo->satadrv_event_flags &
+ (SATA_EVNT_DEVICE_RESET | SATA_EVNT_INPROC_DEVICE_RESET))
+ sdinfo->satadrv_event_flags = 0;
+ sdinfo->satadrv_event_flags |= SATA_EVNT_CLEAR_DEVICE_RESET;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
+
+ if ((tdip = sata_get_target_dip(dip, comp_port)) != NULL) {
+ /* target node still exists */
+ if (ndi_devi_online(tdip, 0) != NDI_SUCCESS) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_ioctl: configure: "
+ "onlining device at cport %d failed",
+ cport));
+ rv = EIO;
+ break;
+ }
+ } else {
+ /*
+ * No target node - need to create a new target node.
+ */
+ tdip = sata_create_target_node(dip, sata_hba_inst,
+ &sata_device.satadev_addr);
+ if (tdip == NULL) {
+ /* configure failed */
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_ioctl: configure: "
+ "configuring device at cport %d "
+ "failed", cport));
+ rv = EIO;
+ break;
+ }
+ }
+
+ break;
+ }
+
+ case DEVCTL_AP_GETSTATE:
+
+ sata_cfgadm_state(sata_hba_inst, comp_port, &ap_state);
+
+ ap_state.ap_last_change = (time_t)-1;
+ ap_state.ap_error_code = 0;
+ ap_state.ap_in_transition = 0;
+
+ /* Copy the return AP-state information to the user space */
+ if (ndi_dc_return_ap_state(&ap_state, dcp) != NDI_SUCCESS) {
+ rv = EFAULT;
+ }
+ break;
+
+ case DEVCTL_AP_CONTROL:
+ {
+ /*
+ * Generic devctl for hardware specific functionality
+ */
+ sata_ioctl_data_t ioc;
+
+ ASSERT(dcp == NULL);
+
+ /* Copy in user ioctl data first */
+#ifdef _MULTI_DATAMODEL
+ if (ddi_model_convert_from(mode & FMODELS) ==
+ DDI_MODEL_ILP32) {
+
+ sata_ioctl_data_32_t ioc32;
+
+ if (ddi_copyin((void *)arg, (void *)&ioc32,
+ sizeof (ioc32), mode) != 0) {
+ rv = EFAULT;
+ break;
+ }
+ ioc.cmd = (uint_t)ioc32.cmd;
+ ioc.port = (uint_t)ioc32.port;
+ ioc.get_size = (uint_t)ioc32.get_size;
+ ioc.buf = (caddr_t)(uintptr_t)ioc32.buf;
+ ioc.bufsiz = (uint_t)ioc32.bufsiz;
+ ioc.misc_arg = (uint_t)ioc32.misc_arg;
+ } else
+#endif /* _MULTI_DATAMODEL */
+ if (ddi_copyin((void *)arg, (void *)&ioc, sizeof (ioc),
+ mode) != 0) {
+ return (EFAULT);
+ }
+
+ SATADBG2(SATA_DBG_IOCTL_IF, sata_hba_inst,
+ "sata_hba_ioctl: DEVCTL_AP_CONTROL "
+ "cmd 0x%x, port 0x%x", ioc.cmd, ioc.port);
+
+ /*
+ * To avoid BE/LE and 32/64 issues, a get_size always returns
+ * a 32-bit number.
+ */
+ if (ioc.get_size != 0 && ioc.bufsiz != (sizeof (uint32_t))) {
+ return (EINVAL);
+ }
+ /* validate address */
+ cport = SCSI_TO_SATA_CPORT(ioc.port);
+ pmport = SCSI_TO_SATA_PMPORT(ioc.port);
+ qual = SCSI_TO_SATA_ADDR_QUAL(ioc.port);
+
+ /* Override address qualifier - handle cport only for now */
+ qual = SATA_ADDR_CPORT;
+
+ if (sata_validate_sata_address(sata_hba_inst, cport,
+ pmport, qual) != 0)
+ return (EINVAL);
+
+ cportinfo = SATA_CPORT_INFO(sata_hba_inst, cport);
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ /* Is the port locked by event processing daemon ? */
+ if (cportinfo->cport_event_flags & SATA_EVNT_LOCK_PORT_BUSY) {
+ /*
+ * Cannot process ioctl request now. Come back later
+ */
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ return (EBUSY);
+ }
+ /* Block event processing for this port */
+ cportinfo->cport_event_flags |= SATA_APCTL_LOCK_PORT_BUSY;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
+
+
+ sata_device.satadev_addr.cport = cport;
+ sata_device.satadev_addr.pmport = pmport;
+ sata_device.satadev_rev = SATA_DEVICE_REV;
+
+ switch (ioc.cmd) {
+
+ case SATA_CFGA_RESET_PORT:
+ /*
+ * There is no protection here for configured
+ * device.
+ */
+
+ /* Sanity check */
+ if (SATA_RESET_DPORT_FUNC(sata_hba_inst) == NULL) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_ioctl: "
+ "sata_hba_tran missing required "
+ "function sata_tran_reset_dport"));
+ rv = EINVAL;
+ break;
+ }
+
+ /* handle cport only for now */
+ sata_device.satadev_addr.qual = SATA_ADDR_CPORT;
+ if ((*SATA_RESET_DPORT_FUNC(sata_hba_inst))
+ (dip, &sata_device) != SATA_SUCCESS) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_ioctl: reset port: "
+ "failed cport %d pmport %d",
+ cport, pmport));
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ sata_update_port_info(sata_hba_inst,
+ &sata_device);
+ SATA_CPORT_STATE(sata_hba_inst, cport) =
+ SATA_PSTATE_FAILED;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ rv = EIO;
+ }
+ /*
+ * Since the port was reset, it should be probed and
+ * attached device reinitialized. At this point the
+ * port state is unknown - it's state is HBA-specific.
+ * Re-probe port to get its state.
+ */
+ if (sata_reprobe_port(sata_hba_inst, &sata_device) !=
+ SATA_SUCCESS) {
+ rv = EIO;
+ break;
+ }
+ break;
+
+ case SATA_CFGA_RESET_DEVICE:
+ /*
+ * There is no protection here for configured
+ * device.
+ */
+
+ /* Sanity check */
+ if (SATA_RESET_DPORT_FUNC(sata_hba_inst) == NULL) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_ioctl: "
+ "sata_hba_tran missing required "
+ "function sata_tran_reset_dport"));
+ rv = EINVAL;
+ break;
+ }
+
+ /* handle only device attached to cports, for now */
+ sata_device.satadev_addr.qual = SATA_ADDR_DCPORT;
+
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ sdinfo = sata_get_device_info(sata_hba_inst,
+ &sata_device);
+ if (sdinfo == NULL) {
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ rv = EINVAL;
+ break;
+ }
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+
+ /* only handle cport for now */
+ sata_device.satadev_addr.qual = SATA_ADDR_DCPORT;
+ if ((*SATA_RESET_DPORT_FUNC(sata_hba_inst))
+ (dip, &sata_device) != SATA_SUCCESS) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_ioctl: reset device: failed "
+ "cport %d pmport %d", cport, pmport));
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ sata_update_port_info(sata_hba_inst,
+ &sata_device);
+ /*
+ * Device info structure remains
+ * attached. Another device reset or
+ * port disconnect/connect and re-probing is
+ * needed to change it's state
+ */
+ sdinfo->satadrv_state &= ~SATA_STATE_READY;
+ sdinfo->satadrv_state |=
+ SATA_DSTATE_FAILED;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ rv = EIO;
+ }
+ /*
+ * Since the device was reset, we expect reset event
+ * to be reported and processed.
+ */
+ break;
+
+ case SATA_CFGA_RESET_ALL:
+ {
+ int tcport;
+
+ /*
+ * There is no protection here for configured
+ * devices.
+ */
+ /* Sanity check */
+ if (SATA_RESET_DPORT_FUNC(sata_hba_inst) == NULL) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_ioctl: "
+ "sata_hba_tran missing required "
+ "function sata_tran_reset_dport"));
+ rv = EINVAL;
+ break;
+ }
+
+ /*
+ * Need to lock all ports, not just one.
+ * If any port is locked by event processing, fail
+ * the whole operation.
+ * One port is already locked, but for simplicity
+ * lock it again.
+ */
+ for (tcport = 0;
+ tcport < SATA_NUM_CPORTS(sata_hba_inst);
+ tcport++) {
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
+ tcport)->cport_mutex);
+ if (((SATA_CPORT_INFO(sata_hba_inst, tcport)->
+ cport_event_flags) &
+ SATA_EVNT_LOCK_PORT_BUSY) != 0) {
+ rv = EBUSY;
+ mutex_exit(
+ &SATA_CPORT_INFO(sata_hba_inst,
+ tcport)->cport_mutex);
+ break;
+ } else {
+ SATA_CPORT_INFO(sata_hba_inst,
+ tcport)->cport_event_flags |=
+ SATA_APCTL_LOCK_PORT_BUSY;
+ }
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
+ tcport)->cport_mutex);
+ }
+
+ if (rv == 0) {
+ /*
+ * All cports successfully locked.
+ * Reset main SATA controller only for now -
+ * no PMult.
+ */
+ sata_device.satadev_addr.qual =
+ SATA_ADDR_CNTRL;
+
+ if ((*SATA_RESET_DPORT_FUNC(sata_hba_inst))
+ (dip, &sata_device) != SATA_SUCCESS) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_ioctl: reset controller "
+ "failed"));
+ rv = EIO;
+ }
+
+ /*
+ * Since ports were reset, they should be
+ * re-probed and attached devices
+ * reinitialized.
+ * At this point port states are unknown,
+ * Re-probe ports to get their state -
+ * cports only for now.
+ */
+ for (tcport = 0;
+ tcport < SATA_NUM_CPORTS(sata_hba_inst);
+ tcport++) {
+ sata_device.satadev_addr.cport =
+ tcport;
+ sata_device.satadev_addr.qual =
+ SATA_ADDR_CPORT;
+
+ if (sata_reprobe_port(sata_hba_inst,
+ &sata_device) != SATA_SUCCESS)
+ rv = EIO;
+
+ }
+ }
+ /*
+ * Unlock all ports
+ */
+ for (tcport = 0;
+ tcport < SATA_NUM_CPORTS(sata_hba_inst);
+ tcport++) {
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
+ tcport)->cport_mutex);
+ SATA_CPORT_INFO(sata_hba_inst, tcport)->
+ cport_event_flags &=
+ ~SATA_APCTL_LOCK_PORT_BUSY;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
+ tcport)->cport_mutex);
+ }
+
+ /*
+ * This operation returns EFAULT if either reset
+ * controller failed or a re-probbing of any ports
+ * failed.
+ * We return here, because common return is for
+ * a single cport operation.
+ */
+ return (rv);
+ }
+
+ case SATA_CFGA_PORT_DEACTIVATE:
+ /* Sanity check */
+ if (SATA_PORT_DEACTIVATE_FUNC(sata_hba_inst) == NULL) {
+ rv = ENOTSUP;
+ break;
+ }
+ /*
+ * Arbitrarily unconfigure attached device, if any.
+ * Even if the unconfigure fails, proceed with the
+ * port deactivation.
+ */
+
+ /* Handle only device attached to cports, for now */
+ sata_device.satadev_addr.qual = SATA_ADDR_DCPORT;
+
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ cportinfo->cport_state &= ~SATA_STATE_READY;
+ if (cportinfo->cport_dev_type != SATA_DTYPE_NONE) {
+ /*
+ * Handle only device attached to cports,
+ * for now
+ */
+ sata_device.satadev_addr.qual =
+ SATA_ADDR_DCPORT;
+ sdinfo = sata_get_device_info(sata_hba_inst,
+ &sata_device);
+ if (sdinfo != NULL &&
+ (sdinfo->satadrv_type &
+ SATA_VALID_DEV_TYPE)) {
+ /*
+ * If a target node exists, try to
+ * offline a device and remove target
+ * node.
+ */
+ mutex_exit(&SATA_CPORT_INFO(
+ sata_hba_inst, cport)->cport_mutex);
+ tdip = sata_get_target_dip(dip, cport);
+ if (tdip != NULL) {
+ /* target node exist */
+ SATADBG1(SATA_DBG_IOCTL_IF,
+ sata_hba_inst,
+ "sata_hba_ioctl: "
+ "port deactivate: "
+ "target node exists.",
+ NULL);
+
+ if (ndi_devi_offline(tdip,
+ NDI_UNCONFIG) !=
+ NDI_SUCCESS) {
+ SATA_LOG_D((
+ sata_hba_inst,
+ CE_WARN,
+ "sata_hba_ioctl:"
+ "port deactivate: "
+ "failed to "
+ "unconfigure "
+ "device at cport "
+ "%d", cport));
+ }
+ if (ndi_devi_offline(tdip,
+ NDI_DEVI_REMOVE) !=
+ NDI_SUCCESS) {
+ /*
+ * Problem;
+ * target node remained
+ * attached.
+ * Too bad...
+ */
+ SATA_LOG_D((
+ sata_hba_inst,
+ CE_WARN,
+ "sata_hba_ioctl: "
+ "port deactivate: "
+ "failed to "
+ "unconfigure "
+ "device at "
+ "cport %d",
+ cport));
+ }
+ }
+ mutex_enter(&SATA_CPORT_INFO(
+ sata_hba_inst, cport)->cport_mutex);
+ /*
+ * In any case,
+ * remove and release sata_drive_info
+ * structure.
+ * (cport attached device ony, for now)
+ */
+ SATA_CPORTINFO_DRV_INFO(cportinfo) =
+ NULL;
+ (void) kmem_free((void *)sdinfo,
+ sizeof (sata_drive_info_t));
+ cportinfo->cport_dev_type =
+ SATA_DTYPE_NONE;
+ }
+ /*
+ * Note: PMult info requires different
+ * handling. This comment is a placeholder for
+ * a code handling PMult, to be implemented
+ * in phase 2.
+ */
+ }
+ cportinfo->cport_state &= ~(SATA_STATE_PROBED |
+ SATA_STATE_PROBING);
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ /* handle cport only for now */
+ sata_device.satadev_addr.qual = SATA_ADDR_CPORT;
+ /* Just let HBA driver to deactivate port */
+ rval = (*SATA_PORT_DEACTIVATE_FUNC(sata_hba_inst))
+ (dip, &sata_device);
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ sata_update_port_info(sata_hba_inst, &sata_device);
+ if (rval != SATA_SUCCESS) {
+ /*
+ * Port deactivation failure - do not
+ * change port state unless the state
+ * returned by HBA indicates a port failure.
+ */
+ if (sata_device.satadev_state &
+ SATA_PSTATE_FAILED) {
+ SATA_CPORT_STATE(sata_hba_inst,
+ cport) = SATA_PSTATE_FAILED;
+ }
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_ioctl: port deactivate: "
+ "cannot deactivate SATA cport %d",
+ cport));
+ rv = EIO;
+ } else {
+ cportinfo->cport_state |= SATA_PSTATE_SHUTDOWN;
+ }
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+
+ break;
+
+ case SATA_CFGA_PORT_ACTIVATE:
+ {
+ boolean_t dev_existed = TRUE;
+
+ /* Sanity check */
+ if (SATA_PORT_ACTIVATE_FUNC(sata_hba_inst) == NULL) {
+ rv = ENOTSUP;
+ break;
+ }
+ /* handle cport only for now */
+ if (cportinfo->cport_state & SATA_PSTATE_SHUTDOWN ||
+ cportinfo->cport_dev_type == SATA_DTYPE_NONE)
+ dev_existed = FALSE;
+
+ sata_device.satadev_addr.qual = SATA_ADDR_CPORT;
+ /* Just let HBA driver to activate port */
+ if ((*SATA_PORT_ACTIVATE_FUNC(sata_hba_inst))
+ (dip, &sata_device) != SATA_SUCCESS) {
+ /*
+ * Port activation failure - do not
+ * change port state unless the state
+ * returned by HBA indicates a port failure.
+ */
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ sata_update_port_info(sata_hba_inst,
+ &sata_device);
+ if (sata_device.satadev_state &
+ SATA_PSTATE_FAILED) {
+ SATA_CPORT_STATE(sata_hba_inst,
+ cport) = SATA_PSTATE_FAILED;
+ }
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_ioctl: port activate: "
+ "cannot activate SATA cport %d",
+ cport));
+ rv = EIO;
+ break;
+ }
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ cportinfo->cport_state &= ~SATA_PSTATE_SHUTDOWN;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+
+ /*
+ * Re-probe port to find its current state and
+ * possibly attached device.
+ * Port re-probing may change the cportinfo device
+ * type if device is found attached.
+ * If port probing failed, the device type would be
+ * set to SATA_DTYPE_NONE.
+ */
+ (void) sata_reprobe_port(sata_hba_inst, &sata_device);
+
+ if (dev_existed == FALSE &&
+ cportinfo->cport_dev_type != SATA_DTYPE_NONE) {
+ /*
+ * That's the transition from "inactive" port
+ * state or active port without a device
+ * attached to the active port state with
+ * a device attached.
+ */
+ sata_log(sata_hba_inst, CE_WARN,
+ "SATA device attached at port %d", cport);
+ }
+
+ break;
+ }
+
+ case SATA_CFGA_PORT_SELF_TEST:
+
+ /* Sanity check */
+ if (SATA_SELFTEST_FUNC(sata_hba_inst) == NULL) {
+ rv = ENOTSUP;
+ break;
+ }
+ /*
+ * There is no protection here for a configured
+ * device attached to this port.
+ */
+
+ /* only handle cport for now */
+ sata_device.satadev_addr.qual = SATA_ADDR_CPORT;
+
+ if ((*SATA_SELFTEST_FUNC(sata_hba_inst))
+ (dip, &sata_device) != SATA_SUCCESS) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_ioctl: port selftest: "
+ "failed cport %d pmport %d",
+ cport, pmport));
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ sata_update_port_info(sata_hba_inst,
+ &sata_device);
+ SATA_CPORT_STATE(sata_hba_inst, cport) =
+ SATA_PSTATE_FAILED;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ rv = EIO;
+ break;
+ }
+ /*
+ * Since the port was reset, it should be probed and
+ * attached device reinitialized. At this point the
+ * port state is unknown - it's state is HBA-specific.
+ * Force port re-probing to get it into a known state.
+ */
+ if (sata_reprobe_port(sata_hba_inst, &sata_device) !=
+ SATA_SUCCESS) {
+ rv = EIO;
+ break;
+ }
+ break;
+
+ case SATA_CFGA_GET_DEVICE_PATH:
+ {
+ char path[MAXPATHLEN];
+ uint32_t size;
+
+ (void) strcpy(path, "/devices");
+ if ((tdip = sata_get_target_dip(dip, ioc.port)) ==
+ NULL) {
+
+ /*
+ * No such device.
+ * If this is a request for a size, do not
+ * return EINVAL for non-exisiting target,
+ * because cfgadm will indicate a meaningless
+ * ioctl failure.
+ * If this is a real request for a path,
+ * indicate invalid argument.
+ */
+ if (!ioc.get_size) {
+ rv = EINVAL;
+ break;
+ }
+ } else {
+ (void) ddi_pathname(tdip, path + strlen(path));
+ }
+ size = strlen(path) + 1;
+
+ if (ioc.get_size) {
+ if (ddi_copyout((void *)&size,
+ ioc.buf, ioc.bufsiz, mode) != 0) {
+ rv = EFAULT;
+ }
+ } else {
+ if (ioc.bufsiz != size) {
+ rv = EINVAL;
+ } else if (ddi_copyout((void *)&path,
+ ioc.buf, ioc.bufsiz, mode) != 0) {
+ rv = EFAULT;
+ }
+ }
+ break;
+ }
+
+ case SATA_CFGA_GET_AP_TYPE:
+ {
+ uint32_t type_len;
+ const char *ap_type;
+
+ /* cport only, no port multiplier support */
+ switch (SATA_CPORT_DEV_TYPE(sata_hba_inst, cport)) {
+ case SATA_DTYPE_NONE:
+ ap_type = "port";
+ break;
+
+ case SATA_DTYPE_ATADISK:
+ ap_type = "disk";
+ break;
+
+ case SATA_DTYPE_ATAPICD:
+ ap_type = "cd/dvd";
+ break;
+
+ case SATA_DTYPE_PMULT:
+ ap_type = "pmult";
+ break;
+
+ case SATA_DTYPE_UNKNOWN:
+ ap_type = "unknown";
+ break;
+
+ default:
+ ap_type = "unsupported";
+ break;
+
+ } /* end of dev_type switch */
+
+ type_len = strlen(ap_type) + 1;
+
+ if (ioc.get_size) {
+ if (ddi_copyout((void *)&type_len,
+ ioc.buf, ioc.bufsiz, mode) != 0) {
+ rv = EFAULT;
+ break;
+ }
+ } else {
+ if (ioc.bufsiz != type_len) {
+ rv = EINVAL;
+ break;
+ }
+ if (ddi_copyout((void *)ap_type, ioc.buf,
+ ioc.bufsiz, mode) != 0) {
+ rv = EFAULT;
+ break;
+ }
+ }
+
+ break;
+ }
+
+ case SATA_CFGA_GET_MODEL_INFO:
+ {
+ uint32_t info_len;
+ char ap_info[sizeof (sdinfo->satadrv_id.ai_model) + 1];
+
+ /*
+ * This operation should return to cfgadm the
+ * device model information string
+ */
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ /* only handle device connected to cport for now */
+ sata_device.satadev_addr.qual = SATA_ADDR_DCPORT;
+ sdinfo = sata_get_device_info(sata_hba_inst,
+ &sata_device);
+ if (sdinfo == NULL) {
+ rv = EINVAL;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ break;
+ }
+ bcopy(sdinfo->satadrv_id.ai_model, ap_info,
+ sizeof (sdinfo->satadrv_id.ai_model));
+ swab(ap_info, ap_info,
+ sizeof (sdinfo->satadrv_id.ai_model));
+ ap_info[sizeof (sdinfo->satadrv_id.ai_model)] = '\0';
+
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+
+ info_len = strlen(ap_info) + 1;
+
+ if (ioc.get_size) {
+ if (ddi_copyout((void *)&info_len,
+ ioc.buf, ioc.bufsiz, mode) != 0) {
+ rv = EFAULT;
+ break;
+ }
+ } else {
+ if (ioc.bufsiz < info_len) {
+ rv = EINVAL;
+ break;
+ }
+ if (ddi_copyout((void *)ap_info, ioc.buf,
+ ioc.bufsiz, mode) != 0) {
+ rv = EFAULT;
+ break;
+ }
+ }
+
+ break;
+ }
+
+ case SATA_CFGA_GET_REVFIRMWARE_INFO:
+ {
+ uint32_t info_len;
+ char ap_info[
+ sizeof (sdinfo->satadrv_id.ai_fw) + 1];
+
+ /*
+ * This operation should return to cfgadm the
+ * device firmware revision information string
+ */
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ /* only handle device connected to cport for now */
+ sata_device.satadev_addr.qual = SATA_ADDR_DCPORT;
+
+ sdinfo = sata_get_device_info(sata_hba_inst,
+ &sata_device);
+ if (sdinfo == NULL) {
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ rv = EINVAL;
+ break;
+ }
+ bcopy(sdinfo->satadrv_id.ai_fw, ap_info,
+ sizeof (sdinfo->satadrv_id.ai_fw));
+ swab(ap_info, ap_info,
+ sizeof (sdinfo->satadrv_id.ai_fw));
+ ap_info[sizeof (sdinfo->satadrv_id.ai_fw)] = '\0';
+
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+
+ info_len = strlen(ap_info) + 1;
+
+ if (ioc.get_size) {
+ if (ddi_copyout((void *)&info_len,
+ ioc.buf, ioc.bufsiz, mode) != 0) {
+ rv = EFAULT;
+ break;
+ }
+ } else {
+ if (ioc.bufsiz < info_len) {
+ rv = EINVAL;
+ break;
+ }
+ if (ddi_copyout((void *)ap_info, ioc.buf,
+ ioc.bufsiz, mode) != 0) {
+ rv = EFAULT;
+ break;
+ }
+ }
+
+ break;
+ }
+
+ case SATA_CFGA_GET_SERIALNUMBER_INFO:
+ {
+ uint32_t info_len;
+ char ap_info[
+ sizeof (sdinfo->satadrv_id.ai_drvser) + 1];
+
+ /*
+ * This operation should return to cfgadm the
+ * device serial number information string
+ */
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+ /* only handle device connected to cport for now */
+ sata_device.satadev_addr.qual = SATA_ADDR_DCPORT;
+
+ sdinfo = sata_get_device_info(sata_hba_inst,
+ &sata_device);
+ if (sdinfo == NULL) {
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
+ cport)->cport_mutex);
+ rv = EINVAL;
+ break;
+ }
+ bcopy(sdinfo->satadrv_id.ai_drvser, ap_info,
+ sizeof (sdinfo->satadrv_id.ai_drvser));
+ swab(ap_info, ap_info,
+ sizeof (sdinfo->satadrv_id.ai_drvser));
+ ap_info[sizeof (sdinfo->satadrv_id.ai_drvser)] = '\0';
+
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->
+ cport_mutex);
+
+ info_len = strlen(ap_info) + 1;
+
+ if (ioc.get_size) {
+ if (ddi_copyout((void *)&info_len,
+ ioc.buf, ioc.bufsiz, mode) != 0) {
+ rv = EFAULT;
+ break;
+ }
+ } else {
+ if (ioc.bufsiz < info_len) {
+ rv = EINVAL;
+ break;
+ }
+ if (ddi_copyout((void *)ap_info, ioc.buf,
+ ioc.bufsiz, mode) != 0) {
+ rv = EFAULT;
+ break;
+ }
+ }
+
+ break;
+ }
+
+ default:
+ rv = EINVAL;
+ break;
+
+ } /* End of DEVCTL_AP_CONTROL cmd switch */
+
+ break;
+ }
+
+ default:
+ {
+ /*
+ * If we got here, we got an IOCTL that SATA HBA Framework
+ * does not recognize. Pass ioctl to HBA driver, in case
+ * it could process it.
+ */
+ sata_hba_tran_t *sata_tran = sata_hba_inst->satahba_tran;
+ dev_info_t *mydip = SATA_DIP(sata_hba_inst);
+
+ SATADBG1(SATA_DBG_IOCTL_IF, sata_hba_inst,
+ "IOCTL 0x%2x not supported in SATA framework, "
+ "passthrough to HBA", cmd);
+
+ if (sata_tran->sata_tran_ioctl == NULL) {
+ rv = EINVAL;
+ break;
+ }
+ rval = (*sata_tran->sata_tran_ioctl)(mydip, cmd, arg);
+ if (rval != 0) {
+ SATADBG1(SATA_DBG_IOCTL_IF, sata_hba_inst,
+ "IOCTL 0x%2x failed in HBA", cmd);
+ rv = rval;
+ }
+ break;
+ }
+
+ } /* End of main IOCTL switch */
+
+ if (dcp) {
+ ndi_dc_freehdl(dcp);
+ }
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
+ cportinfo->cport_event_flags &= ~SATA_APCTL_LOCK_PORT_BUSY;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, cport)->cport_mutex);
+
+ return (rv);
+}
+
+
+
+
+/* ****************** SCSA required entry points *********************** */
+
+/*
+ * Implementation of scsi tran_tgt_init.
+ * sata_scsi_tgt_init() initializes scsi_device structure
+ *
+ * If successful, DDI_SUCCESS is returned.
+ * DDI_FAILURE is returned if addressed device does not exist
+ */
+
+static int
+sata_scsi_tgt_init(dev_info_t *hba_dip, dev_info_t *tgt_dip,
+ scsi_hba_tran_t *hba_tran, struct scsi_device *sd)
+{
+#ifndef __lock_lint
+ _NOTE(ARGUNUSED(hba_dip))
+#endif
+ sata_device_t sata_device;
+ sata_drive_info_t *sdinfo;
+ sata_hba_inst_t *sata_hba_inst;
+
+ sata_hba_inst = (sata_hba_inst_t *)(hba_tran->tran_hba_private);
+
+ /* Validate scsi device address */
+ if (sata_validate_scsi_address(sata_hba_inst, &sd->sd_address,
+ &sata_device) != 0)
+ return (DDI_FAILURE);
+
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+
+ /* sata_device now contains a valid sata address */
+ sdinfo = sata_get_device_info(sata_hba_inst, &sata_device);
+ if (sdinfo == NULL) {
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ return (DDI_FAILURE);
+ }
+ if (sata_device.satadev_type == SATA_DTYPE_ATAPICD) {
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ if (ndi_prop_update_string(DDI_DEV_T_NONE, tgt_dip,
+ "variant", "atapi") != DDI_PROP_SUCCESS) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_scsi_tgt_init: variant atapi "
+ "property could not be created"));
+ return (DDI_FAILURE);
+ }
+ return (DDI_SUCCESS);
+ }
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ return (DDI_SUCCESS);
+}
+
+/*
+ * Implementation of scsi tran_tgt_probe.
+ * Probe target, by calling default scsi routine scsi_hba_probe()
+ */
+static int
+sata_scsi_tgt_probe(struct scsi_device *sd, int (*callback)(void))
+{
+ sata_hba_inst_t *sata_hba_inst =
+ (sata_hba_inst_t *)(sd->sd_address.a_hba_tran->tran_hba_private);
+ int rval;
+
+ rval = scsi_hba_probe(sd, callback);
+
+ if (rval == SCSIPROBE_EXISTS) {
+ /*
+ * Set property "pm-capable" on the target device node, so that
+ * the target driver will not try to fetch scsi cycle counters
+ * before enabling device power-management.
+ */
+ if ((ddi_prop_update_int(DDI_DEV_T_NONE, sd->sd_dev,
+ "pm-capable", 1)) != DDI_PROP_SUCCESS) {
+ sata_log(sata_hba_inst, CE_WARN,
+ "device at port %d: will not be power-managed ",
+ SCSI_TO_SATA_CPORT(sd->sd_address.a_target));
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "failure updating pm-capable property"));
+ }
+ }
+ return (rval);
+}
+
+/*
+ * Implementation of scsi tran_tgt_free.
+ * Release all resources allocated for scsi_device
+ */
+static void
+sata_scsi_tgt_free(dev_info_t *hba_dip, dev_info_t *tgt_dip,
+ scsi_hba_tran_t *hba_tran, struct scsi_device *sd)
+{
+#ifndef __lock_lint
+ _NOTE(ARGUNUSED(hba_dip))
+#endif
+ sata_device_t sata_device;
+ sata_drive_info_t *sdinfo;
+ sata_hba_inst_t *sata_hba_inst;
+
+ sata_hba_inst = (sata_hba_inst_t *)(hba_tran->tran_hba_private);
+
+ /* Validate scsi device address */
+ if (sata_validate_scsi_address(sata_hba_inst, &sd->sd_address,
+ &sata_device) != 0)
+ return;
+
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+
+ /* sata_device now should contain a valid sata address */
+ sdinfo = sata_get_device_info(sata_hba_inst, &sata_device);
+ if (sdinfo == NULL) {
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ return;
+ }
+ /*
+ * We did not allocate any resources in sata_scsi_tgt_init()
+ * other than property for ATAPI device, if any
+ */
+ if (sata_device.satadev_type == SATA_DTYPE_ATAPICD) {
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ if (ndi_prop_remove(DDI_DEV_T_NONE, tgt_dip, "variant") !=
+ DDI_PROP_SUCCESS)
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_scsi_tgt_free: variant atapi "
+ "property could not be removed"));
+ } else {
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ }
+}
+
+/*
+ * Implementation of scsi tran_init_pkt
+ * Upon successful return, scsi pkt buffer has DMA resources allocated.
+ *
+ * It seems that we should always allocate pkt, even if the address is
+ * for non-existing device - just use some default for dma_attr.
+ * The reason is that there is no way to communicate this to a caller here.
+ * Subsequent call to sata_scsi_start may fail appropriately.
+ * Simply returning NULL does not seem to discourage a target driver...
+ *
+ * Returns a pointer to initialized scsi_pkt, or NULL otherwise.
+ */
+static struct scsi_pkt *
+sata_scsi_init_pkt(struct scsi_address *ap, struct scsi_pkt *pkt,
+ struct buf *bp, int cmdlen, int statuslen, int tgtlen, int flags,
+ int (*callback)(caddr_t), caddr_t arg)
+{
+ sata_hba_inst_t *sata_hba_inst =
+ (sata_hba_inst_t *)(ap->a_hba_tran->tran_hba_private);
+ dev_info_t *dip = SATA_DIP(sata_hba_inst);
+ sata_device_t sata_device;
+ sata_drive_info_t *sdinfo;
+ sata_pkt_txlate_t *spx;
+ ddi_dma_attr_t cur_dma_attr;
+ int rval;
+ boolean_t new_pkt = TRUE;
+
+ ASSERT(ap->a_hba_tran->tran_hba_dip == dip);
+
+ /*
+ * We need to translate the address, even if it could be
+ * a bogus one, for a non-existing device
+ */
+ sata_device.satadev_addr.qual = SCSI_TO_SATA_ADDR_QUAL(ap->a_target);
+ sata_device.satadev_addr.cport = SCSI_TO_SATA_CPORT(ap->a_target);
+ sata_device.satadev_addr.pmport = SCSI_TO_SATA_PMPORT(ap->a_target);
+ sata_device.satadev_rev = SATA_DEVICE_REV;
+
+ if (pkt == NULL) {
+ /*
+ * Have to allocate a brand new scsi packet.
+ * We need to operate with auto request sense enabled.
+ */
+ pkt = scsi_hba_pkt_alloc(dip, ap, cmdlen,
+ MAX(statuslen, sizeof (struct scsi_arq_status)),
+ tgtlen, sizeof (sata_pkt_txlate_t), callback, arg);
+
+ if (pkt == NULL)
+ return (NULL);
+
+ /* Fill scsi packet structure */
+ pkt->pkt_comp = (void (*)())NULL;
+ pkt->pkt_time = 0;
+ pkt->pkt_resid = 0;
+ pkt->pkt_statistics = 0;
+ pkt->pkt_reason = 0;
+
+ /*
+ * pkt_hba_private will point to sata pkt txlate structure
+ */
+ spx = (sata_pkt_txlate_t *)pkt->pkt_ha_private;
+ bzero(spx, sizeof (sata_pkt_txlate_t));
+
+ spx->txlt_scsi_pkt = pkt;
+ spx->txlt_sata_hba_inst = sata_hba_inst;
+
+ /* Allocate sata_pkt */
+ spx->txlt_sata_pkt = sata_pkt_alloc(spx, callback);
+ if (spx->txlt_sata_pkt == NULL) {
+ /* Could not allocate sata pkt */
+ scsi_hba_pkt_free(ap, pkt);
+ return (NULL);
+ }
+ /* Set sata address */
+ spx->txlt_sata_pkt->satapkt_device = sata_device;
+
+ if ((bp == NULL) || (bp->b_bcount == 0))
+ return (pkt);
+
+ spx->txlt_total_residue = bp->b_bcount;
+ } else {
+ new_pkt = FALSE;
+ /*
+ * Packet was preallocated/initialized by previous call
+ */
+ spx = (sata_pkt_txlate_t *)pkt->pkt_ha_private;
+
+ if ((bp == NULL) || (bp->b_bcount == 0)) {
+ return (pkt);
+ }
+ ASSERT(spx->txlt_buf_dma_handle != NULL);
+
+ /* Pkt is available already: spx->txlt_scsi_pkt == pkt; */
+ }
+
+ spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp = bp;
+
+ /*
+ * We use an adjusted version of the dma_attr, to account
+ * for device addressing limitations.
+ * sata_adjust_dma_attr() will handle sdinfo == NULL which may
+ * happen when a device is not yet configured.
+ */
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
+ &spx->txlt_sata_pkt->satapkt_device);
+ /* NULL sdinfo may be passsed to sata_adjust_dma_attr() */
+ sata_adjust_dma_attr(sdinfo,
+ SATA_DMA_ATTR(spx->txlt_sata_hba_inst), &cur_dma_attr);
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ /*
+ * Allocate necessary DMA resources for the packet's buffer
+ */
+ if ((rval = sata_dma_buf_setup(spx, flags, callback, arg,
+ &cur_dma_attr)) != DDI_SUCCESS) {
+ sata_pkt_free(spx);
+ /*
+ * If a DMA allocation request fails with
+ * DDI_DMA_NOMAPPING, indicate the error by calling
+ * bioerror(9F) with bp and an error code of EFAULT.
+ * If a DMA allocation request fails with
+ * DDI_DMA_TOOBIG, indicate the error by calling
+ * bioerror(9F) with bp and an error code of EINVAL.
+ */
+ switch (rval) {
+ case DDI_DMA_NORESOURCES:
+ bioerror(bp, 0);
+ break;
+ case DDI_DMA_NOMAPPING:
+ case DDI_DMA_BADATTR:
+ bioerror(bp, EFAULT);
+ break;
+ case DDI_DMA_TOOBIG:
+ default:
+ bioerror(bp, EINVAL);
+ break;
+ }
+ if (new_pkt == TRUE) {
+ sata_pkt_free(spx);
+ scsi_hba_pkt_free(ap, pkt);
+ }
+ return (NULL);
+ }
+ /* Set number of bytes that are not yet accounted for */
+ pkt->pkt_resid = spx->txlt_total_residue;
+ ASSERT(pkt->pkt_resid >= 0);
+
+ return (pkt);
+}
+
+/*
+ * Implementation of scsi tran_start.
+ * Translate scsi cmd into sata operation and return status.
+ * Supported scsi commands:
+ * SCMD_INQUIRY
+ * SCMD_TEST_UNIT_READY
+ * SCMD_START_STOP
+ * SCMD_READ_CAPACITY
+ * SCMD_REQUEST_SENSE
+ * SCMD_LOG_SENSE_G1 (unimplemented)
+ * SCMD_LOG_SELECT_G1 (unimplemented)
+ * SCMD_MODE_SENSE (specific pages)
+ * SCMD_MODE_SENSE_G1 (specific pages)
+ * SCMD_MODE_SELECT (specific pages)
+ * SCMD_MODE_SELECT_G1 (specific pages)
+ * SCMD_SYNCHRONIZE_CACHE
+ * SCMD_SYNCHRONIZE_CACHE_G1
+ * SCMD_READ
+ * SCMD_READ_G1
+ * SCMD_READ_G4
+ * SCMD_READ_G5
+ * SCMD_WRITE
+ * SCMD_WRITE_G1
+ * SCMD_WRITE_G4
+ * SCMD_WRITE_G5
+ * SCMD_SEEK (noop)
+ *
+ * All other commands are rejected as unsupported.
+ *
+ * Returns:
+ * TRAN_ACCEPT if command was executed successfully or accepted by HBA driver
+ * for execution.
+ * TRAN_BADPKT if cmd was directed to invalid address.
+ * TRAN_FATAL_ERROR is command was rejected due to hardware error, including
+ * unexpected removal of a device or some other unspecified error.
+ * TRAN_BUSY if command could not be executed becasue HBA driver or SATA
+ * framework was busy performing some other operation(s).
+ *
+ */
+static int
+sata_scsi_start(struct scsi_address *ap, struct scsi_pkt *pkt)
+{
+ sata_hba_inst_t *sata_hba_inst =
+ (sata_hba_inst_t *)(ap->a_hba_tran->tran_hba_private);
+ sata_pkt_txlate_t *spx = (sata_pkt_txlate_t *)pkt->pkt_ha_private;
+ sata_drive_info_t *sdinfo;
+ struct buf *bp;
+ int cport;
+ int rval;
+
+ SATADBG1(SATA_DBG_SCSI_IF, sata_hba_inst,
+ "sata_scsi_start: cmd 0x%02x\n", pkt->pkt_cdbp[0]);
+
+ ASSERT(spx != NULL &&
+ spx->txlt_scsi_pkt == pkt && spx->txlt_sata_pkt != NULL);
+
+ /*
+ * Mutex-protected section below is just to identify device type
+ * and switch to ATAPI processing, if necessary
+ */
+ cport = SCSI_TO_SATA_CPORT(ap->a_target);
+
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
+
+ sdinfo = sata_get_device_info(sata_hba_inst,
+ &spx->txlt_sata_pkt->satapkt_device);
+ if (sdinfo == NULL) {
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
+ spx->txlt_scsi_pkt->pkt_reason = CMD_DEV_GONE;
+ return (TRAN_FATAL_ERROR);
+ }
+
+ if (sdinfo->satadrv_type == SATA_DTYPE_ATAPICD) {
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
+ rval = sata_txlt_atapi(spx);
+ SATADBG1(SATA_DBG_SCSI_IF, sata_hba_inst,
+ "sata_scsi_start atapi: rval %d\n", rval);
+ return (rval);
+ }
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
+
+ /* ATA Disk commands processing starts here */
+
+ bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
+
+ switch (pkt->pkt_cdbp[0]) {
+
+ case SCMD_INQUIRY:
+ /* Mapped to identify device */
+ if (bp != NULL && (bp->b_flags & (B_PHYS | B_PAGEIO)))
+ bp_mapin(bp);
+ rval = sata_txlt_inquiry(spx);
+ break;
+
+ case SCMD_TEST_UNIT_READY:
+ /*
+ * SAT "SATA to ATA Translation" doc specifies translation
+ * to ATA CHECK POWER MODE.
+ */
+ rval = sata_txlt_test_unit_ready(spx);
+ break;
+
+ case SCMD_START_STOP:
+ /* Mapping depends on the command */
+ rval = sata_txlt_start_stop_unit(spx);
+ break;
+
+ case SCMD_READ_CAPACITY:
+ if (bp != NULL && (bp->b_flags & (B_PHYS | B_PAGEIO)))
+ bp_mapin(bp);
+ rval = sata_txlt_read_capacity(spx);
+ break;
+
+ case SCMD_REQUEST_SENSE:
+ /*
+ * Always No Sense, since we force ARQ
+ */
+ if (bp != NULL && (bp->b_flags & (B_PHYS | B_PAGEIO)))
+ bp_mapin(bp);
+ rval = sata_txlt_request_sense(spx);
+ break;
+
+ case SCMD_LOG_SENSE_G1:
+ if (bp != NULL && (bp->b_flags & (B_PHYS | B_PAGEIO)))
+ bp_mapin(bp);
+ rval = sata_txlt_log_sense(spx);
+ break;
+
+ case SCMD_LOG_SELECT_G1:
+ if (bp != NULL && (bp->b_flags & (B_PHYS | B_PAGEIO)))
+ bp_mapin(bp);
+ rval = sata_txlt_log_select(spx);
+ break;
+
+ case SCMD_MODE_SENSE:
+ case SCMD_MODE_SENSE_G1:
+ if (bp != NULL && (bp->b_flags & (B_PHYS | B_PAGEIO)))
+ bp_mapin(bp);
+ rval = sata_txlt_mode_sense(spx);
+ break;
+
+
+ case SCMD_MODE_SELECT:
+ case SCMD_MODE_SELECT_G1:
+ if (bp != NULL && (bp->b_flags & (B_PHYS | B_PAGEIO)))
+ bp_mapin(bp);
+ rval = sata_txlt_mode_select(spx);
+ break;
+
+ case SCMD_SYNCHRONIZE_CACHE:
+ case SCMD_SYNCHRONIZE_CACHE_G1:
+ rval = sata_txlt_synchronize_cache(spx);
+ break;
+
+ case SCMD_READ:
+ case SCMD_READ_G1:
+ case SCMD_READ_G4:
+ case SCMD_READ_G5:
+ rval = sata_txlt_read(spx);
+ break;
+
+ case SCMD_WRITE:
+ case SCMD_WRITE_G1:
+ case SCMD_WRITE_G4:
+ case SCMD_WRITE_G5:
+ rval = sata_txlt_write(spx);
+ break;
+
+ case SCMD_SEEK:
+ rval = sata_txlt_nodata_cmd_immediate(spx);
+ break;
+
+
+ /* Other cases will be filed later */
+ /* postponed until phase 2 of the development */
+ default:
+ rval = sata_txlt_invalid_command(spx);
+ break;
+ }
+
+ SATADBG1(SATA_DBG_SCSI_IF, sata_hba_inst,
+ "sata_scsi_start: rval %d\n", rval);
+
+ return (rval);
+}
+
+/*
+ * Implementation of scsi tran_abort.
+ * Abort specific pkt or all packets.
+ *
+ * Returns 1 if one or more packets were aborted, returns 0 otherwise
+ *
+ * May be called from an interrupt level.
+ */
+static int
+sata_scsi_abort(struct scsi_address *ap, struct scsi_pkt *scsi_pkt)
+{
+ sata_hba_inst_t *sata_hba_inst =
+ (sata_hba_inst_t *)(ap->a_hba_tran->tran_hba_private);
+ sata_device_t sata_device;
+ sata_pkt_t *sata_pkt;
+
+ SATADBG2(SATA_DBG_SCSI_IF, sata_hba_inst,
+ "sata_scsi_abort: %s at target: 0x%x\n",
+ scsi_pkt == NULL ? "all packets" : "one pkt", ap->a_target);
+
+ /* Validate address */
+ if (sata_validate_scsi_address(sata_hba_inst, ap, &sata_device) != 0)
+ /* Invalid address */
+ return (0);
+
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ if (sata_get_device_info(sata_hba_inst, &sata_device) == NULL) {
+ /* invalid address */
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ return (0);
+ }
+ if (scsi_pkt == NULL) {
+ /*
+ * Abort all packets.
+ * Although we do not have specific packet, we still need
+ * dummy packet structure to pass device address to HBA.
+ * Allocate one, without sleeping. Fail if pkt cannot be
+ * allocated.
+ */
+ sata_pkt = kmem_zalloc(sizeof (sata_pkt_t), KM_NOSLEEP);
+ if (sata_pkt == NULL) {
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ SATA_LOG_D((sata_hba_inst, CE_WARN, "sata_pkt_abort: "
+ "could not allocate sata_pkt"));
+ return (0);
+ }
+ sata_pkt->satapkt_rev = SATA_PKT_REV;
+ sata_pkt->satapkt_device = sata_device;
+ sata_pkt->satapkt_device.satadev_rev = SATA_DEVICE_REV;
+ } else {
+ if (scsi_pkt->pkt_ha_private == NULL) {
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ return (0); /* Bad scsi pkt */
+ }
+ /* extract pointer to sata pkt */
+ sata_pkt = ((sata_pkt_txlate_t *)scsi_pkt->pkt_ha_private)->
+ txlt_sata_pkt;
+ }
+
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ /* Send abort request to HBA */
+ if ((*SATA_ABORT_FUNC(sata_hba_inst))
+ (SATA_DIP(sata_hba_inst), sata_pkt,
+ scsi_pkt == NULL ? SATA_ABORT_ALL_PACKETS : SATA_ABORT_PACKET) ==
+ SATA_SUCCESS) {
+ if (scsi_pkt == NULL)
+ kmem_free(sata_pkt, sizeof (sata_pkt_t));
+ /* Success */
+ return (1);
+ }
+ /* Else, something did not go right */
+ if (scsi_pkt == NULL)
+ kmem_free(sata_pkt, sizeof (sata_pkt_t));
+ /* Failure */
+ return (0);
+}
+
+
+/*
+ * Implementation os scsi tran_reset.
+ * RESET_ALL request is translated into port reset.
+ * RESET_TARGET requests is translated into a device reset,
+ * RESET_LUN request is accepted only for LUN 0 and translated into
+ * device reset.
+ * The target reset should cause all HBA active and queued packets to
+ * be terminated and returned with pkt reason SATA_PKT_RESET prior to
+ * the return. HBA should report reset event for the device.
+ *
+ * Returns 1 upon success, 0 upon failure.
+ */
+static int
+sata_scsi_reset(struct scsi_address *ap, int level)
+{
+ sata_hba_inst_t *sata_hba_inst =
+ (sata_hba_inst_t *)(ap->a_hba_tran->tran_hba_private);
+ sata_device_t sata_device;
+ int val;
+
+ SATADBG2(SATA_DBG_SCSI_IF, sata_hba_inst,
+ "sata_scsi_reset: level %d target: 0x%x\n",
+ level, ap->a_target);
+
+ /* Validate address */
+ val = sata_validate_scsi_address(sata_hba_inst, ap, &sata_device);
+ if (val == -1)
+ /* Invalid address */
+ return (0);
+
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ if (sata_get_device_info(sata_hba_inst, &sata_device) == NULL) {
+ /* invalid address */
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ return (0);
+ }
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ if (level == RESET_ALL) {
+ /* port reset - cport only */
+ sata_device.satadev_addr.qual = SATA_ADDR_CPORT;
+ if ((*SATA_RESET_DPORT_FUNC(sata_hba_inst))
+ (SATA_DIP(sata_hba_inst), &sata_device) == SATA_SUCCESS)
+ return (1);
+ else
+ return (0);
+
+ } else if (val == 0 &&
+ (level == RESET_TARGET || level == RESET_LUN)) {
+ /* reset device (device attached) */
+ if ((*SATA_RESET_DPORT_FUNC(sata_hba_inst))
+ (SATA_DIP(sata_hba_inst), &sata_device) == SATA_SUCCESS)
+ return (1);
+ else
+ return (0);
+ }
+ return (0);
+}
+
+
+/*
+ * Implementation of scsi tran_getcap (get transport/device capabilities).
+ * Supported capabilities:
+ * auto-rqsense (always supported)
+ * tagged-qing (supported if HBA supports it)
+ * dma_max
+ * interconnect-type (INTERCONNECT_SATA)
+ *
+ * Request for other capabilities is rejected as unsupported.
+ *
+ * Returns supported capability value, or -1 if capability is unsuppported or
+ * the address is invalid (no device).
+ */
+
+static int
+sata_scsi_getcap(struct scsi_address *ap, char *cap, int whom)
+{
+
+ sata_hba_inst_t *sata_hba_inst =
+ (sata_hba_inst_t *)(ap->a_hba_tran->tran_hba_private);
+ sata_device_t sata_device;
+ sata_drive_info_t *sdinfo;
+ ddi_dma_attr_t adj_dma_attr;
+ int rval;
+
+ SATADBG2(SATA_DBG_SCSI_IF, sata_hba_inst,
+ "sata_scsi_getcap: target: 0x%x, cap: %s\n",
+ ap->a_target, cap);
+
+ /*
+ * We want to process the capabilities on per port granularity.
+ * So, we are specifically restricting ourselves to whom != 0
+ * to exclude the controller wide handling.
+ */
+ if (cap == NULL || whom == 0)
+ return (-1);
+
+ if (sata_validate_scsi_address(sata_hba_inst, ap, &sata_device) != 0) {
+ /* Invalid address */
+ return (-1);
+ }
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ if ((sdinfo = sata_get_device_info(sata_hba_inst, &sata_device)) ==
+ NULL) {
+ /* invalid address */
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ return (0);
+ }
+
+ switch (scsi_hba_lookup_capstr(cap)) {
+ case SCSI_CAP_ARQ:
+ rval = 1; /* ARQ supported, turned on */
+ break;
+
+ case SCSI_CAP_SECTOR_SIZE:
+ if (sdinfo->satadrv_type == SATA_DTYPE_ATADISK)
+ rval = SATA_DISK_SECTOR_SIZE; /* fixed size */
+ else if (sdinfo->satadrv_type == SATA_DTYPE_ATAPICD)
+ rval = SATA_ATAPI_SECTOR_SIZE;
+ else rval = -1;
+ break;
+
+ case SCSI_CAP_TAGGED_QING:
+ /*
+ * It is enough if the controller supports queuing, regardless
+ * of the device. NCQ support is an internal implementation
+ * feature used between HBA and the device.
+ */
+ if (SATA_QDEPTH(sata_hba_inst) > 1)
+ rval = 1; /* Queuing supported */
+ else
+ rval = -1; /* Queuing not supported */
+ break;
+
+ case SCSI_CAP_DMA_MAX:
+ sata_adjust_dma_attr(sdinfo, SATA_DMA_ATTR(sata_hba_inst),
+ &adj_dma_attr);
+ rval = (int)adj_dma_attr.dma_attr_maxxfer;
+ /* We rely on the fact that dma_attr_maxxfer < 0x80000000 */
+ break;
+
+ case SCSI_CAP_INTERCONNECT_TYPE:
+ rval = INTERCONNECT_SATA; /* SATA interconnect type */
+ break;
+
+ default:
+ rval = -1;
+ break;
+ }
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ return (rval);
+}
+
+/*
+ * Implementation of scsi tran_setcap
+ *
+ * All supported capabilities are fixed/unchangeable.
+ * Returns 0 for all supported capabilities and valid device, -1 otherwise.
+ */
+static int
+sata_scsi_setcap(struct scsi_address *ap, char *cap, int value, int whom)
+{
+#ifndef __lock_lint
+ _NOTE(ARGUNUSED(value))
+#endif
+ sata_hba_inst_t *sata_hba_inst =
+ (sata_hba_inst_t *)(ap->a_hba_tran->tran_hba_private);
+ sata_device_t sata_device;
+ int rval;
+
+ SATADBG2(SATA_DBG_SCSI_IF, sata_hba_inst,
+ "sata_scsi_setcap: target: 0x%x, cap: %s\n", ap->a_target, cap);
+
+ /*
+ * We want to process the capabilities on per port granularity.
+ * So, we are specifically restricting ourselves to whom != 0
+ * to exclude the controller wide handling.
+ */
+ if (cap == NULL || whom == 0) {
+ return (-1);
+ }
+
+ if (sata_validate_scsi_address(sata_hba_inst, ap, &sata_device) != 0) {
+ /* Invalid address */
+ return (-1);
+ }
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ if (sata_get_device_info(sata_hba_inst, &sata_device) == NULL) {
+ /* invalid address */
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+ return (0);
+ }
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device.satadev_addr.cport)));
+
+ switch (scsi_hba_lookup_capstr(cap)) {
+ case SCSI_CAP_ARQ:
+ case SCSI_CAP_SECTOR_SIZE:
+ case SCSI_CAP_TAGGED_QING:
+ case SCSI_CAP_DMA_MAX:
+ case SCSI_CAP_INTERCONNECT_TYPE:
+ rval = 0; /* Capability cannot be changed */
+ break;
+
+ default:
+ rval = -1;
+ break;
+ }
+ return (rval);
+}
+
+/*
+ * Implementations of scsi tran_destroy_pkt.
+ * Free resources allocated by sata_scsi_init_pkt()
+ */
+static void
+sata_scsi_destroy_pkt(struct scsi_address *ap, struct scsi_pkt *pkt)
+{
+ sata_pkt_txlate_t *spx;
+
+ ASSERT(pkt != NULL);
+ spx = (sata_pkt_txlate_t *)pkt->pkt_ha_private;
+
+ if (spx->txlt_buf_dma_handle != NULL) {
+ /*
+ * Free DMA resources - cookies and handles
+ */
+ ASSERT(spx->txlt_dma_cookie_list != NULL);
+ (void) kmem_free(spx->txlt_dma_cookie_list,
+ spx->txlt_dma_cookie_list_len * sizeof (ddi_dma_cookie_t));
+ (void) ddi_dma_unbind_handle(spx->txlt_buf_dma_handle);
+ (void) ddi_dma_free_handle(&spx->txlt_buf_dma_handle);
+ }
+ spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp = NULL;
+ sata_pkt_free(spx);
+
+ scsi_hba_pkt_free(ap, pkt);
+}
+
+/*
+ * Implementation of scsi tran_dmafree.
+ * Free DMA resources allocated by sata_scsi_init_pkt()
+ */
+
+static void
+sata_scsi_dmafree(struct scsi_address *ap, struct scsi_pkt *pkt)
+{
+#ifndef __lock_lint
+ _NOTE(ARGUNUSED(ap))
+#endif
+ sata_pkt_txlate_t *spx;
+
+ ASSERT(pkt != NULL);
+ spx = (sata_pkt_txlate_t *)pkt->pkt_ha_private;
+
+ if (spx->txlt_buf_dma_handle != NULL) {
+ /*
+ * Free DMA resources - cookies and handles
+ */
+ ASSERT(spx->txlt_dma_cookie_list != NULL);
+ (void) kmem_free(spx->txlt_dma_cookie_list,
+ spx->txlt_dma_cookie_list_len * sizeof (ddi_dma_cookie_t));
+ (void) ddi_dma_unbind_handle(spx->txlt_buf_dma_handle);
+ (void) ddi_dma_free_handle(&spx->txlt_buf_dma_handle);
+ }
+}
+
+/*
+ * Implementation of scsi tran_sync_pkt.
+ *
+ * The assumption below is that pkt is unique - there is no need to check ap
+ */
+static void
+sata_scsi_sync_pkt(struct scsi_address *ap, struct scsi_pkt *pkt)
+{
+#ifndef __lock_lint
+ _NOTE(ARGUNUSED(ap))
+#endif
+ int rval;
+ sata_pkt_txlate_t *spx = (sata_pkt_txlate_t *)pkt->pkt_ha_private;
+
+ if (spx->txlt_buf_dma_handle != NULL) {
+ if (spx->txlt_sata_pkt != NULL &&
+ spx->txlt_sata_pkt->satapkt_cmd.satacmd_flags !=
+ SATA_DIR_NODATA_XFER) {
+ rval = ddi_dma_sync(spx->txlt_buf_dma_handle, 0, 0,
+ (spx->txlt_sata_pkt->satapkt_cmd.satacmd_flags &
+ SATA_DIR_WRITE) ?
+ DDI_DMA_SYNC_FORDEV : DDI_DMA_SYNC_FORCPU);
+ if (rval == DDI_SUCCESS) {
+ SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
+ "sata_scsi_sync_pkt: sync pkt failed"));
+ }
+ }
+ }
+}
+
+
+
+/* ******************* SATA - SCSI Translation functions **************** */
+/*
+ * SCSI to SATA pkt and command translation and SATA to SCSI status/error
+ * translation.
+ */
+
+/*
+ * Checks if a device exists and can be access and translates common
+ * scsi_pkt data to sata_pkt data.
+ *
+ * Returns TRAN_ACCEPT if device exists and sata_pkt was set-up.
+ * Returns other TRAN_XXXXX values when error occured.
+ *
+ * This function should be called with port mutex held.
+ */
+static int
+sata_txlt_generic_pkt_info(sata_pkt_txlate_t *spx)
+{
+ sata_drive_info_t *sdinfo;
+ sata_device_t sata_device;
+
+ /* Validate address */
+ switch (sata_validate_scsi_address(spx->txlt_sata_hba_inst,
+ &spx->txlt_scsi_pkt->pkt_address, &sata_device)) {
+
+ case -1:
+ /* Invalid address or invalid device type */
+ return (TRAN_BADPKT);
+ case 1:
+ /* valid address but no device - it has disappeared ? */
+ spx->txlt_scsi_pkt->pkt_reason = CMD_DEV_GONE;
+ return (TRAN_FATAL_ERROR);
+ default:
+ /* all OK */
+ break;
+ }
+ sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
+ &spx->txlt_sata_pkt->satapkt_device);
+
+ /*
+ * If device is in reset condition, reject the packet with
+ * TRAN_BUSY
+ */
+ if (sdinfo->satadrv_event_flags &
+ (SATA_EVNT_DEVICE_RESET | SATA_EVNT_INPROC_DEVICE_RESET)) {
+ spx->txlt_scsi_pkt->pkt_reason = CMD_INCOMPLETE;
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "sata_scsi_start: rejecting command because "
+ "of device reset state\n", NULL);
+ return (TRAN_BUSY);
+ }
+
+ /*
+ * Fix the dev_type in the sata_pkt->satapkt_device. It was not set by
+ * sata_scsi_pkt_init() because pkt init had to work also with
+ * non-existing devices.
+ * Now we know that the packet was set-up for a real device, so its
+ * type is known.
+ */
+ spx->txlt_sata_pkt->satapkt_device.satadev_type = sdinfo->satadrv_type;
+
+ spx->txlt_sata_pkt->satapkt_cmd.satacmd_flags = SATA_DIR_NODATA_XFER;
+
+ spx->txlt_scsi_pkt->pkt_reason = CMD_CMPLT;
+
+ if ((spx->txlt_scsi_pkt->pkt_flags & FLAG_NOINTR) != 0) {
+ /* Synchronous execution */
+ spx->txlt_sata_pkt->satapkt_op_mode = SATA_OPMODE_SYNCH |
+ SATA_OPMODE_POLLING;
+ } else {
+ /* Asynchronous execution */
+ spx->txlt_sata_pkt->satapkt_op_mode = SATA_OPMODE_ASYNCH |
+ SATA_OPMODE_INTERRUPTS;
+ }
+ /* Convert queuing information */
+ if (spx->txlt_scsi_pkt->pkt_flags & FLAG_STAG)
+ spx->txlt_sata_pkt->satapkt_cmd.satacmd_flags |=
+ SATA_QUEUE_STAG_CMD;
+ else if (spx->txlt_scsi_pkt->pkt_flags &
+ (FLAG_OTAG | FLAG_HTAG | FLAG_HEAD))
+ spx->txlt_sata_pkt->satapkt_cmd.satacmd_flags |=
+ SATA_QUEUE_OTAG_CMD;
+
+ /* Always limit pkt time */
+ if (spx->txlt_scsi_pkt->pkt_time == 0)
+ spx->txlt_sata_pkt->satapkt_time = sata_default_pkt_time;
+ else
+ /* Pass on scsi_pkt time */
+ spx->txlt_sata_pkt->satapkt_time =
+ spx->txlt_scsi_pkt->pkt_time;
+
+ return (TRAN_ACCEPT);
+}
+
+
+/*
+ * Translate ATA(ATAPI) Identify (Packet) Device data to SCSI Inquiry data.
+ * SATA Identify Device data has to be valid in sata_rive_info.
+ * Buffer has to accomodate the inquiry length (36 bytes).
+ *
+ * This function should be called with a port mutex held.
+ */
+static void
+sata_identdev_to_inquiry(sata_hba_inst_t *sata_hba_inst,
+ sata_drive_info_t *sdinfo, uint8_t *buf)
+{
+
+ struct scsi_inquiry *inq = (struct scsi_inquiry *)buf;
+ struct sata_id *sid = &sdinfo->satadrv_id;
+
+ /* Rely on the dev_type for setting paripheral qualifier */
+ /* Does DTYPE_RODIRECT apply to CD/DVD R/W devices ? */
+ inq->inq_dtype = sdinfo->satadrv_type == SATA_DTYPE_ATADISK ?
+ DTYPE_DIRECT : DTYPE_RODIRECT;
+
+ inq->inq_rmb = sid->ai_config & SATA_REM_MEDIA ? 1 : 0;
+ inq->inq_qual = 0; /* Device type qualifier (obsolete in SCSI3? */
+ inq->inq_iso = 0; /* ISO version */
+ inq->inq_ecma = 0; /* ECMA version */
+ inq->inq_ansi = 3; /* ANSI version - SCSI 3 */
+ inq->inq_aenc = 0; /* Async event notification cap. */
+ inq->inq_trmiop = 0; /* Supports TERMINATE I/O PROC msg ??? */
+ inq->inq_normaca = 0; /* setting NACA bit supported - NO */
+ inq->inq_rdf = RDF_SCSI2; /* Response data format- SPC-3 */
+ inq->inq_len = 31; /* Additional length */
+ inq->inq_dualp = 0; /* dual port device - NO */
+ inq->inq_reladdr = 0; /* Supports relative addressing - NO */
+ inq->inq_sync = 0; /* Supports synchronous data xfers - NO */
+ inq->inq_linked = 0; /* Supports linked commands - NO */
+ /*
+ * Queuing support - controller has to
+ * support some sort of command queuing.
+ */
+ if (SATA_QDEPTH(sata_hba_inst) > 1)
+ inq->inq_cmdque = 1; /* Supports command queueing - YES */
+ else
+ inq->inq_cmdque = 0; /* Supports command queueing - NO */
+ inq->inq_sftre = 0; /* Supports Soft Reset option - NO ??? */
+ inq->inq_wbus32 = 0; /* Supports 32 bit wide data xfers - NO */
+ inq->inq_wbus16 = 0; /* Supports 16 bit wide data xfers - NO */
+
+#ifdef _LITTLE_ENDIAN
+ /* Swap text fields to match SCSI format */
+ swab(sid->ai_model, inq->inq_vid, 8); /* Vendor ID */
+ swab(&sid->ai_model[8], inq->inq_pid, 16); /* Product ID */
+ swab(sid->ai_fw, inq->inq_revision, 8); /* Revision level */
+#else
+ bcopy(sid->ai_model, inq->inq_vid, 8); /* Vendor ID */
+ bcopy(&sid->ai_model[8], inq->inq_pid, 16); /* Product ID */
+ bcopy(sid->ai_fw, inq->inq_revision, 8); /* Revision level */
+#endif
+}
+
+
+/*
+ * Scsi response set up for invalid command (command not supported)
+ *
+ * Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
+ */
+static int
+sata_txlt_invalid_command(sata_pkt_txlate_t *spx)
+{
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ struct scsi_extended_sense *sense;
+
+ scsipkt->pkt_reason = CMD_CMPLT;
+ scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD | STATE_GOT_STATUS;
+
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+
+ sense = sata_arq_sense(spx);
+ sense->es_key = KEY_ILLEGAL_REQUEST;
+ sense->es_add_code = SD_SCSI_INVALID_COMMAND_CODE;
+
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
+
+ if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
+ scsipkt->pkt_comp != NULL)
+ /* scsi callback required */
+ (*scsipkt->pkt_comp)(scsipkt);
+ return (TRAN_ACCEPT);
+}
+
+/*
+ * Scsi response setup for
+ * emulated non-data command that requires no action/return data
+ *
+ * Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
+ */
+static int
+sata_txlt_nodata_cmd_immediate(sata_pkt_txlate_t *spx)
+{
+ int rval;
+
+ mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+ if ((rval = sata_txlt_generic_pkt_info(spx)) != TRAN_ACCEPT) {
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ return (rval);
+ }
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+ spx->txlt_scsi_pkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD | STATE_GOT_STATUS;
+ spx->txlt_scsi_pkt->pkt_reason = CMD_CMPLT;
+ *(spx->txlt_scsi_pkt->pkt_scbp) = STATUS_GOOD;
+
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "Scsi_pkt completion reason %x\n",
+ spx->txlt_scsi_pkt->pkt_reason);
+
+ if ((spx->txlt_scsi_pkt->pkt_flags & FLAG_NOINTR) == 0 &&
+ spx->txlt_scsi_pkt->pkt_comp != NULL)
+ /* scsi callback required */
+ (*spx->txlt_scsi_pkt->pkt_comp)(spx->txlt_scsi_pkt);
+ return (TRAN_ACCEPT);
+}
+
+
+/*
+ * SATA translate command: Inquiry / Identify Device
+ * Use cached Identify Device data for now, rather then issuing actual
+ * Device Identify cmd request. If device is detached and re-attached,
+ * asynchromous event processing should fetch and refresh Identify Device
+ * data.
+ * Two VPD pages are supported now:
+ * Vital Product Data page
+ * Unit Serial Number page
+ *
+ * Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
+ */
+
+#define EVPD 1 /* Extended Vital Product Data flag */
+#define CMDDT 2 /* Command Support Data - Obsolete */
+#define INQUIRY_SUP_VPD_PAGE 0 /* Supported VDP Pages Page COde */
+#define INQUIRY_USN_PAGE 0x80 /* Unit Serial Number Page Code */
+#define INQUIRY_DEV_IDENTIFICATION_PAGE 0x83 /* Not needed yet */
+
+static int
+sata_txlt_inquiry(sata_pkt_txlate_t *spx)
+{
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ struct buf *bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
+ sata_drive_info_t *sdinfo;
+ struct scsi_extended_sense *sense;
+ int count;
+ uint8_t *p;
+ int i, j;
+ uint8_t page_buf[0xff]; /* Max length */
+ int rval;
+
+ mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+ if ((rval = sata_txlt_generic_pkt_info(spx)) != TRAN_ACCEPT) {
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ return (rval);
+ }
+
+ sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
+ &spx->txlt_sata_pkt->satapkt_device);
+
+ ASSERT(sdinfo != NULL);
+
+ scsipkt->pkt_reason = CMD_CMPLT;
+ scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD | STATE_GOT_STATUS;
+
+ /* Reject not supported request */
+ if (scsipkt->pkt_cdbp[1] & CMDDT) { /* No support for this bit */
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+ sense = sata_arq_sense(spx);
+ sense->es_key = KEY_ILLEGAL_REQUEST;
+ sense->es_add_code = SD_SCSI_INVALID_FIELD_IN_CDB;
+ goto done;
+ }
+
+ /* Valid Inquiry request */
+ *scsipkt->pkt_scbp = STATUS_GOOD;
+
+ if (bp != NULL && bp->b_un.b_addr && bp->b_bcount) {
+
+ if (!(scsipkt->pkt_cdbp[1] & EVPD)) {
+ /* Standard Inquiry Data request */
+ struct scsi_inquiry inq;
+
+ bzero(&inq, sizeof (struct scsi_inquiry));
+ sata_identdev_to_inquiry(spx->txlt_sata_hba_inst,
+ sdinfo, (uint8_t *)&inq);
+ /* Copy no more than requested */
+ count = MIN(bp->b_bcount,
+ sizeof (struct scsi_inquiry));
+ count = MIN(count, scsipkt->pkt_cdbp[4]);
+ bcopy(&inq, bp->b_un.b_addr, count);
+
+ scsipkt->pkt_state |= STATE_XFERRED_DATA;
+ scsipkt->pkt_resid = scsipkt->pkt_cdbp[4] > count ?
+ scsipkt->pkt_cdbp[4] - count : 0;
+ } else {
+ /*
+ * peripheral_qualifier = 0;
+ *
+ * We are dealing only with HD and will be
+ * dealing with CD/DVD devices soon
+ */
+ uint8_t peripheral_device_type =
+ sdinfo->satadrv_type == SATA_DTYPE_ATADISK ?
+ DTYPE_DIRECT : DTYPE_RODIRECT;
+
+ switch ((uint_t)scsipkt->pkt_cdbp[2]) {
+ case INQUIRY_SUP_VPD_PAGE:
+ /*
+ * Request for suported Vital Product Data
+ * pages - assuming only 2 page codes
+ * supported
+ */
+ page_buf[0] = peripheral_device_type;
+ page_buf[1] = INQUIRY_SUP_VPD_PAGE;
+ page_buf[2] = 0;
+ page_buf[3] = 2; /* page length */
+ page_buf[4] = INQUIRY_SUP_VPD_PAGE;
+ page_buf[5] = INQUIRY_USN_PAGE;
+ /* Copy no more than requested */
+ count = MIN(bp->b_bcount, 6);
+ bcopy(page_buf, bp->b_un.b_addr, count);
+ break;
+ case INQUIRY_USN_PAGE:
+ /*
+ * Request for Unit Serial Number page
+ */
+ page_buf[0] = peripheral_device_type;
+ page_buf[1] = INQUIRY_USN_PAGE;
+ page_buf[2] = 0;
+ page_buf[3] = 20; /* remaining page length */
+ p = (uint8_t *)(sdinfo->satadrv_id.ai_drvser);
+#ifdef _LITTLE_ENDIAN
+ swab(p, &page_buf[4], 20);
+#else
+ bcopy(p, &page_buf[4], 20);
+#endif
+ for (i = 0; i < 20; i++) {
+ if (page_buf[4 + i] == '\0' ||
+ page_buf[4 + i] == '\040') {
+ break;
+ }
+ }
+ /*
+ * 'i' contains string length.
+ *
+ * Least significant character of the serial
+ * number shall appear as the last byte,
+ * according to SBC-3 spec.
+ */
+ p = &page_buf[20 + 4 - 1];
+ for (j = i; j > 0; j--, p--) {
+ *p = *(p - 20 + i);
+ }
+ p = &page_buf[4];
+ for (j = 20 - i; j > 0; j--) {
+ *p++ = '\040';
+ }
+ count = MIN(bp->b_bcount, 24);
+ bcopy(page_buf, bp->b_un.b_addr, count);
+ break;
+
+ case INQUIRY_DEV_IDENTIFICATION_PAGE:
+ /*
+ * We may want to implement this page, when
+ * identifiers are common for SATA devices
+ * But not now.
+ */
+ /*FALLTHRU*/
+
+ default:
+ /* Request for unsupported VPD page */
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+ sense = sata_arq_sense(spx);
+ sense->es_key = KEY_ILLEGAL_REQUEST;
+ sense->es_add_code =
+ SD_SCSI_INVALID_FIELD_IN_CDB;
+ goto done;
+ }
+ }
+ scsipkt->pkt_state |= STATE_XFERRED_DATA;
+ scsipkt->pkt_resid = scsipkt->pkt_cdbp[4] > count ?
+ scsipkt->pkt_cdbp[4] - count : 0;
+ }
+done:
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "Scsi_pkt completion reason %x\n",
+ scsipkt->pkt_reason);
+
+ if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
+ scsipkt->pkt_comp != NULL)
+ /* scsi callback required */
+ (*scsipkt->pkt_comp)(scsipkt);
+
+ return (TRAN_ACCEPT);
+}
+
+/*
+ * SATA translate command: Request Sense
+ * emulated command (ATA version so far, no ATAPI)
+ * Always NO SENSE, because any sense data should be reported by ARQ sense.
+ *
+ * Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
+ */
+static int
+sata_txlt_request_sense(sata_pkt_txlate_t *spx)
+{
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ struct scsi_extended_sense sense;
+ struct buf *bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
+ int rval;
+
+ mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+ if ((rval = sata_txlt_generic_pkt_info(spx)) != TRAN_ACCEPT) {
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ return (rval);
+ }
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+
+ scsipkt->pkt_reason = CMD_CMPLT;
+ scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD | STATE_GOT_STATUS;
+ *scsipkt->pkt_scbp = STATUS_GOOD;
+
+ if (bp != NULL && bp->b_un.b_addr && bp->b_bcount) {
+ int count = MIN(bp->b_bcount,
+ sizeof (struct scsi_extended_sense));
+ bzero(&sense, sizeof (struct scsi_extended_sense));
+ sense.es_valid = 0; /* Valid LBA */
+ sense.es_class = 7; /* Response code 0x70 - current err */
+ sense.es_key = KEY_NO_SENSE;
+ sense.es_add_len = 6; /* Additional length */
+ /* Copy no more than requested */
+ bcopy(&sense, bp->b_un.b_addr, count);
+ scsipkt->pkt_state |= STATE_XFERRED_DATA;
+ scsipkt->pkt_resid = 0;
+ }
+
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "Scsi_pkt completion reason %x\n",
+ scsipkt->pkt_reason);
+
+ if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
+ scsipkt->pkt_comp != NULL)
+ /* scsi callback required */
+ (*scsipkt->pkt_comp)(scsipkt);
+ return (TRAN_ACCEPT);
+}
+
+/*
+ * SATA translate command: Test Unit Ready
+ * At the moment this is an emulated command (ATA version so far, no ATAPI).
+ * May be translated into Check Power Mode command in the future
+ *
+ * Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
+ */
+static int
+sata_txlt_test_unit_ready(sata_pkt_txlate_t *spx)
+{
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ struct scsi_extended_sense *sense;
+ int power_state;
+ int rval;
+
+ mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+ if ((rval = sata_txlt_generic_pkt_info(spx)) != TRAN_ACCEPT) {
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ return (rval);
+ }
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+ /* At this moment, emulate it rather than execute anything */
+ power_state = SATA_PWRMODE_ACTIVE;
+
+ scsipkt->pkt_reason = CMD_CMPLT;
+ scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD | STATE_GOT_STATUS;
+
+ switch (power_state) {
+ case SATA_PWRMODE_ACTIVE:
+ case SATA_PWRMODE_IDLE:
+ *scsipkt->pkt_scbp = STATUS_GOOD;
+ break;
+ default:
+ /* PWR mode standby */
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+ sense = sata_arq_sense(spx);
+ sense->es_key = KEY_NOT_READY;
+ sense->es_add_code = SD_SCSI_LU_NOT_READY;
+ break;
+ }
+
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
+
+ if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
+ scsipkt->pkt_comp != NULL)
+ /* scsi callback required */
+ (*scsipkt->pkt_comp)(scsipkt);
+
+ return (TRAN_ACCEPT);
+}
+
+
+/*
+ * SATA translate command: Start Stop Unit
+ * Translation depends on a command:
+ * Start Unit translated into Idle Immediate
+ * Stop Unit translated into Standby Immediate
+ * Unload Media / NOT SUPPORTED YET
+ * Load Media / NOT SUPPROTED YET
+ * Power condition bits are ignored, so is Immediate bit
+ * Requesting synchronous execution.
+ *
+ * Returns TRAN_ACCEPT or code returned by sata_hba_start() and
+ * appropriate values in scsi_pkt fields.
+ */
+static int
+sata_txlt_start_stop_unit(sata_pkt_txlate_t *spx)
+{
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ sata_cmd_t *scmd = &spx->txlt_sata_pkt->satapkt_cmd;
+ struct scsi_extended_sense *sense;
+ sata_hba_inst_t *shi = SATA_TXLT_HBA_INST(spx);
+ int cport = SATA_TXLT_CPORT(spx);
+ int rval;
+ int synch;
+
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "sata_txlt_start_stop_unit: %d\n", scsipkt->pkt_scbp[4] & 1);
+
+ mutex_enter(&SATA_CPORT_MUTEX(shi, cport));
+
+ if ((rval = sata_txlt_generic_pkt_info(spx)) != TRAN_ACCEPT) {
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ return (rval);
+ }
+
+ if (scsipkt->pkt_cdbp[4] & 2) {
+ /* Load/Unload Media - invalid request */
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+ sense = sata_arq_sense(spx);
+ sense->es_key = KEY_ILLEGAL_REQUEST;
+ sense->es_add_code = SD_SCSI_INVALID_FIELD_IN_CDB;
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
+
+ if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
+ scsipkt->pkt_comp != NULL)
+ /* scsi callback required */
+ (*scsipkt->pkt_comp)(scsipkt);
+
+ return (TRAN_ACCEPT);
+ }
+ scmd->satacmd_addr_type = 0;
+ scmd->satacmd_sec_count_lsb = 0;
+ scmd->satacmd_lba_low_lsb = 0;
+ scmd->satacmd_lba_mid_lsb = 0;
+ scmd->satacmd_lba_high_lsb = 0;
+ scmd->satacmd_features_reg = 0;
+ scmd->satacmd_device_reg = 0;
+ scmd->satacmd_status_reg = 0;
+ if (scsipkt->pkt_cdbp[4] & 1) {
+ /* Start Unit */
+ scmd->satacmd_cmd_reg = SATAC_IDLE_IM;
+ } else {
+ /* Stop Unit */
+ scmd->satacmd_cmd_reg = SATAC_STANDBY_IM;
+ }
+
+ if (!(spx->txlt_sata_pkt->satapkt_op_mode & SATA_OPMODE_SYNCH)) {
+ /* Need to set-up a callback function */
+ spx->txlt_sata_pkt->satapkt_comp =
+ sata_txlt_nodata_cmd_completion;
+ synch = FALSE;
+ } else {
+ spx->txlt_sata_pkt->satapkt_op_mode = SATA_OPMODE_SYNCH;
+ synch = TRUE;
+ }
+
+ /* Transfer command to HBA */
+ if (sata_hba_start(spx, &rval) != 0) {
+ /* Pkt not accepted for execution */
+ mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
+ return (rval);
+ }
+
+ /*
+ * If execution is non-synchronous,
+ * a callback function will handle potential errors, translate
+ * the response and will do a callback to a target driver.
+ * If it was synchronous, check execution status using the same
+ * framework callback.
+ */
+ mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
+ if (synch) {
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "synchronous execution status %x\n",
+ spx->txlt_sata_pkt->satapkt_reason);
+
+ sata_txlt_nodata_cmd_completion(spx->txlt_sata_pkt);
+ }
+ return (TRAN_ACCEPT);
+
+}
+
+
+/*
+ * SATA translate command: Read Capacity.
+ * Emulated command for SATA disks.
+ * Capacity is retrieved from cached Idenifty Device data.
+ * Identify Device data shows effective disk capacity, not the native
+ * capacity, which may be limitted by Set Max Address command.
+ * This is ATA version (non-ATAPI).
+ *
+ * Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
+ */
+static int
+sata_txlt_read_capacity(sata_pkt_txlate_t *spx)
+{
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ struct buf *bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
+ sata_drive_info_t *sdinfo;
+ uint64_t val;
+ uchar_t *rbuf;
+ int rval;
+
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "sata_txlt_read_capacity: ", NULL);
+
+ mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+ if ((rval = sata_txlt_generic_pkt_info(spx)) != TRAN_ACCEPT) {
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ return (rval);
+ }
+
+ scsipkt->pkt_reason = CMD_CMPLT;
+ scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD | STATE_GOT_STATUS;
+ *scsipkt->pkt_scbp = STATUS_GOOD;
+ if (bp != NULL && bp->b_un.b_addr && bp->b_bcount) {
+ sdinfo = sata_get_device_info(
+ spx->txlt_sata_hba_inst,
+ &spx->txlt_sata_pkt->satapkt_device);
+ /* Last logical block address */
+ val = sdinfo->satadrv_capacity - 1;
+ rbuf = (uchar_t *)bp->b_un.b_addr;
+ /* Need to swap endians to match scsi format */
+ rbuf[0] = (val >> 24) & 0xff;
+ rbuf[1] = (val >> 16) & 0xff;
+ rbuf[2] = (val >> 8) & 0xff;
+ rbuf[3] = val & 0xff;
+ /* block size - always 512 bytes, for now */
+ rbuf[4] = 0;
+ rbuf[5] = 0;
+ rbuf[6] = 0x02;
+ rbuf[7] = 0;
+ scsipkt->pkt_state |= STATE_XFERRED_DATA;
+ scsipkt->pkt_resid = 0;
+
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst, "%d\n",
+ sdinfo->satadrv_capacity -1);
+ }
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ /*
+ * If a callback was requested, do it now.
+ */
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
+
+ if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
+ scsipkt->pkt_comp != NULL)
+ /* scsi callback required */
+ (*scsipkt->pkt_comp)(scsipkt);
+
+ return (TRAN_ACCEPT);
+}
+
+/*
+ * SATA translate command: Mode Sense.
+ * Translated into appropriate SATA command or emulated.
+ * Saved Values Page Control (03) are not supported.
+ *
+ * NOTE: only caching mode sense page is currently implemented.
+ *
+ * Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
+ */
+
+static int
+sata_txlt_mode_sense(sata_pkt_txlate_t *spx)
+{
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ struct buf *bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
+ sata_drive_info_t *sdinfo;
+ struct scsi_extended_sense *sense;
+ int len, bdlen, count, alc_len;
+ int pc; /* Page Control code */
+ uint8_t *buf; /* mode sense buffer */
+ int rval;
+
+ SATADBG2(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "sata_txlt_mode_sense, pc %x page code 0x%02x\n",
+ spx->txlt_scsi_pkt->pkt_cdbp[2] >> 6,
+ spx->txlt_scsi_pkt->pkt_cdbp[2] & 0x3f);
+
+ buf = kmem_zalloc(1024, KM_SLEEP);
+
+ mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+ if ((rval = sata_txlt_generic_pkt_info(spx)) != TRAN_ACCEPT) {
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ kmem_free(buf, 1024);
+ return (rval);
+ }
+
+ scsipkt->pkt_reason = CMD_CMPLT;
+ scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD | STATE_GOT_STATUS;
+
+ pc = scsipkt->pkt_cdbp[2] >> 6;
+
+ /* Reject not supported request for saved parameters */
+ if (pc == 3) {
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+ sense = sata_arq_sense(spx);
+ sense->es_key = KEY_ILLEGAL_REQUEST;
+ sense->es_add_code = SD_SCSI_SAVING_PARAMS_NOT_SUP;
+ goto done;
+ }
+
+ if (bp != NULL && bp->b_un.b_addr && bp->b_bcount) {
+ len = 0;
+ bdlen = 0;
+ if (!(scsipkt->pkt_cdbp[1] & 8)) {
+ if (scsipkt->pkt_cdbp[0] == SCMD_MODE_SENSE_G1 &&
+ (scsipkt->pkt_cdbp[0] & 0x10))
+ bdlen = 16;
+ else
+ bdlen = 8;
+ }
+ /* Build mode parameter header */
+ if (spx->txlt_scsi_pkt->pkt_cdbp[0] == SCMD_MODE_SENSE) {
+ /* 4-byte mode parameter header */
+ buf[len++] = 0; /* mode data length */
+ buf[len++] = 0; /* medium type */
+ buf[len++] = 0; /* dev-specific param */
+ buf[len++] = bdlen; /* Block Descriptor length */
+ } else {
+ /* 8-byte mode parameter header */
+ buf[len++] = 0; /* mode data length */
+ buf[len++] = 0;
+ buf[len++] = 0; /* medium type */
+ buf[len++] = 0; /* dev-specific param */
+ if (bdlen == 16)
+ buf[len++] = 1; /* long lba descriptor */
+ else
+ buf[len++] = 0;
+ buf[len++] = 0;
+ buf[len++] = 0; /* Block Descriptor length */
+ buf[len++] = bdlen;
+ }
+
+ sdinfo = sata_get_device_info(
+ spx->txlt_sata_hba_inst,
+ &spx->txlt_sata_pkt->satapkt_device);
+
+ /* Build block descriptor only if not disabled (DBD) */
+ if ((scsipkt->pkt_cdbp[1] & 0x08) == 0) {
+ /* Block descriptor - direct-access device format */
+ if (bdlen == 8) {
+ /* build regular block descriptor */
+ buf[len++] =
+ (sdinfo->satadrv_capacity >> 24) & 0xff;
+ buf[len++] =
+ (sdinfo->satadrv_capacity >> 16) & 0xff;
+ buf[len++] =
+ (sdinfo->satadrv_capacity >> 8) & 0xff;
+ buf[len++] = sdinfo->satadrv_capacity & 0xff;
+ buf[len++] = 0; /* density code */
+ buf[len++] = 0;
+ if (sdinfo->satadrv_type ==
+ SATA_DTYPE_ATADISK)
+ buf[len++] = 2;
+ else
+ /* ATAPI */
+ buf[len++] = 8;
+ buf[len++] = 0;
+ } else if (bdlen == 16) {
+ /* Long LBA Accepted */
+ /* build long lba block descriptor */
+#ifndef __lock_lint
+ buf[len++] =
+ (sdinfo->satadrv_capacity >> 56) & 0xff;
+ buf[len++] =
+ (sdinfo->satadrv_capacity >> 48) & 0xff;
+ buf[len++] =
+ (sdinfo->satadrv_capacity >> 40) & 0xff;
+ buf[len++] =
+ (sdinfo->satadrv_capacity >> 32) & 0xff;
+#endif
+ buf[len++] =
+ (sdinfo->satadrv_capacity >> 24) & 0xff;
+ buf[len++] =
+ (sdinfo->satadrv_capacity >> 16) & 0xff;
+ buf[len++] =
+ (sdinfo->satadrv_capacity >> 8) & 0xff;
+ buf[len++] = sdinfo->satadrv_capacity & 0xff;
+ buf[len++] = 0;
+ buf[len++] = 0; /* density code */
+ buf[len++] = 0;
+ buf[len++] = 0;
+ if (sdinfo->satadrv_type ==
+ SATA_DTYPE_ATADISK)
+ buf[len++] = 2;
+ else
+ /* ATAPI */
+ buf[len++] = 8;
+ buf[len++] = 0;
+ }
+ }
+ /*
+ * Add requested pages.
+ * Page 3 and 4 are obsolete and we are not supporting them.
+ * We deal now with:
+ * caching (read/write cache control).
+ * We should eventually deal with following mode pages:
+ * error recovery (0x01),
+ * power condition (0x1a),
+ * exception control page (enables SMART) (0x1c),
+ * enclosure management (ses),
+ * protocol-specific port mode (port control).
+ */
+ switch (scsipkt->pkt_cdbp[2] & 0x3f) {
+ case MODEPAGE_RW_ERRRECOV:
+ /* DAD_MODE_ERR_RECOV */
+ /* R/W recovery */
+ len += sata_build_msense_page_1(sdinfo, pc, buf+len);
+ break;
+ case MODEPAGE_CACHING:
+ /* DAD_MODE_CACHE */
+ /* caching */
+ len += sata_build_msense_page_8(sdinfo, pc, buf+len);
+ break;
+ case MODEPAGE_INFO_EXCPT:
+ /* exception cntrl */
+ len += sata_build_msense_page_1c(sdinfo, pc, buf+len);
+ break;
+ case MODEPAGE_POWER_COND:
+ /* DAD_MODE_POWER_COND */
+ /* power condition */
+ len += sata_build_msense_page_1a(sdinfo, pc, buf+len);
+ break;
+ case MODEPAGE_ALLPAGES:
+ /* all pages */
+ len += sata_build_msense_page_1(sdinfo, pc, buf+len);
+ len += sata_build_msense_page_8(sdinfo, pc, buf+len);
+ len += sata_build_msense_page_1a(sdinfo, pc, buf+len);
+ len += sata_build_msense_page_1c(sdinfo, pc, buf+len);
+ break;
+ default:
+ /* Invalid request */
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+ sense = sata_arq_sense(spx);
+ sense->es_key = KEY_ILLEGAL_REQUEST;
+ sense->es_add_code = SD_SCSI_INVALID_FIELD_IN_CDB;
+ goto done;
+ }
+
+ /* fix total mode data length */
+ if (spx->txlt_scsi_pkt->pkt_cdbp[0] == SCMD_MODE_SENSE) {
+ /* 4-byte mode parameter header */
+ buf[0] = len - 1; /* mode data length */
+ } else {
+ buf[0] = (len -2) >> 8;
+ buf[1] = (len -2) & 0xff;
+ }
+
+
+ /* Check allocation length */
+ if (scsipkt->pkt_cdbp[0] == SCMD_MODE_SENSE) {
+ alc_len = scsipkt->pkt_cdbp[4];
+ } else {
+ alc_len = scsipkt->pkt_cdbp[7];
+ alc_len = (len << 8) | scsipkt->pkt_cdbp[8];
+ }
+ /*
+ * We do not check for possible parameters truncation
+ * (alc_len < len) assuming that the target driver works
+ * correctly. Just avoiding overrun.
+ * Copy no more than requested and possible, buffer-wise.
+ */
+ count = MIN(alc_len, len);
+ count = MIN(bp->b_bcount, count);
+ bcopy(buf, bp->b_un.b_addr, count);
+
+ scsipkt->pkt_state |= STATE_XFERRED_DATA;
+ scsipkt->pkt_resid = alc_len > count ? alc_len - count : 0;
+ }
+ *scsipkt->pkt_scbp = STATUS_GOOD;
+done:
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ (void) kmem_free(buf, 1024);
+
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
+
+ if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
+ scsipkt->pkt_comp != NULL)
+ /* scsi callback required */
+ (*scsipkt->pkt_comp)(scsipkt);
+
+ return (TRAN_ACCEPT);
+}
+
+
+/*
+ * SATA translate command: Mode Select.
+ * Translated into appropriate SATA command or emulated.
+ * Saving parameters is not supported.
+ * Changing device capacity is not supported (although theoretically
+ * possible by executing SET FEATURES/SET MAX ADDRESS)
+ *
+ * Assumption is that the target driver is working correctly.
+ *
+ * More than one SATA command may be executed to perform operations specified
+ * by mode select pages. The first error terminates further execution.
+ * Operations performed successully are not backed-up in such case.
+ *
+ * NOTE: only caching mode select page is implemented.
+ * Caching setup is remembered so it could be re-stored in case of
+ * an unexpected device reset.
+ *
+ * Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
+ */
+
+static int
+sata_txlt_mode_select(sata_pkt_txlate_t *spx)
+{
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ struct buf *bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
+ struct scsi_extended_sense *sense;
+ int len, pagelen, count, pllen;
+ uint8_t *buf; /* mode select buffer */
+ int rval, stat;
+ uint_t nointr_flag;
+ int dmod = 0;
+
+ SATADBG2(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "sata_txlt_mode_select, pc %x page code 0x%02x\n",
+ spx->txlt_scsi_pkt->pkt_cdbp[2] >> 6,
+ spx->txlt_scsi_pkt->pkt_cdbp[2] & 0x3f);
+
+ mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+ if ((rval = sata_txlt_generic_pkt_info(spx)) != TRAN_ACCEPT) {
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ return (rval);
+ }
+
+ rval = TRAN_ACCEPT;
+
+ scsipkt->pkt_reason = CMD_CMPLT;
+ scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD | STATE_GOT_STATUS;
+
+ /* Reject not supported request */
+ if (! (scsipkt->pkt_cdbp[1] & 0x10) || /* No support for PF bit = 0 */
+ (scsipkt->pkt_cdbp[1] & 0x01)) { /* No support for SP (saving) */
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+ sense = sata_arq_sense(spx);
+ sense->es_key = KEY_ILLEGAL_REQUEST;
+ sense->es_add_code = SD_SCSI_INVALID_FIELD_IN_CDB;
+ goto done;
+ }
+
+ if (scsipkt->pkt_cdbp[0] == SCMD_MODE_SELECT) {
+ pllen = scsipkt->pkt_cdbp[4];
+ } else {
+ pllen = scsipkt->pkt_cdbp[7];
+ pllen = (pllen << 8) | scsipkt->pkt_cdbp[7];
+ }
+
+ *scsipkt->pkt_scbp = STATUS_GOOD; /* Presumed outcome */
+
+ if (bp != NULL && bp->b_un.b_addr && bp->b_bcount && pllen != 0) {
+ buf = (uint8_t *)bp->b_un.b_addr;
+ count = MIN(bp->b_bcount, pllen);
+ scsipkt->pkt_state |= STATE_XFERRED_DATA;
+ scsipkt->pkt_resid = 0;
+ pllen = count;
+
+ /*
+ * Check the header to skip the block descriptor(s) - we
+ * do not support setting device capacity.
+ * Existing macros do not recognize long LBA dscriptor,
+ * hence manual calculation.
+ */
+ if (scsipkt->pkt_cdbp[0] == SCMD_MODE_SELECT) {
+ /* 6-bytes CMD, 4 bytes header */
+ if (count <= 4)
+ goto done; /* header only */
+ len = buf[3] + 4;
+ } else {
+ /* 10-bytes CMD, 8 bytes header */
+ if (count <= 8)
+ goto done; /* header only */
+ len = buf[6];
+ len = (len << 8) + buf[7] + 8;
+ }
+ if (len >= count)
+ goto done; /* header + descriptor(s) only */
+
+ pllen -= len; /* remaining data length */
+
+ /*
+ * We may be executing SATA command and want to execute it
+ * in SYNCH mode, regardless of scsi_pkt setting.
+ * Save scsi_pkt setting and indicate SYNCH mode
+ */
+ nointr_flag = scsipkt->pkt_flags & FLAG_NOINTR;
+ if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
+ scsipkt->pkt_comp != NULL) {
+ scsipkt->pkt_flags |= FLAG_NOINTR;
+ }
+ spx->txlt_sata_pkt->satapkt_op_mode = SATA_OPMODE_SYNCH;
+
+ /*
+ * len is now the offset to a first mode select page
+ * Process all pages
+ */
+ while (pllen > 0) {
+ switch ((int)buf[len]) {
+ case MODEPAGE_CACHING:
+ stat = sata_mode_select_page_8(spx,
+ (struct mode_cache_scsi3 *)&buf[len],
+ pllen, &pagelen, &rval, &dmod);
+ /*
+ * The pagelen value indicates number of
+ * parameter bytes already processed.
+ * The rval is return value from
+ * sata_tran_start().
+ * The stat indicates the overall status of
+ * the operation(s).
+ */
+ if (stat != SATA_SUCCESS)
+ /*
+ * Page processing did not succeed -
+ * all error info is already set-up,
+ * just return
+ */
+ pllen = 0; /* this breaks the loop */
+ else {
+ len += pagelen;
+ pllen -= pagelen;
+ }
+ break;
+
+ default:
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+ sense = sata_arq_sense(spx);
+ sense->es_key = KEY_ILLEGAL_REQUEST;
+ sense->es_add_code =
+ SD_SCSI_INVALID_FIELD_IN_PARAMETER_LIST;
+ goto done;
+ }
+ }
+ }
+done:
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ /*
+ * If device parameters were modified, fetch and store the new
+ * Identify Device data. Since port mutex could have been released
+ * for accessing HBA driver, we need to re-check device existence.
+ */
+ if (dmod != 0) {
+ sata_drive_info_t new_sdinfo, *sdinfo;
+ int rv;
+
+ new_sdinfo.satadrv_addr =
+ spx->txlt_sata_pkt->satapkt_device.satadev_addr;
+ rv = sata_fetch_device_identify_data(spx->txlt_sata_hba_inst,
+ &new_sdinfo);
+
+ mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ /*
+ * Since port mutex could have been released when
+ * accessing HBA driver, we need to re-check that the
+ * framework still holds the device info structure.
+ */
+ sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
+ &spx->txlt_sata_pkt->satapkt_device);
+ if (sdinfo != NULL) {
+ /*
+ * Device still has info structure in the
+ * sata framework. Copy newly fetched info
+ */
+ if (rv == 0) {
+ sdinfo->satadrv_id = new_sdinfo.satadrv_id;
+ sata_save_drive_settings(sdinfo);
+ } else {
+ /*
+ * Could not fetch new data - invalidate
+ * sata_drive_info. That makes device
+ * unusable.
+ */
+ sdinfo->satadrv_type = SATA_DTYPE_UNKNOWN;
+ sdinfo->satadrv_state = SATA_STATE_UNKNOWN;
+ }
+ }
+ if (rv != 0 || sdinfo == NULL) {
+ /*
+ * This changes the overall mode select completion
+ * reason to a failed one !!!!!
+ */
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+ sense = sata_arq_sense(spx);
+ scsipkt->pkt_reason = CMD_INCOMPLETE;
+ rval = TRAN_ACCEPT;
+ }
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ }
+ /* Restore the scsi pkt flags */
+ scsipkt->pkt_flags &= ~FLAG_NOINTR;
+ scsipkt->pkt_flags |= nointr_flag;
+
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
+
+ if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
+ scsipkt->pkt_comp != NULL)
+ /* scsi callback required */
+ (*scsipkt->pkt_comp)(scsipkt);
+
+ return (rval);
+}
+
+
+
+/*
+ * Translate command: Log Sense
+ * Not implemented at this time - returns invalid command response.
+ */
+static int
+sata_txlt_log_sense(sata_pkt_txlate_t *spx)
+{
+ SATADBG2(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "sata_txlt_log_sense, pc %x, page code %x\n",
+ spx->txlt_scsi_pkt->pkt_cdbp[2] >> 6,
+ spx->txlt_scsi_pkt->pkt_cdbp[2] & 0x3f);
+
+ return (sata_txlt_invalid_command(spx));
+}
+
+/*
+ * Translate command: Log Select
+ * Not implemented at this time - returns invalid command response.
+ */
+static int
+sata_txlt_log_select(sata_pkt_txlate_t *spx)
+{
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "sata_txlt_log_select\n", NULL);
+
+ return (sata_txlt_invalid_command(spx));
+}
+
+
+/*
+ * Translate command: Read (various types).
+ * Translated into appropriate type of ATA READ command
+ * (NO ATAPI implementation yet).
+ * Both the device capabilities and requested operation mode are
+ * considered.
+ *
+ * Following scsi cdb fields are ignored:
+ * rdprotect, dpo, fua, fua_nv, group_number.
+ *
+ * If SATA_ENABLE_QUEUING flag is set (in the global SATA HBA framework
+ * enable variable sata_func_enable), the capability of the controller and
+ * capability of a device are checked and if both support queueing, read
+ * request will be translated to READ_DMA_QUEUEING or READ_DMA_QUEUEING_EXT
+ * command rather than plain READ_XXX command.
+ * If SATA_ENABLE_NCQ flag is set in addition to SATA_ENABLE_QUEUING flag and
+ * both the controller and device suport such functionality, the read
+ * request will be translated to READ_FPDMA_QUEUED command.
+ *
+ * Returns TRAN_ACCEPT or code returned by sata_hba_start() and
+ * appropriate values in scsi_pkt fields.
+ */
+static int
+sata_txlt_read(sata_pkt_txlate_t *spx)
+{
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ sata_cmd_t *scmd = &spx->txlt_sata_pkt->satapkt_cmd;
+ sata_drive_info_t *sdinfo;
+ sata_hba_inst_t *shi = SATA_TXLT_HBA_INST(spx);
+ int cport = SATA_TXLT_CPORT(spx);
+ uint16_t sec_count;
+ uint64_t lba;
+ int rval;
+ int synch;
+
+ mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+ if ((rval = sata_txlt_generic_pkt_info(spx)) != TRAN_ACCEPT) {
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ return (rval);
+ }
+
+ sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
+ &spx->txlt_sata_pkt->satapkt_device);
+
+ scmd->satacmd_flags &= ~SATA_XFER_DIR_MASK;
+ scmd->satacmd_flags |= SATA_DIR_READ;
+ /*
+ * Build cmd block depending on the device capability and
+ * requested operation mode.
+ * Do not bother with non-dma mode.
+ */
+ switch ((uint_t)scsipkt->pkt_cdbp[0]) {
+ case SCMD_READ:
+ /* 6-byte scsi read cmd : 0x08 */
+ lba = (scsipkt->pkt_cdbp[1] & 0x1f);
+ lba = (lba << 8) | scsipkt->pkt_cdbp[2];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[3];
+ sec_count = scsipkt->pkt_cdbp[4];
+ /* sec_count 0 will be interpreted as 256 by a device */
+ break;
+ case SCMD_READ_G1:
+ /* 10-bytes scsi read command : 0x28 */
+ lba = scsipkt->pkt_cdbp[2];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[3];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[4];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[5];
+ sec_count = scsipkt->pkt_cdbp[7];
+ sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[8];
+ break;
+ case SCMD_READ_G5:
+ /* 12-bytes scsi read command : 0xA8 */
+ lba = scsipkt->pkt_cdbp[2];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[3];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[4];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[5];
+ sec_count = scsipkt->pkt_cdbp[6];
+ sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[7];
+ sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[8];
+ sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[9];
+ break;
+ case SCMD_READ_G4:
+ /* 16-bytes scsi read command : 0x88 */
+ lba = scsipkt->pkt_cdbp[2];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[3];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[4];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[5];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[6];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[7];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[8];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[9];
+ sec_count = scsipkt->pkt_cdbp[10];
+ sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[11];
+ sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[12];
+ sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[13];
+ break;
+ default:
+ /* Unsupported command */
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ return (sata_txlt_invalid_command(spx));
+ }
+
+ /*
+ * Check if specified address exceeds device capacity
+ */
+ if ((lba >= sdinfo->satadrv_capacity) ||
+ ((lba + sec_count) >= sdinfo->satadrv_capacity)) {
+ /* LBA out of range */
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ return (sata_txlt_lba_out_of_range(spx));
+ }
+
+ scmd->satacmd_addr_type = ATA_ADDR_LBA;
+ scmd->satacmd_device_reg = SATA_ADH_LBA;
+ scmd->satacmd_cmd_reg = SATAC_READ_DMA;
+ if (sdinfo->satadrv_features_support & SATA_DEV_F_LBA48) {
+ scmd->satacmd_addr_type = ATA_ADDR_LBA48;
+ scmd->satacmd_cmd_reg = SATAC_READ_DMA_EXT;
+ scmd->satacmd_sec_count_msb = sec_count >> 8;
+#ifndef __lock_lint
+ scmd->satacmd_lba_low_msb = (lba >> 24) & 0xff;
+ scmd->satacmd_lba_mid_msb = (lba >> 32) & 0xff;
+ scmd->satacmd_lba_high_msb = lba >> 40;
+#endif
+ } else if (sdinfo->satadrv_features_support & SATA_DEV_F_LBA28) {
+ scmd->satacmd_addr_type = ATA_ADDR_LBA28;
+ scmd->satacmd_device_reg = SATA_ADH_LBA | ((lba >> 24) & 0xf);
+ }
+ scmd->satacmd_sec_count_lsb = sec_count & 0xff;
+ scmd->satacmd_lba_low_lsb = lba & 0xff;
+ scmd->satacmd_lba_mid_lsb = (lba >> 8) & 0xff;
+ scmd->satacmd_lba_high_lsb = (lba >> 16) & 0xff;
+ scmd->satacmd_features_reg = 0;
+ scmd->satacmd_status_reg = 0;
+ scmd->satacmd_error_reg = 0;
+
+ /*
+ * Check if queueing commands should be used and switch
+ * to appropriate command if possible
+ */
+ if (sata_func_enable & SATA_ENABLE_QUEUING) {
+ if (sdinfo->satadrv_queue_depth > 1 &&
+ SATA_QDEPTH(spx->txlt_sata_hba_inst) > 1) {
+ /* Queuing supported by controller and device */
+ if ((sata_func_enable & SATA_ENABLE_NCQ) &&
+ (sdinfo->satadrv_features_support &
+ SATA_DEV_F_NCQ) &&
+ (SATA_FEATURES(spx->txlt_sata_hba_inst) &
+ SATA_CTLF_NCQ)) {
+ /* NCQ supported - use FPDMA READ */
+ scmd->satacmd_cmd_reg =
+ SATAC_READ_FPDMA_QUEUED;
+ scmd->satacmd_features_reg_ext =
+ scmd->satacmd_sec_count_msb;
+ scmd->satacmd_sec_count_msb = 0;
+ } else {
+ /* Legacy queueing */
+ if (sdinfo->satadrv_features_support &
+ SATA_DEV_F_LBA48) {
+ scmd->satacmd_cmd_reg =
+ SATAC_READ_DMA_QUEUED_EXT;
+ scmd->satacmd_features_reg_ext =
+ scmd->satacmd_sec_count_msb;
+ scmd->satacmd_sec_count_msb = 0;
+ } else {
+ scmd->satacmd_cmd_reg =
+ SATAC_READ_DMA_QUEUED;
+ }
+ }
+ scmd->satacmd_features_reg =
+ scmd->satacmd_sec_count_lsb;
+ scmd->satacmd_sec_count_lsb = 0;
+ scmd->satacmd_flags |= SATA_QUEUED_CMD;
+ }
+ }
+
+ SATADBG3(SATA_DBG_HBA_IF, spx->txlt_sata_hba_inst,
+ "sata_txlt_read cmd 0x%2x, lba %llx, sec count %x\n",
+ scmd->satacmd_cmd_reg, lba, sec_count);
+
+ if (!(spx->txlt_sata_pkt->satapkt_op_mode & SATA_OPMODE_SYNCH)) {
+ /* Need callback function */
+ spx->txlt_sata_pkt->satapkt_comp = sata_txlt_rw_completion;
+ synch = FALSE;
+ } else
+ synch = TRUE;
+
+ /* Transfer command to HBA */
+ if (sata_hba_start(spx, &rval) != 0) {
+ /* Pkt not accepted for execution */
+ mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
+ return (rval);
+ }
+ mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
+ /*
+ * If execution is non-synchronous,
+ * a callback function will handle potential errors, translate
+ * the response and will do a callback to a target driver.
+ * If it was synchronous, check execution status using the same
+ * framework callback.
+ */
+ if (synch) {
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "synchronous execution status %x\n",
+ spx->txlt_sata_pkt->satapkt_reason);
+ sata_txlt_rw_completion(spx->txlt_sata_pkt);
+ }
+ return (TRAN_ACCEPT);
+}
+
+
+/*
+ * SATA translate command: Write (various types)
+ * Translated into appropriate type of ATA WRITE command
+ * (NO ATAPI implementation yet).
+ * Both the device capabilities and requested operation mode are
+ * considered.
+ *
+ * Following scsi cdb fields are ignored:
+ * rwprotect, dpo, fua, fua_nv, group_number.
+ *
+ * Returns TRAN_ACCEPT or code returned by sata_hba_start() and
+ * appropriate values in scsi_pkt fields.
+ */
+static int
+sata_txlt_write(sata_pkt_txlate_t *spx)
+{
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ sata_cmd_t *scmd = &spx->txlt_sata_pkt->satapkt_cmd;
+ sata_drive_info_t *sdinfo;
+ sata_hba_inst_t *shi = SATA_TXLT_HBA_INST(spx);
+ int cport = SATA_TXLT_CPORT(spx);
+ uint16_t sec_count;
+ uint64_t lba;
+ int rval;
+ int synch;
+
+ mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+ if ((rval = sata_txlt_generic_pkt_info(spx)) != TRAN_ACCEPT) {
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ return (rval);
+ }
+
+ sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
+ &spx->txlt_sata_pkt->satapkt_device);
+
+ scmd->satacmd_flags &= ~SATA_XFER_DIR_MASK;
+ scmd->satacmd_flags |= SATA_DIR_WRITE;
+ /*
+ * Build cmd block depending on the device capability and
+ * requested operation mode.
+ * Do not bother with non-dma mode.
+ */
+ switch ((uint_t)scsipkt->pkt_cdbp[0]) {
+ case SCMD_WRITE:
+ /* 6-byte scsi read cmd : 0x0A */
+ lba = (scsipkt->pkt_cdbp[1] & 0x1f);
+ lba = (lba << 8) | scsipkt->pkt_cdbp[2];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[3];
+ sec_count = scsipkt->pkt_cdbp[4];
+ /* sec_count 0 will be interpreted as 256 by a device */
+ break;
+ case SCMD_WRITE_G1:
+ /* 10-bytes scsi write command : 0x2A */
+ lba = scsipkt->pkt_cdbp[2];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[3];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[4];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[5];
+ sec_count = scsipkt->pkt_cdbp[7];
+ sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[8];
+ break;
+ case SCMD_WRITE_G5:
+ /* 12-bytes scsi read command : 0xAA */
+ lba = scsipkt->pkt_cdbp[2];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[3];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[4];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[5];
+ sec_count = scsipkt->pkt_cdbp[6];
+ sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[7];
+ sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[8];
+ sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[9];
+ break;
+ case SCMD_WRITE_G4:
+ /* 16-bytes scsi write command : 0x8A */
+ lba = scsipkt->pkt_cdbp[2];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[3];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[4];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[5];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[6];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[7];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[8];
+ lba = (lba << 8) | scsipkt->pkt_cdbp[9];
+ sec_count = scsipkt->pkt_cdbp[10];
+ sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[11];
+ sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[12];
+ sec_count = (sec_count << 8) | scsipkt->pkt_cdbp[13];
+ break;
+ default:
+ /* Unsupported command */
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ return (sata_txlt_invalid_command(spx));
+ }
+
+ /*
+ * Check if specified address and length exceeds device capacity
+ */
+ if ((lba >= sdinfo->satadrv_capacity) ||
+ ((lba + sec_count) >= sdinfo->satadrv_capacity)) {
+ /* LBA out of range */
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ return (sata_txlt_lba_out_of_range(spx));
+ }
+
+ scmd->satacmd_addr_type = ATA_ADDR_LBA;
+ scmd->satacmd_device_reg = SATA_ADH_LBA;
+ scmd->satacmd_cmd_reg = SATAC_WRITE_DMA;
+ if (sdinfo->satadrv_features_support & SATA_DEV_F_LBA48) {
+ scmd->satacmd_addr_type = ATA_ADDR_LBA48;
+ scmd->satacmd_cmd_reg = SATAC_WRITE_DMA_EXT;
+ scmd->satacmd_sec_count_msb = sec_count >> 8;
+ scmd->satacmd_lba_low_msb = (lba >> 24) & 0xff;
+#ifndef __lock_lint
+ scmd->satacmd_lba_mid_msb = (lba >> 32) & 0xff;
+ scmd->satacmd_lba_high_msb = lba >> 40;
+#endif
+ } else if (sdinfo->satadrv_features_support & SATA_DEV_F_LBA28) {
+ scmd->satacmd_addr_type = ATA_ADDR_LBA28;
+ scmd->satacmd_device_reg = SATA_ADH_LBA | ((lba >> 24) & 0xf);
+ }
+ scmd->satacmd_sec_count_lsb = sec_count & 0xff;
+ scmd->satacmd_lba_low_lsb = lba & 0xff;
+ scmd->satacmd_lba_mid_lsb = (lba >> 8) & 0xff;
+ scmd->satacmd_lba_high_lsb = (lba >> 16) & 0xff;
+ scmd->satacmd_features_reg = 0;
+ scmd->satacmd_status_reg = 0;
+ scmd->satacmd_error_reg = 0;
+
+ /*
+ * Check if queueing commands should be used and switch
+ * to appropriate command if possible
+ */
+ if (sata_func_enable & SATA_ENABLE_QUEUING) {
+ if (sdinfo->satadrv_queue_depth > 1 &&
+ SATA_QDEPTH(spx->txlt_sata_hba_inst) > 1) {
+ /* Queuing supported by controller and device */
+ if ((sata_func_enable & SATA_ENABLE_NCQ) &&
+ (sdinfo->satadrv_features_support &
+ SATA_DEV_F_NCQ) &&
+ (SATA_FEATURES(spx->txlt_sata_hba_inst) &
+ SATA_CTLF_NCQ)) {
+ /* NCQ supported - use FPDMA WRITE */
+ scmd->satacmd_cmd_reg =
+ SATAC_WRITE_FPDMA_QUEUED;
+ scmd->satacmd_features_reg_ext =
+ scmd->satacmd_sec_count_msb;
+ scmd->satacmd_sec_count_msb = 0;
+ scmd->satacmd_rle_sata_cmd = NULL;
+ } else {
+ /* Legacy queueing */
+ if (sdinfo->satadrv_features_support &
+ SATA_DEV_F_LBA48) {
+ scmd->satacmd_cmd_reg =
+ SATAC_WRITE_DMA_QUEUED_EXT;
+ scmd->satacmd_features_reg_ext =
+ scmd->satacmd_sec_count_msb;
+ scmd->satacmd_sec_count_msb = 0;
+ } else {
+ scmd->satacmd_cmd_reg =
+ SATAC_WRITE_DMA_QUEUED;
+ }
+ }
+ scmd->satacmd_features_reg =
+ scmd->satacmd_sec_count_lsb;
+ scmd->satacmd_sec_count_lsb = 0;
+ scmd->satacmd_flags |= SATA_QUEUED_CMD;
+ }
+ }
+
+ SATADBG3(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "sata_txlt_write cmd 0x%2x, lba %llx, sec count %x\n",
+ scmd->satacmd_cmd_reg, lba, sec_count);
+
+ if (!(spx->txlt_sata_pkt->satapkt_op_mode & SATA_OPMODE_SYNCH)) {
+ /* Need callback function */
+ spx->txlt_sata_pkt->satapkt_comp = sata_txlt_rw_completion;
+ synch = FALSE;
+ } else
+ synch = TRUE;
+
+ /* Transfer command to HBA */
+ if (sata_hba_start(spx, &rval) != 0) {
+ /* Pkt not accepted for execution */
+ mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
+ return (rval);
+ }
+ mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
+
+ /*
+ * If execution is non-synchronous,
+ * a callback function will handle potential errors, translate
+ * the response and will do a callback to a target driver.
+ * If it was synchronous, check execution status using the same
+ * framework callback.
+ */
+ if (synch) {
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "synchronous execution status %x\n",
+ spx->txlt_sata_pkt->satapkt_reason);
+ sata_txlt_rw_completion(spx->txlt_sata_pkt);
+ }
+ return (TRAN_ACCEPT);
+}
+
+
+/*
+ * NOTE: NOT FUNCTIONAL IMPLEMENTATION. THIS IS A PLACEHOLDER for the function
+ * that will be fixed in phase 2 of the development.
+ * Currently ATAPI is not supported. ATAPI devices are threated as not-valid
+ * devices.
+ * This function is not called, since scsi_sata_start() will bail-out prior
+ * to calling it.
+ */
+static int
+sata_txlt_atapi(sata_pkt_txlate_t *spx)
+{
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ sata_cmd_t *scmd = &spx->txlt_sata_pkt->satapkt_cmd;
+ struct buf *bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
+ sata_hba_inst_t *shi = SATA_TXLT_HBA_INST(spx);
+ int cport = SATA_TXLT_CPORT(spx);
+ int rval;
+ int synch;
+ union scsi_cdb *cdbp = (union scsi_cdb *)scsipkt->pkt_cdbp;
+
+ mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+ if ((rval = sata_txlt_generic_pkt_info(spx)) != TRAN_ACCEPT) {
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ return (rval);
+ }
+
+ /*
+ * scmd->satacmd_flags default - SATA_DIR_NODATA_XFER - is set by
+ * sata_txlt_generic_pkt_info().
+ */
+ if (scmd->satacmd_bp) {
+ if (scmd->satacmd_bp->b_flags & B_READ) {
+ scmd->satacmd_flags &= ~SATA_XFER_DIR_MASK;
+ scmd->satacmd_flags |= SATA_DIR_READ;
+ } else {
+ scmd->satacmd_flags &= ~SATA_XFER_DIR_MASK;
+ scmd->satacmd_flags |= SATA_DIR_WRITE;
+ }
+ }
+
+ scmd->satacmd_acdb_len = scsi_cdb_size[GETGROUP(cdbp)];
+ scmd->satacmd_cmd_reg = SATAC_PACKET;
+ bcopy(cdbp, scmd->satacmd_acdb, 16);
+
+ /*
+ * For non-read/write commands we need to
+ * map buffer
+ */
+ switch ((uint_t)scsipkt->pkt_cdbp[0]) {
+ case SCMD_READ:
+ case SCMD_READ_G1:
+ case SCMD_READ_G5:
+ case SCMD_READ_G4:
+ case SCMD_WRITE:
+ case SCMD_WRITE_G1:
+ case SCMD_WRITE_G5:
+ case SCMD_WRITE_G4:
+ break;
+ default:
+ if (bp->b_flags & (B_PHYS | B_PAGEIO))
+ bp_mapin(bp);
+ break;
+ }
+
+ if (!(spx->txlt_sata_pkt->satapkt_op_mode & SATA_OPMODE_SYNCH)) {
+ /* Need callback function */
+ spx->txlt_sata_pkt->satapkt_comp = sata_txlt_atapi_completion;
+ synch = FALSE;
+ } else
+ synch = TRUE;
+
+ /* Transfer command to HBA */
+ if (sata_hba_start(spx, &rval) != 0) {
+ /* Pkt not accepted for execution */
+ mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
+ return (rval);
+ }
+ mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
+ /*
+ * If execution is non-synchronous,
+ * a callback function will handle potential errors, translate
+ * the response and will do a callback to a target driver.
+ * If it was synchronous, check execution status using the same
+ * framework callback.
+ */
+ if (synch) {
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "synchronous execution status %x\n",
+ spx->txlt_sata_pkt->satapkt_reason);
+ sata_txlt_atapi_completion(spx->txlt_sata_pkt);
+ }
+ return (TRAN_ACCEPT);
+}
+
+/*
+ * Translate command: Synchronize Cache.
+ * Translates into Flush Cache command.
+ * (NO ATAPI implementation yet).
+ *
+ * NOTE: We should check if Flush Cache is supported by the device (ATAPI
+ * devices)
+ *
+ * Returns TRAN_ACCEPT or code returned by sata_hba_start() and
+ * appropriate values in scsi_pkt fields.
+ */
+static int
+sata_txlt_synchronize_cache(sata_pkt_txlate_t *spx)
+{
+ sata_cmd_t *scmd = &spx->txlt_sata_pkt->satapkt_cmd;
+ sata_hba_inst_t *shi = SATA_TXLT_HBA_INST(spx);
+ int cport = SATA_TXLT_CPORT(spx);
+ int rval;
+ int synch;
+
+ mutex_enter(&(SATA_TXLT_CPORT_MUTEX(spx)));
+
+ if ((rval = sata_txlt_generic_pkt_info(spx)) != TRAN_ACCEPT) {
+ mutex_exit(&(SATA_TXLT_CPORT_MUTEX(spx)));
+ return (rval);
+ }
+
+ scmd->satacmd_addr_type = 0;
+ scmd->satacmd_cmd_reg = SATAC_FLUSH_CACHE;
+ scmd->satacmd_device_reg = 0;
+ scmd->satacmd_sec_count_lsb = 0;
+ scmd->satacmd_lba_low_lsb = 0;
+ scmd->satacmd_lba_mid_lsb = 0;
+ scmd->satacmd_lba_high_lsb = 0;
+ scmd->satacmd_features_reg = 0;
+ scmd->satacmd_status_reg = 0;
+ scmd->satacmd_error_reg = 0;
+
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "sata_txlt_synchronize_cache\n", NULL);
+
+ if (!(spx->txlt_sata_pkt->satapkt_op_mode & SATA_OPMODE_SYNCH)) {
+ /* Need to set-up a callback function */
+ spx->txlt_sata_pkt->satapkt_comp =
+ sata_txlt_nodata_cmd_completion;
+ synch = FALSE;
+ } else
+ synch = TRUE;
+
+ /* Transfer command to HBA */
+ if (sata_hba_start(spx, &rval) != 0) {
+ /* Pkt not accepted for execution */
+ mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
+ return (rval);
+ }
+ mutex_exit(&SATA_CPORT_MUTEX(shi, cport));
+
+ /*
+ * If execution non-synchronous, it had to be completed
+ * a callback function will handle potential errors, translate
+ * the response and will do a callback to a target driver.
+ * If it was synchronous, check status, using the same
+ * framework callback.
+ */
+ if (synch) {
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "synchronous execution status %x\n",
+ spx->txlt_sata_pkt->satapkt_reason);
+ sata_txlt_nodata_cmd_completion(spx->txlt_sata_pkt);
+ }
+ return (TRAN_ACCEPT);
+}
+
+
+/*
+ * Send pkt to SATA HBA driver
+ *
+ * This function may be called only if the operation is requested by scsi_pkt,
+ * i.e. scsi_pkt is not NULL.
+ *
+ * This function has to be called with cport mutex held. It does release
+ * the mutex when it calls HBA driver sata_tran_start function and
+ * re-acquires it afterwards.
+ *
+ * If return value is 0, pkt was accepted, -1 otherwise
+ * rval is set to appropriate sata_scsi_start return value.
+ *
+ * Note 1:If HBA driver returns value other than TRAN_ACCEPT, it should not
+ * have called the sata_pkt callback function for this packet.
+ *
+ * The scsi callback has to be performed by the caller of this routine.
+ *
+ * Note 2: No port multiplier support for now.
+ */
+static int
+sata_hba_start(sata_pkt_txlate_t *spx, int *rval)
+{
+ int stat;
+ sata_hba_inst_t *sata_hba_inst = spx->txlt_sata_hba_inst;
+ sata_drive_info_t *sdinfo;
+ sata_device_t sata_device;
+ uint8_t cmd;
+ uint32_t cmd_flags;
+
+ ASSERT(spx->txlt_sata_pkt != NULL);
+ ASSERT(mutex_owned(&SATA_CPORT_MUTEX(spx->txlt_sata_hba_inst,
+ spx->txlt_sata_pkt->satapkt_device.satadev_addr.cport)));
+
+ sdinfo = sata_get_device_info(sata_hba_inst,
+ &spx->txlt_sata_pkt->satapkt_device);
+ ASSERT(sdinfo != NULL);
+
+ /* Clear device reset state? */
+ if (sdinfo->satadrv_event_flags & SATA_EVNT_CLEAR_DEVICE_RESET) {
+ spx->txlt_sata_pkt->satapkt_cmd.satacmd_flags |=
+ SATA_CLEAR_DEV_RESET_STATE;
+ sdinfo->satadrv_event_flags &= ~SATA_EVNT_CLEAR_DEVICE_RESET;
+ SATADBG1(SATA_DBG_EVENTS, sata_hba_inst,
+ "sata_hba_start: clearing device reset state\n", NULL);
+ }
+ cmd = spx->txlt_sata_pkt->satapkt_cmd.satacmd_cmd_reg;
+ cmd_flags = spx->txlt_sata_pkt->satapkt_cmd.satacmd_flags;
+ sata_device = spx->txlt_sata_pkt->satapkt_device; /* local copy */
+
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sdinfo->satadrv_addr.cport)));
+
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "Sata cmd 0x%2x\n", cmd);
+
+ stat = (*SATA_START_FUNC(sata_hba_inst))(SATA_DIP(sata_hba_inst),
+ spx->txlt_sata_pkt);
+
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sdinfo->satadrv_addr.cport)));
+ /*
+ * If sata pkt was accepted and executed in asynchronous mode, i.e.
+ * with the sata callback, the sata_pkt could be already destroyed
+ * by the time we check ther return status from the hba_start()
+ * function, because sata_scsi_destroy_pkt() could have been already
+ * called (perhaps in the interrupt context). So, in such case, there
+ * should be no references to it. In other cases, sata_pkt still
+ * exists.
+ */
+ switch (stat) {
+ case SATA_TRAN_ACCEPTED:
+ /*
+ * pkt accepted for execution.
+ * If it was executed synchronously, it is already completed
+ * and pkt completion_reason indicates completion status.
+ */
+ *rval = TRAN_ACCEPT;
+ return (0);
+
+ case SATA_TRAN_QUEUE_FULL:
+ /*
+ * Controller detected queue full condition.
+ */
+ SATADBG1(SATA_DBG_HBA_IF, sata_hba_inst,
+ "sata_hba_start: queue full\n", NULL);
+
+ spx->txlt_scsi_pkt->pkt_reason = CMD_INCOMPLETE;
+ *spx->txlt_scsi_pkt->pkt_scbp = STATUS_QFULL;
+
+ *rval = TRAN_BUSY;
+ break;
+
+ case SATA_TRAN_PORT_ERROR:
+ /*
+ * Communication/link with device or general port error
+ * detected before pkt execution begun.
+ */
+ if (spx->txlt_sata_pkt->satapkt_device.satadev_addr.qual ==
+ SATA_ADDR_CPORT)
+ sata_log(sata_hba_inst, CE_CONT,
+ "port %d error",
+ sata_device.satadev_addr.cport);
+ else
+ sata_log(sata_hba_inst, CE_CONT,
+ "port %d pmport %d error\n",
+ sata_device.satadev_addr.cport,
+ sata_device.satadev_addr.pmport);
+
+ /*
+ * Update the port/device structure.
+ * sata_pkt should be still valid. Since port error is
+ * returned, sata_device content should reflect port
+ * state - it means, that sata address have been changed,
+ * because original packet's sata address refered to a device
+ * attached to some port.
+ */
+ sata_update_port_info(sata_hba_inst, &sata_device);
+ spx->txlt_scsi_pkt->pkt_reason = CMD_TRAN_ERR;
+ *rval = TRAN_FATAL_ERROR;
+ break;
+
+ case SATA_TRAN_CMD_UNSUPPORTED:
+ /*
+ * Command rejected by HBA as unsupported. It was HBA driver
+ * that rejected the command, command was not sent to
+ * an attached device.
+ */
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sdinfo->satadrv_addr.cport)));
+ (void) sata_txlt_invalid_command(spx);
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sdinfo->satadrv_addr.cport)));
+
+ if (sdinfo->satadrv_state & SATA_DSTATE_RESET)
+ SATADBG1(SATA_DBG_EVENTS, sata_hba_inst,
+ "sat_hba_start: cmd 0x%2x rejected "
+ "with SATA_TRAN_CMD_UNSUPPORTED status\n", cmd);
+
+ *rval = TRAN_ACCEPT;
+ break;
+
+ case SATA_TRAN_BUSY:
+ /*
+ * Command rejected by HBA because other operation prevents
+ * accepting the packet, or device is in RESET condition.
+ */
+ if (sdinfo != NULL) {
+ sdinfo->satadrv_state =
+ spx->txlt_sata_pkt->satapkt_device.satadev_state;
+
+ if (sdinfo->satadrv_state & SATA_DSTATE_RESET) {
+ SATADBG1(SATA_DBG_EVENTS, sata_hba_inst,
+ "sata_hba_start: cmd 0x%2x rejected "
+ "because of device reset condition\n",
+ cmd);
+ } else {
+ SATADBG1(SATA_DBG_EVENTS, sata_hba_inst,
+ "sata_hba_start: cmd 0x%2x rejected "
+ "with SATA_TRAN_BUSY status\n",
+ cmd);
+ }
+ }
+ spx->txlt_scsi_pkt->pkt_reason = CMD_INCOMPLETE;
+ *rval = TRAN_BUSY;
+ break;
+
+ default:
+ /* Unrecognized HBA response */
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_start: unrecognized HBA response "
+ "to cmd : 0x%2x resp 0x%x", cmd, rval));
+ spx->txlt_scsi_pkt->pkt_reason = CMD_TRAN_ERR;
+ *rval = TRAN_FATAL_ERROR;
+ break;
+ }
+
+ /*
+ * If we got here, the packet was rejected.
+ * Check if we need to remember reset state clearing request
+ */
+ if (cmd_flags & SATA_CLEAR_DEV_RESET_STATE) {
+ /*
+ * Check if device is still configured - it may have
+ * disapeared from the configuration
+ */
+ sdinfo = sata_get_device_info(sata_hba_inst, &sata_device);
+ if (sdinfo != NULL) {
+ /*
+ * Restore the flag that requests clearing of
+ * the device reset state,
+ * so the next sata packet may carry it to HBA.
+ */
+ sdinfo->satadrv_event_flags |=
+ SATA_EVNT_CLEAR_DEVICE_RESET;
+ }
+ }
+ return (-1);
+}
+
+/*
+ * Scsi response setup for invalid LBA
+ *
+ * Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields.
+ */
+static int
+sata_txlt_lba_out_of_range(sata_pkt_txlate_t *spx)
+{
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ struct scsi_extended_sense *sense;
+
+ scsipkt->pkt_reason = CMD_CMPLT;
+ scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD | STATE_GOT_STATUS;
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+ sense = sata_arq_sense(spx);
+ sense->es_key = KEY_ILLEGAL_REQUEST;
+ sense->es_add_code = SD_SCSI_LBA_OUT_OF_RANGE;
+
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
+
+ if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
+ scsipkt->pkt_comp != NULL)
+ /* scsi callback required */
+ (*scsipkt->pkt_comp)(scsipkt);
+ return (TRAN_ACCEPT);
+}
+
+
+/*
+ * Analyze device status and error registers and translate them into
+ * appropriate scsi sense codes.
+ * NOTE: non-packet commands only for now
+ */
+static void
+sata_decode_device_error(sata_pkt_txlate_t *spx,
+ struct scsi_extended_sense *sense)
+{
+ uint8_t err_reg = spx->txlt_sata_pkt->satapkt_cmd.satacmd_error_reg;
+
+ ASSERT(sense != NULL);
+ ASSERT(spx->txlt_sata_pkt->satapkt_cmd.satacmd_status_reg &
+ SATA_STATUS_ERR);
+
+
+ if (err_reg & SATA_ERROR_ICRC) {
+ sense->es_key = KEY_ABORTED_COMMAND;
+ sense->es_add_code = 0x08; /* Communication failure */
+ return;
+ }
+
+ if (err_reg & SATA_ERROR_UNC) {
+ sense->es_key = KEY_MEDIUM_ERROR;
+ /* Information bytes (LBA) need to be set by a caller */
+ return;
+ }
+
+ /* ADD HERE: MC error bit handling for ATAPI CD/DVD */
+ if (err_reg & (SATA_ERROR_MCR | SATA_ERROR_NM)) {
+ sense->es_key = KEY_UNIT_ATTENTION;
+ sense->es_add_code = 0x3a; /* No media present */
+ return;
+ }
+
+ if (err_reg & SATA_ERROR_IDNF) {
+ if (err_reg & SATA_ERROR_ABORT) {
+ sense->es_key = KEY_ABORTED_COMMAND;
+ } else {
+ sense->es_key = KEY_ILLEGAL_REQUEST;
+ sense->es_add_code = 0x21; /* LBA out of range */
+ }
+ return;
+ }
+
+ if (err_reg & SATA_ERROR_ABORT) {
+ ASSERT(spx->txlt_sata_pkt != NULL);
+ sense->es_key = KEY_ABORTED_COMMAND;
+ return;
+ }
+}
+
+/*
+ * Extract error LBA from sata_pkt.satapkt_cmd register fields
+ */
+static void
+sata_extract_error_lba(sata_pkt_txlate_t *spx, uint64_t *lba)
+{
+ sata_cmd_t *sata_cmd = &spx->txlt_sata_pkt->satapkt_cmd;
+
+ *lba = 0;
+ if (sata_cmd->satacmd_addr_type == ATA_ADDR_LBA48) {
+ *lba = sata_cmd->satacmd_lba_high_msb;
+ *lba = (*lba << 8) | sata_cmd->satacmd_lba_mid_msb;
+ *lba = (*lba << 8) | sata_cmd->satacmd_lba_low_msb;
+ } else if (sata_cmd->satacmd_addr_type == ATA_ADDR_LBA28) {
+ *lba = sata_cmd->satacmd_device_reg & 0xf;
+ }
+ *lba = (*lba << 8) | sata_cmd->satacmd_lba_high_lsb;
+ *lba = (*lba << 8) | sata_cmd->satacmd_lba_mid_lsb;
+ *lba = (*lba << 8) | sata_cmd->satacmd_lba_high_lsb;
+}
+
+/*
+ * This is fixed sense format - if LBA exceeds the info field size,
+ * no valid info will be returned (valid bit in extended sense will
+ * be set to 0).
+ */
+static struct scsi_extended_sense *
+sata_arq_sense(sata_pkt_txlate_t *spx)
+{
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ struct scsi_arq_status *arqs;
+ struct scsi_extended_sense *sense;
+
+ /* Fill ARQ sense data */
+ scsipkt->pkt_state |= STATE_ARQ_DONE;
+ arqs = (struct scsi_arq_status *)scsipkt->pkt_scbp;
+ *(uchar_t *)&arqs->sts_status = STATUS_CHECK;
+ *(uchar_t *)&arqs->sts_rqpkt_status = STATUS_GOOD;
+ arqs->sts_rqpkt_reason = CMD_CMPLT;
+ arqs->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_XFERRED_DATA | STATE_SENT_CMD | STATE_GOT_STATUS;
+ arqs->sts_rqpkt_resid = 0;
+ sense = &arqs->sts_sensedata;
+ bzero(sense, sizeof (struct scsi_extended_sense));
+ sense->es_valid = 1; /* Valid sense */
+ sense->es_class = 7; /* Response code 0x70 - current err */
+ sense->es_key = KEY_NO_SENSE;
+ sense->es_info_1 = 0;
+ sense->es_info_2 = 0;
+ sense->es_info_3 = 0;
+ sense->es_info_4 = 0;
+ sense->es_add_len = 6; /* Additional length */
+ sense->es_cmd_info[0] = 0;
+ sense->es_cmd_info[1] = 0;
+ sense->es_cmd_info[2] = 0;
+ sense->es_cmd_info[3] = 0;
+ sense->es_add_code = 0;
+ sense->es_qual_code = 0;
+ return (sense);
+}
+
+
+/*
+ * Translate completion status of SATA read/write commands into scsi response.
+ * pkt completion_reason is checked to determine the completion status.
+ * Do scsi callback if necessary.
+ *
+ * Note: this function may be called also for synchronously executed
+ * commands.
+ * This function may be used only if scsi_pkt is non-NULL.
+ */
+static void
+sata_txlt_rw_completion(sata_pkt_t *sata_pkt)
+{
+ sata_pkt_txlate_t *spx =
+ (sata_pkt_txlate_t *)sata_pkt->satapkt_framework_private;
+ sata_cmd_t *scmd = &sata_pkt->satapkt_cmd;
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ struct scsi_extended_sense *sense;
+ uint64_t lba;
+
+ if (sata_pkt->satapkt_reason == SATA_PKT_COMPLETED) {
+ /* Normal completion */
+ scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD | STATE_XFERRED_DATA | STATE_GOT_STATUS;
+ scsipkt->pkt_reason = CMD_CMPLT;
+ *scsipkt->pkt_scbp = STATUS_GOOD;
+ } else {
+ /*
+ * Something went wrong - analyze return
+ */
+ scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD | STATE_GOT_STATUS;
+ scsipkt->pkt_reason = CMD_INCOMPLETE;
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+ sense = sata_arq_sense(spx);
+ ASSERT(sense != NULL);
+
+ /*
+ * SATA_PKT_DEV_ERROR is the only case where we may be able to
+ * extract form device registers the failing LBA.
+ */
+ if (sata_pkt->satapkt_reason == SATA_PKT_DEV_ERROR) {
+ if ((scmd->satacmd_addr_type == ATA_ADDR_LBA48) &&
+ (scmd->satacmd_lba_mid_msb != 0 ||
+ scmd->satacmd_lba_high_msb != 0)) {
+ /*
+ * We have problem reporting this cmd LBA
+ * in fixed sense data format, because of
+ * the size of the scsi LBA fields.
+ */
+ sense->es_valid = 0;
+ } else {
+ sata_extract_error_lba(spx, &lba);
+ sense->es_info_1 = (lba & 0xFF000000) >> 24;
+ sense->es_info_1 = (lba & 0xFF0000) >> 16;
+ sense->es_info_1 = (lba & 0xFF00) >> 8;
+ sense->es_info_1 = lba & 0xFF;
+ }
+ } else {
+ /* Invalid extended sense info */
+ sense->es_valid = 0;
+ }
+
+ switch (sata_pkt->satapkt_reason) {
+ case SATA_PKT_PORT_ERROR:
+ /* We may want to handle DEV GONE state as well */
+ /*
+ * We have no device data. Assume no data transfered.
+ */
+ sense->es_key = KEY_HARDWARE_ERROR;
+ break;
+
+ case SATA_PKT_DEV_ERROR:
+ if (sata_pkt->satapkt_cmd.satacmd_status_reg &
+ SATA_STATUS_ERR) {
+ /*
+ * determine dev error reason from error
+ * reg content
+ */
+ sata_decode_device_error(spx, sense);
+ if (sense->es_key == KEY_MEDIUM_ERROR) {
+ switch (scmd->satacmd_cmd_reg) {
+ case SATAC_READ_DMA:
+ case SATAC_READ_DMA_EXT:
+ case SATAC_READ_DMA_QUEUED:
+ case SATAC_READ_DMA_QUEUED_EXT:
+ case SATAC_READ_FPDMA_QUEUED:
+ /* Unrecovered read error */
+ sense->es_add_code =
+ SD_SCSI_UNREC_READ_ERROR;
+ break;
+ case SATAC_WRITE_DMA:
+ case SATAC_WRITE_DMA_EXT:
+ case SATAC_WRITE_DMA_QUEUED:
+ case SATAC_WRITE_DMA_QUEUED_EXT:
+ case SATAC_WRITE_FPDMA_QUEUED:
+ /* Write error */
+ sense->es_add_code =
+ SD_SCSI_WRITE_ERROR;
+ break;
+ default:
+ /* Internal error */
+ SATA_LOG_D((
+ spx->txlt_sata_hba_inst,
+ CE_WARN,
+ "sata_txlt_rw_completion :"
+ "internal error - invalid "
+ "command 0x%2x",
+ scmd->satacmd_cmd_reg));
+ break;
+ }
+ }
+ break;
+ }
+ /* No extended sense key - no info available */
+ scsipkt->pkt_reason = CMD_INCOMPLETE;
+ break;
+
+ case SATA_PKT_TIMEOUT:
+ /* scsipkt->pkt_reason = CMD_TIMEOUT; */
+ scsipkt->pkt_reason = CMD_INCOMPLETE;
+ /* No extended sense key ? */
+ break;
+
+ case SATA_PKT_ABORTED:
+ scsipkt->pkt_reason = CMD_ABORTED;
+ /* No extended sense key ? */
+ break;
+
+ case SATA_PKT_RESET:
+ scsipkt->pkt_reason = CMD_RESET;
+ break;
+
+ default:
+ SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
+ "sata_txlt_rw_completion: "
+ "invalid packet completion reason"));
+ scsipkt->pkt_reason = CMD_TRAN_ERR;
+ break;
+ }
+ }
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
+
+ if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
+ scsipkt->pkt_comp != NULL)
+ /* scsi callback required */
+ (*scsipkt->pkt_comp)(scsipkt);
+
+}
+
+/*
+ * NON FUNCTIONAL IMPLEMENTATION. THIS IS A PLACE HOLDER.
+ * ATAPI devices are not supported currently (are not be attached recognized
+ * as valid devices).
+ * Will be fixed in phase 2 of the development.
+ */
+static void
+sata_txlt_atapi_completion(sata_pkt_t *sata_pkt)
+{
+ sata_pkt_txlate_t *spx =
+ (sata_pkt_txlate_t *)sata_pkt->satapkt_framework_private;
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ struct scsi_arq_status *arqs;
+
+ if (sata_pkt->satapkt_reason == SATA_PKT_COMPLETED) {
+ /* Normal completion */
+ scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD | STATE_XFERRED_DATA | STATE_GOT_STATUS;
+ scsipkt->pkt_reason = CMD_CMPLT;
+ *scsipkt->pkt_scbp = STATUS_GOOD;
+ scsipkt->pkt_resid = 0;
+ } else {
+ /*
+ * Something went wrong - analyze return
+ */
+ scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD | STATE_GOT_STATUS | STATE_ARQ_DONE;
+ scsipkt->pkt_reason = CMD_CMPLT;
+
+ arqs = (struct scsi_arq_status *)scsipkt->pkt_scbp;
+ *(uchar_t *)&arqs->sts_status = STATUS_CHECK;
+ *(uchar_t *)&arqs->sts_rqpkt_status = STATUS_GOOD;
+ arqs->sts_rqpkt_reason = CMD_CMPLT;
+ arqs->sts_rqpkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_XFERRED_DATA | STATE_SENT_CMD | STATE_GOT_STATUS;
+ arqs->sts_rqpkt_resid = 0;
+
+ bcopy(sata_pkt->satapkt_cmd.satacmd_rqsense,
+ &arqs->sts_sensedata, SATA_ATAPI_RQSENSE_LEN);
+ }
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
+
+ if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
+ scsipkt->pkt_comp != NULL) {
+ /* scsi callback required */
+ (*scsipkt->pkt_comp)(scsipkt);
+ }
+}
+
+
+/*
+ * Translate completion status of non-data commands (i.e. commands returning
+ * no data).
+ * pkt completion_reason is checked to determine the completion status.
+ * Do scsi callback if necessary (FLAG_NOINTR == 0)
+ *
+ * Note: this function may be called also for synchronously executed
+ * commands.
+ * This function may be used only if scsi_pkt is non-NULL.
+ */
+
+static void
+sata_txlt_nodata_cmd_completion(sata_pkt_t *sata_pkt)
+{
+ sata_pkt_txlate_t *spx =
+ (sata_pkt_txlate_t *)sata_pkt->satapkt_framework_private;
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ struct scsi_extended_sense *sense;
+
+ scsipkt->pkt_state = STATE_GOT_BUS | STATE_GOT_TARGET |
+ STATE_SENT_CMD | STATE_GOT_STATUS;
+ if (sata_pkt->satapkt_reason == SATA_PKT_COMPLETED) {
+ /* Normal completion */
+ scsipkt->pkt_reason = CMD_CMPLT;
+ *scsipkt->pkt_scbp = STATUS_GOOD;
+ } else {
+ /* Something went wrong */
+ scsipkt->pkt_reason = CMD_INCOMPLETE;
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+ sense = sata_arq_sense(spx);
+ switch (sata_pkt->satapkt_reason) {
+ case SATA_PKT_PORT_ERROR:
+ /*
+ * We have no device data. Assume no data transfered.
+ */
+ sense->es_key = KEY_HARDWARE_ERROR;
+ break;
+
+ case SATA_PKT_DEV_ERROR:
+ if (sata_pkt->satapkt_cmd.satacmd_status_reg &
+ SATA_STATUS_ERR) {
+ /*
+ * determine dev error reason from error
+ * reg content
+ */
+ sata_decode_device_error(spx, sense);
+ break;
+ }
+ /* No extended sense key - no info available */
+ break;
+
+ case SATA_PKT_TIMEOUT:
+ /* scsipkt->pkt_reason = CMD_TIMEOUT; */
+ scsipkt->pkt_reason = CMD_INCOMPLETE;
+ /* No extended sense key ? */
+ break;
+
+ case SATA_PKT_ABORTED:
+ scsipkt->pkt_reason = CMD_ABORTED;
+ /* No extended sense key ? */
+ break;
+
+ case SATA_PKT_RESET:
+ /* pkt aborted by an explicit reset from a host */
+ scsipkt->pkt_reason = CMD_RESET;
+ break;
+
+ default:
+ SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
+ "sata_txlt_nodata_cmd_completion: "
+ "invalid packet completion reason %d",
+ sata_pkt->satapkt_reason));
+ scsipkt->pkt_reason = CMD_TRAN_ERR;
+ break;
+ }
+
+ }
+ SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst,
+ "Scsi_pkt completion reason %x\n", scsipkt->pkt_reason);
+
+ if ((scsipkt->pkt_flags & FLAG_NOINTR) == 0 &&
+ scsipkt->pkt_comp != NULL)
+ /* scsi callback required */
+ (*scsipkt->pkt_comp)(scsipkt);
+}
+
+
+/*
+ * Build Mode sense R/W recovery page
+ * NOT IMPLEMENTED
+ */
+
+static int
+sata_build_msense_page_1(sata_drive_info_t *sdinfo, int pcntrl, uint8_t *buf)
+{
+#ifndef __lock_lint
+ _NOTE(ARGUNUSED(sdinfo))
+ _NOTE(ARGUNUSED(pcntrl))
+ _NOTE(ARGUNUSED(buf))
+#endif
+ return (0);
+}
+
+/*
+ * Build Mode sense caching page - scsi-3 implementation.
+ * Page length distinguishes previous format from scsi-3 format.
+ * buf must have space for 0x12 bytes.
+ * Only DRA (disable read ahead ) and WCE (write cache enable) are changeable.
+ *
+ */
+static int
+sata_build_msense_page_8(sata_drive_info_t *sdinfo, int pcntrl, uint8_t *buf)
+{
+ struct mode_cache_scsi3 *page = (struct mode_cache_scsi3 *)buf;
+ sata_id_t *sata_id = &sdinfo->satadrv_id;
+
+ /*
+ * Most of the fields are set to 0, being not supported and/or disabled
+ */
+ bzero(buf, PAGELENGTH_DAD_MODE_CACHE_SCSI3);
+
+ if (pcntrl == 0 || pcntrl == 2) {
+ /*
+ * For now treat current and default parameters as same
+ * That may have to change, if target driver will complain
+ */
+ page->mode_page.code = MODEPAGE_CACHING; /* PS = 0 */
+ page->mode_page.length = PAGELENGTH_DAD_MODE_CACHE_SCSI3;
+
+ if ((sata_id->ai_cmdset82 & SATA_LOOK_AHEAD) &&
+ !(sata_id->ai_features85 & SATA_LOOK_AHEAD)) {
+ page->dra = 1; /* Read Ahead disabled */
+ page->rcd = 1; /* Read Cache disabled */
+ }
+ if ((sata_id->ai_cmdset82 & SATA_WRITE_CACHE) &&
+ (sata_id->ai_features85 & SATA_WRITE_CACHE))
+ page->wce = 1; /* Write Cache enabled */
+ } else {
+ /* Changeable parameters */
+ page->mode_page.code = MODEPAGE_CACHING;
+ page->mode_page.length = PAGELENGTH_DAD_MODE_CACHE_SCSI3;
+ if (sata_id->ai_cmdset82 & SATA_LOOK_AHEAD) {
+ page->dra = 1;
+ page->rcd = 1;
+ }
+ if (sata_id->ai_cmdset82 & SATA_WRITE_CACHE)
+ page->wce = 1;
+ }
+ return (PAGELENGTH_DAD_MODE_CACHE_SCSI3 +
+ sizeof (struct mode_page));
+}
+
+/*
+ * Build Mode sense exception cntrl page
+ * NOT IMPLEMENTED
+ */
+static int
+sata_build_msense_page_1c(sata_drive_info_t *sdinfo, int pcntrl, uint8_t *buf)
+{
+#ifndef __lock_lint
+ _NOTE(ARGUNUSED(sdinfo))
+ _NOTE(ARGUNUSED(pcntrl))
+ _NOTE(ARGUNUSED(buf))
+#endif
+ return (0);
+}
+
+
+/*
+ * Build Mode sense power condition page
+ * NOT IMPLEMENTED.
+ */
+static int
+sata_build_msense_page_1a(sata_drive_info_t *sdinfo, int pcntrl, uint8_t *buf)
+{
+#ifndef __lock_lint
+ _NOTE(ARGUNUSED(sdinfo))
+ _NOTE(ARGUNUSED(pcntrl))
+ _NOTE(ARGUNUSED(buf))
+#endif
+ return (0);
+}
+
+
+/*
+ * Process mode select caching page 8 (scsi3 format only).
+ * Read Ahead (same as read cache) and Write Cache may be turned on and off
+ * if these features are supported by the device. If these features are not
+ * supported, quietly ignore them.
+ * This function fails only if the SET FEATURE command sent to
+ * the device fails. The page format is not varified, assuming that the
+ * target driver operates correctly - if parameters length is too short,
+ * we just drop the page.
+ * Two command may be sent if both Read Cache/Read Ahead and Write Cache
+ * setting have to be changed.
+ * SET FEATURE command is executed synchronously, i.e. we wait here until
+ * it is completed, regardless of the scsi pkt directives.
+ *
+ * Note: Mode Select Caching page RCD and DRA bits are tied together, i.e.
+ * changing DRA will change RCD.
+ *
+ * More than one SATA command may be executed to perform operations specified
+ * by mode select pages. The first error terminates further execution.
+ * Operations performed successully are not backed-up in such case.
+ *
+ * Return SATA_SUCCESS if operation succeeded, SATA_FAILURE otherwise.
+ * If operation resulted in changing device setup, dmod flag should be set to
+ * one (1). If parameters were not changed, dmod flag should be set to 0.
+ * Upon return, if operation required sending command to the device, the rval
+ * should be set to the value returned by sata_hba_start. If operation
+ * did not require device access, rval should be set to TRAN_ACCEPT.
+ * The pagelen should be set to the length of the page.
+ *
+ * This function has to be called with a port mutex held.
+ *
+ * Returns SATA_SUCCESS if operation was successful, SATA_FAILURE otherwise.
+ */
+int
+sata_mode_select_page_8(sata_pkt_txlate_t *spx, struct mode_cache_scsi3 *page,
+ int parmlen, int *pagelen, int *rval, int *dmod)
+{
+ struct scsi_pkt *scsipkt = spx->txlt_scsi_pkt;
+ sata_drive_info_t *sdinfo;
+ sata_cmd_t *scmd = &spx->txlt_sata_pkt->satapkt_cmd;
+ sata_id_t *sata_id;
+ struct scsi_extended_sense *sense;
+ int wce, dra; /* Current settings */
+
+ sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst,
+ &spx->txlt_sata_pkt->satapkt_device);
+ sata_id = &sdinfo->satadrv_id;
+ *dmod = 0;
+
+ /* Verify parameters length. If too short, drop it */
+ if (PAGELENGTH_DAD_MODE_CACHE_SCSI3 +
+ sizeof (struct mode_page) < parmlen) {
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+ sense = sata_arq_sense(spx);
+ sense->es_key = KEY_ILLEGAL_REQUEST;
+ sense->es_add_code = SD_SCSI_INVALID_FIELD_IN_PARAMETER_LIST;
+ *pagelen = parmlen;
+ *rval = TRAN_ACCEPT;
+ return (SATA_FAILURE);
+ }
+
+ *pagelen = PAGELENGTH_DAD_MODE_CACHE_SCSI3 + sizeof (struct mode_page);
+
+ /*
+ * We can manipulate only write cache and read ahead
+ * (read cache) setting.
+ */
+ if (!(sata_id->ai_cmdset82 & SATA_LOOK_AHEAD) &&
+ !(sata_id->ai_cmdset82 & SATA_WRITE_CACHE)) {
+ /*
+ * None of the features is supported - ignore
+ */
+ *rval = TRAN_ACCEPT;
+ return (SATA_SUCCESS);
+ }
+
+ /* Current setting of Read Ahead (and Read Cache) */
+ if (sata_id->ai_features85 & SATA_LOOK_AHEAD)
+ dra = 0; /* 0 == not disabled */
+ else
+ dra = 1;
+ /* Current setting of Write Cache */
+ if (sata_id->ai_features85 & SATA_WRITE_CACHE)
+ wce = 1;
+ else
+ wce = 0;
+
+ if (page->dra == dra && page->wce == wce && page->rcd == dra) {
+ /* nothing to do */
+ *rval = TRAN_ACCEPT;
+ return (SATA_SUCCESS);
+ }
+ /*
+ * Need to flip some setting
+ * Set-up Internal SET FEATURES command(s)
+ */
+ scmd->satacmd_flags &= ~SATA_XFER_DIR_MASK;
+ scmd->satacmd_flags |= SATA_DIR_NODATA_XFER;
+ scmd->satacmd_addr_type = 0;
+ scmd->satacmd_device_reg = 0;
+ scmd->satacmd_status_reg = 0;
+ scmd->satacmd_error_reg = 0;
+ scmd->satacmd_cmd_reg = SATAC_SET_FEATURES;
+ if (page->dra != dra || page->rcd != dra) {
+ /* Need to flip read ahead setting */
+ if (dra == 0)
+ /* Disable read ahead / read cache */
+ scmd->satacmd_features_reg =
+ SATAC_SF_DISABLE_READ_AHEAD;
+ else
+ /* Enable read ahead / read cache */
+ scmd->satacmd_features_reg =
+ SATAC_SF_ENABLE_READ_AHEAD;
+
+ /* Transfer command to HBA */
+ if (sata_hba_start(spx, rval) != 0)
+ /*
+ * Pkt not accepted for execution.
+ */
+ return (SATA_FAILURE);
+
+ *dmod = 1;
+
+ /* Now process return */
+ if (spx->txlt_sata_pkt->satapkt_reason !=
+ SATA_PKT_COMPLETED) {
+ goto failure; /* Terminate */
+ }
+ }
+
+ /* Note that the packet is not removed, so it could be re-used */
+ if (page->wce != wce) {
+ /* Need to flip Write Cache setting */
+ if (page->wce == 1)
+ /* Enable write cache */
+ scmd->satacmd_features_reg =
+ SATAC_SF_ENABLE_WRITE_CACHE;
+ else
+ /* Disable write cache */
+ scmd->satacmd_features_reg =
+ SATAC_SF_DISABLE_WRITE_CACHE;
+
+ /* Transfer command to HBA */
+ if (sata_hba_start(spx, rval) != 0)
+ /*
+ * Pkt not accepted for execution.
+ */
+ return (SATA_FAILURE);
+
+ *dmod = 1;
+
+ /* Now process return */
+ if (spx->txlt_sata_pkt->satapkt_reason !=
+ SATA_PKT_COMPLETED) {
+ goto failure;
+ }
+ }
+ return (SATA_SUCCESS);
+
+failure:
+ scsipkt->pkt_reason = CMD_INCOMPLETE;
+ *scsipkt->pkt_scbp = STATUS_CHECK;
+ sense = sata_arq_sense(spx);
+ switch (spx->txlt_sata_pkt->satapkt_reason) {
+ case SATA_PKT_PORT_ERROR:
+ /*
+ * We have no device data. Assume no data transfered.
+ */
+ sense->es_key = KEY_HARDWARE_ERROR;
+ break;
+
+ case SATA_PKT_DEV_ERROR:
+ if (spx->txlt_sata_pkt->satapkt_cmd.satacmd_status_reg &
+ SATA_STATUS_ERR) {
+ /*
+ * determine dev error reason from error
+ * reg content
+ */
+ sata_decode_device_error(spx, sense);
+ break;
+ }
+ /* No extended sense key - no info available */
+ break;
+
+ case SATA_PKT_TIMEOUT:
+ /*
+ * scsipkt->pkt_reason = CMD_TIMEOUT; This causes problems.
+ */
+ scsipkt->pkt_reason = CMD_INCOMPLETE;
+ /* No extended sense key */
+ break;
+
+ case SATA_PKT_ABORTED:
+ scsipkt->pkt_reason = CMD_ABORTED;
+ /* No extended sense key */
+ break;
+
+ case SATA_PKT_RESET:
+ /*
+ * pkt aborted either by an explicit reset request from
+ * a host, or due to error recovery
+ */
+ scsipkt->pkt_reason = CMD_RESET;
+ break;
+
+ default:
+ scsipkt->pkt_reason = CMD_TRAN_ERR;
+ break;
+ }
+ return (SATA_FAILURE);
+}
+
+
+
+
+
+/* ************************** LOCAL FUNCTIONS ************************** */
+
+/*
+ * Validate sata_tran info
+ * SATA_FAILURE returns if structure is inconsistent or structure revision
+ * does not match one used by the framework.
+ *
+ * Returns SATA_SUCCESS if sata_hba_tran has matching revision and contains
+ * required function pointers.
+ * Returns SATA_FAILURE otherwise.
+ */
+static int
+sata_validate_sata_hba_tran(dev_info_t *dip, sata_hba_tran_t *sata_tran)
+{
+ if (sata_tran->sata_tran_hba_rev != SATA_TRAN_HBA_REV) {
+ sata_log(NULL, CE_WARN,
+ "sata: invalid sata_hba_tran version %d for driver %s",
+ sata_tran->sata_tran_hba_rev, ddi_driver_name(dip));
+ return (SATA_FAILURE);
+ }
+
+ if (dip != sata_tran->sata_tran_hba_dip) {
+ SATA_LOG_D((NULL, CE_WARN,
+ "sata: inconsistent sata_tran_hba_dip "
+ "%p / %p", sata_tran->sata_tran_hba_dip, dip));
+ return (SATA_FAILURE);
+ }
+
+ if (sata_tran->sata_tran_probe_port == NULL ||
+ sata_tran->sata_tran_start == NULL ||
+ sata_tran->sata_tran_abort == NULL ||
+ sata_tran->sata_tran_reset_dport == NULL) {
+ SATA_LOG_D((NULL, CE_WARN, "sata: sata_hba_tran missing "
+ "required functions"));
+ }
+ return (SATA_SUCCESS);
+}
+
+/*
+ * Remove HBA instance from sata_hba_list.
+ */
+static void
+sata_remove_hba_instance(dev_info_t *dip)
+{
+ sata_hba_inst_t *sata_hba_inst;
+
+ mutex_enter(&sata_mutex);
+ for (sata_hba_inst = sata_hba_list;
+ sata_hba_inst != (struct sata_hba_inst *)NULL;
+ sata_hba_inst = sata_hba_inst->satahba_next) {
+ if (sata_hba_inst->satahba_dip == dip)
+ break;
+ }
+
+ if (sata_hba_inst == (struct sata_hba_inst *)NULL) {
+#ifdef SATA_DEBUG
+ cmn_err(CE_WARN, "sata_remove_hba_instance: "
+ "unknown HBA instance\n");
+#endif
+ ASSERT(FALSE);
+ }
+ if (sata_hba_inst == sata_hba_list) {
+ sata_hba_list = sata_hba_inst->satahba_next;
+ if (sata_hba_list) {
+ sata_hba_list->satahba_prev =
+ (struct sata_hba_inst *)NULL;
+ }
+ if (sata_hba_inst == sata_hba_list_tail) {
+ sata_hba_list_tail = NULL;
+ }
+ } else if (sata_hba_inst == sata_hba_list_tail) {
+ sata_hba_list_tail = sata_hba_inst->satahba_prev;
+ if (sata_hba_list_tail) {
+ sata_hba_list_tail->satahba_next =
+ (struct sata_hba_inst *)NULL;
+ }
+ } else {
+ sata_hba_inst->satahba_prev->satahba_next =
+ sata_hba_inst->satahba_next;
+ sata_hba_inst->satahba_next->satahba_prev =
+ sata_hba_inst->satahba_prev;
+ }
+ mutex_exit(&sata_mutex);
+}
+
+
+
+
+
+/*
+ * Probe all SATA ports of the specified HBA instance.
+ * This function is called only from sata_hba_attach(). It does not have to
+ * be protected by controller mutex, because the hba_attached flag is not set
+ * yet and no one would be touching this HBA instance other then this thread.
+ * Determines if port is active and what type of the device is attached
+ * (if any). Allocates necessary structures for each port.
+ * Creates attachment point minor node for each non-failed port.
+ */
+
+static void
+sata_probe_ports(sata_hba_inst_t *sata_hba_inst)
+{
+ dev_info_t *dip = SATA_DIP(sata_hba_inst);
+ int ncport, npmport;
+ sata_cport_info_t *cportinfo;
+ sata_drive_info_t *drive;
+ sata_pmult_info_t *pminfo;
+ sata_pmport_info_t *pmportinfo;
+ sata_device_t sata_device;
+ int rval;
+ dev_t minor_number;
+ char name[16];
+
+ /*
+ * Probe controller ports first, to find port status and
+ * any port multiplier attached.
+ */
+ for (ncport = 0; ncport < SATA_NUM_CPORTS(sata_hba_inst); ncport++) {
+ /* allocate cport structure */
+ cportinfo = kmem_zalloc(sizeof (sata_cport_info_t), KM_SLEEP);
+ ASSERT(cportinfo != NULL);
+ mutex_init(&cportinfo->cport_mutex, NULL, MUTEX_DRIVER, NULL);
+
+ mutex_enter(&cportinfo->cport_mutex);
+
+ cportinfo->cport_addr.cport = ncport;
+ cportinfo->cport_addr.pmport = 0;
+ cportinfo->cport_addr.qual = SATA_ADDR_CPORT;
+ cportinfo->cport_state &= ~SATA_PORT_STATE_CLEAR_MASK;
+ cportinfo->cport_state |= SATA_STATE_PROBING;
+ SATA_CPORT_INFO(sata_hba_inst, ncport) = cportinfo;
+
+ /*
+ * Regardless if a port is usable or not, create
+ * an attachment point
+ */
+ mutex_exit(&cportinfo->cport_mutex);
+ minor_number =
+ SATA_MAKE_AP_MINOR(ddi_get_instance(dip), ncport, 0, 0);
+ (void) sprintf(name, "%d", ncport);
+ if (ddi_create_minor_node(dip, name, S_IFCHR,
+ minor_number, DDI_NT_SATA_ATTACHMENT_POINT, 0) !=
+ DDI_SUCCESS) {
+ sata_log(sata_hba_inst, CE_WARN, "sata_hba_attach: "
+ "cannot create sata attachment point for port %d",
+ ncport);
+ }
+
+ /* Probe port */
+ sata_device.satadev_addr.cport = ncport;
+ sata_device.satadev_addr.pmport = 0;
+ sata_device.satadev_addr.qual = SATA_ADDR_CPORT;
+ sata_device.satadev_rev = SATA_DEVICE_REV;
+
+ rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
+ (dip, &sata_device);
+
+ mutex_enter(&cportinfo->cport_mutex);
+ sata_update_port_scr(&cportinfo->cport_scr, &sata_device);
+ if (rval != SATA_SUCCESS) {
+ /* Something went wrong? Fail the port */
+ cportinfo->cport_state = SATA_PSTATE_FAILED;
+ mutex_exit(&cportinfo->cport_mutex);
+ continue;
+ }
+ cportinfo->cport_state &= ~SATA_STATE_PROBING;
+ cportinfo->cport_state |= SATA_STATE_PROBED;
+ cportinfo->cport_dev_type = sata_device.satadev_type;
+
+ cportinfo->cport_state |= SATA_STATE_READY;
+ if (cportinfo->cport_dev_type == SATA_DTYPE_NONE) {
+ mutex_exit(&cportinfo->cport_mutex);
+ continue;
+ }
+ if (cportinfo->cport_dev_type != SATA_DTYPE_PMULT) {
+ /*
+ * There is some device attached.
+ * Allocate device info structure
+ */
+ mutex_exit(&cportinfo->cport_mutex);
+ drive = kmem_zalloc(sizeof (sata_drive_info_t),
+ KM_SLEEP);
+ mutex_enter(&cportinfo->cport_mutex);
+ SATA_CPORTINFO_DRV_INFO(cportinfo) = drive;
+ drive->satadrv_addr = cportinfo->cport_addr;
+ drive->satadrv_addr.qual = SATA_ADDR_DCPORT;
+ drive->satadrv_type = cportinfo->cport_dev_type;
+ drive->satadrv_state = SATA_STATE_UNKNOWN;
+ } else {
+ ASSERT(cportinfo->cport_dev_type == SATA_DTYPE_PMULT);
+ mutex_exit(&cportinfo->cport_mutex);
+ pminfo = kmem_zalloc(sizeof (sata_pmult_info_t),
+ KM_SLEEP);
+ mutex_enter(&cportinfo->cport_mutex);
+ ASSERT(pminfo != NULL);
+ SATA_CPORTINFO_PMULT_INFO(cportinfo) = pminfo;
+ pminfo->pmult_addr.cport = cportinfo->cport_addr.cport;
+ pminfo->pmult_addr.pmport = SATA_PMULT_HOSTPORT;
+ pminfo->pmult_addr.qual = SATA_ADDR_PMPORT;
+ pminfo->pmult_num_dev_ports =
+ sata_device.satadev_add_info;
+ mutex_init(&pminfo->pmult_mutex, NULL, MUTEX_DRIVER,
+ NULL);
+ pminfo->pmult_state = SATA_STATE_PROBING;
+
+ /* Probe Port Multiplier ports */
+ for (npmport = 0;
+ npmport < pminfo->pmult_num_dev_ports;
+ npmport++) {
+ mutex_exit(&cportinfo->cport_mutex);
+ pmportinfo = kmem_zalloc(
+ sizeof (sata_pmport_info_t), KM_SLEEP);
+ mutex_enter(&cportinfo->cport_mutex);
+ ASSERT(pmportinfo != NULL);
+ pmportinfo->pmport_addr.cport = ncport;
+ pmportinfo->pmport_addr.pmport = npmport;
+ pmportinfo->pmport_addr.qual =
+ SATA_ADDR_PMPORT;
+ pminfo->pmult_dev_port[npmport] = pmportinfo;
+ mutex_init(&pmportinfo->pmport_mutex, NULL,
+ MUTEX_DRIVER, NULL);
+
+ sata_device.satadev_addr.pmport = npmport;
+ sata_device.satadev_addr.qual =
+ SATA_ADDR_PMPORT;
+
+ mutex_exit(&cportinfo->cport_mutex);
+ /* Create an attachment point */
+ minor_number = SATA_MAKE_AP_MINOR(
+ ddi_get_instance(dip), ncport, npmport, 1);
+ (void) sprintf(name, "%d.%d", ncport, npmport);
+ if (ddi_create_minor_node(dip, name, S_IFCHR,
+ minor_number, DDI_NT_SATA_ATTACHMENT_POINT,
+ 0) != DDI_SUCCESS) {
+ sata_log(sata_hba_inst, CE_WARN,
+ "sata_hba_attach: "
+ "cannot create sata attachment "
+ "point for port %d pmult port %d",
+ ncport, npmport);
+ }
+ rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
+ (dip, &sata_device);
+ mutex_enter(&cportinfo->cport_mutex);
+
+ /* sata_update_port_info() */
+ sata_update_port_scr(&pmportinfo->pmport_scr,
+ &sata_device);
+
+ if (rval != SATA_SUCCESS) {
+ pmportinfo->pmport_state =
+ SATA_PSTATE_FAILED;
+ continue;
+ }
+ pmportinfo->pmport_state &=
+ ~SATA_STATE_PROBING;
+ pmportinfo->pmport_state |= SATA_STATE_PROBED;
+ pmportinfo->pmport_dev_type =
+ sata_device.satadev_type;
+
+ pmportinfo->pmport_state |= SATA_STATE_READY;
+ if (pmportinfo->pmport_dev_type ==
+ SATA_DTYPE_NONE)
+ continue;
+
+ /* Port multipliers cannot be chained */
+ ASSERT(pmportinfo->pmport_dev_type !=
+ SATA_DTYPE_PMULT);
+ /*
+ * There is something attached to Port
+ * Multiplier device port
+ * Allocate device info structure
+ */
+ mutex_exit(&cportinfo->cport_mutex);
+ drive = kmem_zalloc(
+ sizeof (sata_drive_info_t), KM_SLEEP);
+ rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
+ (dip, &sata_device);
+ mutex_enter(&cportinfo->cport_mutex);
+
+ /* sata_update_port_info() */
+ sata_update_port_scr(&pmportinfo->pmport_scr,
+ &sata_device);
+
+ pmportinfo->pmport_sata_drive = drive;
+ drive->satadrv_addr.cport =
+ pmportinfo->pmport_addr.cport;
+ drive->satadrv_addr.pmport = npmport;
+ drive->satadrv_addr.qual = SATA_ADDR_DPMPORT;
+ drive->satadrv_type = pmportinfo->
+ pmport_dev_type;
+ drive->satadrv_state = SATA_STATE_UNKNOWN;
+ }
+ pmportinfo->pmport_state =
+ SATA_STATE_PROBED | SATA_STATE_READY;
+ }
+ mutex_exit(&cportinfo->cport_mutex);
+ }
+}
+
+
+
+/*
+ * Create SATA device nodes for specified HBA instance (SCSI target
+ * device nodes).
+ * This function is called only from sata_hba_attach(). The hba_attached flag
+ * is not set yet, so no ports or device data structures would be touched
+ * by anyone other then this thread, therefore per-port mutex protection is
+ * not needed.
+ * The assumption is that there are no target and attachment point minor nodes
+ * created by the boot subsystems, so we do not need to prune device tree.
+ * An AP (Attachement Point) node is created for each SATA device port even
+ * when there is no device attached.
+ * A target node is created when there is a supported type of device attached,
+ * but may be removed if it cannot be put online.
+ *
+ * This function cannot be called from an interrupt context.
+ *
+ * ONLY DISK TARGET NODES ARE CREATED NOW
+ */
+static void
+sata_make_device_nodes(dev_info_t *pdip, sata_hba_inst_t *sata_hba_inst)
+{
+ int ncport, npmport;
+ sata_cport_info_t *cportinfo;
+ sata_pmult_info_t *pminfo;
+ sata_pmport_info_t *pmportinfo;
+ dev_info_t *cdip; /* child dip */
+ sata_device_t sata_device;
+ int rval;
+
+ /*
+ * Walk through pre-probed sata ports info in sata_scsi
+ */
+ for (ncport = 0; ncport < SATA_NUM_CPORTS(sata_hba_inst); ncport++) {
+ cportinfo = SATA_CPORT_INFO(sata_hba_inst, ncport);
+ mutex_enter(&cportinfo->cport_mutex);
+ if (!(cportinfo->cport_state & SATA_STATE_PROBED)) {
+ mutex_exit(&cportinfo->cport_mutex);
+ continue;
+ }
+ if (cportinfo->cport_state == SATA_PSTATE_FAILED) {
+ mutex_exit(&cportinfo->cport_mutex);
+ continue;
+ }
+ if (cportinfo->cport_dev_type == SATA_DTYPE_NONE) {
+ /* No device attached to the controller port */
+ mutex_exit(&cportinfo->cport_mutex);
+ continue;
+ }
+ /*
+ * Some device is attached to a controller port.
+ * We rely on controllers distinquishing between no-device,
+ * attached port multiplier and other kind of attached device.
+ * We need to get Identify Device data and determine
+ * positively the dev type before trying to attach
+ * the target driver.
+ */
+ sata_device.satadev_rev = SATA_DEVICE_REV;
+ if (cportinfo->cport_dev_type != SATA_DTYPE_PMULT) {
+ /*
+ * Not port multiplier.
+ */
+ sata_device.satadev_addr = cportinfo->cport_addr;
+ sata_device.satadev_addr.qual = SATA_ADDR_DCPORT;
+ mutex_exit(&cportinfo->cport_mutex);
+ rval = sata_probe_device(sata_hba_inst, &sata_device);
+ if (rval != SATA_SUCCESS ||
+ sata_device.satadev_type == SATA_DTYPE_UNKNOWN)
+ continue;
+
+ mutex_enter(&cportinfo->cport_mutex);
+ sata_save_drive_settings(
+ SATA_CPORTINFO_DRV_INFO(cportinfo));
+
+ if ((sata_device.satadev_type &
+ SATA_VALID_DEV_TYPE) == 0) {
+ /*
+ * Could not determine device type or
+ * a device is not supported.
+ * Degrade this device to unknown.
+ */
+ cportinfo->cport_dev_type = SATA_DTYPE_UNKNOWN;
+ mutex_exit(&cportinfo->cport_mutex);
+ continue;
+ }
+ cportinfo->cport_dev_type = sata_device.satadev_type;
+
+ sata_show_drive_info(sata_hba_inst,
+ SATA_CPORTINFO_DRV_INFO(cportinfo));
+
+ mutex_exit(&cportinfo->cport_mutex);
+ cdip = sata_create_target_node(pdip, sata_hba_inst,
+ &sata_device.satadev_addr);
+ mutex_enter(&cportinfo->cport_mutex);
+ if (cdip == NULL) {
+ /*
+ * Attaching target node failed.
+ * We retain sata_drive_info structure...
+ */
+ (SATA_CPORTINFO_DRV_INFO(cportinfo))->
+ satadrv_type = SATA_DTYPE_UNKNOWN;
+ (SATA_CPORTINFO_DRV_INFO(cportinfo))->
+ satadrv_state = SATA_STATE_UNKNOWN;
+ cportinfo->cport_dev_type = SATA_DTYPE_UNKNOWN;
+ mutex_exit(&cportinfo->cport_mutex);
+ continue;
+ }
+ (SATA_CPORTINFO_DRV_INFO(cportinfo))->
+ satadrv_state = SATA_STATE_READY;
+ } else {
+ /* This must be Port Multiplier type */
+ if (cportinfo->cport_dev_type != SATA_DTYPE_PMULT) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_make_device_nodes: "
+ "unknown dev type %x",
+ cportinfo->cport_dev_type));
+ mutex_exit(&cportinfo->cport_mutex);
+ continue;
+ }
+ pminfo = SATA_CPORTINFO_PMULT_INFO(cportinfo);
+ for (npmport = 0;
+ npmport < pminfo->pmult_num_dev_ports;
+ npmport++) {
+ pmportinfo = pminfo->pmult_dev_port[npmport];
+ if (pmportinfo->pmport_state &
+ SATA_PSTATE_FAILED) {
+ continue;
+ }
+ if (pmportinfo->pmport_dev_type &
+ SATA_DTYPE_NONE)
+ /* No device attached */
+ continue;
+
+ sata_device.satadev_addr =
+ pmportinfo->pmport_addr;
+ sata_device.satadev_addr.qual =
+ SATA_ADDR_DPMPORT;
+ mutex_exit(&cportinfo->cport_mutex);
+ rval = sata_probe_device(sata_hba_inst,
+ &sata_device);
+ if (rval != SATA_SUCCESS ||
+ sata_device.satadev_type ==
+ SATA_DTYPE_UNKNOWN) {
+ mutex_enter(&cportinfo->cport_mutex);
+ continue;
+ }
+ mutex_enter(&cportinfo->cport_mutex);
+ sata_save_drive_settings(
+ pmportinfo->pmport_sata_drive);
+ if ((sata_device.satadev_type &
+ SATA_VALID_DEV_TYPE) == 0) {
+ /*
+ * Could not determine device type.
+ * Degrade this device to unknown.
+ */
+ pmportinfo->pmport_dev_type =
+ SATA_DTYPE_UNKNOWN;
+ continue;
+ }
+ pmportinfo->pmport_dev_type =
+ sata_device.satadev_type;
+
+ sata_show_drive_info(sata_hba_inst,
+ pmportinfo->pmport_sata_drive);
+
+ mutex_exit(&cportinfo->cport_mutex);
+ cdip = sata_create_target_node(pdip,
+ sata_hba_inst, &sata_device.satadev_addr);
+ mutex_enter(&cportinfo->cport_mutex);
+ if (cdip == NULL) {
+ /*
+ * Attaching target node failed.
+ * We retain sata_drive_info
+ * structure...
+ */
+ pmportinfo->pmport_sata_drive->
+ satadrv_type = SATA_DTYPE_UNKNOWN;
+ pmportinfo->pmport_sata_drive->
+ satadrv_state = SATA_STATE_UNKNOWN;
+ pmportinfo->pmport_dev_type =
+ SATA_DTYPE_UNKNOWN;
+ continue;
+ }
+ pmportinfo->pmport_sata_drive->
+ satadrv_state |= SATA_STATE_READY;
+ }
+ }
+ mutex_exit(&cportinfo->cport_mutex);
+ }
+}
+
+
+
+/*
+ * Create scsi target node for attached device, create node properties and
+ * attach the node.
+ * The node could be removed if the device onlining fails.
+ *
+ * A dev_info_t pointer is returned if operation is successful, NULL is
+ * returned otherwise.
+ */
+
+static dev_info_t *
+sata_create_target_node(dev_info_t *dip, sata_hba_inst_t *sata_hba_inst,
+ sata_address_t *sata_addr)
+{
+ dev_info_t *cdip = NULL;
+ int rval;
+ char *nname = NULL;
+ char **compatible = NULL;
+ int ncompatible;
+ struct scsi_inquiry inq;
+ sata_device_t sata_device;
+ sata_drive_info_t *sdinfo;
+ int target;
+ int i;
+
+ sata_device.satadev_rev = SATA_DEVICE_REV;
+ sata_device.satadev_addr = *sata_addr;
+
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst, sata_addr->cport)));
+
+ sdinfo = sata_get_device_info(sata_hba_inst, &sata_device);
+
+ target = SATA_TO_SCSI_TARGET(sata_addr->cport,
+ sata_addr->pmport, sata_addr->qual);
+
+ if (sdinfo == NULL) {
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_addr->cport)));
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_create_target_node: no sdinfo for target %x",
+ target));
+ return (NULL);
+ }
+
+ /*
+ * create scsi inquiry data, expected by
+ * scsi_hba_nodename_compatible_get()
+ */
+ sata_identdev_to_inquiry(sata_hba_inst, sdinfo, (uint8_t *)&inq);
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, sata_addr->cport)));
+
+ /* determine the node name and compatible */
+ scsi_hba_nodename_compatible_get(&inq, NULL,
+ inq.inq_dtype, NULL, &nname, &compatible, &ncompatible);
+
+#ifdef SATA_DEBUG
+ if (sata_debug_flags & SATA_DBG_NODES) {
+ if (nname == NULL) {
+ cmn_err(CE_NOTE, "sata_create_target_node: "
+ "cannot determine nodename for target %d\n",
+ target);
+ } else {
+ cmn_err(CE_WARN, "sata_create_target_node: "
+ "target %d nodename: %s\n", target, nname);
+ }
+ if (compatible == NULL) {
+ cmn_err(CE_WARN,
+ "sata_create_target_node: no compatible name\n");
+ } else {
+ for (i = 0; i < ncompatible; i++) {
+ cmn_err(CE_WARN, "sata_create_target_node: "
+ "compatible name: %s\n", compatible[i]);
+ }
+ }
+ }
+#endif
+
+ /* if nodename can't be determined, log error and exit */
+ if (nname == NULL) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_create_target_node: cannot determine nodename "
+ "for target %d\n", target));
+ scsi_hba_nodename_compatible_free(nname, compatible);
+ return (NULL);
+ }
+ /*
+ * Create scsi target node
+ */
+ ndi_devi_alloc_sleep(dip, nname, (pnode_t)DEVI_SID_NODEID, &cdip);
+ rval = ndi_prop_update_string(DDI_DEV_T_NONE, cdip,
+ "device-type", "scsi");
+
+ if (rval != DDI_PROP_SUCCESS) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN, "sata_create_target_node: "
+ "updating device_type prop failed %d", rval));
+ goto fail;
+ }
+
+ /*
+ * Create target node properties: target & lun
+ */
+ rval = ndi_prop_update_int(DDI_DEV_T_NONE, cdip, "target", target);
+ if (rval != DDI_PROP_SUCCESS) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN, "sata_create_target_node: "
+ "updating target prop failed %d", rval));
+ goto fail;
+ }
+ rval = ndi_prop_update_int(DDI_DEV_T_NONE, cdip, "lun", 0);
+ if (rval != DDI_PROP_SUCCESS) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN, "sata_create_target_node: "
+ "updating target prop failed %d", rval));
+ goto fail;
+ }
+
+ /* decorate the node with compatible */
+ if (ndi_prop_update_string_array(DDI_DEV_T_NONE, cdip, "compatible",
+ compatible, ncompatible) != DDI_PROP_SUCCESS) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_create_target_node: FAIL compatible props cdip 0x%p",
+ (void *)cdip));
+ goto fail;
+ }
+
+ /*
+ * Now, try to attach the driver. If probing of the device fails,
+ * the target node may be removed
+ */
+ rval = ndi_devi_online(cdip, NDI_ONLINE_ATTACH);
+
+ scsi_hba_nodename_compatible_free(nname, compatible);
+
+ if (rval == NDI_SUCCESS)
+ return (cdip);
+
+ /* target node was removed - are we sure? */
+ return (NULL);
+
+fail:
+ scsi_hba_nodename_compatible_free(nname, compatible);
+ ddi_prop_remove_all(cdip);
+ rval = ndi_devi_free(cdip);
+ if (rval != NDI_SUCCESS) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN, "sata_create_target_node: "
+ "node removal failed %d", rval));
+ }
+ sata_log(sata_hba_inst, CE_WARN, "sata_create_target_node: "
+ "cannot create target node for device at port %d",
+ sata_addr->cport);
+ return (NULL);
+}
+
+
+
+/*
+ * Re-probe sata port, check for a device and attach necessary info
+ * structures when necessary. Identify Device data is fetched, if possible.
+ * Assumption: sata address is already validated.
+ * SATA_SUCCESS is returned if port is re-probed sucessfully, regardless of
+ * the presence of a device and its type.
+ * SATA_FAILURE is returned if one of the operations failed.
+ */
+static int
+sata_reprobe_port(sata_hba_inst_t *sata_hba_inst, sata_device_t *sata_device)
+{
+ sata_cport_info_t *cportinfo;
+ sata_drive_info_t *sdinfo;
+ int rval;
+
+ /* We only care about host sata cport for now */
+ cportinfo = SATA_CPORT_INFO(sata_hba_inst,
+ sata_device->satadev_addr.cport);
+ /* probe port */
+ mutex_enter(&cportinfo->cport_mutex);
+ cportinfo->cport_state &= ~SATA_PORT_STATE_CLEAR_MASK;
+ cportinfo->cport_state |= SATA_STATE_PROBING;
+ mutex_exit(&cportinfo->cport_mutex);
+
+ rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
+ (SATA_DIP(sata_hba_inst), sata_device);
+
+ mutex_enter(&cportinfo->cport_mutex);
+ if (rval != SATA_SUCCESS) {
+ cportinfo->cport_state = SATA_PSTATE_FAILED;
+ mutex_exit(&cportinfo->cport_mutex);
+ SATA_LOG_D((sata_hba_inst, CE_WARN, "sata_hba_ioctl: "
+ "connect: port probbing failed"));
+ return (SATA_FAILURE);
+ }
+
+ /*
+ * update sata port state and set device type
+ */
+ sata_update_port_info(sata_hba_inst, sata_device);
+ cportinfo->cport_state |= SATA_STATE_PROBED;
+
+ /*
+ * Sanity check - Port is active? Is the link active?
+ * Is there any device attached?
+ */
+ if ((cportinfo->cport_state &
+ (SATA_PSTATE_SHUTDOWN | SATA_PSTATE_FAILED)) ||
+ (cportinfo->cport_scr.sstatus & SATA_PORT_DEVLINK_UP_MASK) !=
+ SATA_PORT_DEVLINK_UP) {
+ /*
+ * Port in non-usable state or no link active/no device.
+ * Free info structure if necessary (direct attached drive
+ * only, for now!
+ */
+ sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
+ SATA_CPORTINFO_DRV_INFO(cportinfo) = NULL;
+ /* Add here differentiation for device attached or not */
+ cportinfo->cport_dev_type = SATA_DTYPE_NONE;
+ mutex_exit(&cportinfo->cport_mutex);
+ if (sdinfo != NULL)
+ kmem_free(sdinfo, sizeof (sata_drive_info_t));
+ return (SATA_SUCCESS);
+ }
+
+ cportinfo->cport_state |= SATA_STATE_READY;
+ cportinfo->cport_dev_type = sata_device->satadev_type;
+ sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
+
+ /*
+ * If we are re-probing the port, there may be
+ * sata_drive_info structure attached
+ * (or sata_pm_info, if PMult is supported).
+ */
+ if (sata_device->satadev_type == SATA_DTYPE_NONE) {
+ /*
+ * There is no device, so remove device info structure,
+ * if necessary. Direct attached drive only!
+ */
+ SATA_CPORTINFO_DRV_INFO(cportinfo) = NULL;
+ cportinfo->cport_dev_type = SATA_DTYPE_NONE;
+ if (sdinfo != NULL) {
+ kmem_free(sdinfo, sizeof (sata_drive_info_t));
+ sata_log(sata_hba_inst, CE_WARN,
+ "SATA device detached "
+ "from port %d", cportinfo->cport_addr.cport);
+ }
+ mutex_exit(&cportinfo->cport_mutex);
+ return (SATA_SUCCESS);
+ }
+
+ if (sata_device->satadev_type != SATA_DTYPE_PMULT) {
+ if (sdinfo == NULL) {
+ /*
+ * There is some device attached, but there is
+ * no sata_drive_info structure - allocate one
+ */
+ mutex_exit(&cportinfo->cport_mutex);
+ sdinfo = kmem_zalloc(
+ sizeof (sata_drive_info_t), KM_SLEEP);
+ mutex_enter(&cportinfo->cport_mutex);
+ /*
+ * Recheck, if port state did not change when we
+ * released mutex.
+ */
+ if (cportinfo->cport_state & SATA_STATE_READY) {
+ SATA_CPORTINFO_DRV_INFO(cportinfo) = sdinfo;
+ sdinfo->satadrv_addr = cportinfo->cport_addr;
+ sdinfo->satadrv_addr.qual = SATA_ADDR_DCPORT;
+ sdinfo->satadrv_type = SATA_DTYPE_UNKNOWN;
+ sdinfo->satadrv_state = SATA_STATE_UNKNOWN;
+ sata_log(sata_hba_inst, CE_WARN,
+ "SATA device attached to port %d",
+ cportinfo->cport_addr.cport);
+ } else {
+ /*
+ * Port is not in ready state, we
+ * cannot attach a device.
+ */
+ mutex_exit(&cportinfo->cport_mutex);
+ kmem_free(sdinfo, sizeof (sata_drive_info_t));
+ return (SATA_SUCCESS);
+ }
+ }
+
+ cportinfo->cport_dev_type = SATA_DTYPE_UNKNOWN;
+ sata_device->satadev_addr.qual = sdinfo->satadrv_addr.qual;
+ } else {
+ cportinfo->cport_dev_type = SATA_DTYPE_UNKNOWN;
+ mutex_exit(&cportinfo->cport_mutex);
+ return (SATA_SUCCESS);
+ }
+ mutex_exit(&cportinfo->cport_mutex);
+ /*
+ * Figure out what kind of device we are really
+ * dealing with.
+ */
+ return (sata_probe_device(sata_hba_inst, sata_device));
+}
+
+
+/*
+ * Validate sata address.
+ * Specified cport, pmport and qualifier has to match
+ * passed sata_scsi configuration info.
+ * The presence of an attached device is not verified.
+ *
+ * Returns 0 when address is valid, -1 otherwise.
+ */
+static int
+sata_validate_sata_address(sata_hba_inst_t *sata_hba_inst, int cport,
+ int pmport, int qual)
+{
+ if (qual == SATA_ADDR_DCPORT && pmport != 0)
+ goto invalid_address;
+ if (cport >= SATA_NUM_CPORTS(sata_hba_inst))
+ goto invalid_address;
+ if ((qual == SATA_ADDR_DPMPORT || qual == SATA_ADDR_PMPORT) &&
+ ((SATA_CPORT_DEV_TYPE(sata_hba_inst, cport) != SATA_DTYPE_PMULT) ||
+ (SATA_PMULT_INFO(sata_hba_inst, cport) == NULL) ||
+ (pmport >= SATA_NUM_PMPORTS(sata_hba_inst, cport))))
+ goto invalid_address;
+
+ return (0);
+
+invalid_address:
+ return (-1);
+
+}
+
+/*
+ * Validate scsi address
+ * SCSI target address is translated into SATA cport/pmport and compared
+ * with a controller port/device configuration. LUN has to be 0.
+ * Returns 0 if a scsi target refers to an attached device,
+ * returns 1 if address is valid but device is not attached,
+ * returns -1 if bad address or device is of an unsupported type.
+ * Upon return sata_device argument is set.
+ */
+static int
+sata_validate_scsi_address(sata_hba_inst_t *sata_hba_inst,
+ struct scsi_address *ap, sata_device_t *sata_device)
+{
+ int cport, pmport, qual, rval;
+
+ rval = -1; /* Invalid address */
+ if (ap->a_lun != 0)
+ goto out;
+
+ qual = SCSI_TO_SATA_ADDR_QUAL(ap->a_target);
+ cport = SCSI_TO_SATA_CPORT(ap->a_target);
+ pmport = SCSI_TO_SATA_PMPORT(ap->a_target);
+
+ if (qual != SATA_ADDR_DCPORT && qual != SATA_ADDR_DPMPORT)
+ goto out;
+
+ if (sata_validate_sata_address(sata_hba_inst, cport, pmport, qual) ==
+ 0) {
+
+ sata_cport_info_t *cportinfo;
+ sata_pmult_info_t *pmultinfo;
+ sata_drive_info_t *sdinfo = NULL;
+
+ rval = 1; /* Valid sata address */
+
+ cportinfo = SATA_CPORT_INFO(sata_hba_inst, cport);
+ if (qual == SATA_ADDR_DCPORT) {
+ if (cportinfo == NULL ||
+ cportinfo->cport_dev_type == SATA_DTYPE_NONE)
+ goto out;
+
+ if (cportinfo->cport_dev_type == SATA_DTYPE_PMULT ||
+ (cportinfo->cport_dev_type &
+ SATA_VALID_DEV_TYPE) == 0) {
+ rval = -1;
+ goto out;
+ }
+ sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
+
+ } else if (qual == SATA_ADDR_DPMPORT) {
+ pmultinfo = SATA_CPORTINFO_PMULT_INFO(cportinfo);
+ if (pmultinfo == NULL) {
+ rval = -1;
+ goto out;
+ }
+ if (SATA_PMPORT_INFO(sata_hba_inst, cport, pmport) ==
+ NULL ||
+ SATA_PMPORT_DEV_TYPE(sata_hba_inst, cport,
+ pmport) == SATA_DTYPE_NONE)
+ goto out;
+
+ sdinfo = SATA_PMPORT_DRV_INFO(sata_hba_inst, cport,
+ pmport);
+ } else {
+ rval = -1;
+ goto out;
+ }
+ if ((sdinfo == NULL) ||
+ (sdinfo->satadrv_type & SATA_VALID_DEV_TYPE) == 0)
+ goto out;
+
+ sata_device->satadev_type = sdinfo->satadrv_type;
+ sata_device->satadev_addr.qual = qual;
+ sata_device->satadev_addr.cport = cport;
+ sata_device->satadev_addr.pmport = pmport;
+ sata_device->satadev_rev = SATA_DEVICE_REV_1;
+ return (0);
+ }
+out:
+ if (rval == 1) {
+ SATADBG2(SATA_DBG_SCSI_IF, sata_hba_inst,
+ "sata_validate_scsi_address: no valid target %x lun %x",
+ ap->a_target, ap->a_lun);
+ }
+ return (rval);
+}
+
+/*
+ * Find dip corresponding to passed device number
+ *
+ * Returns NULL if invalid device number is passed or device cannot be found,
+ * Returns dip is device is found.
+ */
+static dev_info_t *
+sata_devt_to_devinfo(dev_t dev)
+{
+ dev_info_t *dip;
+#ifndef __lock_lint
+ struct devnames *dnp;
+ major_t major = getmajor(dev);
+ int instance = SATA_MINOR2INSTANCE(getminor(dev));
+
+ if (major >= devcnt)
+ return (NULL);
+
+ dnp = &devnamesp[major];
+ LOCK_DEV_OPS(&(dnp->dn_lock));
+ dip = dnp->dn_head;
+ while (dip && (ddi_get_instance(dip) != instance)) {
+ dip = ddi_get_next(dip);
+ }
+ UNLOCK_DEV_OPS(&(dnp->dn_lock));
+#endif
+
+ return (dip);
+}
+
+
+/*
+ * Probe device.
+ * This function issues Identify Device command and initialize local
+ * sata_drive_info structure if the device can be identified.
+ * The device type is determined by examining Identify Device
+ * command response.
+ * If the sata_hba_inst has linked drive info structure for this
+ * device address, the Identify Device data is stored into sata_drive_info
+ * structure linked to the port info structure.
+ *
+ * sata_device has to refer to the valid sata port(s) for HBA described
+ * by sata_hba_inst structure.
+ *
+ * Returns: SATA_SUCCESS if device type was successfully probed and port-linked
+ * drive info structure was updated;
+ * SATA_FAILURE if there is no device, or device was not probed
+ * successully.
+ * If a device cannot be identified, sata_device's dev_state and dev_type
+ * fields are set to unknown.
+ *
+ */
+
+static int
+sata_probe_device(sata_hba_inst_t *sata_hba_inst, sata_device_t *sata_device)
+{
+ sata_drive_info_t *sdinfo;
+ sata_drive_info_t new_sdinfo; /* local drive info struct */
+ int retry_cnt;
+
+ ASSERT((SATA_CPORT_STATE(sata_hba_inst,
+ sata_device->satadev_addr.cport) &
+ (SATA_STATE_PROBED | SATA_STATE_READY)) != 0);
+
+ sata_device->satadev_type = SATA_DTYPE_NONE;
+
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device->satadev_addr.cport)));
+
+ /* Get pointer to port-linked sata device info structure */
+ sdinfo = sata_get_device_info(sata_hba_inst, sata_device);
+ if (sdinfo != NULL) {
+ sdinfo->satadrv_state &=
+ ~(SATA_STATE_PROBED | SATA_STATE_READY);
+ sdinfo->satadrv_state |= SATA_STATE_PROBING;
+ } else {
+ /* No device to probe */
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device->satadev_addr.cport)));
+ sata_device->satadev_type = SATA_DTYPE_NONE;
+ sata_device->satadev_state = SATA_STATE_UNKNOWN;
+ return (SATA_FAILURE);
+ }
+ /*
+ * Need to issue both types of identify device command and
+ * determine device type by examining retreived data/status.
+ * First, ATA Identify Device.
+ */
+ bzero(&new_sdinfo, sizeof (sata_drive_info_t));
+ new_sdinfo.satadrv_addr = sata_device->satadev_addr;
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device->satadev_addr.cport)));
+ for (retry_cnt = 0; retry_cnt <= SATA_DEVICE_IDENTIFY_RETRY;
+ retry_cnt++) {
+ new_sdinfo.satadrv_type = SATA_DTYPE_ATADISK;
+ if (sata_identify_device(sata_hba_inst, &new_sdinfo) == 0) {
+ /* Got something responding to ATA Identify Device */
+ if (sata_set_udma_mode(sata_hba_inst, &new_sdinfo) !=
+ SATA_SUCCESS) {
+ /* Try one more time */
+ if (sata_set_udma_mode(sata_hba_inst,
+ &new_sdinfo) != SATA_SUCCESS)
+ goto failure;
+ }
+ sata_device->satadev_type = new_sdinfo.satadrv_type;
+ break;
+ }
+ if (SATA_FEATURES(sata_hba_inst) & SATA_CTLF_ATAPI) {
+ /*
+ * HBA supports ATAPI - try to issue Identify Packet
+ * Device command.
+ */
+ new_sdinfo.satadrv_type = SATA_DTYPE_ATAPICD;
+ if (sata_identify_device(sata_hba_inst,
+ &new_sdinfo) == 0) {
+ /*
+ * Got something responding to Identify Packet
+ * Device cmd.
+ */
+ /* Set UDMA mode here as well ? - phase 2 */
+ sata_device->satadev_type =
+ new_sdinfo.satadrv_type;
+ break;
+ }
+ }
+ }
+ if (retry_cnt <= SATA_DEVICE_IDENTIFY_RETRY) {
+ /* save device info, if possible */
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device->satadev_addr.cport)));
+ sdinfo = sata_get_device_info(sata_hba_inst, sata_device);
+ if (sdinfo == NULL) {
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device->satadev_addr.cport)));
+ return (SATA_FAILURE);
+ }
+ /*
+ * Copy drive info into the port-linked drive info structure.
+ */
+ *sdinfo = new_sdinfo;
+ sdinfo->satadrv_state &= ~SATA_STATE_PROBING;
+ sdinfo->satadrv_state |= SATA_STATE_PROBED;
+ if (sata_device->satadev_addr.qual == SATA_ADDR_DCPORT)
+ SATA_CPORT_DEV_TYPE(sata_hba_inst,
+ sata_device->satadev_addr.cport) =
+ sdinfo->satadrv_type;
+ else /* SATA_ADDR_DPMPORT */
+ SATA_PMPORT_DEV_TYPE(sata_hba_inst,
+ sata_device->satadev_addr.cport,
+ sata_device->satadev_addr.pmport) =
+ sdinfo->satadrv_type;
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device->satadev_addr.cport)));
+ return (SATA_SUCCESS);
+ }
+
+failure:
+ /*
+ * Looks like we cannot determine the device type.
+ */
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device->satadev_addr.cport)));
+ sdinfo = sata_get_device_info(sata_hba_inst, sata_device);
+ if (sdinfo != NULL) {
+ sata_device->satadev_type = SATA_DTYPE_UNKNOWN;
+ sdinfo->satadrv_type = SATA_DTYPE_UNKNOWN;
+ sdinfo->satadrv_state &= ~SATA_STATE_PROBING;
+ sdinfo->satadrv_state = SATA_STATE_PROBED;
+ if (sata_device->satadev_addr.qual == SATA_ADDR_DCPORT)
+ SATA_CPORT_DEV_TYPE(sata_hba_inst,
+ sata_device->satadev_addr.cport) =
+ SATA_DTYPE_UNKNOWN;
+ else {
+ /* SATA_ADDR_DPMPORT */
+ if ((SATA_PMULT_INFO(sata_hba_inst,
+ sata_device->satadev_addr.cport) != NULL) &&
+ (SATA_PMPORT_INFO(sata_hba_inst,
+ sata_device->satadev_addr.cport,
+ sata_device->satadev_addr.pmport) != NULL))
+ SATA_PMPORT_DEV_TYPE(sata_hba_inst,
+ sata_device->satadev_addr.cport,
+ sata_device->satadev_addr.pmport) =
+ SATA_DTYPE_UNKNOWN;
+ }
+ }
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device->satadev_addr.cport)));
+ return (SATA_FAILURE);
+}
+
+
+/*
+ * Get pointer to sata_drive_info structure.
+ *
+ * The sata_device has to contain address (cport, pmport and qualifier) for
+ * specified sata_scsi structure.
+ *
+ * Returns NULL if device address is not valid for this HBA configuration.
+ * Otherwise, returns a pointer to sata_drive_info structure.
+ *
+ * This function should be called with a port mutex held.
+ */
+static sata_drive_info_t *
+sata_get_device_info(sata_hba_inst_t *sata_hba_inst,
+ sata_device_t *sata_device)
+{
+ uint8_t cport = sata_device->satadev_addr.cport;
+ uint8_t pmport = sata_device->satadev_addr.pmport;
+ uint8_t qual = sata_device->satadev_addr.qual;
+
+ if (cport >= SATA_NUM_CPORTS(sata_hba_inst))
+ return (NULL);
+
+ if (!(SATA_CPORT_STATE(sata_hba_inst, cport) &
+ (SATA_STATE_PROBED | SATA_STATE_READY)))
+ /* Port not probed yet */
+ return (NULL);
+
+ if (SATA_CPORT_DEV_TYPE(sata_hba_inst, cport) == SATA_DTYPE_NONE)
+ return (NULL);
+
+ if (qual == SATA_ADDR_DCPORT) {
+ /* Request for a device on a controller port */
+ if (SATA_CPORT_DEV_TYPE(sata_hba_inst, cport) ==
+ SATA_DTYPE_PMULT)
+ /* Port multiplier attached */
+ return (NULL);
+ return (SATA_CPORT_DRV_INFO(sata_hba_inst, cport));
+ }
+ if (qual == SATA_ADDR_DPMPORT) {
+ if (SATA_CPORT_DEV_TYPE(sata_hba_inst, cport) !=
+ SATA_DTYPE_PMULT)
+ return (NULL);
+
+ if (pmport > SATA_NUM_PMPORTS(sata_hba_inst, cport))
+ return (NULL);
+
+ return (SATA_PMPORT_DRV_INFO(sata_hba_inst, cport, pmport));
+ }
+
+ /* we should not get here */
+ return (NULL);
+}
+
+
+/*
+ * sata_identify_device.
+ * Send Identify Device command to SATA HBA driver.
+ * If command executes successfully, update sata_drive_info structure pointed
+ * to by sdinfo argument, including Identify Device data.
+ * If command fails, invalidate data in sata_drive_info.
+ *
+ * Cannot be called from interrupt level.
+ *
+ * Returns 0 if device was identified as supported device, -1 otherwise.
+ */
+static int
+sata_identify_device(sata_hba_inst_t *sata_hba_inst,
+ sata_drive_info_t *sdinfo)
+{
+ uint16_t cfg_word;
+ int i;
+
+ /* fetch device identify data */
+ if (sata_fetch_device_identify_data(sata_hba_inst, sdinfo) != 0)
+ goto fail_unknown;
+
+ cfg_word = sdinfo->satadrv_id.ai_config;
+ if (sdinfo->satadrv_type == SATA_DTYPE_ATADISK &&
+ (cfg_word & SATA_ATA_TYPE_MASK) != SATA_ATA_TYPE) {
+ /* Change device type to reflect Identify Device data */
+ if (((cfg_word & SATA_ATAPI_TYPE_MASK) ==
+ SATA_ATAPI_TYPE) &&
+ ((cfg_word & SATA_ATAPI_ID_DEV_TYPE) ==
+ SATA_ATAPI_CDROM_DEV)) {
+ sdinfo->satadrv_type = SATA_DTYPE_ATAPICD;
+ } else {
+ sdinfo->satadrv_type = SATA_DTYPE_UNKNOWN;
+ }
+ } else if (sdinfo->satadrv_type == SATA_DTYPE_ATAPICD &&
+ (((cfg_word & SATA_ATAPI_TYPE_MASK) != SATA_ATAPI_TYPE) ||
+ ((cfg_word & SATA_ATAPI_ID_DEV_TYPE) != SATA_ATAPI_CDROM_DEV))) {
+ /* Change device type to reflect Identify Device data ! */
+ if ((sdinfo->satadrv_id.ai_config & SATA_ATA_TYPE_MASK) ==
+ SATA_ATA_TYPE) {
+ sdinfo->satadrv_type = SATA_DTYPE_ATADISK;
+ } else {
+ sdinfo->satadrv_type = SATA_DTYPE_UNKNOWN;
+ }
+ }
+ if (sdinfo->satadrv_type == SATA_DTYPE_ATADISK) {
+ if (sdinfo->satadrv_capacity == 0) {
+ /* Non-LBA disk. Too bad... */
+ sata_log(sata_hba_inst, CE_WARN,
+ "SATA disk device at port %d does not support LBA",
+ sdinfo->satadrv_addr.cport);
+ goto fail_unknown;
+ }
+ }
+ /* Check for Ultra DMA modes 6 through 0 being supported */
+ for (i = 6; i >= 0; --i) {
+ if (sdinfo->satadrv_id.ai_ultradma & (1 << i))
+ break;
+ }
+ /*
+ * At least UDMA 4 mode has to be supported. If mode 4 or
+ * higher are not supported by the device, fail this
+ * device.
+ */
+ if (i < 4) {
+ /* No required Ultra DMA mode supported */
+ sata_log(sata_hba_inst, CE_WARN,
+ "SATA disk device at port %d does not support UDMA "
+ "mode 4 or higher", sdinfo->satadrv_addr.cport);
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "mode 4 or higher required, %d supported", i));
+ goto fail_unknown;
+ }
+
+ return (0);
+
+fail_unknown:
+ /* Invalidate sata_drive_info ? */
+ sdinfo->satadrv_type = SATA_DTYPE_UNKNOWN;
+ sdinfo->satadrv_state = SATA_STATE_UNKNOWN;
+ return (-1);
+}
+
+/*
+ * Log/display device information
+ */
+static void
+sata_show_drive_info(sata_hba_inst_t *sata_hba_inst,
+ sata_drive_info_t *sdinfo)
+{
+ int valid_version;
+ char msg_buf[MAXPATHLEN];
+
+ /* Show HBA path */
+ (void) ddi_pathname(SATA_DIP(sata_hba_inst), msg_buf);
+
+ cmn_err(CE_CONT, "?%s :\n", msg_buf);
+
+ if (sdinfo->satadrv_type == SATA_DTYPE_UNKNOWN) {
+ (void) sprintf(msg_buf,
+ "Unsupported SATA device type (cfg 0x%x) at ",
+ sdinfo->satadrv_id.ai_config);
+ } else {
+ (void) sprintf(msg_buf, "SATA %s device at",
+ sdinfo->satadrv_type == SATA_DTYPE_ATADISK ?
+ "disk":"CD/DVD (ATAPI)");
+ }
+ if (sdinfo->satadrv_addr.qual == SATA_ADDR_DCPORT)
+ cmn_err(CE_CONT, "?\t%s port %d\n",
+ msg_buf, sdinfo->satadrv_addr.cport);
+ else
+ cmn_err(CE_CONT, "?\t%s port %d pmport %d\n",
+ msg_buf, sdinfo->satadrv_addr.cport,
+ sdinfo->satadrv_addr.pmport);
+
+ bcopy(&sdinfo->satadrv_id.ai_model, msg_buf,
+ sizeof (sdinfo->satadrv_id.ai_model));
+ swab(msg_buf, msg_buf, sizeof (sdinfo->satadrv_id.ai_model));
+ msg_buf[sizeof (sdinfo->satadrv_id.ai_model)] = '\0';
+ cmn_err(CE_CONT, "?\tmodel %s\n", msg_buf);
+
+ bcopy(&sdinfo->satadrv_id.ai_fw, msg_buf,
+ sizeof (sdinfo->satadrv_id.ai_fw));
+ swab(msg_buf, msg_buf, sizeof (sdinfo->satadrv_id.ai_fw));
+ msg_buf[sizeof (sdinfo->satadrv_id.ai_fw)] = '\0';
+ cmn_err(CE_CONT, "?\tfirmware %s\n", msg_buf);
+
+ bcopy(&sdinfo->satadrv_id.ai_drvser, msg_buf,
+ sizeof (sdinfo->satadrv_id.ai_drvser));
+ swab(msg_buf, msg_buf, sizeof (sdinfo->satadrv_id.ai_drvser));
+ msg_buf[sizeof (sdinfo->satadrv_id.ai_drvser)] = '\0';
+ cmn_err(CE_CONT, "?\tserial number %sn", msg_buf);
+
+#ifdef SATA_DEBUG
+ if (sdinfo->satadrv_id.ai_majorversion != 0 &&
+ sdinfo->satadrv_id.ai_majorversion != 0xffff) {
+ int i;
+ for (i = 14; i >= 2; i--) {
+ if (sdinfo->satadrv_id.ai_majorversion & (1 << i)) {
+ valid_version = i;
+ break;
+ }
+ }
+ cmn_err(CE_CONT,
+ "?\tATA/ATAPI-%d supported, majver 0x%x minver 0x%x\n",
+ valid_version,
+ sdinfo->satadrv_id.ai_majorversion,
+ sdinfo->satadrv_id.ai_minorversion);
+ }
+#endif
+ /* Log some info */
+ cmn_err(CE_CONT, "?\tsupported features:\n");
+ msg_buf[0] = '\0';
+ if (sdinfo->satadrv_features_support & SATA_DEV_F_LBA48)
+ (void) strlcat(msg_buf, "48-bit LBA", MAXPATHLEN);
+ else if (sdinfo->satadrv_features_support & SATA_DEV_F_LBA28)
+ (void) strlcat(msg_buf, "28-bit LBA", MAXPATHLEN);
+ if (sdinfo->satadrv_features_support & SATA_DEV_F_DMA)
+ (void) strlcat(msg_buf, ", DMA", MAXPATHLEN);
+ if (sdinfo->satadrv_features_support & SATA_DEV_F_NCQ)
+ (void) strlcat(msg_buf, ", Native Command Queueing",
+ MAXPATHLEN);
+ else if (sdinfo->satadrv_id.ai_cmdset83 & SATA_RW_DMA_QUEUED_CMD)
+ (void) strlcat(msg_buf, ", Queuing", MAXPATHLEN);
+ cmn_err(CE_CONT, "?\t %s\n", msg_buf);
+ if (sdinfo->satadrv_features_support & SATA_DEV_F_SATA2)
+ cmn_err(CE_CONT, "?\tSATA1 & SATA2 compatible\n");
+ else if (sdinfo->satadrv_features_support & SATA_DEV_F_SATA1)
+ cmn_err(CE_CONT, "?\tSATA1 compatible\n");
+
+#ifdef __i386
+ (void) sprintf(msg_buf, "\tcapacity = %llu sectors\n",
+ sdinfo->satadrv_capacity);
+#else
+ (void) sprintf(msg_buf, "\tcapacity = %lu sectors\n",
+ sdinfo->satadrv_capacity);
+#endif
+ cmn_err(CE_CONT, "?%s", msg_buf);
+}
+
+
+/*
+ * sata_save_drive_settings extracts current setting of the device and stores
+ * it for future reference, in case the device setup would need to be restored
+ * after the device reset.
+ *
+ * At the moment only read ahead and write cache settings are saved, if the
+ * device supports these features at all.
+ */
+static void
+sata_save_drive_settings(sata_drive_info_t *sdinfo)
+{
+ if (!(sdinfo->satadrv_id.ai_cmdset82 & SATA_LOOK_AHEAD) &&
+ !(sdinfo->satadrv_id.ai_cmdset82 & SATA_WRITE_CACHE)) {
+ /* None of the features is supported - do nothing */
+ return;
+ }
+
+ /* Current setting of Read Ahead (and Read Cache) */
+ if (sdinfo->satadrv_id.ai_features85 & SATA_LOOK_AHEAD)
+ sdinfo->satadrv_settings |= SATA_DEV_READ_AHEAD;
+ else
+ sdinfo->satadrv_settings &= ~SATA_DEV_READ_AHEAD;
+
+ /* Current setting of Write Cache */
+ if (sdinfo->satadrv_id.ai_features85 & SATA_WRITE_CACHE)
+ sdinfo->satadrv_settings |= SATA_DEV_WRITE_CACHE;
+ else
+ sdinfo->satadrv_settings &= ~SATA_DEV_WRITE_CACHE;
+}
+
+
+/*
+ * sata_check_capacity function determines a disk capacity
+ * and addressing mode (LBA28/LBA48) by examining a disk identify device data.
+ *
+ * NOTE: CHS mode is not supported! If a device does not support LBA,
+ * this function is not called.
+ *
+ * Returns device capacity in number of blocks, i.e. largest addressable LBA+1
+ */
+static uint64_t
+sata_check_capacity(sata_drive_info_t *sdinfo)
+{
+ uint64_t capacity = 0;
+ int i;
+
+ if (sdinfo->satadrv_type != SATA_DTYPE_ATADISK ||
+ !sdinfo->satadrv_id.ai_cap & SATA_LBA_SUPPORT)
+ /* Capacity valid only for LBA-addressable disk devices */
+ return (0);
+
+ if ((sdinfo->satadrv_id.ai_validinfo & SATA_VALIDINFO_88) &&
+ (sdinfo->satadrv_id.ai_cmdset83 & SATA_EXT48) &&
+ (sdinfo->satadrv_id.ai_features86 & SATA_EXT48)) {
+ /* LBA48 mode supported and enabled */
+ sdinfo->satadrv_features_support |= SATA_DEV_F_LBA48 |
+ SATA_DEV_F_LBA28;
+ for (i = 3; i >= 0; --i) {
+ capacity <<= 16;
+ capacity += sdinfo->satadrv_id.ai_addrsecxt[i];
+ }
+ } else {
+ capacity = sdinfo->satadrv_id.ai_addrsec[1];
+ capacity <<= 16;
+ capacity += sdinfo->satadrv_id.ai_addrsec[0];
+ if (capacity >= 0x1000000)
+ /* LBA28 mode */
+ sdinfo->satadrv_features_support |= SATA_DEV_F_LBA28;
+ }
+ return (capacity);
+}
+
+
+/*
+ * Allocate consistent buffer for DMA transfer
+ *
+ * Cannot be called from interrupt level or with mutex held - it may sleep.
+ *
+ * Returns pointer to allocated buffer structure, or NULL if allocation failed.
+ */
+static struct buf *
+sata_alloc_local_buffer(sata_pkt_txlate_t *spx, int len)
+{
+ struct scsi_address ap;
+ struct buf *bp;
+ ddi_dma_attr_t cur_dma_attr;
+
+ ASSERT(spx->txlt_sata_pkt != NULL);
+ ap.a_hba_tran = spx->txlt_sata_hba_inst->satahba_scsi_tran;
+ ap.a_target = SATA_TO_SCSI_TARGET(
+ spx->txlt_sata_pkt->satapkt_device.satadev_addr.cport,
+ spx->txlt_sata_pkt->satapkt_device.satadev_addr.pmport,
+ spx->txlt_sata_pkt->satapkt_device.satadev_addr.qual);
+ ap.a_lun = 0;
+
+ bp = scsi_alloc_consistent_buf(&ap, NULL, len,
+ B_READ, SLEEP_FUNC, NULL);
+
+ if (bp != NULL) {
+ /* Allocate DMA resources for this buffer */
+ spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp = bp;
+ /*
+ * We use a local version of the dma_attr, to account
+ * for a device addressing limitations.
+ * sata_adjust_dma_attr() will handle sdinfo == NULL which
+ * will cause dma attributes to be adjusted to a lowest
+ * acceptable level.
+ */
+ sata_adjust_dma_attr(NULL,
+ SATA_DMA_ATTR(spx->txlt_sata_hba_inst), &cur_dma_attr);
+
+ if (sata_dma_buf_setup(spx, PKT_CONSISTENT,
+ SLEEP_FUNC, NULL, &cur_dma_attr) != DDI_SUCCESS) {
+ scsi_free_consistent_buf(bp);
+ spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp = NULL;
+ bp = NULL;
+ }
+ }
+ return (bp);
+}
+
+/*
+ * Release local buffer (consistent buffer for DMA transfer) allocated
+ * via sata_alloc_local_buffer().
+ */
+static void
+sata_free_local_buffer(sata_pkt_txlate_t *spx)
+{
+ ASSERT(spx->txlt_sata_pkt != NULL);
+ ASSERT(spx->txlt_dma_cookie_list != NULL);
+ ASSERT(spx->txlt_dma_cookie_list_len != 0);
+ ASSERT(spx->txlt_buf_dma_handle != NULL);
+ ASSERT(spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp != NULL);
+
+ spx->txlt_sata_pkt->satapkt_cmd.satacmd_num_dma_cookies = 0;
+ spx->txlt_sata_pkt->satapkt_cmd.satacmd_dma_cookie_list = NULL;
+
+ /* Free DMA resources */
+ (void) ddi_dma_unbind_handle(spx->txlt_buf_dma_handle);
+ ddi_dma_free_handle(&spx->txlt_buf_dma_handle);
+ spx->txlt_buf_dma_handle = 0;
+
+ kmem_free(spx->txlt_dma_cookie_list,
+ spx->txlt_dma_cookie_list_len * sizeof (ddi_dma_cookie_t));
+ spx->txlt_dma_cookie_list = NULL;
+ spx->txlt_dma_cookie_list_len = 0;
+
+ /* Free buffer */
+ scsi_free_consistent_buf(spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp);
+}
+
+
+
+
+/*
+ * Allocate sata_pkt
+ * Pkt structure version and embedded strcutures version are initialized.
+ * sata_pkt and sata_pkt_txlate structures are cross-linked.
+ *
+ * Since this may be called in interrupt context by sata_scsi_init_pkt,
+ * callback argument determines if it can sleep or not.
+ * Hence, it should not be called from interrupt context.
+ *
+ * If successful, non-NULL pointer to a sata pkt is returned.
+ * Upon failure, NULL pointer is returned.
+ */
+static sata_pkt_t *
+sata_pkt_alloc(sata_pkt_txlate_t *spx, int (*callback)(caddr_t))
+{
+ sata_pkt_t *spkt;
+ int kmsflag;
+
+ kmsflag = (callback == SLEEP_FUNC) ? KM_SLEEP : KM_NOSLEEP;
+ spkt = kmem_zalloc(sizeof (sata_pkt_t), kmsflag);
+ if (spkt == NULL) {
+ SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
+ "sata_pkt_alloc: failed"));
+ return (NULL);
+ }
+ spkt->satapkt_rev = SATA_PKT_REV;
+ spkt->satapkt_cmd.satacmd_rev = SATA_CMD_REV;
+ spkt->satapkt_device.satadev_rev = SATA_DEVICE_REV;
+ spkt->satapkt_framework_private = spx;
+ spx->txlt_sata_pkt = spkt;
+ return (spkt);
+}
+
+/*
+ * Free sata pkt allocated via sata_pkt_alloc()
+ */
+static void
+sata_pkt_free(sata_pkt_txlate_t *spx)
+{
+ ASSERT(spx->txlt_sata_pkt != NULL);
+ ASSERT(spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp == NULL);
+ kmem_free(spx->txlt_sata_pkt, sizeof (sata_pkt_t));
+ spx->txlt_sata_pkt = NULL;
+}
+
+
+/*
+ * Adjust DMA attributes.
+ * SCSI cmds block count is up to 24 bits, SATA cmd block count vary
+ * from 8 bits to 16 bits, depending on a command being used.
+ * Limiting max block count arbitrarily to 256 for all read/write
+ * commands may affects performance, so check both the device and
+ * controller capability before adjusting dma attributes.
+ * For ATAPI CD/DVD dma granularity has to be adjusted as well,
+ * because these devices support block size of 2k rather
+ * then 512 bytes.
+ */
+void
+sata_adjust_dma_attr(sata_drive_info_t *sdinfo, ddi_dma_attr_t *dma_attr,
+ ddi_dma_attr_t *adj_dma_attr)
+{
+ uint32_t count_max;
+
+ /* Copy original attributes */
+ *adj_dma_attr = *dma_attr;
+
+ /*
+ * Things to consider: device addressing capability,
+ * "excessive" controller DMA capabilities.
+ * If a device is being probed/initialized, there are
+ * no device info - use default limits then.
+ */
+ if (sdinfo == NULL) {
+ count_max = dma_attr->dma_attr_granular * 0x100;
+ if (dma_attr->dma_attr_count_max > count_max)
+ adj_dma_attr->dma_attr_count_max = count_max;
+ if (dma_attr->dma_attr_maxxfer > count_max)
+ adj_dma_attr->dma_attr_maxxfer = count_max;
+ return;
+ }
+ if (sdinfo->satadrv_type == SATA_DTYPE_ATAPICD) {
+ /* arbitrarily modify controller dma granularity */
+ adj_dma_attr->dma_attr_granular = SATA_ATAPI_SECTOR_SIZE;
+ }
+
+ if (sdinfo->satadrv_features_support & (SATA_DEV_F_LBA48)) {
+ /*
+ * 16-bit sector count may be used - we rely on
+ * the assumption that only read and write cmds
+ * will request more than 256 sectors worth of data
+ */
+ count_max = adj_dma_attr->dma_attr_granular * 0x10000;
+ } else {
+ /*
+ * 8-bit sector count will be used - default limits
+ * for dma attributes
+ */
+ count_max = adj_dma_attr->dma_attr_granular * 0x100;
+ }
+
+
+ /*
+ * Adjust controler dma attributes, if necessary
+ */
+ if (dma_attr->dma_attr_count_max > count_max)
+ adj_dma_attr->dma_attr_count_max = count_max;
+ if (dma_attr->dma_attr_maxxfer > count_max)
+ adj_dma_attr->dma_attr_maxxfer = count_max;
+}
+
+
+/*
+ * Allocate DMA resources for the buffer
+ * This function handles initial DMA resource allocation as well as
+ * DMA window shift and may be called repeatedly for the same DMA window
+ * until all DMA cookies in the DMA window are processed.
+ *
+ * Returns DDI_SUCCESS upon successful operation,
+ * returns failure code returned by failing commands or DDI_FAILURE when
+ * internal cleanup failed.
+ */
+static int
+sata_dma_buf_setup(sata_pkt_txlate_t *spx, int flags,
+ int (*callback)(caddr_t), caddr_t arg,
+ ddi_dma_attr_t *cur_dma_attr)
+{
+ int rval;
+ ddi_dma_cookie_t cookie;
+ off_t offset;
+ size_t size;
+ int max_sg_len, req_sg_len, i;
+ uint_t dma_flags;
+ struct buf *bp;
+ uint64_t max_txfer_len;
+ uint64_t cur_txfer_len;
+
+ ASSERT(spx->txlt_sata_pkt != NULL);
+ bp = spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp;
+ ASSERT(bp != NULL);
+
+
+ if (spx->txlt_buf_dma_handle == NULL) {
+ /*
+ * No DMA resources allocated so far - this is a first call
+ * for this sata pkt.
+ */
+ rval = ddi_dma_alloc_handle(SATA_DIP(spx->txlt_sata_hba_inst),
+ cur_dma_attr, callback, arg, &spx->txlt_buf_dma_handle);
+
+ if (rval != DDI_SUCCESS) {
+ SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
+ "sata_dma_buf_setup: no buf DMA resources %x",
+ rval));
+ return (rval);
+ }
+
+ if (bp->b_flags & B_READ)
+ dma_flags = DDI_DMA_READ;
+ else
+ dma_flags = DDI_DMA_WRITE;
+
+ if (flags & PKT_CONSISTENT)
+ dma_flags |= DDI_DMA_CONSISTENT;
+
+ if (flags & PKT_DMA_PARTIAL)
+ dma_flags |= DDI_DMA_PARTIAL;
+
+ rval = ddi_dma_buf_bind_handle(spx->txlt_buf_dma_handle,
+ bp, dma_flags, callback, arg,
+ &cookie, &spx->txlt_curwin_num_dma_cookies);
+
+ switch (rval) {
+ case DDI_DMA_PARTIAL_MAP:
+ SATADBG1(SATA_DBG_DMA_SETUP, spx->txlt_sata_hba_inst,
+ "sata_dma_buf_setup: DMA Partial Map\n", NULL);
+ /*
+ * Partial DMA mapping.
+ * Retrieve number of DMA windows for this request.
+ */
+ if (ddi_dma_numwin(spx->txlt_buf_dma_handle,
+ &spx->txlt_num_dma_win) != DDI_SUCCESS) {
+ (void) ddi_dma_unbind_handle(
+ spx->txlt_buf_dma_handle);
+ (void) ddi_dma_free_handle(
+ &spx->txlt_buf_dma_handle);
+ spx->txlt_buf_dma_handle = NULL;
+ SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
+ "sata_dma_buf_setup: numwin failed\n"));
+ return (DDI_FAILURE);
+ }
+ spx->txlt_cur_dma_win = 0;
+ break;
+
+ case DDI_DMA_MAPPED:
+ /* DMA fully mapped */
+ spx->txlt_num_dma_win = 1;
+ spx->txlt_cur_dma_win = 0;
+ break;
+
+ default:
+ /* DMA mapping failed */
+ (void) ddi_dma_free_handle(&spx->txlt_buf_dma_handle);
+ spx->txlt_buf_dma_handle = NULL;
+ SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
+ "sata_dma_buf_setup: buf dma handle binding "
+ "failed %x\n", rval));
+ return (rval);
+ }
+ spx->txlt_curwin_processed_dma_cookies = 0;
+ spx->txlt_dma_cookie_list = NULL;
+ } else {
+ /*
+ * DMA setup is reused. Check if we need to process more
+ * cookies in current window, or to get next window, if any.
+ */
+
+ ASSERT(spx->txlt_curwin_processed_dma_cookies <=
+ spx->txlt_curwin_num_dma_cookies);
+
+ if (spx->txlt_curwin_processed_dma_cookies ==
+ spx->txlt_curwin_num_dma_cookies) {
+ /*
+ * All cookies from current DMA window were processed.
+ * Get next DMA window.
+ */
+ spx->txlt_cur_dma_win++;
+ if (spx->txlt_cur_dma_win < spx->txlt_num_dma_win) {
+ (void) ddi_dma_getwin(spx->txlt_buf_dma_handle,
+ spx->txlt_cur_dma_win, &offset, &size,
+ &cookie,
+ &spx->txlt_curwin_num_dma_cookies);
+ spx->txlt_curwin_processed_dma_cookies = 0;
+
+ } else {
+ /* No more windows! End of request! */
+ /* What to do? - panic for now */
+ ASSERT(spx->txlt_cur_dma_win >=
+ spx->txlt_num_dma_win);
+
+ spx->txlt_curwin_num_dma_cookies = 0;
+ spx->txlt_curwin_processed_dma_cookies = 0;
+ spx->txlt_sata_pkt->
+ satapkt_cmd.satacmd_num_dma_cookies = 0;
+ return (DDI_SUCCESS);
+ }
+ }
+ }
+ /* There better be at least one DMA cookie */
+ ASSERT((spx->txlt_curwin_num_dma_cookies -
+ spx->txlt_curwin_processed_dma_cookies) > 0);
+
+ if (spx->txlt_curwin_processed_dma_cookies == 0) {
+ /*
+ * Processing a new DMA window - set-up dma cookies list.
+ * We may reuse previously allocated cookie array if it is
+ * possible.
+ */
+ if (spx->txlt_dma_cookie_list != NULL &&
+ spx->txlt_dma_cookie_list_len <
+ spx->txlt_curwin_num_dma_cookies) {
+ /*
+ * New DMA window contains more cookies than
+ * the previous one. We need larger cookie list - free
+ * the old one.
+ */
+ (void) kmem_free(spx->txlt_dma_cookie_list,
+ spx->txlt_dma_cookie_list_len *
+ sizeof (ddi_dma_cookie_t));
+ spx->txlt_dma_cookie_list = NULL;
+ spx->txlt_dma_cookie_list_len = 0;
+ }
+ if (spx->txlt_dma_cookie_list == NULL) {
+ /* Allocate new dma cookie array */
+ spx->txlt_dma_cookie_list = kmem_zalloc(
+ sizeof (ddi_dma_cookie_t) *
+ spx->txlt_curwin_num_dma_cookies, KM_SLEEP);
+ spx->txlt_dma_cookie_list_len =
+ spx->txlt_curwin_num_dma_cookies;
+ }
+ /*
+ * Copy all DMA cookies into local list, so we will know their
+ * dma_size in advance of setting the sata_pkt.
+ * One cookie was already fetched, so copy it.
+ */
+ *(&spx->txlt_dma_cookie_list[0]) = cookie;
+ for (i = 1; i < spx->txlt_curwin_num_dma_cookies; i++) {
+ ddi_dma_nextcookie(spx->txlt_buf_dma_handle, &cookie);
+ *(&spx->txlt_dma_cookie_list[i]) = cookie;
+ }
+ } else {
+ SATADBG2(SATA_DBG_DMA_SETUP, spx->txlt_sata_hba_inst,
+ "sata_dma_buf_setup: sliding within DMA window, "
+ "cur cookie %d, total cookies %d\n",
+ spx->txlt_curwin_processed_dma_cookies,
+ spx->txlt_curwin_num_dma_cookies);
+ }
+
+ /*
+ * Set-up sata_pkt cookie list.
+ * No single cookie transfer size would exceed max transfer size of
+ * an ATA command used for addressed device (tha adjustment of the dma
+ * attributes took care of this). But there may be more
+ * then one cookie, so the cmd cookie list has to be
+ * constrained by both a maximum scatter gather list length and
+ * a maximum transfer size restriction of an ATA command.
+ */
+
+ max_sg_len = cur_dma_attr->dma_attr_sgllen;
+ req_sg_len = MIN(max_sg_len,
+ (spx->txlt_curwin_num_dma_cookies -
+ spx->txlt_curwin_processed_dma_cookies));
+
+ ASSERT(req_sg_len > 0);
+
+ max_txfer_len = MAX((cur_dma_attr->dma_attr_granular * 0x100),
+ cur_dma_attr->dma_attr_maxxfer);
+
+ /* One cookie should be always available */
+ spx->txlt_sata_pkt->satapkt_cmd.satacmd_dma_cookie_list =
+ &spx->txlt_dma_cookie_list[spx->txlt_curwin_processed_dma_cookies];
+
+ spx->txlt_sata_pkt->satapkt_cmd.satacmd_num_dma_cookies = 1;
+
+ cur_txfer_len =
+ (uint64_t)spx->txlt_dma_cookie_list[
+ spx->txlt_curwin_processed_dma_cookies].dmac_size;
+
+ spx->txlt_curwin_processed_dma_cookies++;
+
+ ASSERT(cur_txfer_len <= max_txfer_len);
+
+ /* Add more cookies to the scatter-gather list */
+ for (i = 1; i < req_sg_len; i++) {
+ if (cur_txfer_len < max_txfer_len) {
+ /*
+ * Check if the next cookie could be used by
+ * this sata_pkt.
+ */
+ if ((cur_txfer_len +
+ spx->txlt_dma_cookie_list[
+ spx->txlt_curwin_processed_dma_cookies].
+ dmac_size) <= max_txfer_len) {
+ /* Yes, transfer lenght is within bounds */
+ spx->txlt_sata_pkt->
+ satapkt_cmd.satacmd_num_dma_cookies++;
+ cur_txfer_len +=
+ spx->txlt_dma_cookie_list[
+ spx->txlt_curwin_processed_dma_cookies].
+ dmac_size;
+ spx->txlt_curwin_processed_dma_cookies++;
+ } else {
+ /* No, transfer would exceed max lenght. */
+ SATADBG3(SATA_DBG_DMA_SETUP,
+ spx->txlt_sata_hba_inst,
+ "ncookies %d, size 0x%lx, "
+ "max_size 0x%lx\n",
+ spx->txlt_sata_pkt->
+ satapkt_cmd.satacmd_num_dma_cookies,
+ cur_txfer_len, max_txfer_len);
+ break;
+ }
+ } else {
+ /* Cmd max transfer length reached */
+ SATADBG3(SATA_DBG_DMA_SETUP, spx->txlt_sata_hba_inst,
+ "Max transfer length? "
+ "ncookies %d, size 0x%lx, max_size 0x%lx\n",
+ spx->txlt_sata_pkt->
+ satapkt_cmd.satacmd_num_dma_cookies,
+ cur_txfer_len, max_txfer_len);
+ break;
+ }
+ }
+
+ ASSERT(cur_txfer_len != 0);
+ spx->txlt_total_residue -= cur_txfer_len;
+
+ return (DDI_SUCCESS);
+}
+
+/*
+ * Fetch Device Identify data.
+ * Send DEVICE IDENTIFY command to a device and get the device identify data.
+ * The device_info structure has to be set to device type (for selecting proper
+ * device identify command).
+ *
+ * Returns 0 if success, -1 otherwise.
+ *
+ * Cannot be called in an interrupt context.
+ */
+
+static int
+sata_fetch_device_identify_data(sata_hba_inst_t *sata_hba_inst,
+ sata_drive_info_t *sdinfo)
+{
+ struct buf *bp;
+ sata_pkt_t *spkt;
+ sata_cmd_t *scmd;
+ sata_pkt_txlate_t *spx;
+ int rval;
+
+ spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
+ spx->txlt_sata_hba_inst = sata_hba_inst;
+ spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
+ spkt = sata_pkt_alloc(spx, SLEEP_FUNC);
+ if (spkt == NULL) {
+ kmem_free(spx, sizeof (sata_pkt_txlate_t));
+ return (-1);
+ }
+ /* address is needed now */
+ spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
+
+ /*
+ * Allocate buffer for Identify Data return data
+ */
+ bp = sata_alloc_local_buffer(spx, sizeof (sata_id_t));
+ if (bp == NULL) {
+ sata_pkt_free(spx);
+ kmem_free(spx, sizeof (sata_pkt_txlate_t));
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_fetch_device_identify_data: "
+ "cannot allocate buffer for ID"));
+ return (-1);
+ }
+
+ /* Fill sata_pkt */
+ sdinfo->satadrv_state = SATA_STATE_PROBING;
+ spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
+ spkt->satapkt_op_mode = SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
+ /* Synchronous mode, no callback */
+ spkt->satapkt_comp = NULL;
+ /* Timeout 30s */
+ spkt->satapkt_time = sata_default_pkt_time;
+
+ scmd = &spkt->satapkt_cmd;
+ scmd->satacmd_bp = bp;
+ scmd->satacmd_flags = SATA_DIR_READ | SATA_IGNORE_DEV_RESET_STATE;
+
+ /* Build Identify Device cmd in the sata_pkt */
+ scmd->satacmd_addr_type = 0; /* N/A */
+ scmd->satacmd_sec_count_lsb = 0; /* N/A */
+ scmd->satacmd_lba_low_lsb = 0; /* N/A */
+ scmd->satacmd_lba_mid_lsb = 0; /* N/A */
+ scmd->satacmd_lba_high_lsb = 0; /* N/A */
+ scmd->satacmd_features_reg = 0; /* N/A */
+ scmd->satacmd_device_reg = 0; /* Always device 0 */
+ if (sdinfo->satadrv_type == SATA_DTYPE_ATAPICD) {
+ /* Identify Packet Device cmd */
+ scmd->satacmd_cmd_reg = SATAC_ID_PACKET_DEVICE;
+ } else {
+ /* Identify Device cmd - mandatory for all other devices */
+ scmd->satacmd_cmd_reg = SATAC_ID_DEVICE;
+ }
+
+ /* Send pkt to SATA HBA driver */
+ if ((*SATA_START_FUNC(sata_hba_inst))(SATA_DIP(sata_hba_inst), spkt) !=
+ SATA_TRAN_ACCEPTED ||
+ spkt->satapkt_reason != SATA_PKT_COMPLETED) {
+ /*
+ * Woops, no Identify Data.
+ * Invalidate sata_drive_info ?
+ */
+ rval = -1;
+ } else {
+ /* Update sata_drive_info */
+ rval = ddi_dma_sync(spx->txlt_buf_dma_handle, 0, 0,
+ DDI_DMA_SYNC_FORKERNEL);
+ if (rval != DDI_SUCCESS) {
+ SATA_LOG_D((spx->txlt_sata_hba_inst, CE_WARN,
+ "sata_fetch_device_identify_data: "
+ "sync pkt failed"));
+ rval = -1;
+ goto fail;
+ }
+ bcopy(bp->b_un.b_addr, &sdinfo->satadrv_id,
+ sizeof (sata_id_t));
+
+ sdinfo->satadrv_features_support = 0;
+ if (sdinfo->satadrv_type == SATA_DTYPE_ATADISK) {
+ /*
+ * Retrieve capacity (disks only) and addressing mode
+ */
+ sdinfo->satadrv_capacity = sata_check_capacity(sdinfo);
+ } else {
+ /*
+ * For ATAPI devices one has to issue Get Capacity cmd
+ * (not needed at the moment)
+ */
+ sdinfo->satadrv_capacity = 0;
+ }
+ /* Setup supported features flags */
+ if (sdinfo->satadrv_id.ai_cap & SATA_DMA_SUPPORT)
+ sdinfo->satadrv_features_support |= SATA_DEV_F_DMA;
+
+ /* Check for NCQ support */
+ if (sdinfo->satadrv_id.ai_satacap != 0 &&
+ sdinfo->satadrv_id.ai_satacap != 0xffff) {
+ /* SATA compliance */
+ if (sdinfo->satadrv_id.ai_satacap & SATA_NCQ)
+ sdinfo->satadrv_features_support |=
+ SATA_DEV_F_NCQ;
+ if (sdinfo->satadrv_id.ai_satacap &
+ (SATA_1_SPEED | SATA_2_SPEED)) {
+ if (sdinfo->satadrv_id.ai_satacap &
+ SATA_2_SPEED)
+ sdinfo->satadrv_features_support |=
+ SATA_DEV_F_SATA2;
+ if (sdinfo->satadrv_id.ai_satacap &
+ SATA_1_SPEED)
+ sdinfo->satadrv_features_support |=
+ SATA_DEV_F_SATA1;
+ } else {
+ sdinfo->satadrv_features_support |=
+ SATA_DEV_F_SATA1;
+ }
+ }
+
+ sdinfo->satadrv_queue_depth = sdinfo->satadrv_id.ai_qdepth;
+ if (sdinfo->satadrv_id.ai_cmdset83 & SATA_RW_DMA_QUEUED_CMD)
+ if (sdinfo->satadrv_queue_depth == 0)
+ sdinfo->satadrv_queue_depth = 1;
+
+ rval = 0;
+ }
+fail:
+ /* Free allocated resources */
+ sata_free_local_buffer(spx);
+ spx->txlt_sata_pkt->satapkt_cmd.satacmd_bp = NULL;
+ sata_pkt_free(spx);
+ kmem_free(spx, sizeof (sata_pkt_txlate_t));
+
+ return (rval);
+}
+
+
+/*
+ * SATA spec requires that the device supports at least UDMA 4 mode and
+ * UDMA mode is selected.
+ * Some devices (bridged devices) may not come-up with default UDMA mode
+ * set correctly, so this function is setting it.
+ *
+ * Returns SATA_SUCCESS if proper UDMA mode is selected.
+ * Returns SATA_FAILURE if proper UDMA mode could not be selected.
+ */
+static int
+sata_set_udma_mode(sata_hba_inst_t *sata_hba_inst, sata_drive_info_t *sdinfo)
+{
+ sata_pkt_t *spkt;
+ sata_cmd_t *scmd;
+ sata_pkt_txlate_t *spx;
+ int result = SATA_SUCCESS;
+ int i, mode;
+
+ ASSERT(sdinfo != NULL);
+ ASSERT(sata_hba_inst != NULL);
+
+ /* Find highest Ultra DMA mode supported */
+ for (mode = 6; mode >= 0; --mode) {
+ if (sdinfo->satadrv_id.ai_ultradma & (1 << mode))
+ break;
+ }
+ if (mode < 4)
+ return (SATA_FAILURE);
+
+ /* Find UDMA mode currently selected */
+ for (i = 6; i >= 0; --i) {
+ if (sdinfo->satadrv_id.ai_ultradma & (1 << (i + 8)))
+ break;
+ }
+
+ if (i < mode) {
+ /* Set UDMA mode via SET FEATURES COMMAND */
+ /* Prepare packet for SET FEATURES COMMAND */
+ spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
+ spx->txlt_sata_hba_inst = sata_hba_inst;
+ spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
+ spkt = sata_pkt_alloc(spx, SLEEP_FUNC);
+ if (spkt == NULL) {
+ result = SATA_FAILURE;
+ goto failure;
+ }
+ /* Fill sata_pkt */
+ spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
+ /* Timeout 30s */
+ spkt->satapkt_time = sata_default_pkt_time;
+ /* Synchronous mode, no callback, interrupts */
+ spkt->satapkt_op_mode =
+ SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
+ spkt->satapkt_comp = NULL;
+ scmd = &spkt->satapkt_cmd;
+ scmd->satacmd_flags = SATA_DIR_NODATA_XFER |
+ SATA_IGNORE_DEV_RESET_STATE;
+ scmd->satacmd_addr_type = 0;
+ scmd->satacmd_device_reg = 0;
+ scmd->satacmd_status_reg = 0;
+ scmd->satacmd_error_reg = 0;
+ scmd->satacmd_cmd_reg = SATAC_SET_FEATURES;
+ scmd->satacmd_features_reg = SATAC_SF_TRANSFER_MODE;
+ scmd->satacmd_sec_count_lsb =
+ SATAC_TRANSFER_MODE_ULTRA_DMA | mode;
+
+ /* Transfer command to HBA */
+ if ((*SATA_START_FUNC(sata_hba_inst))(SATA_DIP(sata_hba_inst),
+ spkt) != SATA_TRAN_ACCEPTED ||
+ spkt->satapkt_reason != SATA_PKT_COMPLETED) {
+ /* Pkt execution failed */
+ result = SATA_FAILURE;
+ }
+failure:
+ if (result == SATA_FAILURE)
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_set_udma_mode: could not set UDMA "
+ "mode %", mode));
+
+ /* Free allocated resources */
+ if (spkt != NULL)
+ sata_pkt_free(spx);
+ (void) kmem_free(spx, sizeof (sata_pkt_txlate_t));
+ }
+ return (result);
+}
+
+
+/*
+ * Update port SCR block
+ */
+static void
+sata_update_port_scr(sata_port_scr_t *port_scr, sata_device_t *device)
+{
+ port_scr->sstatus = device->satadev_scr.sstatus;
+ port_scr->serror = device->satadev_scr.serror;
+ port_scr->scontrol = device->satadev_scr.scontrol;
+ port_scr->sactive = device->satadev_scr.sactive;
+ port_scr->snotific = device->satadev_scr.snotific;
+}
+
+/*
+ * Update state and copy port ss* values from passed sata_device structure.
+ * sata_address is validated - if not valid, nothing is changed in sata_scsi
+ * configuration struct.
+ *
+ * SATA_PSTATE_SHUTDOWN in port state is not reset to 0 by this function
+ * regardless of the state in device argument.
+ *
+ * Port mutex should be held while calling this function.
+ */
+static void
+sata_update_port_info(sata_hba_inst_t *sata_hba_inst,
+ sata_device_t *sata_device)
+{
+ ASSERT(mutex_owned(&SATA_CPORT_MUTEX(sata_hba_inst,
+ sata_device->satadev_addr.cport)));
+
+ if (sata_device->satadev_addr.qual == SATA_ADDR_CPORT ||
+ sata_device->satadev_addr.qual == SATA_ADDR_DCPORT) {
+
+ sata_cport_info_t *cportinfo;
+
+ if (SATA_NUM_CPORTS(sata_hba_inst) <=
+ sata_device->satadev_addr.cport)
+ return;
+
+ cportinfo = SATA_CPORT_INFO(sata_hba_inst,
+ sata_device->satadev_addr.cport);
+ sata_update_port_scr(&cportinfo->cport_scr, sata_device);
+
+ /* Preserve SATA_PSTATE_SHUTDOWN flag */
+ cportinfo->cport_state &= ~(SATA_PSTATE_PWRON |
+ SATA_PSTATE_PWROFF | SATA_PSTATE_FAILED);
+ cportinfo->cport_state |=
+ sata_device->satadev_state & SATA_PSTATE_VALID;
+ } else {
+ sata_pmport_info_t *pmportinfo;
+
+ if ((sata_device->satadev_addr.qual != SATA_ADDR_PMPORT) ||
+ (sata_device->satadev_addr.qual != SATA_ADDR_DPMPORT) ||
+ SATA_NUM_PMPORTS(sata_hba_inst,
+ sata_device->satadev_addr.cport) <
+ sata_device->satadev_addr.pmport)
+ return;
+
+ pmportinfo = SATA_PMPORT_INFO(sata_hba_inst,
+ sata_device->satadev_addr.cport,
+ sata_device->satadev_addr.pmport);
+ sata_update_port_scr(&pmportinfo->pmport_scr, sata_device);
+
+ /* Preserve SATA_PSTATE_SHUTDOWN flag */
+ pmportinfo->pmport_state &=
+ ~(SATA_PSTATE_PWRON | SATA_PSTATE_PWROFF |
+ SATA_PSTATE_FAILED);
+ pmportinfo->pmport_state |=
+ sata_device->satadev_state & SATA_PSTATE_VALID;
+ }
+}
+
+
+
+/*
+ * Extract SATA port specification from an IOCTL argument.
+ *
+ * This function return the port the user land send us as is, unless it
+ * cannot retrieve port spec, then -1 is returned.
+ *
+ * Note: Only cport - no port multiplier port.
+ */
+static int32_t
+sata_get_port_num(sata_hba_inst_t *sata_hba_inst, struct devctl_iocdata *dcp)
+{
+ int32_t port;
+
+ /* Extract port number from nvpair in dca structure */
+ if (nvlist_lookup_int32(ndi_dc_get_ap_data(dcp), "port", &port) != 0) {
+ SATA_LOG_D((sata_hba_inst, CE_NOTE,
+ "sata_get_port_num: invalid port spec 0x%x in ioctl",
+ port));
+ port = -1;
+ }
+
+ return (port);
+}
+
+/*
+ * Get dev_info_t pointer to the device node pointed to by port argument.
+ * NOTE: target argument is a value used in ioctls to identify
+ * the AP - it is not a sata_address.
+ * It is a combination of cport, pmport and address qualifier, encodded same
+ * way as a scsi target number.
+ * At this moment it carries only cport number.
+ *
+ * No PMult hotplug support.
+ *
+ * Returns dev_info_t pointer if target device was found, NULL otherwise.
+ */
+
+static dev_info_t *
+sata_get_target_dip(dev_info_t *dip, int32_t port)
+{
+ dev_info_t *cdip = NULL;
+ int target, tgt;
+ int ncport;
+ int circ;
+
+ ncport = port & SATA_CFGA_CPORT_MASK;
+ target = SATA_TO_SCSI_TARGET(ncport, 0, SATA_ADDR_DCPORT);
+
+ ndi_devi_enter(dip, &circ);
+ for (cdip = ddi_get_child(dip); cdip != NULL; ) {
+ dev_info_t *next = ddi_get_next_sibling(cdip);
+
+ tgt = ddi_prop_get_int(DDI_DEV_T_ANY, cdip,
+ DDI_PROP_DONTPASS, "target", -1);
+ if (tgt == -1) {
+ /*
+ * This is actually an error condition, but not
+ * a fatal one. Just continue the search.
+ */
+ cdip = next;
+ continue;
+ }
+
+ if (tgt == target)
+ break;
+
+ cdip = next;
+ }
+ ndi_devi_exit(dip, circ);
+
+ return (cdip);
+}
+
+
+/*
+ * sata_cfgadm_state:
+ * Use the sata port state and state of the target node to figure out
+ * the cfgadm_state.
+ *
+ * The port argument is a value with encoded cport,
+ * pmport and address qualifier, in the same manner as a scsi target number.
+ * SCSI_TO_SATA_CPORT macro extracts cport number,
+ * SCSI_TO_SATA_PMPORT extracts pmport number and
+ * SCSI_TO_SATA_ADDR_QUAL extracts port mulitplier qualifier flag.
+ *
+ * For now, support is for cports only - no pmultiplier ports.
+ */
+
+static void
+sata_cfgadm_state(sata_hba_inst_t *sata_hba_inst, int32_t port,
+ devctl_ap_state_t *ap_state)
+{
+ uint16_t cport;
+ int port_state;
+
+ /* Cport only */
+ cport = SCSI_TO_SATA_CPORT(port);
+
+ port_state = SATA_CPORT_STATE(sata_hba_inst, cport);
+ if (port_state & SATA_PSTATE_SHUTDOWN ||
+ port_state & SATA_PSTATE_FAILED) {
+ ap_state->ap_rstate = AP_RSTATE_DISCONNECTED;
+ ap_state->ap_ostate = AP_OSTATE_UNCONFIGURED;
+ if (port_state & SATA_PSTATE_FAILED)
+ ap_state->ap_condition = AP_COND_FAILED;
+ else
+ ap_state->ap_condition = AP_COND_UNKNOWN;
+
+ return;
+ }
+
+ /* Need to check pmult device port here as well, when supported */
+
+ /* Port is enabled and ready */
+
+ switch (SATA_CPORT_DEV_TYPE(sata_hba_inst, cport)) {
+ case SATA_DTYPE_NONE:
+ {
+ /* No device attached */
+ ap_state->ap_rstate = AP_RSTATE_EMPTY;
+ ap_state->ap_ostate = AP_OSTATE_UNCONFIGURED;
+ ap_state->ap_condition = AP_COND_OK;
+ break;
+ }
+ case SATA_DTYPE_UNKNOWN:
+ case SATA_DTYPE_ATAPINONCD:
+ case SATA_DTYPE_PMULT: /* Until PMult is supported */
+ {
+ /* Unknown device attached */
+ ap_state->ap_rstate = AP_RSTATE_CONNECTED;
+ ap_state->ap_ostate = AP_OSTATE_UNCONFIGURED;
+ ap_state->ap_condition = AP_COND_UNKNOWN;
+ break;
+ }
+ case SATA_DTYPE_ATADISK:
+ case SATA_DTYPE_ATAPICD:
+ {
+ dev_info_t *tdip = NULL;
+ dev_info_t *dip = NULL;
+ int circ;
+
+ dip = SATA_DIP(sata_hba_inst);
+ tdip = sata_get_target_dip(dip, port);
+ ap_state->ap_rstate = AP_RSTATE_CONNECTED;
+ if (tdip != NULL) {
+ ndi_devi_enter(dip, &circ);
+ mutex_enter(&(DEVI(tdip)->devi_lock));
+ if ((DEVI_IS_DEVICE_OFFLINE(tdip)) ||
+ (DEVI_IS_DEVICE_DOWN(tdip))) {
+ ap_state->ap_ostate = AP_OSTATE_UNCONFIGURED;
+ } else {
+ ap_state->ap_ostate = AP_OSTATE_CONFIGURED;
+ }
+ ap_state->ap_condition = AP_COND_OK;
+ mutex_exit(&(DEVI(tdip)->devi_lock));
+ ndi_devi_exit(dip, circ);
+ } else {
+ ap_state->ap_ostate = AP_OSTATE_UNCONFIGURED;
+ ap_state->ap_condition = AP_COND_UNKNOWN;
+ }
+ break;
+ }
+ default:
+ ap_state->ap_rstate = AP_RSTATE_CONNECTED;
+ ap_state->ap_ostate = AP_OSTATE_UNCONFIGURED;
+ ap_state->ap_condition = AP_COND_UNKNOWN;
+ /*
+ * This is actually internal error condition (non fatal),
+ * beacuse we already checked all defined device types.
+ */
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_cfgadm_state: Internal error: "
+ "unknown device type"));
+ break;
+ }
+}
+
+/*
+ * Start or terminate the thread, depending on flag arg and current state
+ */
+static void
+sata_event_thread_control(int startstop)
+{
+ static int sata_event_thread_terminating = 0;
+ static int sata_event_thread_starting = 0;
+ int i;
+
+ mutex_enter(&sata_event_mutex);
+
+ if (startstop == 0 && (sata_event_thread_starting == 1 ||
+ sata_event_thread_terminating == 1)) {
+ mutex_exit(&sata_event_mutex);
+ return;
+ }
+ if (startstop == 1 && sata_event_thread_starting == 1) {
+ mutex_exit(&sata_event_mutex);
+ return;
+ }
+ if (startstop == 1 && sata_event_thread_terminating == 1) {
+ sata_event_thread_starting = 1;
+ /* wait til terminate operation completes */
+ i = SATA_EVNT_DAEMON_TERM_WAIT/SATA_EVNT_DAEMON_TERM_TIMEOUT;
+ while (sata_event_thread_terminating == 1) {
+ if (i-- <= 0) {
+ sata_event_thread_starting = 0;
+ mutex_exit(&sata_event_mutex);
+#ifdef SATA_DEBUG
+ cmn_err(CE_WARN, "sata_event_thread_control: "
+ "timeout waiting for thread to terminate");
+#endif
+ return;
+ }
+ mutex_exit(&sata_event_mutex);
+ delay(drv_usectohz(SATA_EVNT_DAEMON_TERM_TIMEOUT));
+ mutex_enter(&sata_event_mutex);
+ }
+ }
+ if (startstop == 1) {
+ if (sata_event_thread == NULL) {
+ sata_event_thread = thread_create(NULL, 0,
+ (void (*)())sata_event_daemon,
+ &sata_hba_list, 0, &p0, TS_RUN, minclsyspri);
+ }
+ sata_event_thread_starting = 0;
+ mutex_exit(&sata_event_mutex);
+ return;
+ }
+
+ /*
+ * If we got here, thread may need to be terminated
+ */
+ if (sata_event_thread != NULL) {
+ int i;
+ /* Signal event thread to go away */
+ sata_event_thread_terminating = 1;
+ sata_event_thread_terminate = 1;
+ cv_signal(&sata_event_cv);
+ /*
+ * Wait til daemon terminates.
+ */
+ i = SATA_EVNT_DAEMON_TERM_WAIT/SATA_EVNT_DAEMON_TERM_TIMEOUT;
+ while (sata_event_thread_terminate == 1) {
+ mutex_exit(&sata_event_mutex);
+ if (i-- <= 0) {
+ /* Daemon did not go away !!! */
+#ifdef SATA_DEBUG
+ cmn_err(CE_WARN, "sata_event_thread_control: "
+ "cannot terminate event daemon thread");
+#endif
+ mutex_enter(&sata_event_mutex);
+ break;
+ }
+ delay(drv_usectohz(SATA_EVNT_DAEMON_TERM_TIMEOUT));
+ mutex_enter(&sata_event_mutex);
+ }
+ sata_event_thread_terminating = 0;
+ }
+ ASSERT(sata_event_thread_terminating == 0);
+ ASSERT(sata_event_thread_starting == 0);
+ mutex_exit(&sata_event_mutex);
+}
+
+
+/*
+ * Log sata message
+ * dev pathname msg line preceeds the logged message.
+ */
+
+static void
+sata_log(sata_hba_inst_t *sata_hba_inst, uint_t level, char *fmt, ...)
+{
+ char pathname[128];
+ dev_info_t *dip;
+ va_list ap;
+
+ mutex_enter(&sata_log_mutex);
+
+ va_start(ap, fmt);
+ (void) vsprintf(sata_log_buf, fmt, ap);
+ va_end(ap);
+
+ if (sata_hba_inst != NULL) {
+ dip = SATA_DIP(sata_hba_inst);
+ (void) ddi_pathname(dip, pathname);
+ } else {
+ pathname[0] = 0;
+ }
+ if (level == CE_CONT) {
+ if (sata_debug_flags == 0)
+ cmn_err(level, "?%s:\n %s\n", pathname, sata_log_buf);
+ else
+ cmn_err(level, "%s:\n %s\n", pathname, sata_log_buf);
+ } else
+ cmn_err(level, "%s:\n %s", pathname, sata_log_buf);
+
+ mutex_exit(&sata_log_mutex);
+}
+
+
+/* ******** Asynchronous HBA events handling & hotplugging support ******** */
+
+/*
+ * SATA HBA event notification function.
+ * Events reported by SATA HBA drivers per HBA instance relate to a change in
+ * a port and/or device state or a controller itself.
+ * Events for different addresses/addr types cannot be combined.
+ * A warning message is generated for each event type.
+ * Events are not processed by this function, so only the
+ * event flag(s)is set for an affected entity and the event thread is
+ * waken up. Event daemon thread processes all events.
+ *
+ * NOTE: Since more than one event may be reported at the same time, one
+ * cannot determine a sequence of events when opposite event are reported, eg.
+ * LINK_LOST and LINK_ESTABLISHED. Actual port status during event processing
+ * is taking precedence over reported events, i.e. may cause ignoring some
+ * events.
+ */
+#define SATA_EVENT_MAX_MSG_LENGTH 79
+
+void
+sata_hba_event_notify(dev_info_t *dip, sata_device_t *sata_device, int event)
+{
+ sata_hba_inst_t *sata_hba_inst = NULL;
+ sata_address_t *saddr;
+ sata_drive_info_t *sdinfo;
+ sata_port_stats_t *pstats;
+ int cport, pmport;
+ char buf1[SATA_EVENT_MAX_MSG_LENGTH + 1];
+ char buf2[SATA_EVENT_MAX_MSG_LENGTH + 1];
+ char *lcp;
+ static char *err_msg_evnt_1 =
+ "sata_hba_event_notify: invalid port event 0x%x ";
+ static char *err_msg_evnt_2 =
+ "sata_hba_event_notify: invalid device event 0x%x ";
+ int linkevent;
+
+ /*
+ * There is a possibility that an event will be generated on HBA
+ * that has not completed attachment or is detaching.
+ * HBA driver should prevent this, but just in case it does not,
+ * we need to ignore events for such HBA.
+ */
+ mutex_enter(&sata_mutex);
+ for (sata_hba_inst = sata_hba_list; sata_hba_inst != NULL;
+ sata_hba_inst = sata_hba_inst->satahba_next) {
+ if (SATA_DIP(sata_hba_inst) == dip)
+ if (sata_hba_inst->satahba_attached == 1)
+ break;
+ }
+ mutex_exit(&sata_mutex);
+ if (sata_hba_inst == NULL)
+ /* HBA not attached */
+ return;
+
+ ASSERT(sata_device != NULL);
+
+ /*
+ * Validate address before - do not proceed with invalid address.
+ */
+ saddr = &sata_device->satadev_addr;
+ if (saddr->cport >= SATA_NUM_CPORTS(sata_hba_inst))
+ return;
+ if (saddr->qual == SATA_ADDR_PMPORT ||
+ saddr->qual == SATA_ADDR_DPMPORT)
+ /* Port Multiplier not supported yet */
+ return;
+
+ cport = saddr->cport;
+ pmport = saddr->pmport;
+
+ buf1[0] = buf2[0] = '\0';
+
+ /*
+ * Events refer to devices, ports and controllers - each has
+ * unique address. Events for different addresses cannot be combined.
+ */
+ if (saddr->qual & (SATA_ADDR_CPORT | SATA_ADDR_PMPORT)) {
+
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
+
+ /* qualify this event(s) */
+ if ((event & SATA_EVNT_PORT_EVENTS) == 0) {
+ /* Invalid event for the device port */
+ (void) sprintf(buf2, err_msg_evnt_1,
+ event & SATA_EVNT_PORT_EVENTS);
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
+ goto event_info;
+ }
+ if (saddr->qual == SATA_ADDR_CPORT) {
+ /* Controller's device port event */
+
+ (SATA_CPORT_INFO(sata_hba_inst, cport))->
+ cport_event_flags |=
+ event & SATA_EVNT_PORT_EVENTS;
+ pstats =
+ &(SATA_CPORT_INFO(sata_hba_inst, cport))->
+ cport_stats;
+ } else {
+ /* Port multiplier's device port event */
+ (SATA_PMPORT_INFO(sata_hba_inst, cport, pmport))->
+ pmport_event_flags |=
+ event & SATA_EVNT_PORT_EVENTS;
+ pstats =
+ &(SATA_PMPORT_INFO(sata_hba_inst, cport, pmport))->
+ pmport_stats;
+ }
+
+ /*
+ * Add to statistics and log the message. We have to do it
+ * here rather than in the event daemon, because there may be
+ * multiple events occuring before they are processed.
+ */
+ linkevent = event &
+ (SATA_EVNT_LINK_LOST | SATA_EVNT_LINK_ESTABLISHED);
+ if (linkevent) {
+ if (linkevent == (SATA_EVNT_LINK_LOST |
+ SATA_EVNT_LINK_ESTABLISHED)) {
+ /* This is likely event combination */
+ (void) strlcat(buf1, "link lost/established, ",
+ SATA_EVENT_MAX_MSG_LENGTH);
+
+ if (pstats->link_lost < 0xffffffffffffffff)
+ pstats->link_lost++;
+ if (pstats->link_established <
+ 0xffffffffffffffff)
+ pstats->link_established++;
+ linkevent = 0;
+ } else if (linkevent & SATA_EVNT_LINK_LOST) {
+ (void) strlcat(buf1, "link lost, ",
+ SATA_EVENT_MAX_MSG_LENGTH);
+
+ if (pstats->link_lost < 0xffffffffffffffff)
+ pstats->link_lost++;
+ } else {
+ (void) strlcat(buf1, "link established, ",
+ SATA_EVENT_MAX_MSG_LENGTH);
+ if (pstats->link_established <
+ 0xffffffffffffffff)
+ pstats->link_established++;
+ }
+ }
+ if (event & SATA_EVNT_DEVICE_ATTACHED) {
+ (void) strlcat(buf1, "device attached, ",
+ SATA_EVENT_MAX_MSG_LENGTH);
+ if (pstats->device_attached < 0xffffffffffffffff)
+ pstats->device_attached++;
+ }
+ if (event & SATA_EVNT_DEVICE_DETACHED) {
+ (void) strlcat(buf1, "device detached, ",
+ SATA_EVENT_MAX_MSG_LENGTH);
+ if (pstats->device_detached < 0xffffffffffffffff)
+ pstats->device_detached++;
+ }
+ if (event & SATA_EVNT_PWR_LEVEL_CHANGED) {
+ SATADBG1(SATA_DBG_EVENTS, sata_hba_inst,
+ "port %d power level changed", cport);
+ if (pstats->port_pwr_changed < 0xffffffffffffffff)
+ pstats->port_pwr_changed++;
+ }
+
+ if ((event & ~SATA_EVNT_PORT_EVENTS) != 0) {
+ /* There should be no other events for this address */
+ (void) sprintf(buf2, err_msg_evnt_1,
+ event & ~SATA_EVNT_PORT_EVENTS);
+ }
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
+
+ } else if (saddr->qual & (SATA_ADDR_DCPORT | SATA_ADDR_DPMPORT)) {
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
+
+ /* qualify this event */
+ if ((event & SATA_EVNT_DEVICE_RESET) == 0) {
+ /* Invalid event for a device */
+ (void) sprintf(buf2, err_msg_evnt_2,
+ event & SATA_EVNT_DEVICE_RESET);
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
+ goto event_info;
+ }
+ /* drive event */
+ sdinfo = sata_get_device_info(sata_hba_inst, sata_device);
+ if (sdinfo != NULL) {
+ if (event & SATA_EVNT_DEVICE_RESET) {
+ (void) strlcat(buf1, "device reset, ",
+ SATA_EVENT_MAX_MSG_LENGTH);
+ if (sdinfo->satadrv_stats.drive_reset <
+ 0xffffffffffffffff)
+ sdinfo->satadrv_stats.drive_reset++;
+ sdinfo->satadrv_event_flags |=
+ SATA_EVNT_DEVICE_RESET;
+ }
+ }
+ if ((event & ~SATA_EVNT_DEVICE_RESET) != 0) {
+ /* Invalid event for a device */
+ (void) sprintf(buf2, err_msg_evnt_2,
+ event & ~SATA_EVNT_DRIVE_EVENTS);
+ }
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, cport)));
+ } else {
+ if (saddr->qual != SATA_ADDR_NULL) {
+ /* Wrong address qualifier */
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_event_notify: invalid address 0x%x",
+ *(uint32_t *)saddr));
+ return;
+ }
+ if ((event & SATA_EVNT_CONTROLLER_EVENTS) == 0 ||
+ (event & ~SATA_EVNT_CONTROLLER_EVENTS) != 0) {
+ /* Invalid event for the controller */
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_hba_event_notify: invalid event 0x%x for "
+ "controller",
+ event & SATA_EVNT_CONTROLLER_EVENTS));
+ return;
+ }
+ buf1[0] = '\0';
+ /* This may be a frequent and not interesting event */
+ SATADBG1(SATA_DBG_EVENTS, sata_hba_inst,
+ "controller power level changed\n", NULL);
+
+ mutex_enter(&sata_hba_inst->satahba_mutex);
+ if (sata_hba_inst->satahba_stats.ctrl_pwr_change <
+ 0xffffffffffffffff)
+ sata_hba_inst->satahba_stats.ctrl_pwr_change++;
+
+ sata_hba_inst->satahba_event_flags |=
+ SATA_EVNT_PWR_LEVEL_CHANGED;
+ mutex_exit(&sata_hba_inst->satahba_mutex);
+ }
+ /*
+ * If we got here, there is something to do with this HBA
+ * instance.
+ */
+ mutex_enter(&sata_hba_inst->satahba_mutex);
+ sata_hba_inst->satahba_event_flags |= SATA_EVNT_MAIN;
+ mutex_exit(&sata_hba_inst->satahba_mutex);
+ mutex_enter(&sata_mutex);
+ sata_event_pending |= SATA_EVNT_MAIN; /* global event indicator */
+ mutex_exit(&sata_mutex);
+
+ /* Tickle event thread */
+ mutex_enter(&sata_event_mutex);
+ if (sata_event_thread_active == 0)
+ cv_signal(&sata_event_cv);
+ mutex_exit(&sata_event_mutex);
+
+event_info:
+ if (buf1[0] != '\0') {
+ lcp = strrchr(buf1, ',');
+ if (lcp != NULL)
+ *lcp = '\0';
+ }
+ if (saddr->qual == SATA_ADDR_CPORT ||
+ saddr->qual == SATA_ADDR_DCPORT) {
+ if (buf1[0] != '\0') {
+ sata_log(sata_hba_inst, CE_NOTE, "port %d: %s\n",
+ cport, buf1);
+ }
+ if (buf2[0] != '\0') {
+ sata_log(sata_hba_inst, CE_NOTE, "port %d: %s\n",
+ cport, buf2);
+ }
+ } else if (saddr->qual == SATA_ADDR_PMPORT ||
+ saddr->qual == SATA_ADDR_DPMPORT) {
+ if (buf1[0] != '\0') {
+ sata_log(sata_hba_inst, CE_NOTE,
+ "port %d pmport %d: %s\n", cport, pmport, buf1);
+ }
+ if (buf2[0] != '\0') {
+ sata_log(sata_hba_inst, CE_NOTE,
+ "port %d pmport %d: %s\n", cport, pmport, buf2);
+ }
+ }
+}
+
+
+/*
+ * Event processing thread.
+ * Arg is a pointer to the sata_hba_list pointer.
+ * It is not really needed, because sata_hba_list is global and static
+ */
+static void
+sata_event_daemon(void *arg)
+{
+#ifndef __lock_lint
+ _NOTE(ARGUNUSED(arg))
+#endif
+ sata_hba_inst_t *sata_hba_inst;
+ clock_t lbolt;
+
+ SATADBG1(SATA_DBG_EVENTS_DAEMON, NULL,
+ "SATA event daemon started\n", NULL);
+loop:
+ /*
+ * Process events here. Walk through all registered HBAs
+ */
+ mutex_enter(&sata_mutex);
+ for (sata_hba_inst = sata_hba_list; sata_hba_inst != NULL;
+ sata_hba_inst = sata_hba_inst->satahba_next) {
+ ASSERT(sata_hba_inst != NULL);
+ mutex_enter(&sata_hba_inst->satahba_mutex);
+ if (sata_hba_inst->satahba_attached != 1 ||
+ (sata_hba_inst->satahba_event_flags &
+ SATA_EVNT_SKIP) != 0) {
+ mutex_exit(&sata_hba_inst->satahba_mutex);
+ continue;
+ }
+ if (sata_hba_inst->satahba_event_flags & SATA_EVNT_MAIN) {
+ sata_hba_inst->satahba_event_flags |= SATA_EVNT_SKIP;
+ mutex_exit(&sata_hba_inst->satahba_mutex);
+ mutex_exit(&sata_mutex);
+ /* Got the controller with pending event */
+ sata_process_controller_events(sata_hba_inst);
+ /*
+ * Since global mutex was released, there is a
+ * possibility that HBA list has changed, so start
+ * over from the top. Just processed controller
+ * will be passed-over because of the SKIP flag.
+ */
+ goto loop;
+ }
+ mutex_exit(&sata_hba_inst->satahba_mutex);
+ }
+ /* Clear SKIP flag in all controllers */
+ for (sata_hba_inst = sata_hba_list; sata_hba_inst != NULL;
+ sata_hba_inst = sata_hba_inst->satahba_next) {
+ mutex_enter(&sata_hba_inst->satahba_mutex);
+ sata_hba_inst->satahba_event_flags &= ~SATA_EVNT_SKIP;
+ mutex_exit(&sata_hba_inst->satahba_mutex);
+ }
+ mutex_exit(&sata_mutex);
+
+ SATADBG1(SATA_DBG_EVENTS_DAEMON, NULL,
+ "SATA EVENT DAEMON suspending itself", NULL);
+
+#ifdef SATA_DEBUG
+ if ((sata_func_enable & SATA_ENABLE_PROCESS_EVENTS) == 0) {
+ sata_log(sata_hba_inst, CE_WARN,
+ "SATA EVENTS PROCESSING DISABLED\n");
+ thread_exit(); /* Daemon will not run again */
+ }
+#endif
+ mutex_enter(&sata_event_mutex);
+ sata_event_thread_active = 0;
+ mutex_exit(&sata_event_mutex);
+ /*
+ * Go to sleep/suspend itself and wake up either because new event or
+ * wait timeout. Exit if there is a termination request (driver
+ * unload).
+ */
+ do {
+ lbolt = ddi_get_lbolt();
+ lbolt += drv_usectohz(SATA_EVNT_DAEMON_SLEEP_TIME);
+ mutex_enter(&sata_event_mutex);
+ (void) cv_timedwait(&sata_event_cv, &sata_event_mutex, lbolt);
+
+ if (sata_event_thread_active != 0) {
+ mutex_exit(&sata_event_mutex);
+ continue;
+ }
+
+ /* Check if it is time to go away */
+ if (sata_event_thread_terminate == 1) {
+ /*
+ * It is up to the thread setting above flag to make
+ * sure that this thread is not killed prematurely.
+ */
+ sata_event_thread_terminate = 0;
+ sata_event_thread = NULL;
+ mutex_exit(&sata_event_mutex);
+ SATADBG1(SATA_DBG_EVENTS_DAEMON, NULL,
+ "SATA_EVENT_DAEMON_TERMINATING", NULL);
+ thread_exit(); { _NOTE(NOT_REACHED) }
+ }
+ mutex_exit(&sata_event_mutex);
+ } while (!(sata_event_pending & SATA_EVNT_MAIN));
+
+ mutex_enter(&sata_event_mutex);
+ sata_event_thread_active = 1;
+ mutex_exit(&sata_event_mutex);
+
+ mutex_enter(&sata_mutex);
+ sata_event_pending &= ~SATA_EVNT_MAIN;
+ mutex_exit(&sata_mutex);
+
+ SATADBG1(SATA_DBG_EVENTS_DAEMON, NULL,
+ "SATA EVENT DAEMON READY TO PROCESS EVENT", NULL);
+
+ goto loop;
+}
+
+/*
+ * Specific HBA instance event processing.
+ *
+ * NOTE: At the moment, device event processing is limited to hard disks
+ * only.
+ * cports only are supported - no pmports.
+ */
+static void
+sata_process_controller_events(sata_hba_inst_t *sata_hba_inst)
+{
+ int ncport;
+ uint32_t event_flags;
+ sata_address_t *saddr;
+
+ SATADBG1(SATA_DBG_EVENTS_CNTRL, sata_hba_inst,
+ "Processing controller %d event(s)",
+ ddi_get_instance(SATA_DIP(sata_hba_inst)));
+
+ mutex_enter(&sata_hba_inst->satahba_mutex);
+ sata_hba_inst->satahba_event_flags &= ~SATA_EVNT_MAIN;
+ event_flags = sata_hba_inst->satahba_event_flags;
+ mutex_exit(&sata_hba_inst->satahba_mutex);
+ /*
+ * Process controller power change first
+ * HERE
+ */
+ if (event_flags & SATA_EVNT_PWR_LEVEL_CHANGED)
+ sata_process_cntrl_pwr_level_change(sata_hba_inst);
+
+ /*
+ * Search through ports/devices to identify affected port/device.
+ * We may have to process events for more than one port/device.
+ */
+ for (ncport = 0; ncport < SATA_NUM_CPORTS(sata_hba_inst); ncport++) {
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst, ncport)));
+ event_flags = (SATA_CPORT_INFO(sata_hba_inst, ncport))->
+ cport_event_flags;
+ /* Check if port was locked by IOCTL processing */
+ if (event_flags & SATA_APCTL_LOCK_PORT_BUSY) {
+ /*
+ * We ignore port events because port is busy
+ * with AP control processing. Set again
+ * controller and main event flag, so that
+ * events may be processed by the next daemon
+ * run.
+ */
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, ncport)));
+ mutex_enter(&sata_hba_inst->satahba_mutex);
+ sata_hba_inst->satahba_event_flags |= SATA_EVNT_MAIN;
+ mutex_exit(&sata_hba_inst->satahba_mutex);
+ mutex_enter(&sata_mutex);
+ sata_event_pending |= SATA_EVNT_MAIN;
+ mutex_exit(&sata_mutex);
+ SATADBG1(SATA_DBG_EVENTS_PROCPST, sata_hba_inst,
+ "Event processing postponed until "
+ "AP control processing completes",
+ NULL);
+ /* Check other ports */
+ continue;
+ } else {
+ /*
+ * Set BSY flag so that AP control would not
+ * interfere with events processing for
+ * this port.
+ */
+ (SATA_CPORT_INFO(sata_hba_inst, ncport))->
+ cport_event_flags |= SATA_EVNT_LOCK_PORT_BUSY;
+ }
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, ncport)));
+
+ saddr = &(SATA_CPORT_INFO(sata_hba_inst, ncport))->cport_addr;
+
+ if ((event_flags &
+ (SATA_EVNT_PORT_EVENTS | SATA_EVNT_DRIVE_EVENTS)) != 0) {
+ /*
+ * Got port event.
+ * We need some hierarchy of event processing as they
+ * are affecting each other:
+ * 1. port failed
+ * 2. device detached/attached
+ * 3. link events - link events may trigger device
+ * detached or device attached events in some
+ * circumstances.
+ * 4. port power level changed
+ */
+ if (event_flags & SATA_EVNT_PORT_FAILED) {
+ sata_process_port_failed_event(sata_hba_inst,
+ saddr);
+ }
+ if (event_flags & SATA_EVNT_DEVICE_DETACHED) {
+ sata_process_device_detached(sata_hba_inst,
+ saddr);
+ }
+ if (event_flags & SATA_EVNT_DEVICE_ATTACHED) {
+ sata_process_device_attached(sata_hba_inst,
+ saddr);
+ }
+ if (event_flags &
+ (SATA_EVNT_LINK_ESTABLISHED |
+ SATA_EVNT_LINK_LOST)) {
+ sata_process_port_link_events(sata_hba_inst,
+ saddr);
+ }
+ if (event_flags & SATA_EVNT_PWR_LEVEL_CHANGED) {
+ sata_process_port_pwr_change(sata_hba_inst,
+ saddr);
+ }
+ }
+ if (SATA_CPORT_DEV_TYPE(sata_hba_inst, ncport) !=
+ SATA_DTYPE_NONE) {
+ /* May have device event */
+ sata_process_device_reset(sata_hba_inst, saddr);
+ }
+ mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst, ncport)));
+ /* Release PORT_BUSY flag */
+ (SATA_CPORT_INFO(sata_hba_inst, ncport))->
+ cport_event_flags &= ~SATA_EVNT_LOCK_PORT_BUSY;
+ mutex_exit(&(SATA_CPORT_MUTEX(sata_hba_inst, ncport)));
+
+ } /* End of loop through the controller SATA ports */
+}
+
+/*
+ * Process HBA power level change reported by HBA driver.
+ * Not implemented at this time - event is ignored.
+ */
+static void
+sata_process_cntrl_pwr_level_change(sata_hba_inst_t *sata_hba_inst)
+{
+ SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
+ "Processing controller power level change", NULL);
+
+ /* Ignoring it for now */
+ mutex_enter(&sata_hba_inst->satahba_mutex);
+ sata_hba_inst->satahba_event_flags &= ~SATA_EVNT_PWR_LEVEL_CHANGED;
+ mutex_exit(&sata_hba_inst->satahba_mutex);
+}
+
+/*
+ * Process port power level change reported by HBA driver.
+ * Not implemented at this time - event is ignored.
+ */
+static void
+sata_process_port_pwr_change(sata_hba_inst_t *sata_hba_inst,
+ sata_address_t *saddr)
+{
+ sata_cport_info_t *cportinfo;
+
+ SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
+ "Processing port power level change", NULL);
+
+ cportinfo = SATA_CPORT_INFO(sata_hba_inst, saddr->cport);
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ /* Reset event flag */
+ cportinfo->cport_event_flags &= ~SATA_EVNT_PWR_LEVEL_CHANGED;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+}
+
+/*
+ * Process port failure reported by HBA driver.
+ * cports support only - no pmports.
+ */
+static void
+sata_process_port_failed_event(sata_hba_inst_t *sata_hba_inst,
+ sata_address_t *saddr)
+{
+ sata_cport_info_t *cportinfo;
+
+ cportinfo = SATA_CPORT_INFO(sata_hba_inst, saddr->cport);
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ /* Reset event flag first */
+ cportinfo->cport_event_flags &= ~SATA_EVNT_PORT_FAILED;
+ /* If the port is in SHUTDOWN or FAILED state, ignore this event. */
+ if ((cportinfo->cport_state &
+ (SATA_PSTATE_SHUTDOWN | SATA_PSTATE_FAILED)) == 0) {
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ return;
+ }
+ /* Fail the port */
+ cportinfo->cport_state = SATA_PSTATE_FAILED;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ sata_log(sata_hba_inst, CE_WARN, "port %d failed", saddr->cport);
+}
+
+/*
+ * Device Reset Event processing.
+ * The seqeunce is managed by 3 stage flags:
+ * - reset event reported,
+ * - reset event being processed,
+ * - request to clear device reset state.
+ */
+static void
+sata_process_device_reset(sata_hba_inst_t *sata_hba_inst,
+ sata_address_t *saddr)
+{
+ sata_drive_info_t old_sdinfo; /* local copy of the drive info */
+ sata_drive_info_t *sdinfo;
+ sata_cport_info_t *cportinfo;
+ sata_device_t sata_device;
+ int rval;
+
+ /* We only care about host sata cport for now */
+ cportinfo = SATA_CPORT_INFO(sata_hba_inst, saddr->cport);
+
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+
+ /* If the port is in SHUTDOWN or FAILED state, ignore reset event. */
+ if ((cportinfo->cport_state &
+ (SATA_PSTATE_SHUTDOWN | SATA_PSTATE_FAILED)) != 0) {
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ return;
+ }
+
+ if ((SATA_CPORT_DEV_TYPE(sata_hba_inst, saddr->cport) &
+ SATA_VALID_DEV_TYPE) == 0) {
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ return;
+ }
+ sdinfo = SATA_CPORT_DRV_INFO(sata_hba_inst, saddr->cport);
+ if (sdinfo == NULL) {
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ return;
+ }
+
+ if ((sdinfo->satadrv_event_flags & SATA_EVNT_DEVICE_RESET) == 0) {
+ /* Nothing to do */
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ return;
+ }
+
+ SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
+ "Processing port %d device reset", saddr->cport);
+
+ if (sdinfo->satadrv_event_flags & SATA_EVNT_INPROC_DEVICE_RESET) {
+ /* Something is weird - new device reset event */
+ SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
+ "Overlapping device reset events!", NULL);
+ /* Just leave */
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ return;
+ }
+
+ /* Clear event flag */
+ sdinfo->satadrv_event_flags &= ~SATA_EVNT_DEVICE_RESET;
+
+ /* It seems that we always need to check the port state first */
+ sata_device.satadev_rev = SATA_DEVICE_REV;
+ sata_device.satadev_addr = *saddr;
+ /*
+ * We have to exit mutex, because the HBA probe port function may
+ * block on its own mutex.
+ */
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
+ (SATA_DIP(sata_hba_inst), &sata_device);
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ sata_update_port_info(sata_hba_inst, &sata_device);
+ if (rval != SATA_SUCCESS) {
+ /* Something went wrong? Fail the port */
+ cportinfo->cport_state = SATA_PSTATE_FAILED;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ SATA_LOG_D((sata_hba_inst, CE_WARN, "Port %d probing failed",
+ saddr->cport));
+ return;
+ }
+ if ((sata_device.satadev_scr.sstatus &
+ SATA_PORT_DEVLINK_UP_MASK) !=
+ SATA_PORT_DEVLINK_UP ||
+ sata_device.satadev_type == SATA_DTYPE_NONE) {
+ /*
+ * No device to process, anymore. Some other event processing
+ * would or have already performed port info cleanup.
+ * To be safe (HBA may need it), request clearing device
+ * reset condition.
+ */
+ sdinfo->satadrv_event_flags = 0;
+ sdinfo->satadrv_event_flags |= SATA_EVNT_CLEAR_DEVICE_RESET;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ return;
+ }
+
+ /* Mark device reset processing as active */
+ sdinfo->satadrv_event_flags |= SATA_EVNT_INPROC_DEVICE_RESET;
+
+ old_sdinfo = *sdinfo; /* local copy of the drive info */
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+
+ if (sata_restore_drive_settings(sata_hba_inst, &old_sdinfo) ==
+ SATA_FAILURE) {
+ /*
+ * Restoring drive setting failed.
+ * Probe the port first, to check if the port state has changed
+ */
+ sata_device.satadev_rev = SATA_DEVICE_REV;
+ sata_device.satadev_addr = *saddr;
+ sata_device.satadev_addr.qual = SATA_ADDR_CPORT;
+ /* probe port */
+ rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
+ (SATA_DIP(sata_hba_inst), &sata_device);
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ if (rval == SATA_SUCCESS &&
+ (sata_device.satadev_state &
+ (SATA_PSTATE_SHUTDOWN | SATA_PSTATE_FAILED)) == 0 &&
+ (sata_device.satadev_scr.sstatus &
+ SATA_PORT_DEVLINK_UP_MASK) == SATA_PORT_DEVLINK_UP &&
+ (sata_device.satadev_type & SATA_DTYPE_ATADISK) != 0) {
+ /*
+ * We may retry this a bit later - reinstate reset
+ * condition
+ */
+ if ((cportinfo->cport_dev_type &
+ SATA_VALID_DEV_TYPE) != 0 &&
+ SATA_CPORTINFO_DRV_INFO(cportinfo) != NULL) {
+ sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
+ sdinfo->satadrv_event_flags |=
+ SATA_EVNT_DEVICE_RESET;
+ sdinfo->satadrv_event_flags &=
+ ~SATA_EVNT_INPROC_DEVICE_RESET;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst,
+ saddr->cport)->cport_mutex);
+ mutex_enter(&sata_hba_inst->satahba_mutex);
+ sata_hba_inst->satahba_event_flags |=
+ SATA_EVNT_MAIN;
+ mutex_exit(&sata_hba_inst->satahba_mutex);
+ return;
+ }
+ } else {
+ /*
+ * No point of retrying - some other event processing
+ * would or already did port info cleanup.
+ * To be safe (HBA may need it),
+ * request clearing device reset condition.
+ */
+ sdinfo->satadrv_event_flags = 0;
+ sdinfo->satadrv_event_flags |=
+ SATA_EVNT_CLEAR_DEVICE_RESET;
+ }
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ return;
+ }
+
+ /*
+ * Raise the flag indicating that the next sata command could
+ * be sent with SATA_CLEAR_DEV_RESET_STATE flag, if no new device
+ * reset is reported.
+ */
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ if ((cportinfo->cport_dev_type & SATA_VALID_DEV_TYPE) != 0 &&
+ SATA_CPORTINFO_DRV_INFO(cportinfo) != NULL) {
+ sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
+ sdinfo->satadrv_event_flags &= ~SATA_EVNT_INPROC_DEVICE_RESET;
+ sdinfo->satadrv_event_flags |= SATA_EVNT_CLEAR_DEVICE_RESET;
+ }
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+}
+
+
+/*
+ * Port Link Events processing.
+ * Every link established event may involve device reset (due to
+ * COMRESET signal, equivalent of the hard reset) so arbitrarily
+ * set device reset event for an attached device (if any).
+ * If the port is in SHUTDOWN or FAILED state, ignore link events.
+ *
+ * The link established event processing varies, depending on the state
+ * of the target node, HBA hotplugging capabilities, state of the port.
+ * If the link is not active, the link established event is ignored.
+ * If HBA cannot detect device attachment and there is no target node,
+ * the link established event triggers device attach event processing.
+ * Else, link established event triggers device reset event processing.
+ *
+ * The link lost event processing varies, depending on a HBA hotplugging
+ * capability and the state of the port (link active or not active).
+ * If the link is active, the lost link event is ignored.
+ * If HBA cannot detect device removal, the lost link event triggers
+ * device detached event processing after link lost timeout.
+ * Else, the event is ignored.
+ *
+ * NOTE: Only cports are processed for now, i.e. no port multiplier ports
+ */
+static void
+sata_process_port_link_events(sata_hba_inst_t *sata_hba_inst,
+ sata_address_t *saddr)
+{
+ sata_device_t sata_device;
+ sata_cport_info_t *cportinfo;
+ sata_drive_info_t *sdinfo;
+ int event_flags;
+ int rval;
+
+ SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
+ "Processing port %d link event(s)", saddr->cport);
+
+ cportinfo = SATA_CPORT_INFO(sata_hba_inst, saddr->cport);
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ event_flags = cportinfo->cport_event_flags;
+
+ /* Reset event flags first */
+ cportinfo->cport_event_flags &=
+ ~(SATA_EVNT_LINK_ESTABLISHED | SATA_EVNT_LINK_LOST);
+
+ /* If the port is in SHUTDOWN or FAILED state, ignore link events. */
+ if ((cportinfo->cport_state &
+ (SATA_PSTATE_SHUTDOWN | SATA_PSTATE_FAILED)) != 0) {
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ return;
+ }
+
+ /*
+ * For the sanity sake get current port state.
+ * Set device address only. Other sata_device fields should be
+ * set by HBA driver.
+ */
+ sata_device.satadev_rev = SATA_DEVICE_REV;
+ sata_device.satadev_addr = *saddr;
+ /*
+ * We have to exit mutex, because the HBA probe port function may
+ * block on its own mutex.
+ */
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
+ (SATA_DIP(sata_hba_inst), &sata_device);
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ sata_update_port_info(sata_hba_inst, &sata_device);
+ if (rval != SATA_SUCCESS) {
+ /* Something went wrong? Fail the port */
+ cportinfo->cport_state = SATA_PSTATE_FAILED;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ SATA_LOG_D((sata_hba_inst, CE_WARN, "Port %d probing failed",
+ saddr->cport));
+ /*
+ * We may want to release device info structure, but
+ * it is not necessary.
+ */
+ return;
+ } else {
+ /* port probed successfully */
+ cportinfo->cport_state |= SATA_STATE_PROBED | SATA_STATE_READY;
+ }
+ if (event_flags & SATA_EVNT_LINK_ESTABLISHED) {
+
+ if ((sata_device.satadev_scr.sstatus &
+ SATA_PORT_DEVLINK_UP_MASK) != SATA_PORT_DEVLINK_UP) {
+ /* Ignore event */
+ SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
+ "Ignoring port %d link established event - "
+ "link down",
+ saddr->cport);
+ goto linklost;
+ }
+
+ SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
+ "Processing port %d link established event",
+ saddr->cport);
+
+ /*
+ * For the sanity sake check if a device is attached - check
+ * return state of a port probing.
+ */
+ if (sata_device.satadev_type != SATA_DTYPE_NONE &&
+ sata_device.satadev_type != SATA_DTYPE_PMULT) {
+ /*
+ * HBA port probe indicated that there is a device
+ * attached. Check if the framework had device info
+ * structure attached for this device.
+ */
+ if (cportinfo->cport_dev_type != SATA_DTYPE_NONE) {
+ ASSERT(SATA_CPORTINFO_DRV_INFO(cportinfo) !=
+ NULL);
+
+ sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
+ if ((sdinfo->satadrv_type &
+ SATA_VALID_DEV_TYPE) != 0) {
+ /*
+ * Dev info structure is present.
+ * If dev_type is set to known type in
+ * the framework's drive info struct
+ * then the device existed before and
+ * the link was probably lost
+ * momentarily - in such case
+ * we may want to check device
+ * identity.
+ * Identity check is not supported now.
+ *
+ * Link established event
+ * triggers device reset event.
+ */
+ (SATA_CPORTINFO_DRV_INFO(cportinfo))->
+ satadrv_event_flags |=
+ SATA_EVNT_DEVICE_RESET;
+ }
+ } else if (cportinfo->cport_dev_type ==
+ SATA_DTYPE_NONE) {
+ /*
+ * We got new device attached! If HBA does not
+ * generate device attached events, trigger it
+ * here.
+ */
+ if (!(SATA_FEATURES(sata_hba_inst) &
+ SATA_CTLF_HOTPLUG)) {
+ cportinfo->cport_event_flags |=
+ SATA_EVNT_DEVICE_ATTACHED;
+ }
+ }
+ /* Reset link lost timeout */
+ cportinfo->cport_link_lost_time = 0;
+ }
+ }
+linklost:
+ if (event_flags & SATA_EVNT_LINK_LOST) {
+ if ((sata_device.satadev_scr.sstatus &
+ SATA_PORT_DEVLINK_UP_MASK) == SATA_PORT_DEVLINK_UP) {
+ /* Ignore event */
+ SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
+ "Ignoring port %d link lost event - link is up",
+ saddr->cport);
+ goto done;
+ }
+#ifdef SATA_DEBUG
+ if (cportinfo->cport_link_lost_time == 0) {
+ SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
+ "Processing port %d link lost event",
+ saddr->cport);
+ }
+#endif
+ /*
+ * When HBA cannot generate device attached/detached events,
+ * we need to track link lost time and eventually generate
+ * device detach event.
+ */
+ if (!(SATA_FEATURES(sata_hba_inst) & SATA_CTLF_HOTPLUG)) {
+ /* We are tracking link lost time */
+ if (cportinfo->cport_link_lost_time == 0) {
+ /* save current time (lbolt value) */
+ cportinfo->cport_link_lost_time =
+ ddi_get_lbolt();
+ /* just keep link lost event */
+ cportinfo->cport_event_flags |=
+ SATA_EVNT_LINK_LOST;
+ } else {
+ clock_t cur_time = ddi_get_lbolt();
+ if ((cur_time -
+ cportinfo->cport_link_lost_time) >=
+ drv_usectohz(
+ SATA_EVNT_LINK_LOST_TIMEOUT)) {
+ /* trigger device detach event */
+ cportinfo->cport_event_flags |=
+ SATA_EVNT_DEVICE_DETACHED;
+ cportinfo->cport_link_lost_time = 0;
+ SATADBG1(SATA_DBG_EVENTS,
+ sata_hba_inst,
+ "Triggering port %d "
+ "device detached event",
+ saddr->cport);
+ } else {
+ /* keep link lost event */
+ cportinfo->cport_event_flags |=
+ SATA_EVNT_LINK_LOST;
+ }
+ }
+ }
+ /*
+ * We could change port state to disable/delay access to
+ * the attached device until the link is recovered.
+ */
+ }
+done:
+ event_flags = cportinfo->cport_event_flags;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ if (event_flags != 0) {
+ mutex_enter(&sata_hba_inst->satahba_mutex);
+ sata_hba_inst->satahba_event_flags |= SATA_EVNT_MAIN;
+ mutex_exit(&sata_hba_inst->satahba_mutex);
+ mutex_enter(&sata_mutex);
+ sata_event_pending |= SATA_EVNT_MAIN;
+ mutex_exit(&sata_mutex);
+ }
+}
+
+/*
+ * Device Detached Event processing.
+ * Port is probed to find if a device is really gone. If so,
+ * the device info structure is detached from the SATA port info structure
+ * and released.
+ * Port status is updated.
+ *
+ * NOTE: Process cports event only, no port multiplier ports.
+ */
+static void
+sata_process_device_detached(sata_hba_inst_t *sata_hba_inst,
+ sata_address_t *saddr)
+{
+ sata_cport_info_t *cportinfo;
+ sata_drive_info_t *sdevinfo;
+ sata_device_t sata_device;
+ dev_info_t *tdip;
+ int rval;
+
+ SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
+ "Processing port %d device detached", saddr->cport);
+
+ cportinfo = SATA_CPORT_INFO(sata_hba_inst, saddr->cport);
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ /* Clear event flag */
+ cportinfo->cport_event_flags &= ~SATA_EVNT_DEVICE_DETACHED;
+
+ /* If the port is in SHUTDOWN or FAILED state, ignore detach event. */
+ if ((cportinfo->cport_state &
+ (SATA_PSTATE_SHUTDOWN | SATA_PSTATE_FAILED)) != 0) {
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ return;
+ }
+ /* For sanity, re-probe the port */
+ sata_device.satadev_rev = SATA_DEVICE_REV;
+ sata_device.satadev_addr = *saddr;
+
+ /*
+ * We have to exit mutex, because the HBA probe port function may
+ * block on its own mutex.
+ */
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ rval = (*SATA_PROBE_PORT_FUNC(sata_hba_inst))
+ (SATA_DIP(sata_hba_inst), &sata_device);
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ sata_update_port_info(sata_hba_inst, &sata_device);
+ if (rval != SATA_SUCCESS) {
+ /* Something went wrong? Fail the port */
+ cportinfo->cport_state = SATA_PSTATE_FAILED;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ SATA_LOG_D((sata_hba_inst, CE_WARN, "Port %d probing failed",
+ saddr->cport));
+ /*
+ * We may want to release device info structure, but
+ * it is not necessary.
+ */
+ return;
+ } else {
+ /* port probed successfully */
+ cportinfo->cport_state |= SATA_STATE_PROBED | SATA_STATE_READY;
+ }
+ /*
+ * Check if a device is still attached. For sanity, check also
+ * link status - if no link, there is no device.
+ */
+ if ((sata_device.satadev_scr.sstatus & SATA_PORT_DEVLINK_UP_MASK) ==
+ SATA_PORT_DEVLINK_UP && sata_device.satadev_type !=
+ SATA_DTYPE_NONE) {
+ /*
+ * Device is still attached - ignore detach event.
+ */
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
+ "Ignoring detach - device still attached to port %d",
+ sata_device.satadev_addr.cport);
+ return;
+ }
+ /*
+ * We need to detach and release device info structure here
+ */
+ if (SATA_CPORTINFO_DRV_INFO(cportinfo) != NULL) {
+ sdevinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
+ SATA_CPORTINFO_DRV_INFO(cportinfo) = NULL;
+ (void) kmem_free((void *)sdevinfo,
+ sizeof (sata_drive_info_t));
+ }
+ cportinfo->cport_dev_type = SATA_DTYPE_NONE;
+ /*
+ * Device cannot be reached anymore, even if the target node may be
+ * still present.
+ */
+
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ sata_log(sata_hba_inst, CE_WARN, "SATA device detached at port %d",
+ sata_device.satadev_addr.cport);
+
+ /*
+ * Try to offline a device and remove target node if it still exists
+ */
+ tdip = sata_get_target_dip(SATA_DIP(sata_hba_inst), saddr->cport);
+ if (tdip != NULL) {
+ /*
+ * target node exist - unconfigure device first, then remove
+ * the node
+ */
+ if (ndi_devi_offline(tdip, NDI_UNCONFIG) != NDI_SUCCESS) {
+ /*
+ * PROBLEM - no device, but target node remained
+ * This happens when the file was open or node was
+ * waiting for resources.
+ */
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_process_device_detached: "
+ "Failed to unconfigure removed device."));
+ }
+ if (ndi_devi_offline(tdip, NDI_DEVI_REMOVE) != NDI_SUCCESS) {
+ /*
+ * PROBLEM - no device, but target node remained
+ * This happens when the file was open or node was
+ * waiting for resources.
+ */
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_process_device_detached: "
+ "Failed to remove target node for "
+ "removed device."));
+ }
+ }
+
+}
+
+
+/*
+ * Device Attached Event processing.
+ * Port state is checked to verify that a device is really attached. If so,
+ * the device info structure is created and attached to the SATA port info
+ * structure.
+ *
+ * This function cannot be called in interrupt context (it may sleep).
+ *
+ * NOTE: Process cports event only, no port multiplier ports.
+ */
+static void
+sata_process_device_attached(sata_hba_inst_t *sata_hba_inst,
+ sata_address_t *saddr)
+{
+ sata_cport_info_t *cportinfo;
+ sata_drive_info_t *sdevinfo;
+ sata_device_t sata_device;
+ dev_info_t *tdip;
+
+ SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
+ "Processing port %d device attached", saddr->cport);
+
+ cportinfo = SATA_CPORT_INFO(sata_hba_inst, saddr->cport);
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+
+ /* Clear event flag first */
+ cportinfo->cport_event_flags &= ~SATA_EVNT_DEVICE_ATTACHED;
+ /* If the port is in SHUTDOWN or FAILED state, ignore event. */
+ if ((cportinfo->cport_state &
+ (SATA_PSTATE_SHUTDOWN | SATA_PSTATE_FAILED)) != 0) {
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->
+ cport_mutex);
+ return;
+ }
+
+ /*
+ * If the sata_drive_info structure is found attached to the port info,
+ * something went wrong in the event reporting and processing sequence.
+ * To recover, arbitrarily release device info structure and issue
+ * a warning.
+ */
+ if (SATA_CPORTINFO_DRV_INFO(cportinfo) != NULL) {
+ sdevinfo = SATA_CPORTINFO_DRV_INFO(cportinfo);
+ SATA_CPORTINFO_DRV_INFO(cportinfo) = NULL;
+ (void) kmem_free((void *)sdevinfo,
+ sizeof (sata_drive_info_t));
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "Arbitrarily detaching old device info."));
+ }
+ cportinfo->cport_dev_type = SATA_DTYPE_NONE;
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ /*
+ * Make sure that there is no target node for that device.
+ * If so, release it. It should not happen, unless we had problem
+ * removing the node when device was detached.
+ */
+ tdip = sata_get_target_dip(SATA_DIP(sata_hba_inst), saddr->cport);
+ if (tdip != NULL) {
+
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_process_device_attached: "
+ "old device target node exists!!!"));
+ /*
+ * target node exist - unconfigure device first, then remove
+ * the node
+ */
+ if (ndi_devi_offline(tdip, NDI_UNCONFIG) != NDI_SUCCESS) {
+ /*
+ * PROBLEM - no device, but target node remained
+ * This happens when the file was open or node was
+ * waiting for resources.
+ */
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_process_device_attached: "
+ "Failed to unconfigure old target node!"));
+ }
+ /* Following call will retry node offlining and removing it */
+ if (ndi_devi_offline(tdip, NDI_DEVI_REMOVE) != NDI_SUCCESS) {
+ /* PROBLEM - no device, but target node remained */
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_process_device_attached: "
+ "Failed to remove old target node!"));
+ /*
+ * It is not clear, what should be done here.
+ * For now, we will not attach a new device
+ */
+ return;
+ }
+ }
+
+ /*
+ * Reprobing port will take care of detecting device presence,
+ * creation of the device info structure and determination of the
+ * device type.
+ */
+ sata_device.satadev_addr = *saddr;
+ (void) sata_reprobe_port(sata_hba_inst, &sata_device);
+ /*
+ * If device was successfully attached, an explicit
+ * 'configure' command is needed to configure it.
+ */
+ mutex_enter(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+ if ((cportinfo->cport_state & SATA_STATE_READY) &&
+ cportinfo->cport_dev_type != SATA_DTYPE_NONE &&
+ SATA_CPORTINFO_DRV_INFO(cportinfo) != NULL) {
+ sata_drive_info_t new_sdinfo;
+
+ /* Log device info data */
+ new_sdinfo = *(SATA_CPORTINFO_DRV_INFO(cportinfo));
+ sata_show_drive_info(sata_hba_inst, &new_sdinfo);
+ }
+ mutex_exit(&SATA_CPORT_INFO(sata_hba_inst, saddr->cport)->cport_mutex);
+}
+
+
+/*
+ * sata_restore_drive_settings function compares current device setting
+ * with the saved device setting and, if there is a difference, restores
+ * device setting to the stored state.
+ * Device Identify data has to be current.
+ * At the moment only read ahead and write cache settings are considered.
+ *
+ * This function cannot be called in the interrupt context (it may sleep).
+ *
+ * Returns TRUE if successful or there was nothing to do.
+ * Returns FALSE if device setting could not be restored.
+ *
+ * Note: This function may fail the port, making it inaccessible.
+ * Explicit port disconnect/connect or physical device
+ * detach/attach is required to re-evaluate it's state afterwards
+ */
+static int
+sata_restore_drive_settings(sata_hba_inst_t *sata_hba_inst,
+ sata_drive_info_t *sdinfo)
+{
+ sata_pkt_t *spkt;
+ sata_cmd_t *scmd;
+ sata_pkt_txlate_t *spx;
+ int rval = SATA_SUCCESS;
+ sata_drive_info_t new_sdinfo;
+
+ bzero(&new_sdinfo, sizeof (sata_drive_info_t));
+ new_sdinfo.satadrv_addr = sdinfo->satadrv_addr;
+ new_sdinfo.satadrv_type = sdinfo->satadrv_type;
+ if (sata_fetch_device_identify_data(sata_hba_inst, &new_sdinfo) != 0) {
+ /*
+ * Cannot get device identification - retry later
+ */
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_restore_drive_settings: "
+ "cannot fetch device identify data\n"));
+ return (SATA_FAILURE);
+ }
+ /* Arbitrarily set UDMA mode */
+ if (sata_set_udma_mode(sata_hba_inst, &new_sdinfo) != SATA_SUCCESS) {
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_restore_drive_settings: cannot set UDMA mode\n"));
+ return (SATA_FAILURE);
+ }
+
+ if (!(new_sdinfo.satadrv_id.ai_cmdset82 & SATA_LOOK_AHEAD) &&
+ !(new_sdinfo.satadrv_id.ai_cmdset82 & SATA_WRITE_CACHE)) {
+ /* None of the features is supported - do nothing */
+ SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
+ "restorable features not supported\n", NULL);
+ return (SATA_SUCCESS);
+ }
+
+ if (((new_sdinfo.satadrv_id.ai_features85 & SATA_LOOK_AHEAD) &&
+ (sdinfo->satadrv_settings & SATA_DEV_READ_AHEAD)) &&
+ ((new_sdinfo.satadrv_id.ai_features85 & SATA_WRITE_CACHE) &&
+ (sdinfo->satadrv_settings & SATA_DEV_WRITE_CACHE))) {
+ /* Nothing to do */
+ SATADBG1(SATA_DBG_EVENTS_PROC, sata_hba_inst,
+ "nothing to restore\n", NULL);
+ return (SATA_SUCCESS);
+ }
+
+ /* Prepare packet for SET FEATURES COMMAND */
+ spx = kmem_zalloc(sizeof (sata_pkt_txlate_t), KM_SLEEP);
+ spx->txlt_sata_hba_inst = sata_hba_inst;
+ spx->txlt_scsi_pkt = NULL; /* No scsi pkt involved */
+ spkt = sata_pkt_alloc(spx, SLEEP_FUNC);
+ if (spkt == NULL) {
+ kmem_free(spx, sizeof (sata_pkt_txlate_t));
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_restore_drive_settings: could not "
+ "restore device settings\n"));
+ return (SATA_FAILURE);
+ }
+
+ /* Fill sata_pkt */
+ spkt->satapkt_device.satadev_addr = sdinfo->satadrv_addr;
+ /* Timeout 30s */
+ spkt->satapkt_time = sata_default_pkt_time;
+ /* Synchronous mode, no callback */
+ spkt->satapkt_op_mode = SATA_OPMODE_SYNCH | SATA_OPMODE_INTERRUPTS;
+ spkt->satapkt_comp = NULL;
+ scmd = &spkt->satapkt_cmd;
+ scmd->satacmd_flags =
+ SATA_DIR_NODATA_XFER | SATA_IGNORE_DEV_RESET_STATE;
+ scmd->satacmd_addr_type = 0;
+ scmd->satacmd_device_reg = 0;
+ scmd->satacmd_status_reg = 0;
+ scmd->satacmd_error_reg = 0;
+ scmd->satacmd_cmd_reg = SATAC_SET_FEATURES;
+
+ if (!((new_sdinfo.satadrv_id.ai_features85 & SATA_LOOK_AHEAD) &&
+ (sdinfo->satadrv_settings & SATA_DEV_READ_AHEAD))) {
+ if (sdinfo->satadrv_settings & SATA_DEV_READ_AHEAD)
+ /* Enable read ahead / read cache */
+ scmd->satacmd_features_reg =
+ SATAC_SF_ENABLE_READ_AHEAD;
+ else
+ /* Disable read ahead / read cache */
+ scmd->satacmd_features_reg =
+ SATAC_SF_DISABLE_READ_AHEAD;
+
+ /* Transfer command to HBA */
+ if (((*SATA_START_FUNC(sata_hba_inst))
+ (SATA_DIP(sata_hba_inst), spkt) != 0) ||
+ (spkt->satapkt_reason != SATA_PKT_COMPLETED)) {
+ /* Pkt execution failed */
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_restore_drive_settings: could not "
+ "restore device settings\n"));
+ rval = SATA_FAILURE;
+ }
+ }
+ /* Note that the sata packet is not removed, so it could be re-used */
+
+ if (!((new_sdinfo.satadrv_id.ai_features85 & SATA_WRITE_CACHE) &&
+ (sdinfo->satadrv_settings & SATA_DEV_WRITE_CACHE))) {
+ if (sdinfo->satadrv_settings & SATA_DEV_WRITE_CACHE)
+ /* Enable write cache */
+ scmd->satacmd_features_reg =
+ SATAC_SF_ENABLE_WRITE_CACHE;
+ else
+ /* Disable write cache */
+ scmd->satacmd_features_reg =
+ SATAC_SF_DISABLE_WRITE_CACHE;
+
+ /* Transfer command to HBA */
+ if (((*SATA_START_FUNC(sata_hba_inst))(
+ SATA_DIP(sata_hba_inst), spkt) != 0) ||
+ (spkt->satapkt_reason != SATA_PKT_COMPLETED)) {
+ /* Pkt execution failed */
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_restore_drive_settings: could not "
+ "restore device settings\n"));
+ rval = SATA_FAILURE;
+ }
+ }
+
+ /* Free allocated resources */
+ sata_pkt_free(spx);
+ (void) kmem_free(spx, sizeof (sata_pkt_txlate_t));
+
+ /*
+ * We need to fetch Device Identify data again
+ */
+ if (sata_fetch_device_identify_data(sata_hba_inst, &new_sdinfo) != 0) {
+ /*
+ * Cannot get device identification - retry later
+ */
+ SATA_LOG_D((sata_hba_inst, CE_WARN,
+ "sata_restore_drive_settings: "
+ "cannot re-fetch device identify data\n"));
+ rval = SATA_FAILURE;
+ }
+ /* Copy device sata info. */
+ sdinfo->satadrv_id = new_sdinfo.satadrv_id;
+
+ return (rval);
+}
diff --git a/usr/src/uts/common/sys/Makefile b/usr/src/uts/common/sys/Makefile
index 22b2f9654b..bb8e6e8d7a 100644
--- a/usr/src/uts/common/sys/Makefile
+++ b/usr/src/uts/common/sys/Makefile
@@ -785,6 +785,11 @@ FCHDRS= \
fcal_linkapp.h \
fcio.h
+SATAGENHDRS= \
+ sata_hba.h \
+ sata_defs.h \
+ sata_cfgadm.h
+
SYSEVENTHDRS= \
ap_driver.h \
dev.h \
@@ -926,6 +931,7 @@ CHECKHDRS= \
$(SCSITARGETSHDRS:%.h=scsi/targets/%.check) \
$(SCSIVHCIHDRS:%.h=scsi/adapters/%.check) \
$(FCHDRS:%.h=fc4/%.check) \
+ $(SATAGENHDRS:%.h=sata/%.check) \
$(SYSEVENTHDRS:%.h=sysevent/%.check) \
$(CONTRACTHDRS:%.h=contract/%.check) \
$(USBAUDHDRS:%.h=usb/clients/audio/%.check) \
diff --git a/usr/src/uts/common/sys/Makefile.syshdrs b/usr/src/uts/common/sys/Makefile.syshdrs
index cca54ae583..cdc3436049 100644
--- a/usr/src/uts/common/sys/Makefile.syshdrs
+++ b/usr/src/uts/common/sys/Makefile.syshdrs
@@ -117,7 +117,7 @@ sparc_ROOTDIRS= $(ROOTDKTPDIR) $(ROOTDIR)/scsi/adapters \
$(ROOTDIR)/av
i386_ROOTDIRS= $(ROOTDKTPDIR) $(ROOTDIR)/scsi/adapters $(ROOTDIR)/scsi/targets \
- $(ROOTDIR)/i2o $(ROOTDIR)/agp
+ $(ROOTDIR)/i2o $(ROOTDIR)/agp $(ROOTDIR)/sata
ROOTDIRS= \
$(ROOTDIR) \
@@ -195,6 +195,7 @@ ROOTLVMHDRS= $(ALL_LVMHDRS:%=$(ROOTDIR)/lvm/%)
ROOTPCMCIAHDRS= $(PCMCIAHDRS:%=$(ROOTDIR)/pcmcia/%)
ROOTSCSIHDRS= $(SCSIHDRS:%=$(ROOTDIR)/scsi/%)
+ROOTSATAGENHDRS= $(SATAGENHDRS:%=$(ROOTDIR)/sata/%)
ROOTSCSICONFHDRS= $(SCSICONFHDRS:%=$(ROOTDIR)/scsi/conf/%)
ROOTSCSIGENHDRS= $(SCSIGENHDRS:%=$(ROOTDIR)/scsi/generic/%)
ROOTSCSIIMPLHDRS= $(SCSIIMPLHDRS:%=$(ROOTDIR)/scsi/impl/%)
@@ -238,7 +239,7 @@ sparc_ROOTHDRS= $(ROOTSDKTPHDRS) $(ROOTSCSICADHDRS) $(ROOTSCSITARGETSHDRS) \
i386_ROOTHDRS= $(ROOTDKTPHDRS) $(ROOTPCHDRS) $(ROOTSCSITARGETSHDRS) \
$(ROOTSCSIVHCIHDRS) $(ROOTFCHDRS) \
$(ROOTI2OHDRS) $(ROOTPCMCIAHDRS) $(ROOTHOTPLUGHDRS) \
- $(ROOTHOTPLUGPCIHDRS)
+ $(ROOTHOTPLUGPCIHDRS) $(ROOTSATAGENHDRS)
# install rules
$(ROOTDIR)/%: %
diff --git a/usr/src/uts/common/sys/sata/adapters/si3124/si3124reg.h b/usr/src/uts/common/sys/sata/adapters/si3124/si3124reg.h
new file mode 100644
index 0000000000..65b3021536
--- /dev/null
+++ b/usr/src/uts/common/sys/sata/adapters/si3124/si3124reg.h
@@ -0,0 +1,388 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SI3124REG_H
+#define _SI3124REG_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#pragma pack(1)
+
+typedef struct si_sge {
+ /* offset 0x00 */
+ union {
+ uint64_t _sge_addr_ll;
+ uint32_t _sge_addr_la[2];
+ } _sge_addr_un;
+
+#define sge_addr_low _sge_addr_un._sge_addr_la[0]
+#define sge_addr_high _sge_addr_un._sge_addr_la[1]
+#define sge_addr _sge_addr_un._sge_addr_ll
+
+ /* offset 0x08 */
+ uint32_t sge_data_count;
+
+ /* offset 0x0c */
+ uint32_t sge_trm_lnk_drd_xcf_rsvd;
+
+#define SET_SGE_LNK(sge) (sge.sge_trm_lnk_drd_xcf_rsvd = 0x40000000)
+#define SET_SGE_TRM(sge) (sge.sge_trm_lnk_drd_xcf_rsvd = 0x80000000)
+#define IS_SGE_TRM_SET(sge) (sge.sge_trm_lnk_drd_xcf_rsvd & 0x80000000)
+
+} si_sge_t;
+
+/* Scatter Gather Table consists of four SGE entries */
+typedef struct si_sgt {
+ si_sge_t sgt_sge[4];
+} si_sgt_t;
+
+
+/* Register - Host to Device FIS (from SATA spec) */
+typedef struct fis_reg_h2d {
+ /* offset 0x00 */
+ uint32_t fish_type_pmp_rsvd_cmddevctl_cmd_features;
+
+#define SET_FIS_TYPE(fis, type) \
+ (fis.fish_type_pmp_rsvd_cmddevctl_cmd_features |= (type & 0xff))
+
+#define SET_FIS_PMP(fis, pmp) \
+ (fis.fish_type_pmp_rsvd_cmddevctl_cmd_features |= ((pmp & 0xf) << 8))
+
+#define SET_FIS_CDMDEVCTL(fis, cmddevctl) \
+ (fis.fish_type_pmp_rsvd_cmddevctl_cmd_features |= \
+ ((cmddevctl & 0x1) << 15))
+
+#define SET_FIS_COMMAND(fis, command) \
+ (fis.fish_type_pmp_rsvd_cmddevctl_cmd_features |= \
+ ((command & 0xff) << 16))
+
+#define GET_FIS_COMMAND(fis) \
+ ((fis.fish_type_pmp_rsvd_cmddevctl_cmd_features >> 16) & 0xff)
+
+#define SET_FIS_FEATURES(fis, features) \
+ (fis.fish_type_pmp_rsvd_cmddevctl_cmd_features |= \
+ ((features & 0xff) << 24))
+
+#define GET_FIS_FEATURES(fis) \
+ ((fis.fish_type_pmp_rsvd_cmddevctl_cmd_features >> 24) & 0xff)
+
+ /* offset 0x04 */
+ uint32_t fish_sector_cyllow_cylhi_devhead;
+
+#define SET_FIS_SECTOR(fis, sector) \
+ (fis.fish_sector_cyllow_cylhi_devhead |= ((sector & 0xff)))
+
+#define GET_FIS_SECTOR(fis) \
+ (fis.fish_sector_cyllow_cylhi_devhead & 0xff)
+
+#define SET_FIS_CYL_LOW(fis, cyl_low) \
+ (fis.fish_sector_cyllow_cylhi_devhead |= ((cyl_low & 0xff) << 8))
+
+#define GET_FIS_CYL_LOW(fis) \
+ ((fis.fish_sector_cyllow_cylhi_devhead >> 8) & 0xff)
+
+#define SET_FIS_CYL_HI(fis, cyl_hi) \
+ (fis.fish_sector_cyllow_cylhi_devhead |= ((cyl_hi & 0xff) << 16))
+
+#define GET_FIS_CYL_HI(fis) \
+ ((fis.fish_sector_cyllow_cylhi_devhead >> 16) & 0xff)
+
+#define SET_FIS_DEV_HEAD(fis, dev_head) \
+ (fis.fish_sector_cyllow_cylhi_devhead |= ((dev_head & 0xff) << 24))
+
+
+ /* offset 0x08 */
+ uint32_t fish_sectexp_cyllowexp_cylhiexp_featuresexp;
+
+#define SET_FIS_SECTOR_EXP(fis, sectorexp) \
+ (fis.fish_sectexp_cyllowexp_cylhiexp_featuresexp |= \
+ ((sectorexp & 0xff)))
+
+#define SET_FIS_CYL_LOW_EXP(fis, cyllowexp) \
+ (fis.fish_sectexp_cyllowexp_cylhiexp_featuresexp |= \
+ ((cyllowexp & 0xff) << 8))
+
+#define SET_FIS_CYL_HI_EXP(fis, cylhiexp) \
+ (fis.fish_sectexp_cyllowexp_cylhiexp_featuresexp |= \
+ ((cylhiexp & 0xff) << 16))
+
+#define SET_FIS_FEATURES_EXP(fis, features_exp) \
+ (fis.fish_sectexp_cyllowexp_cylhiexp_featuresexp |= \
+ ((features_exp & 0xff) << 24))
+
+ /* offset 0x0c */
+ uint32_t fish_sectcount_sectcountexp_rsvd_devctl;
+
+#define SET_FIS_SECTOR_COUNT(fis, sector_count) \
+ (fis.fish_sectcount_sectcountexp_rsvd_devctl |= ((sector_count & 0xff)))
+
+#define GET_FIS_SECTOR_COUNT(fis) \
+ (fis.fish_sectcount_sectcountexp_rsvd_devctl & 0xff)
+
+#define SET_FIS_SECTOR_COUNT_EXP(fis, sector_count_exp) \
+ (fis.fish_sectcount_sectcountexp_rsvd_devctl |= \
+ ((sector_count_exp & 0xff) << 8))
+
+#define SET_FIS_SECTOR_DEVCTL(fis, devctl) \
+ (fis.fish_sectcount_sectcountexp_rsvd_devctl |= ((devctl & 0xff) << 24))
+
+ /* offset 0x10 */
+ uint32_t fish_rsvd3; /* should be zero */
+} fis_reg_h2d_t;
+
+
+
+
+/*
+ * Port Request Block
+ */
+typedef struct si_prb {
+ /* offset 0x00 */
+ uint32_t prb_control_override;
+
+#define SET_PRB_CONTROL_PKT_READ(prb) \
+ (prb->prb_control_override |= (0x1 << 4))
+
+#define SET_PRB_CONTROL_PKT_WRITE(prb) \
+ (prb->prb_control_override |= (0x1 << 5))
+
+#define SET_PRB_CONTROL_SOFT_RESET(prb) \
+ (prb->prb_control_override |= (0x1 << 7))
+
+ /* offset 0x04 */
+ uint32_t prb_received_count;
+
+ /* offset 0x08 */
+ fis_reg_h2d_t prb_fis; /* this is of 0x14 bytes size */
+
+ /* offset 0x1c */
+ uint32_t prb_rsvd3;
+
+ /* offset 0x20 */
+ si_sge_t prb_sge0;
+
+ /* offset 0x30 */
+ si_sge_t prb_sge1;
+
+} si_prb_t;
+
+#pragma pack()
+
+
+/* Various interrupt bits */
+#define INTR_COMMAND_COMPLETE (0x1 << 0)
+#define INTR_COMMAND_ERROR (0x1 << 1)
+#define INTR_PORT_READY (0x1 << 2)
+#define INTR_POWER_CHANGE (0x1 << 3)
+#define INTR_PHYRDY_CHANGE (0x1 << 4)
+#define INTR_COMWAKE_RECEIVED (0x1 << 5)
+#define INTR_UNRECOG_FIS (0x1 << 6)
+#define INTR_DEV_XCHANGED (0x1 << 7)
+#define INTR_8B10B_DECODE_ERROR (0x1 << 8)
+#define INTR_CRC_ERROR (0x1 << 9)
+#define INTR_HANDSHAKE_ERROR (0x1 << 10)
+#define INTR_SETDEVBITS_NOTIFY (0x1 << 11)
+#define INTR_MASK (0xfff)
+
+/* Device signatures */
+#define SI_SIGNATURE_PORT_MULTIPLIER 0x96690101
+#define SI_SIGNATURE_ATAPI 0xeb140101
+#define SI_SIGNATURE_DISK 0x00000101
+
+
+/* Global definitions */
+#define GLOBAL_OFFSET(si_ctlp) (si_ctlp->sictl_global_addr)
+#define GLOBAL_CONTROL_REG(si_ctlp) (GLOBAL_OFFSET(si_ctlp)+0x40)
+#define GLOBAL_INTERRUPT_STATUS(si_ctlp) (GLOBAL_OFFSET(si_ctlp)+0x44)
+
+/* Per port definitions */
+#define PORT_OFFSET(si_ctlp, port) (si_ctlp->sictl_port_addr + port*0x2000)
+#define PORT_LRAM(si_ctlp, port, slot) \
+ (PORT_OFFSET(si_ctlp, port) + 0x0 + slot*0x80)
+#define PORT_CONTROL_SET(si_ctlp, port) \
+ (PORT_OFFSET(si_ctlp, port) + 0x1000)
+#define PORT_STATUS(si_ctlp, port) \
+ (PORT_OFFSET(si_ctlp, port) + 0x1000)
+#define PORT_CONTROL_CLEAR(si_ctlp, port) \
+ (PORT_OFFSET(si_ctlp, port) + 0x1004)
+#define PORT_INTERRUPT_STATUS(si_ctlp, port) \
+ (PORT_OFFSET(si_ctlp, port) + 0x1008)
+#define PORT_INTERRUPT_ENABLE_SET(si_ctlp, port) \
+ (PORT_OFFSET(si_ctlp, port) + 0x1010)
+#define PORT_INTERRUPT_ENABLE_CLEAR(si_ctlp, port) \
+ (PORT_OFFSET(si_ctlp, port) + 0x1014)
+#define PORT_COMMAND_ERROR(si_ctlp, port) \
+ (PORT_OFFSET(si_ctlp, port) + 0x1024)
+#define PORT_SLOT_STATUS(si_ctlp, port) (PORT_OFFSET(si_ctlp, port) + 0x1800)
+
+#define PORT_SCONTROL(si_ctlp, port) (PORT_OFFSET(si_ctlp, port) + 0x1f00)
+#define PORT_SSTATUS(si_ctlp, port) (PORT_OFFSET(si_ctlp, port) + 0x1f04)
+#define PORT_SERROR(si_ctlp, port) (PORT_OFFSET(si_ctlp, port) + 0x1f08)
+#define PORT_SACTIVE(si_ctlp, port) (PORT_OFFSET(si_ctlp, port) + 0x1f0c)
+
+#define PORT_COMMAND_ACTIVATION(si_ctlp, port, slot) \
+ (PORT_OFFSET(si_ctlp, port) + 0x1c00 + slot*0x8)
+
+#define PORT_SIGNATURE_MSB(si_ctlp, port, slot) \
+ (PORT_OFFSET(si_ctlp, port) + slot*0x80 + 0x0c)
+#define PORT_SIGNATURE_LSB(si_ctlp, port, slot) \
+ (PORT_OFFSET(si_ctlp, port) + slot*0x80 + 0x14)
+
+/* Interesting bits of Port Control Set register */
+#define PORT_CONTROL_SET_BITS_PORT_RESET 0x1
+#define PORT_CONTROL_SET_BITS_DEV_RESET 0x2
+#define PORT_CONTROL_SET_BITS_PORT_INITIALIZE 0x4
+#define PORT_CONTROL_SET_BITS_PACKET_LEN 0x20
+#define PORT_CONTROL_SET_BITS_RESUME 0x40
+#define PORT_CONTROL_SET_BITS_PM_ENABLE 0x2000
+
+/* Interesting bits of Port Control Clear register */
+#define PORT_CONTROL_CLEAR_BITS_PORT_RESET 0x1
+#define PORT_CONTROL_CLEAR_BITS_INTR_NCoR 0x8
+#define PORT_CONTROL_CLEAR_BITS_PACKET_LEN 0x20
+#define PORT_CONTROL_CLEAR_BITS_RESUME 0x40
+
+/* Interesting bits of Port Status register */
+#define PORT_STATUS_BITS_PORT_READY 0x80000000
+
+/* Interesting bits of Global Control register */
+#define GLOBAL_CONTROL_REG_BITS_CLEAR 0x00000000
+
+#define POST_PRB_ADDR(si_ctlp, si_portp, port, slot) \
+ (void) ddi_dma_sync(si_portp->siport_prbpool_dma_handle, \
+ slot * sizeof (si_prb_t), \
+ sizeof (si_prb_t), \
+ DDI_DMA_SYNC_FORDEV); \
+ \
+ (void) ddi_dma_sync(si_portp->siport_sgbpool_dma_handle, \
+ slot * sizeof (si_sgblock_t), \
+ sizeof (si_sgblock_t), \
+ DDI_DMA_SYNC_FORDEV); \
+ \
+ ddi_put64(si_ctlp->sictl_port_acc_handle, \
+ (uint64_t *)PORT_COMMAND_ACTIVATION(si_ctlp, port, slot), \
+ (uint64_t)(si_portp->siport_prbpool_physaddr + \
+ slot*sizeof (si_prb_t)));
+
+#define SI_SLOT_MASK 0x7fffffff
+#define SI_NUM_SLOTS 0x1f /* 31 */
+
+#define ATTENTION_BIT 0x80000000
+#define IS_ATTENTION_RAISED(slot_status) (slot_status & ATTENTION_BIT)
+
+#define SI3124_DEV_ID 3124
+#define SI3132_DEV_ID 3132
+
+#define PM_CSR(devid) ((devid == SI3124_DEV_ID) ? 0x68 : 0x58)
+
+#define REGISTER_FIS_H2D 0x27
+
+#define SI31xx_INTR_PORT_MASK 0xf
+
+/* PCI BAR registers */
+#define PCI_BAR0 1 /* Contains global register set */
+#define PCI_BAR1 2 /* Contains port register set */
+
+/* Port Status and Control Registers (from port multiplier spec) */
+#define PSCR_REG0 0
+#define PSCR_REG1 1
+#define PSCR_REG2 2
+#define PSCR_REG3 3
+
+/* SStatus bit fields */
+#define SSTATUS_DET_MASK 0x0000000f
+#define SSTATUS_SPD_MASK 0x000000f0
+#define SSTATUS_SPD_SHIFT 4
+#define SSTATUS_IPM_MASK 0x00000f00
+#define SSTATUS_IPM_SHIFT 8
+
+#define SSTATUS_GET_DET(x) \
+ (x & SSTATUS_DET_MASK)
+
+#define SSTATUS_SET_DET(x, new_val) \
+ (x = (x & ~SSTATUS_DET_MASK) | (new_val & SSTATUS_DET_MASK))
+
+#define SSTATUS_DET_NODEV_NOPHY 0x0 /* No device, no PHY */
+#define SSTATUS_DET_DEVPRESENT_NOPHY 0x1 /* Dev present, no PHY */
+#define SSTATUS_DET_DEVPRESENT_PHYONLINE 0x3 /* Dev present, PHY online */
+#define SSTATUS_DET_PHYOFFLINE 0x4 /* PHY offline */
+
+#define SSTATUS_GET_IPM(x) \
+ ((x & SSTATUS_IPM_MASK) >> SSTATUS_IPM_SHIFT)
+
+#define SSTATUS_SET_IPM(x, new_val) \
+ (x = (x & ~SSTATUS_IPM_MASK) | \
+ ((new_val << SSTATUS_IPM_SHIFT) & SSTATUS_IPM_MASK))
+
+#define SSTATUS_IPM_NODEV_NOPHY 0x0 /* No dev, no PHY */
+#define SSTATUS_IPM_INTERFACE_ACTIVE 0x1 /* Interface active */
+#define SSTATUS_IPM_INTERFACE_POWERPARTIAL 0x2 /* partial power mgmnt */
+#define SSTATUS_IPM_INTERFACE_POWERSLUMBER 0x6 /* slumber power mgmt */
+
+/* SControl bit fields */
+#define SCONTROL_DET_MASK 0x0000000f
+
+#define SCONTROL_GET_DET(x) \
+ (x & SCONTROL_DET_MASK)
+
+#define SCONTROL_SET_DET(x, new_val) \
+ (x = (x & ~SCONTROL_DET_MASK) | (new_val & SCONTROL_DET_MASK))
+
+#define SCONTROL_DET_NOACTION 0x0 /* No action requested */
+#define SCONTROL_DET_COMRESET 0x1 /* Send COMRESET */
+
+/* Command Error codes */
+#define CMD_ERR_DEVICEERRROR 1
+#define CMD_ERR_SDBERROR 2
+#define CMD_ERR_DATAFISERROR 3
+#define CMD_ERR_SENDFISERROR 4
+#define CMD_ERR_INCONSISTENTSTATE 5
+#define CMD_ERR_DIRECTIONERROR 6
+#define CMD_ERR_UNDERRUNERROR 7
+#define CMD_ERR_OVERRUNERROR 8
+#define CMD_ERR_PACKETPROTOCOLERROR 11
+#define CMD_ERR_PLDSGTERRORBOUNDARY 16
+#define CMD_ERR_PLDSGTERRORTARETABORT 17
+#define CMD_ERR_PLDSGTERRORMASTERABORT 18
+#define CMD_ERR_PLDSGTERRORPCIERR 19
+#define CMD_ERR_PLDCMDERRORBOUNDARY 24
+#define CMD_ERR_PLDCMDERRORTARGETABORT 25
+#define CMD_ERR_PLDCMDERRORMASTERABORT 26
+#define CMD_ERR_PLDCMDERORPCIERR 27
+#define CMD_ERR_PSDERRORTARGETABORT 33
+#define CMD_ERR_PSDERRORMASTERABORT 34
+#define CMD_ERR_PSDERRORPCIERR 35
+#define CMD_ERR_SENDSERVICEERROR 36
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SI3124REG_H */
diff --git a/usr/src/uts/common/sys/sata/adapters/si3124/si3124var.h b/usr/src/uts/common/sys/sata/adapters/si3124/si3124var.h
new file mode 100644
index 0000000000..2ab96fda96
--- /dev/null
+++ b/usr/src/uts/common/sys/sata/adapters/si3124/si3124var.h
@@ -0,0 +1,287 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SI3124VAR_H
+#define _SI3124VAR_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SI3124_MAX_PORTS 4
+#define SI3132_MAX_PORTS 2
+#define SI_MAX_PORTS SI3124_MAX_PORTS
+
+#define SI_SUCCESS (0) /* successful return */
+#define SI_TIMEOUT (1) /* timed out */
+#define SI_FAILURE (-1) /* unsuccessful return */
+
+#define SI_MAX_SGT_TABLES_PER_PRB 10
+
+/*
+ * While the si_sge_t and si_sgt_t correspond to the actual SGE and SGT
+ * definitions as per the datasheet, the si_sgblock_t (i.e scatter gather
+ * block) is a logical data structure which holds multiple SGT tables.
+ * The idea is to use multiple chained SGT tables per each PRB request.
+ */
+
+typedef struct si_sgblock {
+ si_sgt_t sgb_sgt[SI_MAX_SGT_TABLES_PER_PRB];
+} si_sgblock_t;
+
+/*
+ * Each SGT (Scatter Gather Table) has 4 SGEs (Scatter Gather Entries).
+ * But each SGT effectively can host only 3 SGEs since the last SGE entry
+ * is used to hold a link to the next SGT in the chain. However the last
+ * SGT in the chain can host all the 4 entries since it does not need to
+ * link any more.
+ */
+#define SI_MAX_SGL_LENGTH (3*SI_MAX_SGT_TABLES_PER_PRB)+1
+
+typedef struct si_portmult_state {
+ int sipm_num_ports;
+ uint8_t sipm_port_type[15];
+ /* one of PORT_TYPE_[NODEV | MULTIPLIER | ATAPI | DISK | UNKNOWN] */
+
+ /*
+ * sipm_port_type[] is good enough to capture the state of ports
+ * behind the multiplier. Since any of the port behind a multiplier
+ * is accessed through the same main controller port, we don't need
+ * additional si_port_state_t here.
+ */
+
+} si_portmult_state_t;
+
+
+/* The following are for port types */
+#define PORT_TYPE_NODEV 0x0
+#define PORT_TYPE_MULTIPLIER 0x1
+#define PORT_TYPE_ATAPI 0x2
+#define PORT_TYPE_DISK 0x3
+#define PORT_TYPE_UNKNOWN 0x4
+
+/* The following are for active state */
+#define PORT_INACTIVE 0x0
+#define PORT_ACTIVE 0x1
+
+typedef struct si_port_state {
+ uint8_t siport_port_type;
+ /* one of PORT_TYPE_[NODEV | MULTIPLIER | ATAPI | DISK | UNKNOWN] */
+
+ uint8_t siport_active; /* one of ACTIVE or INACTIVE */
+
+ si_portmult_state_t siport_portmult_state;
+
+ si_prb_t *siport_prbpool; /* These are 31 incore PRBs */
+ uint64_t siport_prbpool_physaddr;
+ ddi_dma_handle_t siport_prbpool_dma_handle;
+ ddi_acc_handle_t siport_prbpool_acc_handle;
+
+
+ si_sgblock_t *siport_sgbpool; /* These are 31 incore sg blocks */
+ uint64_t siport_sgbpool_physaddr;
+ ddi_dma_handle_t siport_sgbpool_dma_handle;
+ ddi_acc_handle_t siport_sgbpool_acc_handle;
+
+ kmutex_t siport_mutex; /* main per port mutex */
+ uint32_t siport_pending_tags; /* remembers the pending tags */
+ sata_pkt_t *siport_slot_pkts[SI_NUM_SLOTS];
+
+ /*
+ * While the reset is in progress, we don't accept any more commands
+ * until we receive the command with SATA_CLEAR_DEV_RESET_STATE flag.
+ * However any commands with SATA_IGNORE_DEV_RESET_STATE are allowed in
+ * during such blockage.
+ */
+ int siport_reset_in_progress;
+
+ /*
+ * We mop the commands for either abort, reset, timeout or
+ * error handling cases. Only one such mopping thread is allowed
+ * at a time.
+ */
+ int mopping_in_progress;
+ kmutex_t siport_mop_mutex; /* limits one mop at a time */
+
+ /* error recovery related info */
+ uint32_t siport_err_tags_SDBERROR;
+ uint32_t siport_err_tags_nonSDBERROR;
+ int siport_pending_ncq_count;
+
+} si_port_state_t;
+
+/* Warlock annotation */
+_NOTE(MUTEX_PROTECTS_DATA(si_port_state_t::siport_mutex, si_port_state_t))
+_NOTE(READ_ONLY_DATA(si_port_state_t::siport_prbpool_dma_handle))
+_NOTE(READ_ONLY_DATA(si_port_state_t::siport_sgbpool_dma_handle))
+
+
+typedef struct si_ctl_state {
+
+ dev_info_t *sictl_devinfop;
+
+ int sictl_num_ports; /* number of controller ports */
+ si_port_state_t *sictl_ports[SI_MAX_PORTS];
+
+ int sictl_devid; /* whether it is 3124 or 3132 */
+ int sictl_flags; /* some important state of controller */
+ int sictl_power_level;
+
+ /* pci config space handle */
+ ddi_acc_handle_t sictl_pci_conf_handle;
+
+ /* mapping into bar 0 */
+ ddi_acc_handle_t sictl_global_acc_handle;
+ uintptr_t sictl_global_addr;
+
+ /* mapping into bar 1 */
+ ddi_acc_handle_t sictl_port_acc_handle;
+ uintptr_t sictl_port_addr;
+
+ struct sata_hba_tran *sictl_sata_hba_tran;
+ timeout_id_t sictl_timeout_id;
+
+ kmutex_t sictl_mutex; /* per controller mutex */
+
+ ddi_intr_handle_t *sictl_htable; /* For array of interrupts */
+ int sictl_intr_type; /* What type of interrupt */
+ int sictl_intr_cnt; /* # of intrs count returned */
+ size_t sictl_intr_size; /* Size of intr array */
+ uint_t sictl_intr_pri; /* Interrupt priority */
+ int sictl_intr_cap; /* Interrupt capabilities */
+
+} si_ctl_state_t;
+
+/* Warlock annotation */
+_NOTE(MUTEX_PROTECTS_DATA(si_ctl_state_t::sictl_mutex,
+ si_ctl_state_t::sictl_ports))
+_NOTE(MUTEX_PROTECTS_DATA(si_ctl_state_t::sictl_mutex,
+ si_ctl_state_t::sictl_power_level))
+_NOTE(MUTEX_PROTECTS_DATA(si_ctl_state_t::sictl_mutex,
+ si_ctl_state_t::sictl_flags))
+_NOTE(MUTEX_PROTECTS_DATA(si_ctl_state_t::sictl_mutex,
+ si_ctl_state_t::sictl_timeout_id))
+/*
+ * flags for si_flags
+ */
+#define SI_PM 0x01
+#define SI_ATTACH 0x02
+#define SI_DETACH 0x04
+#define SI_NO_TIMEOUTS 0x08
+#define SI_FRAMEWORK_ATTACHED 0x10 /* are we attached to framework ? */
+
+/* progress values for si_attach */
+#define ATTACH_PROGRESS_NONE (1<<0)
+#define ATTACH_PROGRESS_STATEP_ALLOC (1<<1)
+#define ATTACH_PROGRESS_CONF_HANDLE (1<<2)
+#define ATTACH_PROGRESS_BAR0_MAP (1<<3)
+#define ATTACH_PROGRESS_BAR1_MAP (1<<4)
+#define ATTACH_PROGRESS_INTR_ADDED (1<<5)
+#define ATTACH_PROGRESS_MUTEX_INIT (1<<6)
+#define ATTACH_PROGRESS_HW_INIT (1<<7)
+
+#define SI_10MS_TICKS (drv_usectohz(10000)) /* ticks in 10 millisec */
+#define SI_1MS_TICKS (drv_usectohz(1000)) /* ticks in 1 millisec */
+#define SI_1MS_USECS (1000) /* usecs in 1 millisec */
+#define SI_POLLRATE_SOFT_RESET 1000
+#define SI_POLLRATE_SSTATUS 10
+#define SI_POLLRATE_PORTREADY 50
+#define SI_POLLRATE_SLOTSTATUS 50
+#define SI_POLLRATE_RECOVERPORTMULT 1000
+
+#define PORTMULT_CONTROL_PORT 0xf
+
+/* clearing & setting the n'th bit in a given tag */
+#define CLEAR_BIT(tag, bit) (tag &= ~(0x1<<bit))
+#define SET_BIT(tag, bit) (tag |= (0x1<<bit))
+
+#if DEBUG
+
+#define SI_DBUG 1
+
+#define SIDBG_TEST 0x0001
+#define SIDBG_INIT 0x0002
+#define SIDBG_ENTRY 0x0004
+#define SIDBG_DUMP_PRB 0x0008
+#define SIDBG_EVENT 0x0010
+#define SIDBG_POLL_LOOP 0x0020
+#define SIDBG_PKTCOMP 0x0040
+#define SIDBG_TIMEOUT 0x0080
+#define SIDBG_INFO 0x0100
+#define SIDBG_VERBOSE 0x0200
+#define SIDBG_INTR 0x0400
+#define SIDBG_ERRS 0x0800
+#define SIDBG_COOKIES 0x1000
+#define SIDBG_POWER 0x2000
+
+extern int si_debug_flag;
+
+#define SIDBG0(flag, softp, format) \
+ if (si_debug_flags & (flag)) { \
+ si_log(softp, CE_WARN, format); \
+ }
+
+#define SIDBG1(flag, softp, format, arg1) \
+ if (si_debug_flags & (flag)) { \
+ si_log(softp, CE_WARN, format, arg1); \
+ }
+
+#define SIDBG2(flag, softp, format, arg1, arg2) \
+ if (si_debug_flags & (flag)) { \
+ si_log(softp, CE_WARN, format, arg1, arg2); \
+ }
+
+#define SIDBG3(flag, softp, format, arg1, arg2, arg3) \
+ if (si_debug_flags & (flag)) { \
+ si_log(softp, CE_WARN, format, arg1, arg2, arg3); \
+ }
+
+#define SIDBG4(flag, softp, format, arg1, arg2, arg3, arg4) \
+ if (si_debug_flags & (flag)) { \
+ si_log(softp, CE_WARN, format, arg1, arg2, arg3, arg4); \
+ }
+#else
+
+#define SIDBG0(flag, dip, frmt)
+#define SIDBG1(flag, dip, frmt, arg1)
+#define SIDBG2(flag, dip, frmt, arg1, arg2)
+#define SIDBG3(flag, dip, frmt, arg1, arg2, arg3)
+#define SIDBG4(flag, dip, frmt, arg1, arg2, arg3, arg4)
+
+#endif /* DEBUG */
+
+/* Flags controlling the reset behavior */
+#define SI_PORT_RESET 0x1 /* Reset the port */
+#define SI_DEVICE_RESET 0x2 /* Reset the device, not the port */
+#define SI_RESET_NO_EVENTS_UP 0x4 /* Don't send reset events up */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SI3124VAR_H */
diff --git a/usr/src/uts/common/sys/sata/impl/sata.h b/usr/src/uts/common/sys/sata/impl/sata.h
new file mode 100644
index 0000000000..5d4d7b3fcd
--- /dev/null
+++ b/usr/src/uts/common/sys/sata/impl/sata.h
@@ -0,0 +1,694 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SATA_H
+#define _SATA_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Generic SATA Host Adapter Implementation
+ */
+
+#include <sys/types.h>
+#include <sys/scsi/scsi.h>
+#include <sys/scsi/impl/services.h>
+#include <sys/sata/sata_defs.h>
+#include <sys/sata/sata_hba.h>
+
+/* Statistics counters */
+struct sata_port_stats {
+ uint64_t link_lost; /* event counter */
+ uint64_t link_established; /* event counter */
+ uint64_t device_attached; /* event counter */
+ uint64_t device_detached; /* event counter */
+ uint64_t port_reset; /* event counter */
+ uint64_t port_pwr_changed; /* event counter */
+};
+
+typedef struct sata_port_stats sata_port_stats_t;
+
+struct sata_drive_stats {
+ uint64_t media_error; /* available ??? */
+ uint64_t drive_reset; /* event counter */
+} sata_drv_stats_t;
+
+typedef struct sata_drive_stats sata_drive_stats_t;
+
+struct sata_ctrl_stats {
+ uint64_t ctrl_reset; /* event counter */
+ uint64_t ctrl_pwr_change; /* event counter */
+};
+
+typedef struct sata_ctrl_stats sata_ctrl_stats_t;
+
+
+/*
+ * SATA HBA instance info structure
+ */
+struct sata_hba_inst {
+ dev_info_t *satahba_dip; /* this HBA instance devinfo */
+ struct sata_hba_inst *satahba_next; /* ptr to next sata_hba_inst */
+ struct sata_hba_inst *satahba_prev; /* ptr to prev sata_hba_inst */
+ struct scsi_hba_tran *satahba_scsi_tran; /* scsi_hba_tran */
+ struct sata_hba_tran *satahba_tran; /* sata_hba_tran */
+ kmutex_t satahba_mutex; /* sata hba cntrl mutex */
+
+ /*
+ * HBA event flags:
+ * SATA_EVNT_MAIN
+ * SATA_EVNT_PWR_LEVEL_CHANGED
+ * SATA_EVNT_SKIP
+ */
+ uint_t satahba_event_flags;
+
+ struct sata_cport_info *satahba_dev_port[SATA_MAX_CPORTS];
+
+ /*
+ * DEVCTL open flag:
+ * SATA_DEVCTL_SOPENED
+ * SATA_DEVCTL_EXOPENED
+ */
+ uint_t satahba_open_flag; /* shared open flag */
+ struct sata_ctrl_stats satahba_stats; /* HBA cntrl statistics */
+
+ uint_t satahba_attached; /* HBA attaching: */
+ /* 0 - not completed */
+ /* 1 - completed */
+};
+
+typedef struct sata_hba_inst sata_hba_inst_t;
+
+/*
+ * SATA controller's device port info and state.
+ * This structure is pointed to by the sata_hba_inst.satahba_dev_port[x]
+ * where x is a device port number.
+ * cport_state holds port state flags, defined in sata_hba.h file.
+ * cport_event_flags holds SATA_EVNT_* flags defined in this file and in
+ * sata_hba.h file.
+ * cport_dev_type holds SATA_DTYPE_* types defined in sata_hba.h file.
+ */
+struct sata_cport_info {
+ sata_address_t cport_addr; /* this port SATA address */
+ kmutex_t cport_mutex; /* port mutex */
+
+ /*
+ * Port state flags
+ * SATA_STATE_UNKNOWN
+ * SATA_STATE_PROBING
+ * SATA_STATE_PROBED
+ * SATA_STATE_READY
+ * SATA_PSTATE_PWRON
+ * SATA_PSTATE_PWROFF
+ * SATA_PSTATE_SHUTDOWN
+ * SATA_PSTATE_FAILED
+ */
+ uint32_t cport_state;
+
+ /*
+ * Port event flags:
+ * SATA_EVNT_DEVICE_ATTACHED
+ * SATA_EVNT_DEVICE_DETACHED
+ * SATA_EVNT_LINK_LOST
+ * SATA_EVNT_LINK_ESTABLISHED
+ * SATA_EVNT_PORT_FAILED
+ * SATA_EVNT_PWR_LEVEL_CHANGED
+ */
+ uint32_t cport_event_flags;
+
+ struct sata_port_scr cport_scr; /* Port status and ctrl regs */
+
+ /*
+ * Attached device type:
+ * SATA_DTYPE_NONE
+ * SATA_DTYPE_ATADISK
+ * SATA_DTYPE_ATAPICD
+ * SATA_DTYPE_ATAPINONCD
+ * SATA_DTYPE_PMULT
+ * SATA_DTYPE_UNKNOWN
+ */
+ uint32_t cport_dev_type;
+ union {
+ struct sata_drive_info *cport_sata_drive; /* Attached drive info */
+ struct sata_pmult_info *cport_sata_pmult; /* Attached Port Mult */
+ } cport_devp;
+ /* lbolt value at link lost */
+ clock_t cport_link_lost_time;
+
+ struct sata_port_stats cport_stats; /* Port statistics */
+};
+
+typedef struct sata_cport_info sata_cport_info_t;
+
+/*
+ * Attached SATA drive info and state.
+ * This structure is pointed to by sata_cport_info's cport_sata_drive field
+ * when a drive is attached directly to a controller device port.
+ */
+struct sata_drive_info {
+ sata_address_t satadrv_addr; /* this drive SATA address */
+
+ /*
+ * Drive state flags
+ * SATA_STATE_UNKNOWN
+ * SATA_STATE_PROBING
+ * SATA_STATE_PROBED
+ * SATA_STATE_READY
+ * SATA_DSTATE_PWR_ACTIVE
+ * SATA_DSTATE_PWR_IDLE
+ * SATA_DSTATE_RESET
+ * SATA_DSTATE_FAILED
+ */
+ uint32_t satadrv_state;
+
+ /*
+ * drive event flags:
+ * SATA_EVNT_DRIVE_RESET
+ */
+ uint32_t satadrv_event_flags;
+
+ /*
+ * Attached device type:
+ * SATA_DTYPE_ATADISK
+ * SATA_DTYPE_ATAPICD
+ * SATA_DTYPE_ATAPINONCD
+ */
+ uint32_t satadrv_type;
+
+ uint32_t satadrv_status_reg; /* drive status reg */
+ uint32_t satadrv_error_reg; /* drive error reg */
+ uint16_t satadrv_features_support; /* drive features support */
+ uint16_t satadrv_queue_depth; /* drive queue depth */
+ uint16_t satadrv_settings; /* drive settings flags */
+ uint16_t satadrv_pad2; /* struct alignment pad */
+ uint64_t satadrv_capacity; /* drive capacity */
+ sata_id_t satadrv_id; /* Device Identify Data */
+ struct sata_drive_stats satadrv_stats; /* drive statistics */
+};
+
+typedef struct sata_drive_info sata_drive_info_t;
+
+_NOTE(SCHEME_PROTECTS_DATA("unshared data", sata_drive_info))
+
+
+/* Port Multiplier & host port info and state */
+struct sata_pmult_info {
+ sata_address_t pmult_addr; /* this PMult SATA Address */
+ kmutex_t pmult_mutex; /* pmult (host port) mutex */
+
+ /*
+ * PMult state flags
+ * SATA_STATE_UNKNOWN
+ * SATA_STATE_PROBING
+ * SATA_STATE_PROBED
+ * SATA_STATE_READY
+ * SATA_PSTATE_FAILED
+ */
+ uint32_t pmult_state;
+ uint32_t pmult_event_flags; /* Undefined for now */
+ struct sata_port_scr pmult_scr; /* Host port SCR block */
+ uint32_t pmult_num_dev_ports; /* Number of data ports */
+ struct sata_pmport_info *pmult_dev_port[SATA_MAX_PMPORTS - 1];
+};
+
+typedef struct sata_pmult_info sata_pmult_info_t;
+
+/* Port Multiplier's device port info & state */
+struct sata_pmport_info {
+ sata_address_t pmport_addr; /* this SATA port address */
+ kmutex_t pmport_mutex; /* pmult device port mutex */
+
+ /*
+ * Port state flags
+ * SATA_STATE_UNKNOWN
+ * SATA_STATE_PROBING
+ * SATA_STATE_PROBED
+ * SATA_STATE_READY
+ * SATA_PSTATE_PWRON
+ * SATA_PSTATE_PWROFF
+ * SATA_PSTATE_SHUTDOWN
+ * SATA_PSTATE_FAILED
+ */
+ uint32_t pmport_state;
+
+ /*
+ * Port event flags:
+ * SATA_EVNT_DEVICE_ATTACHED
+ * SATA_EVNT_DEVICE_DETACHED
+ * SATA_EVNT_LINK_LOST
+ * SATA_EVNT_LINK_ESTABLISHED
+ * SATA_EVNT_PORT_FAILED
+ * SATA_EVNT_PWR_LEVEL_CHANGED
+ */
+ uint32_t pmport_event_flags;
+
+ struct sata_port_scr pmport_scr; /* PMult device port scr */
+
+ /*
+ * Attached device type:
+ * SATA_DTYPE_NONE
+ * SATA_DTYPE_ATADISK
+ * SATA_DTYPE_ATAPICD
+ * SATA_DTYPE_ATAPINONCD
+ * SATA_DTYPE_UNKNOWN
+ */
+ uint32_t pmport_dev_type;
+
+ struct sata_drive_info *pmport_sata_drive; /* Attached drive info */
+
+ /* lbolt value at link lost */
+ clock_t pmport_link_lost_time;
+
+ struct sata_port_stats pmport_stats; /* Port statistics */
+};
+
+typedef struct sata_pmport_info sata_pmport_info_t;
+
+/*
+ * Port SSTATUS register (sata_port_scr sport_sstatus field).
+ * Link bits are valid only in port active state.
+ */
+#define SATA_PORT_DEVLINK_UP 0x00000103 /* Link with dev established */
+#define SATA_PORT_DEVLINK_UP_MASK 0x0000010F /* Mask for link bits */
+
+/*
+ * Port state clear mask (cport_state and pmport_state fields).
+ * SATA_PSTATE_SHUTDOWN and power state are preserved.
+ */
+#define SATA_PORT_STATE_CLEAR_MASK (~(SATA_PSTATE_SHUTDOWN))
+
+/*
+ * Valid i.e.supported device types mask (cport_dev_type, satadrv_type,
+ * pmult_dev_type fields).
+ */
+#define SATA_VALID_DEV_TYPE (SATA_DTYPE_ATADISK) /* only disks now */
+
+/*
+ * Device feature_support (satadrv_features_support)
+ */
+#define SATA_DEV_F_DMA 0x01
+#define SATA_DEV_F_LBA28 0x02
+#define SATA_DEV_F_LBA48 0x04
+#define SATA_DEV_F_NCQ 0x08
+#define SATA_DEV_F_SATA1 0x10
+#define SATA_DEV_F_SATA2 0x20
+
+/*
+ * Drive settings flags (satdrv_settings)
+ */
+#define SATA_DEV_READ_AHEAD 0x0001 /* Read Ahead enabled */
+#define SATA_DEV_WRITE_CACHE 0x0002 /* Write cache ON */
+#define SATA_DEV_SERIAL_FEATURES 0x8000 /* Serial ATA feat. enabled */
+#define SATA_DEV_ASYNCH_NOTIFY 0x2000 /* Asynch-event enabled */
+
+
+/*
+ * Internal event and flags.
+ * These flags are set in the *_event_flags fields of various structures.
+ * Events and lock flags defined below are used internally by the
+ * SATA framework (they are not reported by SATA HBA drivers).
+ */
+#define SATA_EVNT_MAIN 0x80000000
+#define SATA_EVNT_SKIP 0x40000000
+#define SATA_EVNT_INPROC_DEVICE_RESET 0x08000000
+#define SATA_EVNT_CLEAR_DEVICE_RESET 0x04000000
+
+/*
+ * Lock flags - used to serialize configuration operations
+ * on ports and devices.
+ * SATA_EVNT_LOCK_PORT_BUSY is set by event daemon to prevent
+ * simultaneous cfgadm operations.
+ * SATA_APCTL_LOCK_PORT_BUSY is set by cfgadm ioctls to prevent
+ * simultaneous event processing.
+ */
+#define SATA_EVNT_LOCK_PORT_BUSY 0x00800000
+#define SATA_APCTL_LOCK_PORT_BUSY 0x00400000
+
+/* Mask for port events */
+#define SATA_EVNT_PORT_EVENTS (SATA_EVNT_DEVICE_ATTACHED | \
+ SATA_EVNT_DEVICE_DETACHED | \
+ SATA_EVNT_LINK_LOST | \
+ SATA_EVNT_LINK_ESTABLISHED | \
+ SATA_EVNT_PORT_FAILED)
+/* Mask for drive events */
+#define SATA_EVNT_DRIVE_EVENTS SATA_EVNT_DEVICE_RESET
+#define SATA_EVNT_CONTROLLER_EVENTS SATA_EVNT_PWR_LEVEL_CHANGED
+
+/* Delays and timeounts definitions */
+#define SATA_EVNT_DAEMON_SLEEP_TIME 50000 /* 50 ms */
+#define SATA_EVNT_DAEMON_TERM_TIMEOUT 100000 /* 100 ms */
+#define SATA_EVNT_DAEMON_TERM_WAIT 60000000 /* 60 s */
+#define SATA_EVNT_LINK_LOST_TIMEOUT 1000000 /* 1 s */
+
+#define SATA_DEVICE_IDENTIFY_RETRY 2
+
+/*
+ * sata_scsi's hba_open_flag: field indicating open devctl instance.
+ * 0 = closed, 1 = shared open, 2 = exclusive open.
+ */
+#define SATA_DEVCTL_CLOSED 0
+#define SATA_DEVCTL_SOPENED 1
+#define SATA_DEVCTL_EXOPENED 2
+
+/*
+ * sata_pkt_txlate structure contains info about resources allocated
+ * for the packet
+ * Address of this structure is stored in scsi_pkt.pkt_ha_private and
+ * in sata_pkt.sata_hba_private fields, so all three strucures are
+ * cross-linked, with sata_pkt_txlate as a centerpiece.
+ */
+
+typedef struct sata_pkt_txlate {
+ struct sata_hba_inst *txlt_sata_hba_inst;
+ struct scsi_pkt *txlt_scsi_pkt;
+ struct sata_pkt *txlt_sata_pkt;
+ ddi_dma_handle_t txlt_buf_dma_handle;
+ uint_t txlt_flags; /* data-in / data-out */
+ uint_t txlt_num_dma_win; /* number of DMA windows */
+ uint_t txlt_cur_dma_win; /* current DMA window */
+
+ /* cookies in the current DMA window */
+ uint_t txlt_curwin_num_dma_cookies;
+
+ /* procesed dma cookies in current DMA win */
+ uint_t txlt_curwin_processed_dma_cookies;
+ size_t txlt_total_residue;
+ int txlt_dma_cookie_list_len; /* alloc list len */
+ ddi_dma_cookie_t *txlt_dma_cookie_list; /* dma cookie list */
+ int txlt_num_dma_cookies; /* dma cookies in list */
+} sata_pkt_txlate_t;
+
+_NOTE(SCHEME_PROTECTS_DATA("unshared data", sata_pkt_txlate))
+_NOTE(SCHEME_PROTECTS_DATA("unshared data", scsi_pkt))
+
+
+/*
+ * Additional scsi sense code definitions.
+ * These definition should eventually be moved to scsi header files.
+ */
+#define SD_SCSI_NO_ADD_SENSE 0x00
+#define SD_SCSI_LU_NOT_READY 0x04
+#define SD_SCSI_WRITE_ERROR 0x0c
+#define SD_SCSI_UNREC_READ_ERROR 0x11
+#define SD_SCSI_INVALID_COMMAND_CODE 0x20
+#define SD_SCSI_LBA_OUT_OF_RANGE 0x21
+#define SD_SCSI_INVALID_FIELD_IN_CDB 0x24
+#define SD_SCSI_INVALID_FIELD_IN_PARAMETER_LIST 0x26
+#define SD_SCSI_SAVING_PARAMS_NOT_SUP 0x39
+
+
+/* SCSI defs missing from scsi headers */
+/* Missing from sys/scsi/generic/commands.h */
+#define SCMD_SYNCHRONIZE_CACHE_G1 0x91
+/*
+ * Missing from sys/scsi/impl/mode.h, although defined
+ * in sys/scsi/targets/sddefs.h as MODEPAGE_ERR_RECOV
+ */
+#define MODEPAGE_RW_ERRRECOV 0x01 /* read/write recovery */
+
+/*
+ * Macros for accessing various structure fields
+ *
+ */
+
+#define SATA_TRAN(sata_hba_inst) \
+ sata_hba_inst->satahba_tran
+
+#define SATA_DIP(sata_hba_inst) \
+ sata_hba_inst->satahba_dip
+
+#define SATA_NUM_CPORTS(sata_hba_inst) \
+ sata_hba_inst->satahba_tran->sata_tran_hba_num_cports
+
+#define SATA_QDEPTH(sata_hba_inst) \
+ sata_hba_inst->satahba_tran->sata_tran_hba_qdepth
+
+#define SATA_FEATURES(sata_hba_inst) \
+ sata_hba_inst->satahba_tran->sata_tran_hba_features_support
+
+#define SATA_DMA_ATTR(sata_hba_inst) \
+ sata_hba_inst->satahba_tran->sata_tran_hba_dma_attr
+
+#define SATA_START_FUNC(sata_hba_inst) \
+ sata_hba_inst->satahba_tran->sata_tran_start
+
+#define SATA_ABORT_FUNC(sata_hba_inst) \
+ sata_hba_inst->satahba_tran->sata_tran_abort
+
+#define SATA_RESET_DPORT_FUNC(sata_hba_inst) \
+ sata_hba_inst->satahba_tran->sata_tran_reset_dport
+
+#define SATA_PORT_DEACTIVATE_FUNC(sata_hba_inst) \
+ (sata_hba_inst->satahba_tran->sata_tran_hotplug_ops == NULL ? \
+ NULL : \
+ sata_hba_inst->satahba_tran->sata_tran_hotplug_ops->\
+ sata_tran_port_deactivate)
+
+#define SATA_PORT_ACTIVATE_FUNC(sata_hba_inst) \
+ (sata_hba_inst->satahba_tran->sata_tran_hotplug_ops == NULL ? \
+ NULL : \
+ sata_hba_inst->satahba_tran->sata_tran_hotplug_ops->\
+ sata_tran_port_activate)
+
+#define SATA_PROBE_PORT_FUNC(sata_hba_inst) \
+ sata_hba_inst->satahba_tran->sata_tran_probe_port
+
+#define SATA_SELFTEST_FUNC(sata_hba_inst) \
+ sata_hba_inst->satahba_tran->sata_tran_selftest
+
+#define SATA_CPORT_MUTEX(sata_hba_inst, cport) \
+ sata_hba_inst->satahba_dev_port[cport]->cport_mutex
+
+#define SATA_CPORT_INFO(sata_hba_inst, cport) \
+ sata_hba_inst->satahba_dev_port[cport]
+
+#define SATA_CPORT_STATE(sata_hba_inst, cport) \
+ sata_hba_inst->satahba_dev_port[cport]->cport_state
+
+#define SATA_CPORT_SCR(sata_hba_inst, cport) \
+ sata_hba_inst->satahba_dev_port[cport]->cport_scr
+
+#define SATA_CPORT_DEV_TYPE(sata_hba_inst, cport) \
+ sata_hba_inst->satahba_dev_port[cport]->cport_dev_type
+
+#define SATA_CPORT_DRV_INFO(sata_hba_inst, cport) \
+ sata_hba_inst->satahba_dev_port[cport]->cport_devp.cport_sata_drive
+
+#define SATA_CPORTINFO_DRV_TYPE(cportinfo) \
+ cportinfo->cport_dev_type
+
+#define SATA_CPORTINFO_DRV_INFO(cportinfo) \
+ cportinfo->cport_devp.cport_sata_drive
+
+#define SATA_CPORTINFO_PMULT_INFO(cportinfo) \
+ cportinfo->cport_devp.cport_sata_pmult
+
+#define SATA_PMULT_INFO(sata_hba_inst, cport) \
+ sata_hba_inst->satahba_dev_port[cport]->cport_devp.cport_sata_pmult
+
+#define SATA_NUM_PMPORTS(sata_hba_inst, cport) \
+ sata_hba_inst->satahba_dev_port[cport]->\
+ cport_devp.cport_sata_pmult->pmult_num_dev_ports
+
+#define SATA_PMPORT_INFO(sata_hba_inst, cport, pmport) \
+ sata_hba_inst->satahba_dev_port[cport]->\
+ cport_devp.cport_sata_pmult->pmult_dev_port[pmport]
+
+#define SATA_PMPORT_DRV_INFO(sata_hba_inst, cport, pmport) \
+ sata_hba_inst->satahba_dev_port[cport]->\
+ cport_devp.cport_sata_pmult->pmult_dev_port[pmport]->\
+ pmport_sata_drive
+
+#define SATA_PMPORT_STATE(sata_hba_inst, cport, pmport) \
+ sata_hba_inst->satahba_dev_port[cport]->\
+ cport_devp.cport_sata_pmult->pmult_dev_port[pmport]->pmport_state
+
+#define SATA_PMPORT_SCR(sata_hba_inst, cport, pmport) \
+ sata_hba_inst->satahba_dev_port[cport]->\
+ cport_devp.cport_sata_pmult->pmult_dev_port[pmport]->pmport_scr
+
+#define SATA_PMPORT_DEV_TYPE(sata_hba_inst, cport, pmport) \
+ sata_hba_inst->satahba_dev_port[cport]->\
+ cport_devp.cport_sata_pmult->pmult_dev_port[pmport]->pmport_dev_type
+
+#define SATA_TXLT_HBA_INST(spx) \
+ spx->txlt_sata_hba_inst
+
+#define SATA_TXLT_CPORT(spx) \
+ spx->txlt_sata_pkt->satapkt_device.satadev_addr.cport
+
+#define SATA_TXLT_CPORT_MUTEX(spx) \
+ spx->txlt_sata_hba_inst->\
+ satahba_dev_port[spx->txlt_sata_pkt->\
+ satapkt_device.satadev_addr.cport]->cport_mutex
+
+/*
+ * Minor number construction for devctl and attachment point nodes.
+ * All necessary information has to be encoded in NBITSMINOR32 bits.
+ *
+ * Devctl node minor number:
+ * ((controller_instance << SATA_CNTRL_INSTANCE_SHIFT) | SATA_DEVCTL_NODE)
+ *
+ * Attachment point node minor number has to include controller
+ * instance (7 bits), controller port number (5 bits) and port multiplier
+ * device port number (4 bits) and port multiplier device port
+ * indicator (1 bit). Additionally, a single bit is used to
+ * differentiate between attachment point node and device control node.
+ *
+ * Attachment point minor number:
+ * ((controller_instance << SATA_CNTRL_INSTANCE_SHIFT) | SATA_AP_NODE |
+ * [(port_multiplier_device_port << SATA_PMULT_PORT_SHIFT) | SATA_PMULT_AP] |
+ * (controller_port))
+ *
+ * 17 bits are used (if 64 instances of controllers are expected)
+ * bit 18 is reserved for future use.
+ *
+ * --------------------------------------------------------
+ * |17|16|15|14|13|12|11|10 |09|08|07|06|05|04|03|02|01|00|
+ * --------------------------------------------------------
+ * | R| c| c| c| c| c| c|a/d|pm|pp|pp|pp|pp|cp|cp|cp|cp|cp|
+ * --------------------------------------------------------
+ * Where:
+ * cp - device port number on the HBA SATA controller
+ * pp - device port number on the port multiplier
+ * pm - 0 - target attached to controller device port
+ * 1 - target attached to port multiplier's device port
+ * a/d - 0 - devctl node
+ * 1 - attachment point node
+ * c - controller number
+ * R - reserved bit
+ */
+
+#define SATA_AP_NODE 0x400 /* Attachment Point node */
+#define SATA_DEVCTL_NODE 0x000 /* DEVCTL node */
+#define SATA_PMULT_AP 0x200 /* device on PMult port */
+#define SATA_PMULT_PORT_SHIFT 5
+#define SATA_CNTRL_INSTANCE_SHIFT 11
+#define SATA_CPORT_MASK 0x1f /* 32 device ports */
+#define SATA_PMULT_PORT_MASK 0xf /* 15 device ports */
+#define SATA_CNTRL_INSTANCE_MASK 0x03F /* 64 controllers */
+
+/* Macro for creating devctl node minor number */
+#define SATA_MAKE_DEVCTL_MINOR(controller_instance) \
+ ((controller_instance << SATA_CNTRL_INSTANCE_SHIFT) | \
+ SATA_DEVCTL_NODE)
+
+/* Macro for creating an attachment point node minor number */
+#define SATA_MAKE_AP_MINOR(cntrl_instance, cport, pmport, qual) \
+ (qual & (SATA_ADDR_PMPORT | SATA_ADDR_DPMPORT) ? \
+ (((cntrl_instance) << SATA_CNTRL_INSTANCE_SHIFT) | \
+ SATA_AP_NODE | SATA_PMULT_AP | \
+ (pmport << SATA_PMULT_PORT_SHIFT) | cport) : \
+ (((cntrl_instance) << SATA_CNTRL_INSTANCE_SHIFT) | \
+ SATA_AP_NODE | cport))
+
+/* Macro retrieving controller number from a minor number */
+#define SATA_MINOR2INSTANCE(minor) \
+ ((minor >> SATA_CNTRL_INSTANCE_SHIFT) & SATA_CNTRL_INSTANCE_MASK)
+
+
+/*
+ * SCSI target number format
+ *
+ * -------------------------------
+ * | 9| 8| 7| 6| 5| 4| 3| 2| 1| 0| Bit number
+ * -------------------------------
+ * |pm|pp|pp|pp|pp|cp|cp|cp|cp|cp|
+ * -------------------------------
+ * Where:
+ * cp - device port number on the HBA SATA controller
+ * pp - device port number on the port multiplier
+ * pm - 0 - target attached to controller device port
+ * 1 - target attached to port multiplier's device port
+ */
+
+/* SATA ports to SCSI target number translation */
+
+#define SATA_TO_SCSI_TARGET(cport, pmport, qual) \
+ (qual == SATA_ADDR_DCPORT ? cport : \
+ (cport | (pmport << SATA_PMULT_PORT_SHIFT) | SATA_PMULT_AP))
+
+/* SCSI target number to SATA cntrl/pmport/cport translations */
+#define SCSI_TO_SATA_CPORT(scsi_target) \
+ (scsi_target & SATA_CPORT_MASK)
+
+#define SCSI_TO_SATA_PMPORT(scsi_target) \
+ ((scsi_target >> SATA_PMULT_PORT_SHIFT) & SATA_PMULT_PORT_MASK)
+
+#define SCSI_TO_SATA_ADDR_QUAL(scsi_target) \
+ ((scsi_target & SATA_PMULT_AP) ? SATA_ADDR_DPMPORT : \
+ SATA_ADDR_DCPORT)
+
+
+/* Debug flags */
+#if DEBUG
+
+#define SATA_DEBUG
+#define SATA_DBG_SCSI_IF 1
+#define SATA_DBG_HBA_IF 2
+#define SATA_DBG_NODES 4
+#define SATA_DBG_IOCTL_IF 8
+#define SATA_DBG_EVENTS 0x10
+#define SATA_DBG_EVENTS_PROC 0x20
+#define SATA_DBG_EVENTS_PROCPST 0x40
+#define SATA_DBG_EVENTS_CNTRL 0x80
+#define SATA_DBG_EVENTS_DAEMON 0x100
+#define SATA_DBG_DMA_SETUP 0x400
+
+extern int sata_debug_flag;
+
+/* Debug macros */
+#define SATADBG1(flag, sata, format, arg1) \
+ if (sata_debug_flags & (flag)) { \
+ sata_log(sata, CE_CONT, format, arg1); \
+ }
+
+#define SATADBG2(flag, sata, format, arg1, arg2) \
+ if (sata_debug_flags & (flag)) { \
+ sata_log(sata, CE_CONT, format, arg1, arg2); \
+ }
+
+#define SATADBG3(flag, sata, format, arg1, arg2, arg3) \
+ if (sata_debug_flags & (flag)) { \
+ sata_log(sata, CE_CONT, format, arg1, arg2, arg3); \
+ }
+#else
+
+#define SATADBG1(flag, dip, frmt, arg1)
+#define SATADBG2(flag, dip, frmt, arg1, arg2)
+#define SATADBG3(flag, dip, frmt, arg1, arg2, arg3)
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SATA_H */
diff --git a/usr/src/uts/common/sys/sata/sata_cfgadm.h b/usr/src/uts/common/sys/sata/sata_cfgadm.h
new file mode 100644
index 0000000000..9d0376e902
--- /dev/null
+++ b/usr/src/uts/common/sys/sata/sata_cfgadm.h
@@ -0,0 +1,90 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SATA_CFGADM_H
+#define _SATA_CFGADM_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* SATA cfgadm plugin interface definitions */
+
+/*
+ * Sub-commands of DEVCTL_AP_CONTROL.
+ */
+typedef enum {
+ SATA_CFGA_GET_AP_TYPE = 1,
+ SATA_CFGA_GET_MODEL_INFO,
+ SATA_CFGA_GET_REVFIRMWARE_INFO,
+ SATA_CFGA_GET_SERIALNUMBER_INFO,
+ SATA_CFGA_RESET_PORT,
+ SATA_CFGA_RESET_DEVICE,
+ SATA_CFGA_RESET_ALL,
+ SATA_CFGA_PORT_DEACTIVATE,
+ SATA_CFGA_PORT_ACTIVATE,
+ SATA_CFGA_PORT_SELF_TEST,
+ SATA_CFGA_GET_DEVICE_PATH
+} sata_cfga_apctl_t;
+
+/* SATA cfgadm plugin interface implementation definitions */
+
+typedef struct sata_ioctl_data {
+ uint_t cmd; /* one of the above commands */
+ uint_t port; /* port */
+ uint_t get_size; /* get size/data flag */
+ caddr_t buf; /* data buffer */
+ uint_t bufsiz; /* data buffer size */
+ uint_t misc_arg; /* reserved */
+} sata_ioctl_data_t;
+
+
+/* For 32-bit app/64-bit kernel */
+typedef struct sata_ioctl_data_32 {
+ uint32_t cmd; /* one of the above commands */
+ uint32_t port; /* port */
+ uint32_t get_size; /* get size/data flag */
+ caddr32_t buf; /* data buffer */
+ uint32_t bufsiz; /* data buffer size */
+ uint32_t misc_arg; /* reserved */
+} sata_ioctl_data_32_t;
+
+/*
+ * Port encoding for ioctl "port" parameter - corresponds to
+ * scsi target encoding for sata devices
+ */
+#define SATA_CFGA_CPORT_MASK 0x1f
+#define SATA_CFGA_PMPORT_MASK 0xf
+#define SATA_CFGA_PMPORT_SHIFT 0x5
+#define SATA_CFGA_PMPORT_QUAL 0x200
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SATA_CFGADM_H */
diff --git a/usr/src/uts/common/sys/sata/sata_defs.h b/usr/src/uts/common/sys/sata/sata_defs.h
new file mode 100644
index 0000000000..034c321ce8
--- /dev/null
+++ b/usr/src/uts/common/sys/sata/sata_defs.h
@@ -0,0 +1,339 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SATA_DEFS_H
+#define _SATA_DEFS_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Common ATA commands (subset)
+ */
+#define SATAC_DIAG 0x90 /* diagnose command */
+#define SATAC_RECAL 0x10 /* restore cmd, 4 bits step rate */
+#define SATAC_FORMAT 0x50 /* format track command */
+#define SATAC_SET_FEATURES 0xef /* set features */
+#define SATAC_IDLE_IM 0xe1 /* idle immediate */
+#define SATAC_STANDBY_IM 0xe0 /* standby immediate */
+#define SATAC_DOOR_LOCK 0xde /* door lock */
+#define SATAC_DOOR_UNLOCK 0xdf /* door unlock */
+#define SATAC_IDLE 0xe3 /* idle */
+
+/*
+ * ATA/ATAPI disk commands (subset)
+ */
+#define SATAC_DEVICE_RESET 0x08 /* ATAPI device reset */
+#define SATAC_EJECT 0xed /* media eject */
+#define SATAC_FLUSH_CACHE 0xe7 /* flush write-cache */
+#define SATAC_ID_DEVICE 0xec /* IDENTIFY DEVICE */
+#define SATAC_ID_PACKET_DEVICE 0xa1 /* ATAPI identify packet device */
+#define SATAC_INIT_DEVPARMS 0x91 /* initialize device parameters */
+#define SATAC_PACKET 0xa0 /* ATAPI packet */
+#define SATAC_RDMULT 0xc4 /* read multiple w/DMA */
+#define SATAC_RDSEC 0x20 /* read sector */
+#define SATAC_RDVER 0x40 /* read verify */
+#define SATAC_READ_DMA 0xc8 /* read DMA */
+#define SATAC_SEEK 0x70 /* seek */
+#define SATAC_SERVICE 0xa2 /* queued/overlap service */
+#define SATAC_SETMULT 0xc6 /* set multiple mode */
+#define SATAC_WRITE_DMA 0xca /* write (multiple) w/DMA */
+#define SATAC_WRMULT 0xc5 /* write multiple */
+#define SATAC_WRSEC 0x30 /* write sector */
+#define SATAC_RDSEC_EXT 0x24 /* read sector extended (LBA48) */
+#define SATAC_READ_DMA_EXT 0x25 /* read DMA extended (LBA48) */
+#define SATAC_RDMULT_EXT 0x29 /* read multiple extended (LBA48) */
+#define SATAC_WRSEC_EXT 0x34 /* read sector extended (LBA48) */
+#define SATAC_WRITE_DMA_EXT 0x35 /* read DMA extended (LBA48) */
+#define SATAC_WRMULT_EXT 0x39 /* read multiple extended (LBA48) */
+
+#define SATAC_READ_DMA_QUEUED 0xc7 /* read DMA / may be queued */
+#define SATAC_READ_DMA_QUEUED_EXT 0x26 /* read DMA ext / may be queued */
+#define SATAC_WRITE_DMA_QUEUED 0xcc /* read DMA / may be queued */
+#define SATAC_WRITE_DMA_QUEUED_EXT 0x36 /* read DMA ext / may be queued */
+#define SATAC_READ_PM_REG 0xe4 /* read port mult reg */
+#define SATAC_WRITE_PM_REG 0xe8 /* write port mult reg */
+
+#define SATAC_READ_FPDMA_QUEUED 0x60 /* First-Party-DMA read queued */
+#define SATAC_WRITE_FPDMA_QUEUED 0x61 /* First-Party-DMA write queued */
+
+#define SATAC_READ_LOG_EXT 0x2f /* read log */
+#define SATA_LOG_PAGE_10 0x10 /* log page 0x10 - SATA error */
+/*
+ * Power Managment Commands (subset)
+ */
+#define SATAC_CHECK_POWER_MODE 0xe5 /* check power mode */
+
+#define SATA_PWRMODE_STANDBY 0 /* standby mode */
+#define SATA_PWRMODE_IDLE 0x80 /* idle mode */
+#define SATA_PWRMODE_ACTIVE 0xFF /* active or idle mode, rev7 spec */
+
+
+/*
+ * SET FEATURES Subcommands
+ */
+#define SATAC_SF_ENABLE_WRITE_CACHE 0x02
+#define SATAC_SF_TRANSFER_MODE 0x03
+#define SATAC_SF_DISABLE_READ_AHEAD 0x55
+#define SATAC_SF_DISABLE_WRITE_CACHE 0x82
+#define SATAC_SF_ENABLE_READ_AHEAD 0xaa
+
+/*
+ * SET FEATURES transfer mode values
+ */
+#define SATAC_TRANSFER_MODE_PIO_DEFAULT 0x00
+#define SATAC_TRANSFER_MODE_PIO_DISABLE_IODRY 0x01
+#define SATAC_TRANSFER_MODE_PIO_FLOW_CONTROL 0x08
+#define SATAC_TRANSFER_MODE_MULTI_WORD_DMA 0x20
+#define SATAC_TRANSFER_MODE_ULTRA_DMA 0x40
+
+/* Generic ATA definitions */
+
+/*
+ * Identify Device data
+ * Although bot ATA and ATAPI devices' Identify Data has the same lenght,
+ * some words have different meaning/content and/or are irrelevant for
+ * other type of device.
+ * Following is the ATA Device Identify data layout
+ */
+typedef struct sata_id {
+/* WORD */
+/* OFFSET COMMENT */
+ ushort_t ai_config; /* 0 general configuration bits */
+ ushort_t ai_fixcyls; /* 1 # of cylinders (obsolete) */
+ ushort_t ai_resv0; /* 2 # reserved */
+ ushort_t ai_heads; /* 3 # of heads (obsolete) */
+ ushort_t ai_trksiz; /* 4 # of bytes/track (retired) */
+ ushort_t ai_secsiz; /* 5 # of bytes/sector (retired) */
+ ushort_t ai_sectors; /* 6 # of sectors/track (obsolete) */
+ ushort_t ai_resv1[3]; /* 7 "Vendor Unique" */
+ char ai_drvser[20]; /* 10 Serial number */
+ ushort_t ai_buftype; /* 20 Buffer type */
+ ushort_t ai_bufsz; /* 21 Buffer size in 512 byte incr */
+ ushort_t ai_ecc; /* 22 # of ecc bytes avail on rd/wr */
+ char ai_fw[8]; /* 23 Firmware revision */
+ char ai_model[40]; /* 27 Model # */
+ ushort_t ai_mult1; /* 47 Multiple command flags */
+ ushort_t ai_dwcap; /* 48 Doubleword capabilities */
+ ushort_t ai_cap; /* 49 Capabilities */
+ ushort_t ai_resv2; /* 50 Reserved */
+ ushort_t ai_piomode; /* 51 PIO timing mode */
+ ushort_t ai_dmamode; /* 52 DMA timing mode */
+ ushort_t ai_validinfo; /* 53 bit0: wds 54-58, bit1: 64-70 */
+ ushort_t ai_curcyls; /* 54 # of current cylinders */
+ ushort_t ai_curheads; /* 55 # of current heads */
+ ushort_t ai_cursectrk; /* 56 # of current sectors/track */
+ ushort_t ai_cursccp[2]; /* 57 current sectors capacity */
+ ushort_t ai_mult2; /* 59 multiple sectors info */
+ ushort_t ai_addrsec[2]; /* 60 LBA only: no of addr secs */
+ ushort_t ai_sworddma; /* 62 single word dma modes */
+ ushort_t ai_dworddma; /* 63 double word dma modes */
+ ushort_t ai_advpiomode; /* 64 advanced PIO modes supported */
+ ushort_t ai_minmwdma; /* 65 min multi-word dma cycle info */
+ ushort_t ai_recmwdma; /* 66 rec multi-word dma cycle info */
+ ushort_t ai_minpio; /* 67 min PIO cycle info */
+ ushort_t ai_minpioflow; /* 68 min PIO cycle info w/flow ctl */
+ ushort_t ai_resv3[2]; /* 69,70 reserved */
+ ushort_t ai_typtime[2]; /* 71-72 timing */
+ ushort_t ai_resv4[2]; /* 73-74 reserved */
+ ushort_t ai_qdepth; /* 75 queue depth */
+ ushort_t ai_satacap; /* 76 SATA capabilities */
+ ushort_t ai_resv5; /* 77 reserved */
+ ushort_t ai_satafsup; /* 78 SATA features supported */
+ ushort_t ai_satafenbl; /* 79 SATA features enabled */
+ ushort_t ai_majorversion; /* 80 major versions supported */
+ ushort_t ai_minorversion; /* 81 minor version number supported */
+ ushort_t ai_cmdset82; /* 82 command set supported */
+ ushort_t ai_cmdset83; /* 83 more command sets supported */
+ ushort_t ai_cmdset84; /* 84 more command sets supported */
+ ushort_t ai_features85; /* 85 enabled features */
+ ushort_t ai_features86; /* 86 enabled features */
+ ushort_t ai_features87; /* 87 enabled features */
+ ushort_t ai_ultradma; /* 88 Ultra DMA mode */
+ ushort_t ai_erasetime; /* 89 security erase time */
+ ushort_t ai_erasetimex; /* 90 enhanced security erase time */
+ ushort_t ai_padding1[9]; /* pad through 99 */
+ ushort_t ai_addrsecxt[4]; /* 100 extended max LBA sector */
+ ushort_t ai_padding2[22]; /* pad to 126 */
+ ushort_t ai_lastlun; /* 126 last LUN, as per SFF-8070i */
+ ushort_t ai_resv6; /* 127 reserved */
+ ushort_t ai_securestatus; /* 128 security status */
+ ushort_t ai_vendor[31]; /* 129-159 vendor specific */
+ ushort_t ai_padding3[16]; /* 160 pad to 176 */
+ ushort_t ai_curmedser[30]; /* 176-205 current media serial number */
+ ushort_t ai_padding4[49]; /* 206 pad to 255 */
+ ushort_t ai_integrity; /* 255 integrity word */
+} sata_id_t;
+
+
+/* Identify Device: general config bits - word 0 */
+
+#define SATA_ATA_TYPE_MASK 0x8001 /* ATA Device type mask */
+#define SATA_ATA_TYPE 0x0000 /* ATA device */
+#define SATA_REM_MEDIA 0x0080 /* Removable media */
+
+#define SATA_ID_SERIAL_OFFSET 10
+#define SATA_ID_SERIAL_LEN 20
+#define SATA_ID_MODEL_OFFSET 27
+#define SATA_ID_MODEL_LEN 40
+
+/* Identify Device: common capability bits - word 49 */
+
+#define SATA_DMA_SUPPORT 0x0100
+#define SATA_LBA_SUPPORT 0x0200
+#define SATA_IORDY_DISABLE 0x0400
+#define SATA_IORDY_SUPPORT 0x0800
+#define SATA_STANDBYTIMER 0x2000
+
+/* Identify Device: ai_validinfo (word 53) */
+
+#define SATA_VALIDINFO_88 0x0004 /* word 88 supported fields valid */
+
+/* Identify Device: ai_majorversion (word 80) */
+
+#define SATA_MAJVER_6 0x0040 /* ATA/ATAPI-6 version supported */
+#define SATA_MAJVER_4 0x0010 /* ATA/ATAPI-4 version supported */
+
+/* Identify Device: command set supported/enabled bits - words 83 and 86 */
+
+#define SATA_EXT48 0x0400 /* 48 bit address feature */
+#define SATA_RW_DMA_QUEUED_CMD 0x0002 /* R/W DMA Queued supported */
+#define SATA_DWNLOAD_MCODE_CMD 0x0001 /* Download Microcode CMD supp/enbld */
+
+/* Identify Device: command set supported/enabled bits - words 82 and 85 */
+
+#define SATA_WRITE_CACHE 0x0020 /* Write Cache supported/enabled */
+#define SATA_LOOK_AHEAD 0x0040 /* Look Ahead supported/enabled */
+#define SATA_DEVICE_RESET_CMD 0x0200 /* Device Reset CMD supported/enbld */
+#define SATA_READ_BUFFER_CMD 0x2000 /* Read Buffer CMD supported/enbld */
+#define SATA_WRITE_BUFFER_CMD 0x1000 /* Write Buffer CMD supported/enbld */
+
+#define SATA_MDMA_SEL_MASK 0x0700 /* Multiword DMA selected */
+#define SATA_MDMA_2_SEL 0x0400 /* Multiword DMA mode 2 selected */
+#define SATA_MDMA_1_SEL 0x0200 /* Multiword DMA mode 1 selected */
+#define SATA_MDMA_0_SEL 0x0100 /* Multiword DMA mode 0 selected */
+#define SATA_MDMA_2_SUP 0x0004 /* Multiword DMA mode 2 supported */
+#define SATA_MDMA_1_SUP 0x0002 /* Multiword DMA mode 1 supported */
+#define SATA_MDMA_0_SUP 0x0001 /* Multiword DMA mode 0 supported */
+
+#define SATA_DISK_SECTOR_SIZE 512 /* HD physical sector size */
+
+/* Identify Packet Device data definitions (ATAPI devices) */
+
+/* Identify Packet Device: general config bits - word 0 */
+
+#define SATA_ATAPI_TYPE_MASK 0xc000
+#define SATA_ATAPI_TYPE 0x8000 /* ATAPI device */
+#define SATA_ATAPI_ID_PKT_SZ 0x0003 /* Packet size mask */
+#define SATA_ATAPI_ID_PKT_12B 0x0000 /* Packet size 12 bytes */
+#define SATA_ATAPI_ID_PKT_16B 0x0001 /* Packet size 16 bytes */
+#define SATA_ATAPI_ID_DRQ_TYPE 0x0060 /* DRQ asserted in 3ms after pkt */
+#define SATA_ATAPI_ID_DRQ_INTR 0x0020 /* Obsolete in ATA/ATAPI 7 */
+
+#define SATA_ATAPI_ID_DEV_TYPE 0x0f00 /* device type/command set mask */
+#define SATA_ATAPI_ID_DEV_SHFT 8
+#define SATA_ATAPI_DIRACC_DEV 0x0000 /* Direct Access device */
+#define SATA_ATAPI_SQACC_DEV 0x0100 /* Sequential access dev (tape ?) */
+#define SATA_ATAPI_CDROM_DEV 0x0500 /* CD_ROM device */
+
+/*
+ * Status bits from ATAPI Interrupt reason register (AT_COUNT) register
+ */
+#define SATA_ATAPI_I_COD 0x01 /* Command or Data */
+#define SATA_ATAPI_I_IO 0x02 /* IO direction */
+#define SATA_ATAPI_I_RELEASE 0x04 /* Release for ATAPI overlap */
+
+/* ATAPI feature reg definitions */
+
+#define SATA_ATAPI_F_OVERLAP 0x02
+
+
+/*
+ * ATAPI IDENTIFY_DRIVE capabilities word
+ */
+
+#define SATA_ATAPI_ID_CAP_DMA 0x0100
+#define SATA_ATAPI_ID_CAP_OVERLAP 0x2000
+
+/*
+ * ATAPI signature bits
+ */
+#define SATA_ATAPI_SIG_HI 0xeb /* in high cylinder register */
+#define SATA_ATAPI_SIG_LO 0x14 /* in low cylinder register */
+
+/* These values are pre-set for CD_ROM/DVD ? */
+
+#define SATA_ATAPI_SECTOR_SIZE 2048
+#define SATA_ATAPI_MAX_BYTES_PER_DRQ 0xf800 /* 16 bits - 2KB ie 62KB */
+#define SATA_ATAPI_HEADS 64
+#define SATA_ATAPI_SECTORS_PER_TRK 32
+
+/* SATA Capabilites bits (word 76) */
+
+#define SATA_NCQ 0x100
+#define SATA_2_SPEED 0x004
+#define SATA_1_SPEED 0x002
+
+/* SATA Features Supported (word 78) - not used */
+
+/* SATA Features Enabled (word 79) - not used */
+
+/*
+ * Status bits from AT_STATUS register
+ */
+#define SATA_STATUS_BSY 0x80 /* controller busy */
+#define SATA_STATUS_DRDY 0x40 /* drive ready */
+#define SATA_STATUS_DF 0x20 /* device fault */
+#define SATA_STATUS_DSC 0x10 /* seek operation complete */
+#define SATA_STATUS_DRQ 0x08 /* data request */
+#define SATA_STATUS_CORR 0x04 /* obsolete */
+#define SATA_STATUS_IDX 0x02 /* obsolete */
+#define SATA_STATUS_ERR 0x01 /* error flag */
+
+/*
+ * Status bits from AT_ERROR register
+ */
+#define SATA_ERROR_ICRC 0x80 /* CRC data transfer error detected */
+#define SATA_ERROR_UNC 0x40 /* uncorrectable data error */
+#define SATA_ERROR_MC 0x20 /* Media change */
+#define SATA_ERROR_IDNF 0x10 /* ID/Address not found */
+#define SATA_ERROR_MCR 0x08 /* media change request */
+#define SATA_ERROR_ABORT 0x04 /* aborted command */
+#define SATA_ERROR_NM 0x02 /* no media */
+#define SATA_ERROR_EOM 0x02 /* end of media (Packet cmds) */
+#define SATA_ERROR_ILI 0x01 /* cmd sepcific */
+
+/* device_reg */
+#define SATA_ADH_LBA 0x40 /* addressing in LBA mode not chs */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SATA_DEFS_H */
diff --git a/usr/src/uts/common/sys/sata/sata_hba.h b/usr/src/uts/common/sys/sata/sata_hba.h
new file mode 100644
index 0000000000..10af0e8ca2
--- /dev/null
+++ b/usr/src/uts/common/sys/sata/sata_hba.h
@@ -0,0 +1,681 @@
+/*
+ * 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 2005 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _SATA_HBA_H
+#define _SATA_HBA_H
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <sys/sata/sata_defs.h>
+
+/*
+ * SATA Host Bus Adapter (HBA) driver transport definitions
+ */
+
+#include <sys/types.h>
+
+#ifndef TRUE
+#define TRUE 1
+#define FALSE 0
+#endif
+
+#define SATA_SUCCESS 0
+#define SATA_FAILURE -1
+
+
+/* SATA Framework definitions */
+
+#define SATA_MAX_CPORTS 32 /* Max number of controller ports */
+
+ /* Multiplier (PMult) */
+#define SATA_MAX_PMPORTS 16 /* Maximum number of ports on PMult */
+#define SATA_PMULT_HOSTPORT 0xf /* Port Multiplier host port number */
+
+
+/*
+ * SATA device address
+ * Address qualifier flags are used to specify what is addressed (device
+ * or port) and where (controller or port multiplier data port).
+ */
+struct sata_address {
+ uint8_t cport; /* Controller's SATA port number */
+ uint8_t pmport; /* Port Multiplier SATA port number */
+ uint8_t qual; /* Address Qualifier flags */
+ uint8_t pad; /* Reserved */
+};
+
+typedef struct sata_address sata_address_t;
+
+/*
+ * SATA address Qualifier flags (in qual field of sata_address struct).
+ * They are mutually exclusive.
+ */
+
+#define SATA_ADDR_NULL 0x00 /* No address */
+#define SATA_ADDR_DCPORT 0x01 /* Device attched to controller port */
+#define SATA_ADDR_DPMPORT 0x02 /* Device attched to PM device port */
+#define SATA_ADDR_CPORT 0x04 /* Controller's device port */
+#define SATA_ADDR_PMPORT 0x08 /* Port Multiplier's device port */
+#define SATA_ADDR_CNTRL 0x10 /* Controller */
+#define SATA_ADDR_PMULT 0x20 /* Port Multiplier */
+
+/*
+ * SATA port status and control register block.
+ * The sstatus, serror, scontrol, sactive and snotific
+ * are the copies of the SATA port status and control registers.
+ * (Port SStatus, SError, SControl, SActive and SNotification are
+ * defined by Serial ATA r1.0a sepc and Serial ATA II spec.
+ */
+
+struct sata_port_scr
+{
+ uint32_t sstatus; /* Port SStatus register */
+ uint32_t serror; /* Port SError register */
+ uint32_t scontrol; /* Port SControl register */
+ uint32_t sactive; /* Port SActive register */
+ uint32_t snotific; /* Port SNotification register */
+};
+
+typedef struct sata_port_scr sata_port_scr_t;
+
+/*
+ * SATA Device Structure (rev 1)
+ * Used to request/return state of the controller, port, port multiplier
+ * or an attached drive:
+ * The satadev_addr.cport, satadev_addr.pmport and satadev_addr.qual
+ * fields are used to specify SATA address (see sata_address structure
+ * description).
+ * The satadev_scr structure is used to pass the content of a port
+ * status and control registers.
+ * The satadev_add_info field is used by SATA HBA driver to return an
+ * additional information, which type depends on the function using
+ * sata_device as argument. For example:
+ * - in case of sata_tran_probe_port() this field should contain
+ * a number of available Port Multiplier device ports;
+ * - in case of sata_hba_event_notify() this field may contain
+ * a value specific for a reported event.
+ */
+#define SATA_DEVICE_REV_1 1
+#define SATA_DEVICE_REV SATA_DEVICE_REV_1
+
+struct sata_device
+{
+ int satadev_rev; /* structure version */
+ struct sata_address satadev_addr; /* sata port/device address */
+ uint32_t satadev_state; /* Port or device state */
+ uint32_t satadev_type; /* Attached device type */
+ struct sata_port_scr satadev_scr; /* Port status and ctrl regs */
+ uint32_t satadev_add_info; /* additional information, */
+ /* function specific */
+};
+
+typedef struct sata_device sata_device_t;
+
+_NOTE(SCHEME_PROTECTS_DATA("unshared data", sata_device))
+
+
+/*
+ * satadev_state field of sata_device structure.
+ * Common flags specifying current state of a port or an attached drive.
+ * These states are mutually exclusive, except SATA_STATE_PROBED and
+ * SATA_STATE_READY that may be set at the same time.
+ */
+#define SATA_STATE_UNKNOWN 0x000000
+#define SATA_STATE_PROBING 0x000001
+#define SATA_STATE_PROBED 0x000002
+#define SATA_STATE_READY 0x000010
+
+/*
+ * Attached drive specific states (satadev_state field of the sata_device
+ * structure).
+ * SATA_DSTATE_PWR_ACTIVE, SATA_DSTATE_PWR_IDLE and SATA_DSTATE_PWR_STANDBY
+ * are mutually exclusive. All other states may be combined with each other
+ * and with one of the power states.
+ * These flags may be used only if the address qualifier (satadev_addr.qual) is
+ * set to SATA_ADDR_DCPORT or SATA_ADDR_DPMPORT value.
+ */
+
+#define SATA_DSTATE_PWR_ACTIVE 0x000100
+#define SATA_DSTATE_PWR_IDLE 0x000200
+#define SATA_DSTATE_PWR_STANDBY 0x000400
+#define SATA_DSTATE_RESET 0x001000
+#define SATA_DSTATE_FAILED 0x008000
+
+/* Mask for drive power states */
+#define SATA_DSTATE_PWR (SATA_DSTATE_PWR_ACTIVE | \
+ SATA_DSTATE_PWR_IDLE | \
+ SATA_DSTATE_PWR_STANDBY)
+/*
+ * SATA Port specific states (satadev_state field of sata_device structure).
+ * SATA_PSTATE_PWRON and SATA_PSTATE_PWROFF are mutually exclusive.
+ * All other states may be combined with each other and with one of the power
+ * level state.
+ * These flags may be used only if the address qualifier (satadev_addr.qual) is
+ * set to SATA_ADDR_CPORT or SATA_ADDR_PMPORT value.
+ */
+
+#define SATA_PSTATE_PWRON 0x010000
+#define SATA_PSTATE_PWROFF 0X020000
+#define SATA_PSTATE_SHUTDOWN 0x040000
+#define SATA_PSTATE_FAILED 0x080000
+
+/* Mask for the valid port-specific state flags */
+#define SATA_PSTATE_VALID (SATA_PSTATE_PWRON | \
+ SATA_PSTATE_PWROFF | \
+ SATA_PSTATE_SHUTDOWN | \
+ SATA_PSTATE_FAILED)
+
+/* Mask for a port power states */
+#define SATA_PSTATE_PWR (SATA_PSTATE_PWRON | \
+ SATA_PSTATE_PWROFF)
+
+/*
+ * Device type (in satadev_type field of sata_device structure).
+ * More device types may be added in the future.
+ */
+
+#define SATA_DTYPE_NONE 0x00 /* No device attached */
+#define SATA_DTYPE_ATADISK 0x01 /* ATA Disk */
+#define SATA_DTYPE_ATAPICD 0x02 /* Atapi CD/DVD device */
+#define SATA_DTYPE_ATAPINONCD 0x03 /* Atapi non-CD/DVD device */
+#define SATA_DTYPE_PMULT 0x10 /* Port Multiplier */
+#define SATA_DTYPE_UNKNOWN 0x20 /* Device attached, unkown */
+
+
+/*
+ * SATA cmd structure (rev 1)
+ *
+ * SATA HBA framework always sets all fields except status_reg and error_reg.
+ * SATA HBA driver action depends on the addressing type specified by
+ * addr_type field:
+ * If LBA48 addressing is indicated, SATA HBA driver has to load values from
+ * satacmd_sec_count_msb_reg, satacmd_lba_low_msb_reg,
+ * satacmd_lba_mid_msb_reg and satacmd_lba_hi_msb_reg
+ * to appropriate registers prior to loading other registers.
+ * For other addressing modes, SATA HBA driver should skip loading values
+ * from satacmd_sec_count_msb_reg, satacmd_lba_low_msb_reg,
+ * satacmd_lba_mid_msb_reg and satacmd_lba_hi_msb_reg
+ * fields and load only remaining field values to corresponding registers.
+ *
+ * satacmd_sec_count_msb and satamcd_sec_count_lsb values are loaded into
+ * sec_count register, satacmd_sec_count_msb loaded first (if LBA48
+ * addressing is used).
+ * satacmd_lba_low_msb and satacmd_lba_low_lsb values are loaded into the
+ * lba_low register, satacmd_lba_low_msb loaded first (if LBA48 addressing
+ * is used). The lba_low register is the newer name for the old
+ * sector_number register.
+ * satacmd_lba_mid_msb and satacmd_lba_mid_lsb values are loaded into lba_mid
+ * register, satacmd_lba_mid_msb loaded first (if LBA48 addressing is used).
+ * The lba_mid register is the newer name for the old cylinder_low register.
+ * satacmd_lba_high_msb and satacmd_lba_high_lsb values are loaded into
+ * the lba_high regster, satacmd_lba_high_msb loaded first (if LBA48
+ * addressing is used). The lba_high register is a newer name for the old
+ * cylinder_high register.
+ *
+ * No addressing mode is selected when an ata command does not involve actual
+ * reading/writing data from/to the media (for example IDENTIFY DEVICE or
+ * SET FEATURE command), or the ATAPI PACKET command is sent.
+ * If ATAPI PACKET command is sent and tagged commands are used,
+ * SATA HBA driver has to provide and manage a tag value and
+ * set it into the sector_count register.
+ *
+ * Device Control register is not specified in sata_cmd structure - SATA HBA
+ * driver shall set it accordingly to current mode of operation (interrupt
+ * enable/disable).
+ *
+ * Buffer structure's b_flags should be used to determine the
+ * address type of b_un.b_addr. However, there is no need to allocate DMA
+ * resources for the buffer in SATA HBA driver.
+ * DMA resources for a buffer structure are allocated by the SATA HBA
+ * framework. Scatter/gather list is to be used only for DMA transfers
+ * and it should be based on the DMA cookies list.
+ *
+ * Upon completion of a command, SATA HBA driver has to update
+ * satacmd_status_reg and satacmd_error_reg to reflect the contents of
+ * the corresponding device status and error registers.
+ * If the command completed with error, SATA HBA driver has to update
+ * satacmd_sec_count_msb, satacmd_sec_count_lsb, satacmd_lba_low_msb,
+ * satacmd_lba_low_lsb, satacmd_lba_mid_msb, satacmd_lba_mid_lsb,
+ * satacmd_lba_high_msb and satacmd_lba_high_lsb to values read from the
+ * corresponding device registers.
+ * If an operation could not complete because of the port error, the
+ * sata_pkt.satapkt_device.satadev_scr structure has to be updated.
+ *
+ * If ATAPI PACKET command was sent and command completed with error,
+ * rqsense structure has to be filed by SATA HBA driver. The satacmd_arq_cdb
+ * points to pre-set request sense cdb that may be used for issuing request
+ * sense data from the device.
+ *
+ * If FPDMA-type command was sent and command completed with error, the HBA
+ * driver may use pre-set command READ LOG EXTENDED command pointed to
+ * by satacmd_rle_sata_cmd field to retrieve error data from a device.
+ * Only ATA register fields of the sata_cmd are set-up for that purpose.
+ *
+ * If the READ MULTIPLIER command was specified in cmd_reg (command directed
+ * to a port multiplier host port rather then to an attached device),
+ * upon the command completion SATA HBA driver has to update_sector count
+ * and lba fields of the sata_cmd structure to values returned via
+ * command block registers (task file registers).
+ */
+#define SATA_CMD_REV_1 1
+#define SATA_CMD_REV SATA_CMD_REV_1
+
+#define SATA_ATAPI_MAX_CDB_LEN 16 /* Covers both 12 and 16 byte cdbs */
+#define SATA_ATAPI_RQSENSE_LEN 24 /* Fixed size Request Sense data */
+
+struct sata_cmd {
+ int satacmd_rev; /* version */
+ struct buf *satacmd_bp; /* ptr to buffer structure */
+ uint32_t satacmd_flags; /* transfer direction */
+ uint8_t satacmd_addr_type; /* addr type: LBA28, LBA48 */
+ uint8_t satacmd_features_reg_ext; /* features reg extended */
+ uint8_t satacmd_sec_count_msb; /* sector count MSB (LBA48) */
+ uint8_t satacmd_lba_low_msb; /* LBA Low MSB (LBA48) */
+ uint8_t satacmd_lba_mid_msb; /* LBA Mid MSB (LBA48) */
+ uint8_t satacmd_lba_high_msb; /* LBA High MSB (LBA48) */
+ uint8_t satacmd_sec_count_lsb; /* sector count LSB */
+ uint8_t satacmd_lba_low_lsb; /* LBA Low LSB */
+ uint8_t satacmd_lba_mid_lsb; /* LBA Mid LSB */
+ uint8_t satacmd_lba_high_lsb; /* LBA High LSB */
+ uint8_t satacmd_device_reg; /* ATA dev reg & LBA28 MSB */
+ uint8_t satacmd_cmd_reg; /* ata command code */
+ uint8_t satacmd_features_reg; /* ATA features register */
+ uint8_t satacmd_status_reg; /* ATA status register */
+ uint8_t satacmd_error_reg; /* ATA error register */
+ uint8_t satacmd_acdb_len; /* ATAPI cdb length */
+ uint8_t satacmd_acdb[SATA_ATAPI_MAX_CDB_LEN]; /* ATAPI cdb */
+
+ /*
+ * Ptr to request sense cdb
+ * request sense buf
+ */
+ uint8_t *satacmd_arq_cdb;
+
+ uint8_t satacmd_rqsense[SATA_ATAPI_RQSENSE_LEN];
+
+ /*
+ * Ptr to FPDMA error
+ * retrieval cmd
+ */
+ struct sata_cmd *satacmd_rle_sata_cmd;
+
+ int satacmd_num_dma_cookies; /* number of dma cookies */
+ /* ptr to dma cookie list */
+ ddi_dma_cookie_t *satacmd_dma_cookie_list;
+};
+
+typedef struct sata_cmd sata_cmd_t;
+
+_NOTE(SCHEME_PROTECTS_DATA("unshared data", sata_cmd))
+
+
+/* ATA address type (in satacmd_addr_type field */
+#define ATA_ADDR_LBA 0x1
+#define ATA_ADDR_LBA28 0x2
+#define ATA_ADDR_LBA48 0x4
+
+/*
+ * satacmd_flags : contain data transfer direction flags,
+ * tagged queuing type flags, queued command flag, and reset state handling
+ * flag.
+ */
+
+/*
+ * Data transfer direction flags (satacmd_flags)
+ * Direction flags are mutually exclusive.
+ */
+#define SATA_DIR_NODATA_XFER 0x0001 /* No data transfer */
+#define SATA_DIR_READ 0x0002 /* Reading data from a device */
+#define SATA_DIR_WRITE 0x0004 /* Writing data to a device */
+
+#define SATA_XFER_DIR_MASK 0x0007
+
+/*
+ * Tagged Queuing type flags (satacmd_flags).
+ * These flags indicate how the SATA command should be queued.
+ *
+ * SATA_QUEUE_STAG_CMD
+ * Simple-queue-tagged command. It may be executed out-of-order in respect
+ * to other queued commands.
+ * SATA_QUEUE_OTAG_CMD
+ * Ordered-queue-tagged command. It cannot be executed out-of-order in
+ * respect to other commands, i.e. it should be executed in the order of
+ * being transported to the HBA.
+ *
+ * Translated head-of-queue-tagged scsi commands and commands that are
+ * to be put at the head of the queue are treated as SATA_QUEUE_OTAG_CMD
+ * tagged commands.
+ */
+#define SATA_QUEUE_STAG_CMD 0x0010 /* simple-queue-tagged command */
+#define SATA_QUEUE_OTAG_CMD 0x0020 /* ordered-queue-tagged command */
+
+
+/*
+ * Queuing command set-up flag (satacmd_flags).
+ * This flag indicates that sata_cmd was set-up for DMA Queued command
+ * (either READ_DMA_QUEUED, READ_DMA_QUEUED_EXT, WRITE_DMA_QUEUED or
+ * WRITE_DMA_QUEUED_EXT command) or one of the Native Command Queuing commands
+ * (either READ_FPDMA_QUEUED or WRITE_FPDMA_QUEUED).
+ * This flag will be used only if sata_tran_hba_flags indicates controller
+ * support for queuing and the device for which sata_cmd is prepared supports
+ * either legacy queuing (indicated by Device Identify data word 83 bit 2)
+ * or NCQ (indicated by word 76 of Device Identify data).
+ */
+#define SATA_QUEUED_CMD 0x0100
+
+
+/*
+ * Reset state handling (satacmd_flags).
+ * SATA HBA device enters reset state if the device was subjected to
+ * the Device Reset (may also enter this state if the device was reset
+ * as a side effect of port reset). SATA HBA driver sets this state.
+ * Device stays in this condition until explicit request from SATA HBA
+ * framework (SATA_CLEAR_DEV_RESET_STATE flag) to clear the state.
+ */
+#define SATA_IGNORE_DEV_RESET_STATE 0x1000
+#define SATA_CLEAR_DEV_RESET_STATE 0x2000
+
+/*
+ * SATA Packet structure (rev 1)
+ * hba_driver_private is for a private use of the SATA HBA driver;
+ * satapkt_framework_private is used only by SATA HBA framework;
+ * satapkt_comp is a callback function to be called when packet
+ * execution is completed (for any reason) if mode of operation is not
+ * synchronous (SATA_OPMODE_SYNCH);
+ * satapkt_reason specifies why the packet operation was completed
+ *
+ * NOTE: after the packet completion callback SATA HBA driver should not
+ * attempt to access any sata_pkt fields because sata_pkt is not valid anymore
+ * (it could have been destroyed).
+ * Since satapkt_hba_driver_private field cannot be retrieved, any hba private
+ * data respources allocated per packet and accessed via this pointer should
+ * either be freed before the completion callback is done, or the pointer has
+ * to be saved by the HBA driver before the completion callback.
+ */
+#define SATA_PKT_REV_1 1
+#define SATA_PKT_REV SATA_PKT_REV_1
+
+struct sata_pkt {
+ int satapkt_rev; /* version */
+ struct sata_device satapkt_device; /* Device address/type */
+
+ /* HBA driver private data */
+ void *satapkt_hba_driver_private;
+
+ /* SATA framework priv data */
+ void *satapkt_framework_private;
+
+ /* Rqsted mode of operation */
+ uint32_t satapkt_op_mode;
+
+ struct sata_cmd satapkt_cmd; /* composite sata command */
+ int satapkt_time; /* time allotted to command */
+ void (*satapkt_comp)(struct sata_pkt *); /* callback */
+ int satapkt_reason; /* completion reason */
+};
+
+typedef struct sata_pkt sata_pkt_t;
+
+_NOTE(SCHEME_PROTECTS_DATA("unshared data", sata_pkt))
+
+
+/*
+ * Operation mode flags (in satapkt_op_mode field of sata_pkt structure).
+ * Use to specify what should be a mode of operation for specified command.
+ * Default (000b) means use Interrupt and Asynchronous mode to
+ * perform an operation.
+ * Synchronous operation menas that the packet operation has to be completed
+ * before the function called to initiate the operation returns.
+ */
+#define SATA_OPMODE_INTERRUPTS 0 /* Use interrupts (hint) */
+#define SATA_OPMODE_POLLING 1 /* Use polling instead of interrupts */
+#define SATA_OPMODE_ASYNCH 0 /* Return immediately after accepting pkt */
+#define SATA_OPMODE_SYNCH 4 /* Perform synchronous operation */
+
+/*
+ * satapkt_reason values:
+ *
+ * SATA_PKT_QUEUE_FULL - cmd not sent because of queue full (detected
+ * by the controller). If a device reject command for this reason, it
+ * should be reported as SATA_PKT_DEV_ERROR
+ *
+ * SATA_PKT_CMD_NOT_SUPPORTED - command not supported by a controller
+ * Controller is unable to send such command to a device.
+ * If device rejects a command, it should be reported as
+ * SATA_PKT_DEV_ERROR.
+ *
+ * SATA_PKT_DEV_ERROR - cmd failed because of device reported an error.
+ * The content of status_reg (ERROR bit has to be set) and error_reg
+ * fields of the sata_cmd structure have to be set and will be used
+ * by SATA HBA Framework to determine the error cause.
+ *
+ * SATA_PKT_PORT_ERROR - cmd failed because of a link or a port error.
+ * Link failed / no communication with a device / communication error
+ * or other port related error was detected by a controller.
+ * sata_pkt.satapkt_device.satadev_scr.sXXXXXXX words have to be set.
+ *
+ * SATA_PKT_ABORTED - cmd execution was aborted by the request from the
+ * framework. Abort mechanism is HBA driver specific.
+ *
+ * SATA_PKT_TIMEOUT - cmd execution has timed-out. Timeout specified by
+ * pkt_time was exceeded. The command was terminated by the SATA HBA
+ * driver.
+ *
+ * SATA_PKT_COMPLETED - this is a value returned when an operation
+ * completes without errors.
+ *
+ * SATA_PKT_BUSY - packet was not accepted for execution because the
+ * driver was busy performing some other operation(s).
+ *
+ * SATA_PKT_RESET - packet execution was aborted because of device
+ * reset originated by either the HBA driver or the SATA framework.
+ *
+ */
+
+#define SATA_PKT_BUSY -1 /* Not completed, busy */
+#define SATA_PKT_COMPLETED 0 /* No error */
+#define SATA_PKT_DEV_ERROR 1 /* Device reported error */
+#define SATA_PKT_QUEUE_FULL 2 /* Not accepted, queue full */
+#define SATA_PKT_PORT_ERROR 3 /* Not completed, port error */
+#define SATA_PKT_CMD_UNSUPPORTED 4 /* Cmd unsupported */
+#define SATA_PKT_ABORTED 5 /* Aborted by request */
+#define SATA_PKT_TIMEOUT 6 /* Operation timeut */
+#define SATA_PKT_RESET 7 /* Aborted by reset request */
+
+/*
+ * Hoplug functions vector structure (rev 1)
+ */
+#define SATA_TRAN_HOTPLUG_OPS_REV_1 1
+
+struct sata_tran_hotplug_ops {
+ int sata_tran_hotplug_ops_rev; /* version */
+ int (*sata_tran_port_activate)(dev_info_t *, sata_device_t *);
+ int (*sata_tran_port_deactivate)(dev_info_t *, sata_device_t *);
+};
+
+typedef struct sata_tran_hotplug_ops sata_tran_hotplug_ops_t;
+
+
+/*
+ * Power management functions vector structure (rev 1)
+ * The embedded function returns information about the controller's
+ * power level.
+ * Additional functions may be added in the future without changes to
+ * sata_tran structure.
+ */
+#define SATA_TRAN_PWRMGT_OPS_REV_1 1
+
+struct sata_tran_pwrmgt_ops {
+ int sata_tran_pwrmgt_ops_rev; /* version */
+ int (*sata_tran_get_pwr_level)(dev_info_t *, sata_device_t *);
+};
+
+typedef struct sata_tran_pwrmgt_ops sata_tran_pwrmgt_ops_t;
+
+
+/*
+ * SATA port PHY Power Level
+ * These states correspond to the interface power management state as defined
+ * in Serial ATA spec.
+ */
+#define SATA_TRAN_PORTPWR_LEVEL1 1 /* Interface in active PM state */
+#define SATA_TRAN_PORTPWR_LEVEL2 2 /* Interface in PARTIAL PM state */
+#define SATA_TRAN_PORTPWR_LEVEL3 3 /* Interface in SLUMBER PM state */
+
+/*
+ * SATA HBA Tran structure (rev 1)
+ * Registered with SATA Framework
+ *
+ * dma_attr is a pointer to data (buffer) dma attibutes of the controller
+ * DMA engine.
+ *
+ * The qdepth field specifies number of commands that may be accepted by
+ * the controller. Value range 1-32. A value greater than 1 indicates that
+ * the controller supports queuing. Support for Native Command Queuing
+ * indicated by SATA_CTLF_NCQ flag also requires qdepth set to a value
+ * greater then 1.
+ *
+ */
+#define SATA_TRAN_HBA_REV_1 1
+#define SATA_TRAN_HBA_REV SATA_TRAN_HBA_REV_1
+
+struct sata_hba_tran {
+ int sata_tran_hba_rev; /* version */
+ dev_info_t *sata_tran_hba_dip; /* Controler dev info */
+ ddi_dma_attr_t *sata_tran_hba_dma_attr; /* DMA attributes */
+ int sata_tran_hba_num_cports; /* Num of HBA device ports */
+ uint16_t sata_tran_hba_features_support; /* HBA features */
+ uint16_t sata_tran_hba_qdepth; /* HBA-supported queue depth */
+
+ int (*sata_tran_probe_port)(dev_info_t *, sata_device_t *);
+ int (*sata_tran_start)(dev_info_t *, sata_pkt_t *);
+ int (*sata_tran_abort)(dev_info_t *, sata_pkt_t *, int);
+ int (*sata_tran_reset_dport)(dev_info_t *,
+ sata_device_t *);
+ int (*sata_tran_selftest)(dev_info_t *, sata_device_t *);
+
+ /* Hotplug vector */
+ struct sata_tran_hotplug_ops *sata_tran_hotplug_ops;
+
+ /* Power mgt vector */
+ struct sata_tran_pwrmgt_ops *sata_tran_pwrmgt_ops;
+
+ int (*sata_tran_ioctl)(dev_info_t *, int, intptr_t);
+};
+
+typedef struct sata_hba_tran sata_hba_tran_t;
+
+
+/*
+ * Controller's features support flags (sata_tran_hba_features_support).
+ * Note: SATA_CTLF_NCQ indicates that SATA controller supports NCQ in addition
+ * to legacy queuing commands, indicated by SATA_CTLF_QCMD flag.
+ */
+
+#define SATA_CTLF_ATAPI 0x001 /* ATAPI support */
+#define SATA_CTLF_PORT_MULTIPLIER 0x010 /* Port Multiplier suport */
+#define SATA_CTLF_HOTPLUG 0x020 /* Hotplug support */
+#define SATA_CTLF_ASN 0x040 /* Asynchronous Event Support */
+#define SATA_CTLF_QCMD 0x080 /* Queued commands support */
+#define SATA_CTLF_NCQ 0x100 /* NCQ support */
+
+/*
+ * sata_tran_start() return values.
+ * When pkt is not accepted, the satapkt_reason has to be updated
+ * before function returns - it should reflect the same reason for not being
+ * executed as the return status of above functions.
+ * If pkt was accepted and executed synchronously,
+ * satapk_reason should indicate a completion status.
+ */
+#define SATA_TRAN_ACCEPTED 0 /* accepted */
+#define SATA_TRAN_QUEUE_FULL 1 /* not accepted, queue full */
+#define SATA_TRAN_PORT_ERROR 2 /* not accepted, port error */
+#define SATA_TRAN_CMD_UNSUPPORTED 3 /* not accepted, cmd not supported */
+#define SATA_TRAN_BUSY 4 /* not accepted, busy */
+
+
+/*
+ * sata_tran_abort() abort type flag
+ */
+#define SATA_ABORT_PACKET 0
+#define SATA_ABORT_ALL_PACKETS 1
+
+
+/*
+ * Events handled by SATA HBA Framework
+ * More then one event may be reported at the same time
+ *
+ * SATA_EVNT__DEVICE_ATTACHED
+ * HBA detected the presence of a device ( electrical connection with
+ * a device was detected ).
+ *
+ * SATA_EVNT_DEVICE_DETACHED
+ * HBA detected the detachment of a device (electrical connection with
+ * a device was broken)
+ *
+ * SATA_EVNT_LINK_LOST
+ * HBA lost link with an attached device
+ *
+ * SATA_EVNT_LINK_ESTABLISHED
+ * HBA established a link with an attached device
+ *
+ * SATA_EVNT_PORT_FAILED
+ * HBA has determined that the port failed and is unuseable
+ *
+ * SATA_EVENT_DEVICE_RESET
+ * SATA device was reset, causing loss of the device setting
+ *
+ * SATA_EVNT_PWR_LEVEL_CHANGED
+ * A port or entire SATA controller power level has changed
+ *
+ */
+#define SATA_EVNT_DEVICE_ATTACHED 0x01
+#define SATA_EVNT_DEVICE_DETACHED 0x02
+#define SATA_EVNT_LINK_LOST 0x04
+#define SATA_EVNT_LINK_ESTABLISHED 0x08
+#define SATA_EVNT_PORT_FAILED 0x10
+#define SATA_EVNT_DEVICE_RESET 0x20
+#define SATA_EVNT_PWR_LEVEL_CHANGED 0x40
+
+/*
+ * SATA Framework interface entry points
+ */
+int sata_hba_init(struct modlinkage *);
+int sata_hba_attach(dev_info_t *, sata_hba_tran_t *, ddi_attach_cmd_t);
+int sata_hba_detach(dev_info_t *, ddi_detach_cmd_t);
+void sata_hba_fini(struct modlinkage *);
+void sata_hba_event_notify(dev_info_t *, sata_device_t *, int);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _SATA_HBA_H */
diff --git a/usr/src/uts/common/sys/sunddi.h b/usr/src/uts/common/sys/sunddi.h
index 7c8aad2560..96e832ad17 100644
--- a/usr/src/uts/common/sys/sunddi.h
+++ b/usr/src/uts/common/sys/sunddi.h
@@ -18,6 +18,7 @@
*
* CDDL HEADER END
*/
+
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
@@ -192,10 +193,16 @@ extern "C" {
#define DDI_NT_SCSI_NEXUS "ddi_ctl:devctl:scsi" /* nexus drivers */
+#define DDI_NT_SATA_NEXUS "ddi_ctl:devctl:sata" /* nexus drivers */
+
#define DDI_NT_ATTACHMENT_POINT "ddi_ctl:attachment_point" /* attachment pt */
#define DDI_NT_SCSI_ATTACHMENT_POINT "ddi_ctl:attachment_point:scsi"
/* scsi attachment pt */
+
+#define DDI_NT_SATA_ATTACHMENT_POINT "ddi_ctl:attachment_point:sata"
+ /* sata attachment pt */
+
#define DDI_NT_PCI_ATTACHMENT_POINT "ddi_ctl:attachment_point:pci"
/* PCI attachment pt */
#define DDI_NT_SBD_ATTACHMENT_POINT "ddi_ctl:attachment_point:sbd"
diff --git a/usr/src/uts/intel/Makefile.intel.shared b/usr/src/uts/intel/Makefile.intel.shared
index f932b55190..4a12cd59ac 100644
--- a/usr/src/uts/intel/Makefile.intel.shared
+++ b/usr/src/uts/intel/Makefile.intel.shared
@@ -272,6 +272,7 @@ DRV_KMODS += sad
DRV_KMODS += sctp
DRV_KMODS += sctp6
DRV_KMODS += sd
+DRV_KMODS += si3124
DRV_KMODS += spdsock
DRV_KMODS += smbios
DRV_KMODS += sppp
@@ -483,6 +484,7 @@ MISC_KMODS += pcmcia
MISC_KMODS += rpcsec
MISC_KMODS += rpcsec_gss
MISC_KMODS += rsmops
+MISC_KMODS += sata
MISC_KMODS += scsi
MISC_KMODS += strategy
MISC_KMODS += strplumb
diff --git a/usr/src/uts/intel/sata/Makefile b/usr/src/uts/intel/sata/Makefile
new file mode 100644
index 0000000000..7f914a6b1a
--- /dev/null
+++ b/usr/src/uts/intel/sata/Makefile
@@ -0,0 +1,103 @@
+#
+# 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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+#
+# uts/intel/io/sata/Makefile
+#
+# This makefile drives the production of the sata "misc"
+# 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 = sata
+OBJECTS = $(SATA_OBJS:%=$(OBJS_DIR)/%)
+LINTS = $(SATA_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)
+
+#
+# lint pass one enforcement
+#
+CFLAGS += $(CCVERBOSE)
+
+#
+# dependency on scsi module
+#
+LDFLAGS += -dy -Nmisc/scsi
+
+#
+# Default build targets.
+#
+.KEEP_STATE:
+
+def: $(DEF_DEPS)
+
+all: $(ALL_DEPS)
+
+clean: $(CLEAN_DEPS)
+
+clobber: $(CLOBBER_DEPS)
+
+lint: $(LINT_DEPS)
+
+modlintlib: $(MODLINTLIB_DEPS)
+
+clean.lint: $(CLEAN_LINT_DEPS)
+
+install: $(INSTALL_DEPS)
+
+#
+# Include common targets.
+#
+include $(UTSBASE)/intel/Makefile.targ
diff --git a/usr/src/uts/intel/si3124/Makefile b/usr/src/uts/intel/si3124/Makefile
new file mode 100644
index 0000000000..4126a22405
--- /dev/null
+++ b/usr/src/uts/intel/si3124/Makefile
@@ -0,0 +1,102 @@
+#
+# 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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+#
+# This makefile drives the production of the
+# "platform/i86pc/kernel/drv/si3124" 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 = si3124
+OBJECTS = $(SI3124_OBJS:%=$(OBJS_DIR)/%)
+LINTS = $(SI3124_OBJS:%.o=$(LINTS_DIR)/%.ln)
+ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE)
+CONF_SRCDIR = $(UTSBASE)/common/io/sata/adapters/si3124
+
+#
+# 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)
+
+#
+# lint pass one enforcement
+#
+CFLAGS += $(CCVERBOSE)
+
+#
+#
+# we depend on the sata module
+LDFLAGS += -dy -N misc/sata
+
+#
+# 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/sata/Makefile b/usr/src/uts/sparc/sata/Makefile
new file mode 100644
index 0000000000..f0a1fc5a8e
--- /dev/null
+++ b/usr/src/uts/sparc/sata/Makefile
@@ -0,0 +1,123 @@
+#
+# 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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+#
+# uts/sparc/sata/Makefile
+#
+# This makefile drives the production of the sata "misc"
+# kernel module.
+#
+# sparc architecture dependent
+#
+
+#
+# Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+UTSBASE = ../..
+
+#
+# Define the module and object file sets.
+#
+MODULE = sata
+OBJECTS = $(SATA_OBJS:%=$(OBJS_DIR)/%)
+LINTS = $(SATA_OBJS:%.o=$(LINTS_DIR)/%.ln)
+WARLOCK_OUT = $(SATA_OBJS:%.o=%.ll)
+WARLOCK_OK = $(MODULE).ok
+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)
+
+#
+# lint pass one enforcement
+#
+CFLAGS += $(CCVERBOSE)
+
+#
+# Default build targets.
+#
+.KEEP_STATE:
+
+def: $(DEF_DEPS)
+
+all: $(ALL_DEPS)
+
+clean: $(CLEAN_DEPS); \
+ $(RM) $(WARLOCK_OUT) $(WARLOCK_OK)
+
+clobber: $(CLOBBER_DEPS); \
+ $(RM) $(WARLOCK_OUT) $(WARLOCK_OK)
+
+lint: $(LINT_DEPS)
+
+modlintlib: $(MODLINTLIB_DEPS)
+
+clean.lint: $(CLEAN_LINT_DEPS)
+
+install: $(INSTALL_DEPS)
+
+#
+# Include common targets.
+#
+include $(UTSBASE)/sparc/Makefile.targ
+
+#
+# Defines for local commands.
+#
+WLCC = wlcc
+TOUCH = touch
+WARLOCK = warlock
+
+#
+# Warlock targets
+#
+
+SATA_FILES = $(SATA_OBJS:%.o=%.ll)
+
+warlock: $(MODULE).ok
+
+%.ok: $(SATA_FILES)
+ $(TOUCH) $@
+
+%.ll: $(UTSBASE)/common/io/sata/impl/%.c
+ $(WLCC) $(CPPFLAGS) -D __sparcv9 -DDEBUG -o $@ $<
diff --git a/usr/src/uts/sparc/si3124/Makefile b/usr/src/uts/sparc/si3124/Makefile
new file mode 100644
index 0000000000..2df008bbb3
--- /dev/null
+++ b/usr/src/uts/sparc/si3124/Makefile
@@ -0,0 +1,148 @@
+# 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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+#
+# uts/sparc/si3124/Makefile
+#
+# This makefile drives the production of the
+# "platform/i86pc/kernel/drv/si3124" kernel module.
+#
+# sparc architecture dependent
+#
+
+#
+# Path to the base of the uts directory tree (usually /usr/src/uts).
+#
+UTSBASE = ../..
+
+#
+# Define the module and object file sets.
+#
+MODULE = si3124
+OBJECTS = $(SI3124_OBJS:%=$(OBJS_DIR)/%)
+LINTS = $(SI3124_OBJS:%.o=$(LINTS_DIR)/%.ln)
+WARLOCK_OUT = $(SI3124_OBJS:%.o=%.ll)
+WARLOCK_OK = $(MODULE).ok
+ROOTMODULE = $(ROOT_DRV_DIR)/$(MODULE)
+CONF_SRCDIR = $(UTSBASE)/common/io/sata/adapters/si3124
+
+#
+# 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)
+
+#
+# lint pass one enforcement
+#
+CFLAGS += $(CCVERBOSE)
+
+#
+# Default build targets.
+#
+.KEEP_STATE:
+
+def: $(DEF_DEPS)
+
+all: $(ALL_DEPS)
+
+clean: $(CLEAN_DEPS); \
+ $(RM) $(WARLOCK_OUT) $(WARLOCK_OK)
+
+clobber: $(CLOBBER_DEPS); \
+ $(RM) $(WARLOCK_OUT) $(WARLOCK_OK)
+
+lint: $(LINT_DEPS)
+
+modlintlib: $(MODLINTLIB_DEPS)
+
+clean.lint: $(CLEAN_LINT_DEPS)
+
+install: $(INSTALL_DEPS)
+
+#
+# Include common targets.
+#
+include $(UTSBASE)/sparc/Makefile.targ
+
+
+#
+# Defines for local commands.
+#
+WARLOCK = warlock
+WLCC = wlcc
+TOUCH = touch
+SCCS = sccs
+TEST = test
+
+
+SI3124_FILES = $(MODULE).ll
+SD_FILES = $(SD_OBJS:%.o=../sd/%.ll)
+SATA_FILES = $(SATA_OBJS:%.o=-l ../sata/%.ll)
+SCSI_FILES = $(SCSI_OBJS:%.o=-l ../scsi/%.ll)
+
+warlock: $(MODULE).ok
+
+%.wlcmd:
+ $(TEST) -f $@ || $(SCCS) get $@
+
+
+si3124.ok: si3124.wlcmd $(SI3124_FILES) warlock_ddi.files \
+ sata.files scsi.files sd.files
+ $(WARLOCK) -c ./si3124.wlcmd $(SI3124_FILES) \
+ $(SD_FILES) \
+ $(SCSI_FILES) \
+ $(SATA_FILES) \
+ -l ../warlock/ddi_dki_impl.ll
+ $(TOUCH) $@
+
+%.ll: $(UTSBASE)/common/io/sata/adapters/si3124/%.c
+ $(WLCC) $(CPPFLAGS) -D DEBUG -D __sparcv9 -o $@ $<
+
+sata.files:
+ @cd ../sata; pwd; $(MAKE) warlock
+
+scsi.files:
+ @cd ../scsi; pwd; $(MAKE) warlock
+
+sd.files:
+ @cd ../sd; pwd; $(MAKE) warlock_alone
+
+warlock_ddi.files:
+ @cd ../warlock; pwd; $(MAKE) warlock
diff --git a/usr/src/uts/sparc/si3124/si3124.wlcmd b/usr/src/uts/sparc/si3124/si3124.wlcmd
new file mode 100644
index 0000000000..3c027dc8c8
--- /dev/null
+++ b/usr/src/uts/sparc/si3124/si3124.wlcmd
@@ -0,0 +1,119 @@
+#
+# 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 2005 Sun Microsystems, Inc. All rights reserved.
+# Use is subject to license terms.
+#
+# ident "%Z%%M% %I% %E% SMI"
+#
+
+# usr/src/uts/sparc/si3124/si3124.wlcmd
+
+one si_ctl_state
+one scsi_device
+one __ddi_xbuf_attr
+one sd_lun
+one sd_resv_reclaim_request
+
+
+root sata_hba_ioctl
+root sata_hba_open
+root sata_hba_close
+root sata_scsi_reset
+root sata_scsi_init_pkt
+root sata_scsi_start
+root sata_scsi_destroy_pkt
+root sata_scsi_sync_pkt
+root sata_scsi_tgt_init
+root sata_scsi_tgt_free
+root sata_scsi_tgt_probe
+root sata_scsi_dmafree
+root sata_scsi_abort
+root sata_scsi_getcap
+root sata_scsi_setcap
+
+add sd.c:sd_start_cmds/funcp target sd_initpkt_for_buf sd_initpkt_for_uscsi
+root sd.c:sd_handle_mchange sd_media_change_task sd_start_stop_unit_task
+root sd.c:sd_wm_cache_constructor sd_wm_cache_destructor
+root sd.c:sd_read_modify_write_task
+root sd.c:sd_failfast_flushq_callback sd_start_direct_priority_command
+root sd.c:sdstrategy sdioctl
+
+root scsi_hba.c:scsi_hba_bus_power
+
+ignore sd.c:sd_scsi_probe_cache_fini
+ignore sd.c:sd_scsi_probe_cache_init
+root sd.c:sd_taskq_create
+root sd.c:sd_taskq_delete
+
+add bus_ops::bus_add_eventcall targets warlock_dummy
+add bus_ops::bus_config targets warlock_dummy
+add bus_ops::bus_get_eventcookie targets warlock_dummy
+add bus_ops::bus_intr_ctl targets warlock_dummy
+add bus_ops::bus_post_event targets warlock_dummy
+add bus_ops::bus_remove_eventcall targets warlock_dummy
+add bus_ops::bus_unconfig targets warlock_dummy
+
+add scsi_hba_tran::tran_tgt_init targets sata_scsi_tgt_init
+add scsi_hba_tran::tran_tgt_probe targets sata_scsi_tgt_probe
+add scsi_hba_tran::tran_tgt_free targets sata_scsi_tgt_free
+add scsi_hba_tran::tran_start targets sata_scsi_start
+add scsi_hba_tran::tran_abort targets sata_scsi_abort
+add scsi_hba_tran::tran_reset targets sata_scsi_reset
+add scsi_hba_tran::tran_getcap targets sata_scsi_getcap
+add scsi_hba_tran::tran_setcap targets sata_scsi_setcap
+add scsi_hba_tran::tran_init_pkt targets sata_scsi_init_pkt
+add scsi_hba_tran::tran_destroy_pkt targets sata_scsi_destroy_pkt
+
+add scsi_hba_tran::tran_add_eventcall targets warlock_dummy
+add scsi_hba_tran::tran_bus_config targets warlock_dummy
+add scsi_hba_tran::tran_bus_power targets warlock_dummy
+add scsi_hba_tran::tran_bus_unconfig targets warlock_dummy
+add scsi_hba_tran::tran_get_eventcookie targets warlock_dummy
+add scsi_hba_tran::tran_get_name targets warlock_dummy
+add scsi_hba_tran::tran_post_event targets warlock_dummy
+add scsi_hba_tran::tran_remove_eventcall targets warlock_dummy
+root scsi_hba.c:scsi_hba_bus_power
+
+add dk_callback::dkc_callback targets warlock_dummy
+add sd_uscsi_info::ui_dkc.dkc_callback targets warlock_dummy
+
+add scsi_watch_request::swr_callback targets \
+ sd.c:sd_mhd_watch_cb \
+ sd.c:sd_media_watch_cb
+
+add scsi_pkt::pkt_comp targets \
+ scsi_watch.c:scsi_watch_request_intr \
+ sd.c:sdintr \
+ sata_scsi_destroy_pkt \
+ sata_scsi_init_pkt \
+ sata_scsi_start \
+ sata_scsi_abort \
+ sata_scsi_reset \
+ sata_scsi_start
+
+add __ddi_xbuf_attr::xa_strategy targets sd_xbuf_strategy
+
+ignore sd.c:sd_mhd_reset_notify_cb
+ignore si_mop_commands
+
+assert order si_ctl_state::sictl_mutex si_port_state::siport_mutex
diff --git a/usr/src/uts/sparc/warlock/Makefile b/usr/src/uts/sparc/warlock/Makefile
index 66d9f7cb84..a67a32a818 100644
--- a/usr/src/uts/sparc/warlock/Makefile
+++ b/usr/src/uts/sparc/warlock/Makefile
@@ -2,9 +2,8 @@
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
-# Common Development and Distribution License, Version 1.0 only
-# (the "License"). You may not use this file except in compliance
-# with the License.
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# or http://www.opensolaris.org/os/licensing.
@@ -19,10 +18,13 @@
#
# CDDL HEADER END
#
+
+#
# Copyright 2005 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
# ident "%Z%%M% %I% %E% SMI"
+#
# This makefile drives the production of the fas driver kernel module.
#
@@ -58,7 +60,7 @@ include $(UTSBASE)/sparc/Makefile.sparc
# lock_lint rules
#
all: warlock warlock.1394 warlock.audio warlock.ecpp warlock.scsi \
- warlock.smartcard warlock.usb warlock.ib
+ warlock.smartcard warlock.usb warlock.ib warlock.sata
warlock: $(MODULE).ok
@@ -134,3 +136,7 @@ warlock.ib:
@cd ../ibtl; rm -f *.ll *.ok; $(MAKE) warlock
@cd ../ibcm; rm -f *.ll *.ok; $(MAKE) warlock
@cd ../ibd; rm -f *.ll *.ok; $(MAKE) warlock
+
+warlock.sata:
+ @cd ../sata; rm -f *.ll *.ok; $(MAKE) warlock
+ @cd ../si3124; rm -f *.ll *.ok; $(MAKE) warlock