diff options
author | mlf <none@none> | 2006-01-13 09:07:56 -0800 |
---|---|---|
committer | mlf <none@none> | 2006-01-13 09:07:56 -0800 |
commit | 66f9d5cb3cc0652e2d9d1366fb950efbe4ca2f24 (patch) | |
tree | fbf77a0c942a6159b219a211ebae2372b5724151 | |
parent | dbd19a093870d9271e11398b346816e7ea2e7eaa (diff) | |
download | illumos-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
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 |