From: Luben Tuikov <luben_tuikov@adaptec.com>

This is The SAS Layer with suggestions by Nish and Alexey,

http://www.geocities.com/ltuikov/mm2/sas-class.patch.gz

Cc: James Bottomley <James.Bottomley@steeleye.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 drivers/scsi/sas-class/Kconfig         |   40 
 drivers/scsi/sas-class/Makefile        |   42 
 drivers/scsi/sas-class/README          |  440 +++++++
 drivers/scsi/sas-class/expander_conf.c |  463 ++++++++
 drivers/scsi/sas-class/sas_common.c    |  115 ++
 drivers/scsi/sas-class/sas_discover.c  | 1574 ++++++++++++++++++++++++++++
 drivers/scsi/sas-class/sas_dump.c      |   77 +
 drivers/scsi/sas-class/sas_dump.h      |   43 
 drivers/scsi/sas-class/sas_event.c     |  294 +++++
 drivers/scsi/sas-class/sas_expander.c  | 1829 +++++++++++++++++++++++++++++++++
 drivers/scsi/sas-class/sas_init.c      |  229 ++++
 drivers/scsi/sas-class/sas_internal.h  |   79 +
 drivers/scsi/sas-class/sas_phy.c       |  307 +++++
 drivers/scsi/sas-class/sas_port.c      |  430 +++++++
 drivers/scsi/sas-class/sas_scsi_host.c |  991 +++++++++++++++++
 15 files changed, 6953 insertions(+)

diff -puN /dev/null drivers/scsi/sas-class/expander_conf.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ devel-akpm/drivers/scsi/sas-class/expander_conf.c	2005-09-09 19:06:37.000000000 -0700
@@ -0,0 +1,463 @@
+/*
+ * Serial Attached SCSI (SAS) Expander communication user space program
+ *
+ * Copyright (C) 2005 Adaptec, Inc.  All rights reserved.
+ * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
+ *
+ * This file is licensed under GPLv2.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * $Id: //depot/sas-class/expander_conf.c#6 $
+ */
+
+/* This is a simple program to show how to communicate with
+ * expander devices in a SAS domain.
+ *
+ * The process is simple:
+ * 1. Build the SMP frame you want to send. The format and layout
+ *    is described in the SAS spec.  Leave the CRC field equal 0.
+ * 2. Open the expander's SMP portal sysfs file in RW mode.
+ * 3. Write the frame you built in 1.
+ * 4. Read the amount of data you expect to receive for the frame you built.
+ *    If you receive different amount of data you expected to receive,
+ *    then there was some kind of error.
+ * All this process is shown in detail in the function do_smp_func()
+ * and its callers, below.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <string.h>
+#include <errno.h>
+#include <endian.h>
+#include <byteswap.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#define LEFT_FIELD_SIZE 25
+
+#ifdef __LITTLE_ENDIAN
+#define be64_to_cpu(_x)  __bswap_64(*(uint64_t *)(&(_x)))
+#define be32_to_cpu(_x)  __bswap_32(*(uint32_t *)(&(_x)))
+#define be16_to_cpu(_x)  __bswap_16(*(uint16_t *)(&(_x)))
+#define cpu_to_be64(_x)  __bswap_64(*(uint64_t *)(&(_x)))
+#define cpu_to_be32(_x)  __bswap_32(*(uint32_t *)(&(_x)))
+#define cpu_to_be16(_x)  __bswap_16(*(uint16_t *)(&(_x)))
+#else
+#define be64_to_cpu(_x)  (_x)
+#define be32_to_cpu(_x)  (_x)
+#define be16_to_cpu(_x)  (_x)
+#define cpu_to_be64(_x)  (_x)
+#define cpu_to_be32(_x)  (_x)
+#define cpu_to_be16(_x)  (_x)
+#endif
+
+#define SAS_ADDR(_x) ((unsigned long long) be64_to_cpu(*(uint64_t *)(_x)))
+#define SAS_ADDR_SIZE 8
+
+const char *prog;
+
+struct route_table_entry {
+	int      disabled;
+	uint8_t  routed_sas_addr[SAS_ADDR_SIZE];
+};
+
+struct expander {
+	int     num_phys;
+	uint8_t *phy_attr;
+	int     route_indexes;
+};
+
+static int do_smp_func(char *smp_portal_name, void *smp_req, int smp_req_size,
+		       void *smp_resp, int smp_resp_size)
+{
+	int fd;
+	ssize_t res;
+
+	fd = open(smp_portal_name, O_RDWR);
+	if (fd == -1) {
+		printf("%s: opening %s: %s(%d)\n", prog, smp_portal_name,
+		       strerror(errno), errno);
+		return fd;
+	}
+
+	res = write(fd, smp_req, smp_req_size);
+	if (!res) {
+		printf("%s: nothing could be written to %s\n", prog,
+		       smp_portal_name);
+		goto out_err;
+	} else if (res == -1) {
+		printf("%s: writing to %s: %s(%d)\n", prog, smp_portal_name,
+		       strerror(errno), errno);
+		goto out_err;
+	}
+
+	res = read(fd, smp_resp, smp_resp_size);
+	if (!res) {
+		printf("%s: nothing could be read from %s\n", prog,
+		       smp_portal_name);
+		goto out_err;
+	} else if (res == -1) {
+		printf("%s: reading from %s: %s(%d)\n", prog, smp_portal_name,
+		       strerror(errno), errno);
+		goto out_err;
+	}
+	close(fd);
+	return res;
+ out_err:
+	close(fd);
+	return -1;
+}
+
+#define MI_REQ_SIZE   8
+#define MI_RESP_SIZE 64
+
+static unsigned char mi_req[MI_REQ_SIZE] = { 0x40, 1, 0, };
+static unsigned char mi_resp[MI_RESP_SIZE];
+
+#define MI_FIELD_SIZE 20
+#define MI_PRINTS(a, b) printf("%*s %*s\n",LEFT_FIELD_SIZE,a,MI_FIELD_SIZE,b)
+#define MI_PRINTD(a, b) printf("%*s %*u\n",LEFT_FIELD_SIZE,a,MI_FIELD_SIZE,b)
+#define MI_PRINTA(a, b) printf("%*s %0*llx\n",LEFT_FIELD_SIZE,a,MI_FIELD_SIZE,b)
+
+static int mi_expander(char *smp_portal_name, struct expander *ex)
+{
+	int res;
+
+	res = do_smp_func(smp_portal_name, mi_req, MI_REQ_SIZE,
+			  mi_resp, MI_RESP_SIZE);
+	if (res == MI_RESP_SIZE && mi_resp[2] == 0) {
+		char buf[20];
+
+		memcpy(buf, mi_resp+12, 8);
+		buf[8] = 0;
+		MI_PRINTS("Vendor:", buf);
+
+		memcpy(buf, mi_resp+20, 16);
+		buf[16] = 0;
+		MI_PRINTS("Product:", buf);
+
+		memcpy(buf, mi_resp+36, 4);
+		buf[4] = 0;
+		MI_PRINTS("Revision:", buf);
+
+		if (!(mi_resp[8] & 1))
+			return 0;
+
+		memcpy(buf, mi_resp+40, 8);
+		buf[8] = 0;
+		MI_PRINTS("Component:", buf);
+
+		MI_PRINTD("Component ID:", be16_to_cpu(mi_resp[48]));
+		MI_PRINTD("Component revision:", mi_resp[50]);
+	}
+	return 0;
+}
+
+#define RG_REQ_SIZE   8
+#define RG_RESP_SIZE 32
+
+static unsigned char rg_req[RG_REQ_SIZE] = { 0x40, 0, };
+static unsigned char rg_resp[RG_RESP_SIZE];
+
+static int rg_expander(char *smp_portal_name, struct expander *ex)
+{
+	int res;
+
+	res = do_smp_func(smp_portal_name, rg_req, RG_REQ_SIZE, rg_resp,
+			  RG_RESP_SIZE);
+
+	if (res == RG_RESP_SIZE && rg_resp[2] == 0) {
+		MI_PRINTD("Expander Change Count:", be16_to_cpu(rg_resp[4]));
+		MI_PRINTD("Expander Route Indexes:", be16_to_cpu(rg_resp[6]));
+		ex->route_indexes = be16_to_cpu(rg_resp[6]);
+		MI_PRINTD("Number of phys:", rg_resp[9]);
+		ex->num_phys = rg_resp[9];
+		MI_PRINTS("Configuring:", (rg_resp[10] & 2) ? "Yes" : "No");
+		MI_PRINTS("Configurable route table:",
+			  (rg_resp[10] & 1) ? "Yes" : "No");
+		MI_PRINTA("Enclosure Logical Identifier:",
+			  SAS_ADDR(rg_resp+12));
+		ex->phy_attr = malloc(ex->num_phys * sizeof(*ex->phy_attr));
+	}
+	return 0;
+}
+
+#define DISCOVER_REQ_SIZE  16
+#define DISCOVER_RESP_SIZE 56
+
+static unsigned char disc_req[DISCOVER_REQ_SIZE] = {0x40, 0x10, 0, };
+static unsigned char disc_resp[DISCOVER_RESP_SIZE];
+
+#define PHY_EEXIST 0x10
+#define PHY_VACANT 0x16
+
+static const char *attached_dev_type[8] = {
+	[0] = "none",
+	[1] = "end device",
+	[2] = "edge expander",
+	[3] = "fanout expander",
+	[4 ... 7] = "unknown",
+};
+
+static const char *phy_link_rate[16] = {
+	[0] = "unknown",
+	[1] = "disabled",
+	[2] = "phy reset problem",
+	[3] = "spinup hold",
+	[4] = "port selector",
+	[5 ... 7] = "unknown",
+	[8] = "G1 (1,5 Gb/s)",
+	[9] = "G2 (3 GB/s)",
+	[10 ... 15] = "Unknown",
+};
+
+static const char *proto_table[8] = {
+	"",
+	"SMP", "STP", "STP|SMP", "SSP",
+	"SSP|SMP", "SSP|STP", "SSP|STP|SMP",
+};
+
+#define DIRECT_ROUTING      0
+#define SUBTRACTIVE_ROUTING 1
+#define TABLE_ROUTING       2
+
+static const char *routing_attr[8] = {
+	[DIRECT_ROUTING] = "D",
+	[SUBTRACTIVE_ROUTING] = "S",
+	[TABLE_ROUTING] = "T",
+	[3 ... 7] = "x",
+};
+
+static const char *conn_type[0x80] = {
+	[0] = "No information",
+	[1] = "SAS external receptacle (i.e., SFF-8470)(see SAS-1.1)",
+	[2] = "SAS external compact receptacle (i.e., SFF-8088)(see SAS-1.1)",
+	[3 ... 0x0f ] = "External connector",
+	[0x10] = "SAS internal wide plug (i.e., SFF-8484)(see SAS-1.1)",
+	[0x11] = "SAS internal compact wide plug (i.e., SFF-8087)(see SAS-1.1)",
+	[0x12 ... 0x1f] = "Internal wide connector",
+	[0x20] = "SAS backplane receptacle (i.e., SFF-8482)(see SAS-1.1)",
+	[0x21] = "SATA-style host plug (i.e., ATA/ATAPI-7 V3)(see SAS-1.1)",
+	[0x22] = "SAS plug (i.e., SFF-8482)(see SAS-1.1)",
+	[0x23] = "SATA device plug (i.e., ATA/ATAPI-7 V3)(see SAS-1.1)",
+	[0x24 ... 0x2f] = "Internal connector to end device",
+	[0x30 ... 0x6f] = "Unknown\n",
+	[0x70 ... 0x7f] = "Vendor specific",
+};
+
+static int discover_phy(char *smp_portal_name, int phy_id, struct expander *ex)
+{
+	int res;
+	const char *dev_str;
+
+	disc_req[9] = phy_id;
+
+	res = do_smp_func(smp_portal_name, disc_req, DISCOVER_REQ_SIZE,
+			  disc_resp, DISCOVER_RESP_SIZE);
+
+	if (res != DISCOVER_RESP_SIZE) {
+		printf("%s: error disovering phy %d\n", prog, phy_id);
+		goto out;
+	}
+	switch (disc_resp[2]) {
+	case PHY_VACANT:
+		printf("phy%02d: vacant\n", phy_id);
+		goto out; break;
+	case PHY_EEXIST:
+		printf("phy%02d doesn't exist\n", phy_id);
+		goto out; break;
+	case 0:
+		break;
+	default:
+		printf("phy%02d SMP function result: 0x%x\n",
+		       phy_id, disc_resp[2]);
+		goto out;
+	}
+
+	printf("Phy%02d:%s    attached: %016llx:%02d    chg count:%02d\n",
+	       phy_id, routing_attr[disc_resp[44] & 0x0F],
+	       SAS_ADDR(disc_resp+24), disc_resp[32], disc_resp[42]);
+
+	if (ex->phy_attr)
+		ex->phy_attr[phy_id] = disc_resp[44] & 0x0F;
+
+	if (disc_resp[14] & 1)
+		dev_str = "SATA Host";
+	else if (disc_resp[15] & 0x80)
+		dev_str = "SATA Port Selector";
+	else if (disc_resp[15] & 1)
+		dev_str = "SATA device";
+	else
+		dev_str = attached_dev_type[(disc_resp[12] & 0x70) >> 4];
+	printf(" Attached device: %15s    Link rate: %15s\n",
+	       dev_str, phy_link_rate[disc_resp[13] & 0xf]);
+
+	printf(" Tproto: %15s    Iproto: %15s\n",
+	       proto_table[(disc_resp[15] & 0xe) >> 1],
+	       proto_table[(disc_resp[14] & 0xe) >> 1]);
+
+	printf(" Programmed MIN-MAX linkrate: %s - %s\n",
+	       phy_link_rate[(disc_resp[40] & 0xF0)>>4],
+	       phy_link_rate[(disc_resp[41] & 0xF0)>>4]);
+
+	printf(" Hardware   MIN-MAX linkrate: %s - %s\n",
+	       phy_link_rate[(disc_resp[40] & 0x0F)],
+	       phy_link_rate[(disc_resp[41] & 0x0F)]);
+
+	printf(" %s phy\n", (disc_resp[43] & 0x80) ? "Virtual" : "Physical");
+	printf(" Partial pathway timeout value: %d microseconds\n",
+	       disc_resp[43] & 0x0F);
+
+	printf(" Connector type: %s\n", conn_type[disc_resp[45] & 0x7F]);
+	printf(" Connector element index: %d\n", disc_resp[46]);
+	printf(" Connector physical link: %d\n", disc_resp[47]);
+out:
+	return 0;
+}
+
+#define RPEL_REQ_SIZE  16
+#define RPEL_RESP_SIZE 32
+
+static unsigned char rpel_req[RPEL_REQ_SIZE] = { 0x40, 0x11, 0, };
+static unsigned char rpel_resp[RPEL_RESP_SIZE];
+
+static int report_phy_error_log(char *smp_portal_name, int phy_id)
+{
+	int res;
+
+	rpel_req[9] = phy_id;
+
+	res = do_smp_func(smp_portal_name, rpel_req, RPEL_REQ_SIZE,
+			  rpel_resp, RPEL_RESP_SIZE);
+	if (res != RPEL_RESP_SIZE) {
+		printf("%s: error reading error log for phy %d (%d)\n",
+		       prog, phy_id, res);
+		goto out;
+	}
+	MI_PRINTD(" Invalid DW count:", be32_to_cpu(rpel_resp[12]));
+	MI_PRINTD(" RD error count:", be32_to_cpu(rpel_resp[16]));
+	MI_PRINTD(" DW sync loss count:", be32_to_cpu(rpel_resp[20]));
+	MI_PRINTD(" Reset problem count:", be32_to_cpu(rpel_resp[24]));
+out:
+	return 0;
+}
+
+#define RRI_REQ_SIZE  16
+#define RRI_RESP_SIZE 44
+
+static unsigned char rri_req[RRI_REQ_SIZE] = { 0x40, 0x13, 0, };
+static unsigned char rri_resp[RRI_RESP_SIZE];
+
+static int show_routing_table(char *smp_portal_name, int phy_id,
+			      struct expander *ex)
+{
+	struct route_table_entry *rt;
+	int res, i, last_non_zero = -1;
+
+	if (!ex->phy_attr || ex->phy_attr[phy_id] != TABLE_ROUTING)
+		return 0;
+
+	rt = malloc(sizeof(struct route_table_entry)*ex->route_indexes);
+	if (!rt)
+		return 0;
+
+	rri_req[9] = phy_id;
+
+	for (i = 0; i < ex->route_indexes; i++) {
+		*(uint16_t *)(rri_req+6) = cpu_to_be16(i);
+		res = do_smp_func(smp_portal_name, rri_req, RRI_REQ_SIZE,
+				  rri_resp, RRI_RESP_SIZE);
+		if (res != RRI_RESP_SIZE) {
+			printf("Error getting phy %d route index %d (%d)\n",
+			       phy_id, i, res);
+			goto out;
+		}
+		if (rri_resp[2] != 0)
+			break;
+		if (be16_to_cpu(rri_resp[6]) != i) {
+			printf("Expander FW error for phy %d index %d\n",
+			       phy_id, i);
+			goto out;
+		}
+		rt[i].disabled = rri_resp[12] & 0x80 ? 1 : 0;
+		memcpy(rt[i].routed_sas_addr, rri_resp+16, SAS_ADDR_SIZE);
+		if (rt[i].routed_sas_addr[0])
+			last_non_zero = i;
+	}
+	printf(" Routing Table\n");
+	if (last_non_zero == -1)
+		printf("  Empty (all zero)\n");
+	else {
+		for (i = 0; i <= last_non_zero; i++)
+			printf("  %02d %8s %016llx\n",
+			       i, rt[i].disabled ? "disabled" : "enabled",
+			       SAS_ADDR(rt[i].routed_sas_addr));
+	}
+out:
+	free(rt);
+	return 0;
+}
+
+static int discover_expander(char *smp_portal_name, struct expander *ex)
+{
+	int i;
+
+	for (i = 0; i < ex->num_phys; i++) {
+		printf("\n");
+		discover_phy(smp_portal_name, i, ex);
+		report_phy_error_log(smp_portal_name, i);
+		show_routing_table(smp_portal_name, i, ex);
+	}
+
+	return 0;
+}
+
+static void print_info(void)
+{
+	printf("%s <smp portal file>\n", prog);
+	printf("\tWhere <smp portal file> is the binary attribute file of the"
+	       "\n\texpander device in sysfs.\n");
+}
+
+int main(int argc, char *argv[])
+{
+	prog = strrchr(argv[0], '/');
+	if (prog)
+		prog++;
+	else
+		prog = argv[0];
+
+	if (argc < 2) {
+		print_info();
+		return 0;
+	} else {
+		struct expander ex;
+
+		memset(&ex, 0, sizeof(ex));
+
+		mi_expander(argv[1], &ex);
+		rg_expander(argv[1], &ex);
+		discover_expander(argv[1], &ex);
+		if (ex.phy_attr)
+			free(ex.phy_attr);
+	}
+	return 0;
+}
diff -puN /dev/null drivers/scsi/sas-class/Kconfig
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ devel-akpm/drivers/scsi/sas-class/Kconfig	2005-09-09 19:06:37.000000000 -0700
@@ -0,0 +1,40 @@
+#
+# Kernel configuration file for the SAS Class
+#
+# Copyright (C) 2005 Adaptec, Inc.  All rights reserved.
+# Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
+#
+# This file is licensed under GPLv2.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; version 2 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+#
+# $Id: //depot/sas-class/Kconfig#2 $
+#
+
+config SAS_CLASS
+	tristate "SAS Layer and Discovery"
+	depends on SCSI
+	help
+		If you wish to use a SAS Low Level Device Driver (LLDD)
+		say Y or M here.  Otherwise, say N.
+
+config SAS_DEBUG
+	bool "Compile the SAS Layer in debug mode"
+	default y
+	depends on SAS_CLASS
+	help
+		Compiles the SAS Layer in debug mode.  In debug mode, the
+		SAS Layer prints diagnostic and debug messages.
diff -puN /dev/null drivers/scsi/sas-class/Makefile
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ devel-akpm/drivers/scsi/sas-class/Makefile	2005-09-09 19:06:37.000000000 -0700
@@ -0,0 +1,42 @@
+#
+# Kernel Makefile for the SAS Class
+#
+# Copyright (C) 2005 Adaptec, Inc.  All rights reserved.
+# Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
+#
+# This file is licensed under GPLv2.
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; version 2 of the
+# License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+# USA
+#
+# $Id: //depot/sas-class/Makefile#19 $
+#
+
+ifeq ($(CONFIG_SAS_DEBUG),y)
+	EXTRA_CFLAGS += -DSAS_DEBUG -g
+endif
+
+clean-files += expander_conf
+
+obj-$(CONFIG_SAS_CLASS) += sas_class.o
+sas_class-y +=  sas_init.o     \
+		sas_common.o   \
+		sas_phy.o      \
+		sas_port.o     \
+		sas_event.o    \
+		sas_dump.o     \
+		sas_discover.o \
+		sas_expander.o \
+		sas_scsi_host.o
diff -puN /dev/null drivers/scsi/sas-class/README
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ devel-akpm/drivers/scsi/sas-class/README	2005-09-09 19:06:37.000000000 -0700
@@ -0,0 +1,440 @@
+SAS Layer
+---------
+
+The SAS Layer is a management infrastructure which manages
+SAS LLDDs.  It sits between SCSI Core and SAS LLDDs.  The
+layout is as follows: while SCSI Core is concerned with
+SAM/SPC issues, and a SAS LLDD+sequencer is concerned with
+phy/OOB/link management, the SAS layer is concerned with:
+
+      * SAS Phy/Port/HA event management (LLDD generates,
+        SAS Layer processes),
+      * SAS Port management (creation/destruction),
+      * SAS Domain discovery and revalidation,
+      * SAS Domain device management,
+      * SCSI Host registration/unregistration,
+      * Device registration with SCSI Core (SAS) or libata
+        (SATA), and
+      * Expander management and exporting expander control
+        to user space.
+
+A SAS LLDD is a PCI device driver.  It is concerned with
+phy/OOB management, and vendor specific tasks and generates
+events to the SAS layer.
+
+The SAS Layer does most SAS tasks as outlined in the SAS 1.1
+spec.
+
+The sas_ha_struct describes the SAS LLDD to the SAS layer.
+Most of it is used by the SAS Layer but a few fields need to
+be initialized by the LLDDs.
+
+After initializing your hardware, from the probe() function
+you call sas_register_ha(). It will register your LLDD with
+the SCSI subsystem, creating a SCSI host and it will
+register your SAS driver with the sysfs SAS tree it creates.
+It will then return.  Then you enable your phys to actually
+start OOB (at which point your driver will start calling the
+notify_* event callbacks).
+
+Structure descriptions:
+
+struct sas_phy --------------------
+Normally this is statically embedded to your driver's
+phy structure:
+	struct my_phy {
+	       blah;
+	       struct sas_phy sas_phy;
+	       bleh;
+	};
+And then all the phys are an array of my_phy in your HA
+struct (shown below).
+
+Then as you go along and initialize your phys you also
+initialize the sas_phy struct, along with your own
+phy structure.
+
+In general, the phys are managed by the LLDD and the ports
+are managed by the SAS layer.  So the phys are initialized
+and updated by the LLDD and the ports are initialized and
+updated by the SAS layer.
+
+There is a scheme where the LLDD can RW certain fields,
+and the SAS layer can only read such ones, and vice versa.
+The idea is to avoid unnecessary locking.
+
+enabled -- must be set (0/1)
+id -- must be set [0,MAX_PHYS)
+class, proto, type, role, oob_mode, linkrate -- must be set
+oob_mode --  you set this when OOB has finished and then notify
+the SAS Layer.
+
+sas_addr -- this normally points to an array holding the sas
+address of the phy, possibly somewhere in your my_phy
+struct.
+
+attached_sas_addr -- set this when you (LLDD) receive an
+IDENTIFY frame or a FIS frame, _before_ notifying the SAS
+layer.  The idea is that sometimes the LLDD may want to fake
+or provide a different SAS address on that phy/port and this
+allows it to do this.  At best you should copy the sas
+address from the IDENTIFY frame or maybe generate a SAS
+address for SATA directly attached devices.  The Discover
+process may later change this.
+
+frame_rcvd -- this is where you copy the IDENTIFY/FIS frame
+when you get it; you lock, copy, set frame_rcvd_size and
+unlock the lock, and then call the event.  It is a pointer
+since there's no way to know your hw frame size _exactly_,
+so you define the actual array in your phy struct and let
+this pointer point to it.  You copy the frame from your
+DMAable memory to that area holding the lock.
+
+sas_prim -- this is where primitives go when they're
+received.  See sas.h. Grab the lock, set the primitive,
+release the lock, notify.
+
+port -- this points to the sas_port if the phy belongs
+to a port -- the LLDD only reads this. It points to the
+sas_port this phy is part of.  Set by the SAS Layer.
+
+ha -- may be set; the SAS layer sets it anyway.
+
+lldd_phy -- you should set this to point to your phy so you
+can find your way around faster when the SAS layer calls one
+of your callbacks and passes you a phy.  If the sas_phy is
+embedded you can also use container_of -- whatever you
+prefer.
+
+
+struct sas_port --------------------
+The LLDD doesn't set any fields of this struct -- it only
+reads them.  They should be self explanatory.
+
+phy_mask is 32 bit, this should be enough for now, as I
+haven't heard of a HA having more than 8 phys.
+
+lldd_port -- I haven't found use for that -- maybe other
+LLDD who wish to have internal port representation can make
+use of this.
+
+
+struct sas_ha_struct --------------------
+It normally is statically declared in your own LLDD
+structure describing your adapter:
+struct my_sas_ha {
+       blah;
+       struct sas_ha_struct sas_ha;
+       struct my_phy phys[MAX_PHYS];
+       struct sas_port sas_ports[MAX_PHYS]; /* (1) */
+       bleh;
+};
+
+(1) If your LLDD doesn't have its own port representation.
+
+What needs to be initialized (sample function given below).
+
+pcidev
+sas_addr -- since the SAS layer doesn't want to mess with
+	 memory allocation, etc, this points to statically
+	 allocated array somewhere (say in your host adapter
+	 structure) and holds the SAS address of the host
+	 adapter as given by you or the manufacturer, etc.
+sas_port
+sas_phy -- an array of pointers to structures. (see
+	note above on sas_addr).
+	These must be set.  See more notes below.
+num_phys -- the number of phys present in the sas_phy array,
+	 and the number of ports present in the sas_port
+	 array.  There can be a maximum num_phys ports (one per
+	 port) so we drop the num_ports, and only use
+	 num_phys.
+
+The event interface:
+
+	/* LLDD calls these to notify the class of an event. */
+	void (*notify_ha_event)(struct sas_ha_struct *, enum ha_event);
+	void (*notify_port_event)(struct sas_phy *, enum port_event);
+	void (*notify_phy_event)(struct sas_phy *, enum phy_event);
+
+When sas_register_ha() returns, those are set and can be
+called by the LLDD to notify the SAS layer of such events
+the SAS layer.
+
+The port notification:
+
+	/* The class calls these to notify the LLDD of an event. */
+	void (*lldd_port_formed)(struct sas_phy *);
+	void (*lldd_port_deformed)(struct sas_phy *);
+
+If the LLDD wants notification when a port has been formed
+or deformed it sets those to a function satisfying the type.
+
+A SAS LLDD should also implement at least one of the Task
+Management Functions (TMFs) described in SAM:
+
+	/* Task Management Functions. Must be called from process context. */
+	int (*lldd_abort_task)(struct sas_task *);
+	int (*lldd_abort_task_set)(struct domain_device *, u8 *lun);
+	int (*lldd_clear_aca)(struct domain_device *, u8 *lun);
+	int (*lldd_clear_task_set)(struct domain_device *, u8 *lun);
+	int (*lldd_I_T_nexus_reset)(struct domain_device *);
+	int (*lldd_lu_reset)(struct domain_device *, u8 *lun);
+	int (*lldd_query_task)(struct sas_task *);
+
+For more information please read SAM from T10.org.
+
+Port and Adapter management:
+
+	/* Port and Adapter management */
+	int (*lldd_clear_nexus_port)(struct sas_port *);
+	int (*lldd_clear_nexus_ha)(struct sas_ha_struct *);
+
+A SAS LLDD should implement at least one of those.
+
+Phy management:
+
+	/* Phy management */
+	int (*lldd_control_phy)(struct sas_phy *, enum phy_func);
+
+lldd_ha -- set this to point to your HA struct. You can also
+use container_of if you embedded it as shown above.
+
+A sample initialization and registration function
+can look like this (called last thing from probe())
+*but* before you enable the phys to do OOB:
+
+static int register_sas_ha(struct my_sas_ha *my_ha)
+{
+	int i;
+	static struct sas_phy   *sas_phys[MAX_PHYS];
+	static struct sas_port  *sas_ports[MAX_PHYS];
+
+	my_ha->sas_ha.sas_addr = &my_ha->sas_addr[0];
+
+	for (i = 0; i < MAX_PHYS; i++) {
+		sas_phys[i] = &my_ha->phys[i].sas_phy;
+		sas_ports[i] = &my_ha->sas_ports[i];
+	}
+
+	my_ha->sas_ha.sas_phy  = sas_phys;
+	my_ha->sas_ha.sas_port = sas_ports;
+	my_ha->sas_ha.num_phys = MAX_PHYS;
+
+	my_ha->sas_ha.lldd_port_formed = my_port_formed;
+
+	return sas_register_ha(&my_ha->sas_ha);
+}
+
+
+Events
+------
+
+Events are _the only way_ a SAS LLDD notifies the SAS layer
+of anything.  There is no other method or way a LLDD to tell
+the SAS layer of anything happening internally or on the SAS
+domain.
+
+Phy events:
+	PHYE_LOSS_OF_SIGNAL, (C)
+	PHYE_OOB_DONE,
+	PHYE_OOB_ERROR,      (C)
+	PHYE_SPINUP_HOLD.
+
+Port events, passed on a _phy_:
+	PORTE_BYTES_DMAED,      (M)
+	PORTE_BROADCAST_RCVD,   (E)
+	PORTE_LINK_RESET_ERR,   (C)
+	PORTE_TIMER_EVENT,      (C)
+	PORTE_HARD_RESET.
+
+Host Adapter event:
+	HAE_RESET
+
+A SAS LLDD should be able to generate
+	- at least one event from group C (choice),
+	- events marked M (mandatory) are mandatory (only one),
+	- events marked E (expander) if it wants the SAS layer
+	  to handle domain revalidation (only one such).
+	- Unmarked events are optional.
+
+Meaning:
+
+HAE_RESET -- when your HA got internal error and was reset.
+
+PORTE_BYTES_DMAED -- on receiving an IDENTIFY/FIS frame
+PORTE_BROADCAST_RCVD -- on receiving a primitive
+PORTE_LINK_RESET_ERR -- timer expired, loss of signal, loss
+of DWS, etc. (*)
+PORTE_TIMER_EVENT -- DWS reset timeout timer expired (*)
+PORTE_HARD_RESET -- Hard Reset primitive received.
+
+PHYE_LOSS_OF_SIGNAL -- the device is gone (*)
+PHYE_OOB_DONE -- OOB went fine and oob_mode is valid
+PHYE_OOB_ERROR -- Error while doing OOB, the device probably
+got disconnected. (*)
+PHYE_SPINUP_HOLD -- SATA is present, COMWAKE not sent.
+
+(*) should set/clear the appropriate fields in the phy,
+    or alternatively call the inlined sas_phy_disconnected()
+    which is just a helper, from their tasklet.
+
+The Execute Command SCSI RPC:
+
+	int (*lldd_execute_task)(struct sas_task *, int num,
+				 unsigned long gfp_flags);
+
+Used to queue a task to the SAS LLDD.  @task is the tasks to
+be executed.  @num should be the number of tasks being
+queued at this function call (they are linked listed via
+task::list), @gfp_mask should be the gfp_mask defining the
+context of the caller.
+
+This function should implement the Execute Command SCSI RPC,
+or if you're sending a SCSI Task as linked commands, you
+should also use this function.
+
+That is, when lldd_execute_task() is called, the command(s)
+go out on the transport *immediately*.  There is *no*
+queuing of any sort and at any level in a SAS LLDD.
+
+The use of task::list is two-fold, one for linked commands,
+the other discussed below.
+
+It is possible to queue up more than one task at a time, by
+initializing the list element of struct sas_task, and
+passing the number of tasks enlisted in this manner in num.
+
+Returns: -SAS_QUEUE_FULL, -ENOMEM, nothing was queued;
+	 0, the task(s) were queued.
+
+If you want to pass num > 1, then either
+A) you're the only caller of this function and keep track
+   of what you've queued to the LLDD, or
+B) you know what you're doing and have a strategy of
+   retrying.
+
+As opposed to queuing one task at a time (function call),
+batch queuing of tasks, by having num > 1, greatly
+simplifies LLDD code, sequencer code, and _hardware design_,
+and has some performance advantages in certain situations
+(DBMS).
+
+The LLDD advertises if it can take more than one command at
+a time at lldd_execute_task(), by setting the
+lldd_max_execute_num parameter (controlled by "collector"
+module parameter in aic94xx SAS LLDD).
+
+You should leave this to the default 1, unless you know what
+you're doing.
+
+This is a function of the LLDD, to which the SAS layer can
+cater to.
+
+int lldd_queue_size
+	The host adapter's queue size.  This is the maximum
+number of commands the lldd can have pending to domain
+devices on behalf of all upper layers submitting through
+lldd_execute_task().
+
+You really want to set this to something (much) larger than
+1.
+
+This _really_ has absolutely nothing to do with queuing.
+There is no queuing in SAS LLDDs.
+
+struct sas_task {
+	dev -- the device this task is destined to
+	list -- must be initialized (INIT_LIST_HEAD)
+	task_proto -- _one_ of enum sas_proto
+	scatter -- pointer to scatter gather list array
+	num_scatter -- number of elements in scatter
+	total_xfer_len -- total number of bytes expected to be transfered
+	data_dir -- PCI_DMA_...
+	task_done -- callback when the task has finished execution
+};
+
+When an external entity, entity other than the LLDD or the
+SAS Layer, wants to work with a struct domain_device, it
+_must_ call kobject_get() when getting a handle on the
+device and kobject_put() when it is done with the device.
+
+This does two things:
+     A) implements proper kfree() for the device;
+     B) increments/decrements the kref for all players:
+     domain_device
+	all domain_device's ... (if past an expander)
+	    port
+		host adapter
+		     pci device
+			 and up the ladder, etc.
+
+DISCOVERY
+---------
+
+The sysfs tree has the following purposes:
+    a) It shows you the physical layout of the SAS domain at
+       the current time, i.e. how the domain looks in the
+       physical world right now.
+    b) Shows some device parameters _at_discovery_time_.
+
+This is a link to the tree(1) program, very useful in
+viewing the SAS domain:
+ftp://mama.indstate.edu/linux/tree/
+I expect user space applications to actually create a
+graphical interface of this.
+
+That is, the sysfs domain tree doesn't show or keep state if
+you e.g., change the meaning of the READY LED MEANING
+setting, but it does show you the current connection status
+of the domain device.
+
+Keeping internal device state changes is responsibility of
+upper layers (Command set drivers) and user space.
+
+When a device or devices are unplugged from the domain, this
+is reflected in the sysfs tree immediately, and the device(s)
+removed from the system.
+
+The structure domain_device describes any device in the SAS
+domain.  It is completely managed by the SAS layer.  A task
+points to a domain device, this is how the SAS LLDD knows
+where to send the task(s) to.  A SAS LLDD only reads the
+contents of the domain_device structure, but it never creates
+or destroys one.
+
+Expander management from User Space
+-----------------------------------
+
+In each expander directory in sysfs, there is a file called
+"smp_portal".  It is a binary sysfs attribute file, which
+implements an SMP portal (Note: this is *NOT* an SMP port),
+to which user space applications can send SMP requests and
+receive SMP responses.
+
+Functionality is deceptively simple:
+
+1. Build the SMP frame you want to send. The format and layout
+   is described in the SAS spec.  Leave the CRC field equal 0.
+open(2)
+2. Open the expander's SMP portal sysfs file in RW mode.
+write(2)
+3. Write the frame you built in 1.
+read(2)
+4. Read the amount of data you expect to receive for the frame you built.
+   If you receive different amount of data you expected to receive,
+   then there was some kind of error.
+close(2)
+All this process is shown in detail in the function do_smp_func()
+and its callers, in the file "expander_conf.c".
+
+The kernel functionality is implemented in the file
+"sas_expander.c".
+
+The program "expander_conf.c" implements this. It takes one
+argument, the sysfs file name of the SMP portal to the
+expander, and gives expander information, including routing
+tables.
+
+The SMP portal gives you complete control of the expander,
+so please be careful.
diff -puN /dev/null drivers/scsi/sas-class/sas_common.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ devel-akpm/drivers/scsi/sas-class/sas_common.c	2005-09-09 19:06:37.000000000 -0700
@@ -0,0 +1,115 @@
+/*
+ * Serial Attached SCSI (SAS) class common functions
+ *
+ * Copyright (C) 2005 Adaptec, Inc.  All rights reserved.
+ * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
+ *
+ * This file is licensed under GPLv2.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * $Id: //depot/sas-class/sas_common.c#8 $
+ */
+
+#include <scsi/sas/sas_class.h>
+#include "sas_internal.h"
+
+int sas_show_class(enum sas_class class, char *buf)
+{
+	static const char *class_str[] = {
+		[SAS] = "SAS",
+		[EXPANDER] = "EXPANDER",
+	};
+	return sprintf(buf, "%s\n", class_str[class]);
+}
+
+int sas_show_proto(enum sas_proto proto, char *page)
+{
+	static const char *proto_str[] = {
+		[SATA_PROTO] = "SATA",
+		[SAS_PROTO_SMP] = "SMP",
+		[SAS_PROTO_STP] = "STP",
+		[SAS_PROTO_SSP] = "SSP",
+	};
+	int  v;
+	char *buf = page;
+
+	for (v = 1; proto != 0 && v <= SAS_PROTO_SSP; v <<= 1) {
+		if (v & proto) {
+			buf += sprintf(buf, "%s", proto_str[v]);
+
+			if (proto & ~((v<<1)-1))
+				buf += sprintf(buf, "|");
+			else
+				buf += sprintf(buf, "\n");
+		}
+	}
+	return buf-page;
+}
+
+int sas_show_linkrate(enum sas_phy_linkrate linkrate, char *page)
+{
+	static const char *phy_linkrate_str[] = {
+		[PHY_LINKRATE_NONE] = "",
+		[PHY_DISABLED] = "disabled",
+		[PHY_RESET_PROBLEM] = "phy reset problem",
+		[PHY_SPINUP_HOLD] = "phy spinup hold",
+		[PHY_PORT_SELECTOR] = "phy port selector",
+		[PHY_LINKRATE_1_5] = "1,5 GB/s",
+		[PHY_LINKRATE_3]  = "3,0 GB/s",
+		[PHY_LINKRATE_6] = "6,0 GB/s",
+	};
+	return sprintf(page, "%s\n", phy_linkrate_str[linkrate]);
+}
+
+int sas_show_oob_mode(enum sas_oob_mode oob_mode, char *buf)
+{
+	switch (oob_mode) {
+	case OOB_NOT_CONNECTED:
+		return sprintf(buf, "%s", "");
+		break;
+	case SATA_OOB_MODE:
+		return sprintf(buf, "%s\n", "SATA");
+		break;
+	case SAS_OOB_MODE:
+		return sprintf(buf, "%s\n", "SAS");
+		break;
+	}
+	return 0;
+}
+
+void sas_hash_addr(u8 *hashed, const u8 *sas_addr)
+{
+	const u32 poly = 0x00DB2777;
+	u32 	r = 0;
+	int 	i;
+
+	for (i = 0; i < 8; i++) {
+		int b;
+		for (b = 7; b >= 0; b--) {
+			r <<= 1;
+			if ((1 << b) & sas_addr[i]) {
+				if (!(r & 0x01000000))
+					r ^= poly;
+			} else if (r & 0x01000000)
+				r ^= poly;
+		}
+	}
+
+	hashed[0] = (r >> 16) & 0xFF;
+	hashed[1] = (r >> 8) & 0xFF ;
+	hashed[2] = r & 0xFF;
+}
diff -puN /dev/null drivers/scsi/sas-class/sas_discover.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ devel-akpm/drivers/scsi/sas-class/sas_discover.c	2005-09-09 19:06:37.000000000 -0700
@@ -0,0 +1,1574 @@
+/*
+ * Serial Attached SCSI (SAS) Discover process
+ *
+ * Copyright (C) 2005 Adaptec, Inc.  All rights reserved.
+ * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
+ *
+ * This file is licensed under GPLv2.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * $Id: //depot/sas-class/sas_discover.c#131 $
+ */
+
+#include <linux/pci.h>
+#include <linux/scatterlist.h>
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_eh.h>
+#include "sas_internal.h"
+#include <scsi/sas/sas_task.h>
+#include <scsi/sas/sas_discover.h>
+
+/* ---------- Domain device attributes ---------- */
+
+ssize_t dev_show_type(struct domain_device *dev, char *page)
+{
+	static const char *dev_type[] = {
+		"no device",
+		"end device",
+		"edge expander",
+		"fanout expander",
+		"host adapter",
+		"sata device",
+		"sata port multiplier",
+		"sata port multiplier port",
+	};
+	return sprintf(page, "%s\n", dev_type[dev->dev_type]);
+}
+
+ssize_t dev_show_iproto(struct domain_device *dev, char *page)
+{
+	return sas_show_proto(dev->iproto, page);
+}
+
+ssize_t dev_show_tproto(struct domain_device *dev, char *page)
+{
+	return sas_show_proto(dev->tproto, page);
+}
+
+ssize_t dev_show_sas_addr(struct domain_device *dev, char *page)
+{
+	return sprintf(page, "%llx\n", SAS_ADDR(dev->sas_addr));
+}
+
+ssize_t dev_show_linkrate(struct domain_device *dev, char *page)
+{
+	return sas_show_linkrate(dev->linkrate, page);
+}
+
+ssize_t dev_show_min_linkrate(struct domain_device *dev, char *page)
+{
+	return sas_show_linkrate(dev->min_linkrate, page);
+}
+
+ssize_t dev_show_max_linkrate(struct domain_device *dev, char *page)
+{
+	return sas_show_linkrate(dev->max_linkrate, page);
+}
+
+ssize_t dev_show_pathways(struct domain_device *dev, char *page)
+{
+	return sprintf(page, "%d\n", dev->pathways);
+}
+
+/* ---------- SATA specific sysfs ---------- */
+
+static ssize_t sata_show_command_set(struct domain_device *dev, char *page)
+{
+	static const char *cs[] = {
+		"ATA",
+		"ATAPI",
+	};
+	return sprintf(page, "%s\n", cs[dev->sata_dev.command_set]);
+}
+
+static ssize_t sata_show_rps_resp(struct domain_device *dev, char *page)
+{
+	char *buf = page;
+	if ((dev->tproto & SAS_PROTO_STP) &&
+	    dev->sata_dev.rps_resp.frame_type == SMP_RESPONSE &&
+	    dev->sata_dev.rps_resp.function == SMP_REPORT_PHY_SATA &&
+	    dev->sata_dev.rps_resp.result == SMP_RESP_FUNC_ACC) {
+		int i = 0;
+		u8 *p = (u8 *) &dev->sata_dev.rps_resp;
+		for (i = 0; i < sizeof(struct smp_resp); i+=4, p+=4) {
+			buf += sprintf(buf, "%02x %02x %02x %02x\n",
+				       *(p+0), *(p+1), *(p+2), *(p+3));
+		}
+	}
+	return buf-page;
+}
+
+static inline int show_chars(__le16 *p, int start, int words, char *page)
+{
+	int i;
+	char *buf = page;
+
+	for (i = start; i < start+words; i++) {
+		u16  s = le16_to_cpu(p[i]);
+		char a = (s&0xFF00)>>8;
+		char b = s&0x00FF;
+
+		if (a == 0)
+			break;
+		buf += sprintf(buf, "%c", a);
+		if (b == 0)
+			break;
+		buf += sprintf(buf, "%c", b);
+
+	}
+	return buf-page;
+}
+
+static ssize_t sata_show_serial_number(struct domain_device *dev, char *page)
+{
+	char *buf = page;
+	__le16 *identify_x = NULL;
+
+	if (dev->sata_dev.command_set == ATA_COMMAND_SET)
+		identify_x = dev->sata_dev.identify_device;
+	else
+		identify_x = dev->sata_dev.identify_packet_device;
+
+	if (identify_x &&
+	    (dev->dev_type == SATA_DEV || dev->dev_type == SATA_PM_PORT)) {
+		buf += show_chars(identify_x, 10, 10, buf);
+		buf += sprintf(buf, "\n");
+	}
+	return buf-page;
+}
+
+static ssize_t sata_show_firmware_rev(struct domain_device *dev, char *page)
+{
+	char *buf = page;
+	__le16 *identify_x = NULL;
+
+	if (dev->sata_dev.command_set == ATA_COMMAND_SET)
+		identify_x = dev->sata_dev.identify_device;
+	else
+		identify_x = dev->sata_dev.identify_packet_device;
+
+	if (identify_x &&
+	    (dev->dev_type == SATA_DEV || dev->dev_type == SATA_PM_PORT)) {
+		buf += show_chars(identify_x, 23, 4, buf);
+		buf += sprintf(buf, "\n");
+	}
+	return buf-page;
+}
+
+static ssize_t sata_show_model_number(struct domain_device *dev, char *page)
+{
+	char *buf = page;
+	__le16 *identify_x = NULL;
+
+	if (dev->sata_dev.command_set == ATA_COMMAND_SET)
+		identify_x = dev->sata_dev.identify_device;
+	else
+		identify_x = dev->sata_dev.identify_packet_device;
+
+	if (identify_x &&
+	    (dev->dev_type == SATA_DEV || dev->dev_type == SATA_PM_PORT)) {
+		buf += show_chars(identify_x, 27, 20, buf);
+		buf += sprintf(buf, "\n");
+	}
+	return buf-page;
+}
+
+static ssize_t sata_show_identify_device(struct domain_device *dev, char *page)
+{
+	char *buf = page;
+
+	if (dev->sata_dev.identify_device &&
+	    (dev->dev_type == SATA_DEV || dev->dev_type == SATA_PM_PORT)) {
+		__le16 *p = dev->sata_dev.identify_device;
+		int i;
+
+		for (i = 0; i < 16; i++) {
+			int k;
+			for (k = 0; k < 16; k++)
+				buf += sprintf(buf, "%04x%s",
+					      le16_to_cpu(p[i*16+k]),
+					      k==15 ? "\n" : " ");
+		}
+	}
+	return buf-page;
+}
+
+static ssize_t sata_show_identify_packet(struct domain_device *dev, char *page)
+{
+	char *buf = page;
+
+	if (dev->sata_dev.identify_packet_device &&
+	    (dev->dev_type == SATA_DEV || dev->dev_type == SATA_PM_PORT)) {
+		__le16 *p = dev->sata_dev.identify_packet_device;
+		int i;
+
+		for (i = 0; i < 16; i++) {
+			int k;
+			for (k = 0; k < 16; k++)
+				buf += sprintf(buf, "%04x%s",
+					      le16_to_cpu(p[i*16+k]),
+					      k==15 ? "\n" : " ");
+		}
+	}
+
+	return buf-page;
+}
+
+static ssize_t sata_show_port_no(struct domain_device *dev, char *page)
+{
+	int res = 0;
+
+	if (dev->dev_type == SATA_PM || dev->dev_type == SATA_PM_PORT)
+		res = sprintf(page, "%02Xh\n", dev->sata_dev.port_no);
+	return res;
+}
+
+/* ---------- SAS end device specific ---------- */
+
+static ssize_t sas_show_rled_meaning(struct domain_device *dev, char *page)
+{
+	return sprintf(page, "%d\n", dev->end_dev.ready_led_meaning);
+}
+
+static ssize_t sas_show_itnl_timeout(struct domain_device *dev, char *page)
+{
+	return sprintf(page, "0x%04x\n", dev->end_dev.itnl_timeout);
+}
+
+static ssize_t sas_show_iresp_timeout(struct domain_device *dev, char *page)
+{
+	return sprintf(page, "0x%04x\n", dev->end_dev.iresp_timeout);
+}
+
+static ssize_t sas_show_rl_wlun(struct domain_device *dev, char *page)
+{
+	return sprintf(page, "%d\n", dev->end_dev.rl_wlun);
+}
+
+/* ---------- LU specific ---------- */
+
+static ssize_t lu_show_lun(struct LU *lu, char *page)
+{
+	return sprintf(page, "%016llx\n", SAS_ADDR(lu->LUN));
+}
+
+static ssize_t lu_show_inq(struct LU *lu, char *_page)
+{
+	int i;
+	char *buf = _page;
+	if (lu->inquiry_valid_data_len <= 0)
+		return 0;
+	for (i = 0; i < lu->inquiry_valid_data_len; i += 4) {
+		buf += sprintf(buf, "%02x %02x %02x %02x\n",
+			       lu->inquiry_data[i+0], lu->inquiry_data[i+1],
+			       lu->inquiry_data[i+2], lu->inquiry_data[i+3]);
+	}
+
+	return buf-_page;
+}
+
+static ssize_t lu_show_tm_type(struct LU *lu, char *page)
+{
+	static const char *tm_type[] = {
+		"none",
+		"full",
+		"basic",
+	};
+	return sprintf(page, "%s\n", tm_type[lu->tm_type]);
+}
+
+static ssize_t lu_show_channel(struct LU *lu, char *page)
+{
+	if (lu->uldd_dev)
+		return sprintf(page, "%d\n", lu->map.channel);
+	return 0;
+}
+
+static ssize_t lu_show_id(struct LU *lu, char *page)
+{
+	if (lu->uldd_dev)
+		return sprintf(page, "%d\n", lu->map.id);
+	return 0;
+}
+
+/* ---------- Sysfs attribute implementation ---------- */
+
+struct lu_dev_attribute {
+	struct attribute attr;
+	ssize_t (*show)(struct LU *lu, char *);
+	ssize_t (*store)(struct LU *lu, const char *, size_t);
+};
+
+static struct lu_dev_attribute lu_attrs[] = {
+	__ATTR(lun, 0444, lu_show_lun, NULL),
+	__ATTR(inquiry_data, 0444, lu_show_inq, NULL),
+	__ATTR(channel, 0444, lu_show_channel, NULL),
+	__ATTR(id, 0444, lu_show_id, NULL),
+	__ATTR(task_management, 0444, lu_show_tm_type, NULL),
+	__ATTR_NULL,
+};
+
+static struct domain_dev_attribute dev_attrs[] = {
+	__ATTR(dev_type, 0444, dev_show_type, NULL),
+	__ATTR(iproto, 0444, dev_show_iproto, NULL),
+	__ATTR(tproto, 0444, dev_show_tproto, NULL),
+	__ATTR(sas_addr, 0444, dev_show_sas_addr, NULL),
+	__ATTR(ready_led_meaning, 0444, sas_show_rled_meaning, NULL),
+	__ATTR(itnl_timeout, 0444, sas_show_itnl_timeout, NULL),
+	__ATTR(iresp_timeout, 0444, sas_show_iresp_timeout, NULL),
+	__ATTR(rl_wlun, 0444, sas_show_rl_wlun, NULL),
+	__ATTR(linkrate, 0444, dev_show_linkrate, NULL),
+	__ATTR(min_linkrate, 0444, dev_show_min_linkrate, NULL),
+	__ATTR(max_linkrate, 0444, dev_show_max_linkrate, NULL),
+	__ATTR(pathways, 0444, dev_show_pathways, NULL),
+	__ATTR_NULL,
+};
+
+static struct domain_dev_attribute sata_attrs[] = {
+	__ATTR(dev_type, 0444, dev_show_type, NULL),
+	__ATTR(iproto, 0444, dev_show_iproto, NULL),
+	__ATTR(tproto, 0444, dev_show_tproto, NULL),
+	__ATTR(sas_addr, 0444, dev_show_sas_addr, NULL),
+	__ATTR(linkrate, 0444, dev_show_linkrate, NULL),
+	__ATTR(min_linkrate, 0444, dev_show_min_linkrate, NULL),
+	__ATTR(max_linkrate, 0444, dev_show_max_linkrate, NULL),
+	__ATTR(command_set, 0444, sata_show_command_set, NULL),
+	__ATTR(report_phy_sata_resp, 0444, sata_show_rps_resp, NULL),
+	__ATTR(serial_number, 0444, sata_show_serial_number, NULL),
+	__ATTR(firmware_rev, 0444, sata_show_firmware_rev, NULL),
+	__ATTR(model_number, 0444, sata_show_model_number, NULL),
+	__ATTR(identify_device, 0444, sata_show_identify_device, NULL),
+	__ATTR(identify_packet_device, 0444, sata_show_identify_packet, NULL),
+	__ATTR(port_no, 0444, sata_show_port_no, NULL),
+	__ATTR_NULL,
+};
+
+static void end_dev_release(struct kobject *obj)
+{
+	struct domain_device *dev = to_dom_device(obj);
+	BUG_ON(!list_empty(&dev->end_dev.LU_list));
+	SAS_DPRINTK("freeing dev %llx\n", SAS_ADDR(dev->sas_addr));
+	kfree(dev);
+}
+
+static void sata_dev_release(struct kobject *obj)
+{
+	struct domain_device *dev = to_dom_device(obj);
+
+	SAS_DPRINTK("freeing SATA dev %llx\n", SAS_ADDR(dev->sas_addr));
+	if (dev->sata_dev.identify_device) {
+		void *p = dev->sata_dev.identify_device;
+		mb();
+		dev->sata_dev.identify_device = NULL;
+		kfree(p);
+	}
+	if (dev->sata_dev.identify_packet_device) {
+		void *p = dev->sata_dev.identify_packet_device;
+		mb();
+		dev->sata_dev.identify_packet_device = NULL;
+		kfree(p);
+	}
+	kfree(dev);
+}
+
+static void sas_lu_release(struct kobject *obj)
+{
+	struct LU *lu = to_lu_device(obj);
+	SAS_DPRINTK("freeing LUN %016llx\n", SAS_ADDR(lu->LUN));
+	sas_release_scsi_id(lu->parent->port, lu->map.id);
+	kfree(lu);
+}
+
+static ssize_t dev_show_attr(struct kobject *kobj, struct attribute *attr,
+			     char *page)
+{
+	ssize_t ret = 0;
+	struct domain_device *dev = to_dom_device(kobj);
+	struct domain_dev_attribute *dev_attr = to_dev_attr(attr);
+
+	if (dev_attr->show)
+		ret = dev_attr->show(dev, page);
+	return ret;
+}
+
+static ssize_t lu_show_attr(struct kobject *obj, struct attribute *attr,
+			    char *page)
+{
+	ssize_t ret = 0;
+	struct LU *lu = to_lu_device(obj);
+	struct lu_dev_attribute *lu_attr = to_lu_attr(attr);
+
+	if (lu_attr->show)
+		ret = lu_attr->show(lu, page);
+	return ret;
+}
+
+struct sysfs_ops dev_sysfs_ops = {
+	.show = dev_show_attr,
+};
+static struct sysfs_ops lu_sysfs_ops = {
+	.show = lu_show_attr,
+};
+
+static struct attribute *end_dev_attrs[ARRAY_SIZE(dev_attrs)];
+static struct attribute *sata_dev_attrs[ARRAY_SIZE(sata_attrs)];
+static struct attribute *lu_dev_attrs[ARRAY_SIZE(lu_attrs)];
+
+static struct kobj_type end_dev_ktype = {
+	.release = end_dev_release,
+	.sysfs_ops = &dev_sysfs_ops,
+	.default_attrs = end_dev_attrs,
+};
+static struct kobj_type sata_dev_ktype = {
+	.release = sata_dev_release,
+	.sysfs_ops = &dev_sysfs_ops,
+	.default_attrs = sata_dev_attrs,
+};
+static struct kobj_type lu_dev_ktype = {
+	.release = sas_lu_release,
+	.sysfs_ops = &lu_sysfs_ops,
+	.default_attrs = lu_dev_attrs,
+};
+
+struct kobj_type *dev_ktype[] = {
+	NULL,
+	&end_dev_ktype,
+	&ex_dev_ktype,
+	&ex_dev_ktype,
+	NULL,
+	&sata_dev_ktype,
+	NULL,
+	NULL,
+};
+
+/* ---------- Basic task processing for discovery purposes ---------- */
+
+static void sas_task_timedout(unsigned long _task)
+{
+	struct sas_task *task = (void *) _task;
+	unsigned long flags;
+
+	spin_lock_irqsave(&task->task_state_lock, flags);
+	if (!(task->task_state_flags & SAS_TASK_STATE_DONE))
+		task->task_state_flags |= SAS_TASK_STATE_ABORTED;
+	spin_unlock_irqrestore(&task->task_state_lock, flags);
+
+	complete(&task->completion);
+}
+
+static void sas_disc_task_done(struct sas_task *task)
+{
+	if (!del_timer(&task->timer))
+		return;
+	complete(&task->completion);
+}
+
+#define SAS_DEV_TIMEOUT 10
+
+/**
+ * sas_execute_task -- Basic task processing for discovery
+ * @task: the task to be executed
+ * @buffer: pointer to buffer to do I/O
+ * @size: size of @buffer
+ * @pci_dma_dir: PCI_DMA_...
+ */
+static int sas_execute_task(struct sas_task *task, void *buffer, int size,
+			    int pci_dma_dir)
+{
+	int res = 0;
+	struct scatterlist *scatter = NULL;
+	struct task_status_struct *ts = &task->task_status;
+	int num_scatter = 0;
+	int retries = 0;
+
+	if (pci_dma_dir != PCI_DMA_NONE) {
+		scatter = kmalloc(sizeof(*scatter), GFP_KERNEL);
+		if (!scatter)
+			goto out;
+		memset(scatter, 0, sizeof(scatter));
+
+		sg_init_one(scatter, buffer, size);
+		num_scatter = 1;
+	}
+
+	task->task_proto = task->dev->tproto;
+	task->scatter = scatter;
+	task->num_scatter = num_scatter;
+	task->total_xfer_len = size;
+	task->data_dir = pci_dma_dir;
+	task->task_done = sas_disc_task_done;
+
+	for (retries = 0; retries < 5; retries++) {
+		task->task_state_flags = SAS_TASK_STATE_PENDING;
+		init_completion(&task->completion);
+
+		task->timer.data = (unsigned long) task;
+		task->timer.function = sas_task_timedout;
+		task->timer.expires = jiffies + SAS_DEV_TIMEOUT*HZ;
+		add_timer(&task->timer);
+
+		res = task->dev->port->ha->lldd_execute_task(task, 1,
+							     GFP_KERNEL);
+		if (res) {
+			del_timer(&task->timer);
+			SAS_DPRINTK("executing SAS discovery task failed:%d\n",
+				    res);
+			goto ex_err;
+		}
+		wait_for_completion(&task->completion);
+		res = -ETASK;
+		if (task->task_state_flags & SAS_TASK_STATE_ABORTED) {
+			int res2;
+			SAS_DPRINTK("task aborted, flags:0x%x\n",
+				    task->task_state_flags);
+			res2 = task->dev->port->ha->lldd_abort_task(task);
+			SAS_DPRINTK("came back from abort task\n");
+			if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) {
+				if (res2 == TMF_RESP_FUNC_COMPLETE)
+					continue; /* Retry the task */
+				else
+					goto ex_err;
+			}
+		}
+		if (task->task_status.stat == SAM_BUSY ||
+			   task->task_status.stat == SAM_TASK_SET_FULL ||
+			   task->task_status.stat == SAS_QUEUE_FULL) {
+			SAS_DPRINTK("task: q busy, sleeping...\n");
+			schedule_timeout_interruptible(HZ);
+		} else if (task->task_status.stat == SAM_CHECK_COND) {
+			struct scsi_sense_hdr shdr;
+
+			if (!scsi_normalize_sense(ts->buf, ts->buf_valid_size,
+						  &shdr)) {
+				SAS_DPRINTK("couldn't normalize sense\n");
+				continue;
+			}
+			if ((shdr.sense_key == 6 && shdr.asc == 0x29) ||
+			    (shdr.sense_key == 2 && shdr.asc == 4 &&
+			     shdr.ascq == 1)) {
+				SAS_DPRINTK("device %016llx LUN: %016llx "
+					    "powering up or not ready yet, "
+					    "sleeping...\n",
+					    SAS_ADDR(task->dev->sas_addr),
+					    SAS_ADDR(task->ssp_task.LUN));
+
+				schedule_timeout_interruptible(5*HZ);
+			} else if (shdr.sense_key == 1) {
+				res = 0;
+				break;
+			} else if (shdr.sense_key == 5) {
+				break;
+			} else {
+				SAS_DPRINTK("dev %016llx LUN: %016llx "
+					    "sense key:0x%x ASC:0x%x ASCQ:0x%x"
+					    "\n",
+					    SAS_ADDR(task->dev->sas_addr),
+					    SAS_ADDR(task->ssp_task.LUN),
+					    shdr.sense_key,
+					    shdr.asc, shdr.ascq);
+			}
+		} else if (task->task_status.resp != SAS_TASK_COMPLETE ||
+			   task->task_status.stat != SAM_GOOD) {
+			SAS_DPRINTK("task finished with resp:0x%x, "
+				    "stat:0x%x\n",
+				    task->task_status.resp,
+				    task->task_status.stat);
+			goto ex_err;
+		} else {
+			res = 0;
+			break;
+		}
+	}
+ex_err:
+	if (pci_dma_dir != PCI_DMA_NONE)
+		kfree(scatter);
+out:
+	return res;
+}
+
+/* ---------- Domain device discovery ---------- */
+
+/**
+ * sas_get_port_device -- Discover devices which caused port creation
+ * @port: pointer to struct sas_port of interest
+ *
+ * Devices directly attached to a HA port, have no parent.  This is
+ * how we know they are (domain) "root" devices.  All other devices
+ * do, and should have their "parent" pointer set appropriately as
+ * soon as a child device is discovered.
+ */
+static int sas_get_port_device(struct sas_port *port)
+{
+	unsigned long flags;
+	struct sas_phy *phy;
+	struct domain_device *dev;
+
+	dev = kmalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+	memset(dev, 0, sizeof(*dev));
+
+	spin_lock_irqsave(&port->phy_list_lock, flags);
+	if (list_empty(&port->phy_list)) {
+		spin_unlock_irqrestore(&port->phy_list_lock, flags);
+		kfree(dev);
+		return -ENODEV;
+	}
+	phy = container_of(port->phy_list.next, struct sas_phy, port_phy_el);
+	spin_lock(&phy->frame_rcvd_lock);
+	memcpy(dev->frame_rcvd, phy->frame_rcvd, min(sizeof(dev->frame_rcvd),
+					     (size_t)phy->frame_rcvd_size));
+	spin_unlock(&phy->frame_rcvd_lock);
+	spin_unlock_irqrestore(&port->phy_list_lock, flags);
+
+	if (dev->frame_rcvd[0] == 0x34 && port->oob_mode == SATA_OOB_MODE) {
+		struct dev_to_host_fis *fis =
+			(struct dev_to_host_fis *) dev->frame_rcvd;
+		if (fis->interrupt_reason == 1 && fis->lbal == 1 &&
+		    fis->byte_count_low==0x69 && fis->byte_count_high == 0x96
+		    && (fis->device & ~0x10) == 0)
+			dev->dev_type = SATA_PM;
+		else
+			dev->dev_type = SATA_DEV;
+		dev->tproto = SATA_PROTO;
+	} else {
+		struct sas_identify_frame *id =
+			(struct sas_identify_frame *) dev->frame_rcvd;
+		dev->dev_type = id->dev_type;
+		dev->iproto = id->initiator_bits;
+		dev->tproto = id->target_bits;
+	}
+
+	sas_init_dev(dev);
+
+	memcpy(dev->sas_addr, port->attached_sas_addr, SAS_ADDR_SIZE);
+	sas_hash_addr(dev->hashed_sas_addr, dev->sas_addr);
+	port->port_dev = dev;
+	dev->port = port;
+	dev->linkrate = port->linkrate;
+	dev->min_linkrate = port->linkrate;
+	dev->max_linkrate = port->linkrate;
+	dev->pathways = port->num_phys;
+	memset(port->disc.fanout_sas_addr, 0, SAS_ADDR_SIZE);
+	memset(port->disc.eeds_a, 0, SAS_ADDR_SIZE);
+	memset(port->disc.eeds_b, 0, SAS_ADDR_SIZE);
+	port->disc.max_level = 0;
+
+	return 0;
+}
+
+/* ---------- Discover and Revalidate ---------- */
+
+/* ---------- SATA ---------- */
+
+static inline void sas_get_ata_command_set(struct domain_device *dev)
+{
+	struct dev_to_host_fis *fis =
+		(struct dev_to_host_fis *) dev->frame_rcvd;
+
+	if ((fis->sector_count == 1 && /* ATA */
+	     fis->lbal         == 1 &&
+	     fis->lbam         == 0 &&
+	     fis->lbah         == 0 &&
+	     fis->device       == 0))
+
+		dev->sata_dev.command_set = ATA_COMMAND_SET;
+
+	else if ((fis->interrupt_reason == 1 &&	/* ATAPI */
+		  fis->lbal             == 1 &&
+		  fis->byte_count_low   == 0x14 &&
+		  fis->byte_count_high  == 0xEB &&
+		  (fis->device & ~0x10) == 0))
+
+		dev->sata_dev.command_set = ATAPI_COMMAND_SET;
+
+	else if ((fis->sector_count == 1 && /* SEMB */
+		  fis->lbal         == 1 &&
+		  fis->lbam         == 0x3C &&
+		  fis->lbah         == 0xC3 &&
+		  fis->device       == 0)
+		||
+		 (fis->interrupt_reason == 1 &&	/* SATA PM */
+		  fis->lbal             == 1 &&
+		  fis->byte_count_low   == 0x69 &&
+		  fis->byte_count_high  == 0x96 &&
+		  (fis->device & ~0x10) == 0))
+
+		dev->sata_dev.command_set = ATAPI_COMMAND_SET;
+}
+
+/**
+ * sas_issue_ata_cmd -- Basic SATA command processing for discovery
+ * @dev: the device to send the command to
+ * @command: the command register
+ * @features: the features register
+ * @buffer: pointer to buffer to do I/O
+ * @size: size of @buffer
+ * @pci_dma_dir: PCI_DMA_...
+ */
+static int sas_issue_ata_cmd(struct domain_device *dev, u8 command,
+			     u8 features, void *buffer, int size,
+			     int pci_dma_dir)
+{
+	int res = 0;
+	struct sas_task *task;
+	struct dev_to_host_fis *d2h_fis = (struct dev_to_host_fis *)
+		&dev->frame_rcvd[0];
+
+	res = -ENOMEM;
+	task = sas_alloc_task(GFP_KERNEL);
+	if (!task)
+		goto out;
+
+	task->dev = dev;
+
+	task->ata_task.fis.command = command;
+	task->ata_task.fis.features = features;
+	task->ata_task.fis.device = d2h_fis->device;
+	task->ata_task.retry_count = 1;
+
+	res = sas_execute_task(task, buffer, size, pci_dma_dir);
+
+	sas_free_task(task);
+out:
+	return res;
+}
+
+static void sas_sata_propagate_sas_addr(struct domain_device *dev)
+{
+	unsigned long flags;
+	struct sas_port *port = dev->port;
+	struct sas_phy  *phy;
+
+	BUG_ON(dev->parent);
+
+	memcpy(port->attached_sas_addr, dev->sas_addr, SAS_ADDR_SIZE);
+	spin_lock_irqsave(&port->phy_list_lock, flags);
+	list_for_each_entry(phy, &port->phy_list, port_phy_el)
+		memcpy(phy->attached_sas_addr, dev->sas_addr, SAS_ADDR_SIZE);
+	spin_unlock_irqrestore(&port->phy_list_lock, flags);
+}
+
+#define ATA_IDENTIFY_DEV         0xEC
+#define ATA_IDENTIFY_PACKET_DEV  0xA1
+#define ATA_SET_FEATURES         0xEF
+#define ATA_FEATURE_PUP_STBY_SPIN_UP 0x07
+
+/**
+ * sas_discover_sata_dev -- discover a STP/SATA device (SATA_DEV)
+ * @dev: STP/SATA device of interest (ATA/ATAPI)
+ *
+ * The LLDD has already been notified of this device, so that we can
+ * send FISes to it.  Here we try to get IDENTIFY DEVICE or IDENTIFY
+ * PACKET DEVICE, if ATAPI device, so that the LLDD can fine-tune its
+ * performance for this device.
+ */
+static int sas_discover_sata_dev(struct domain_device *dev)
+{
+	int     res;
+	__le16  *identify_x;
+	u8      command;
+
+	identify_x = kmalloc(512, GFP_KERNEL);
+	if (!identify_x)
+		return -ENOMEM;
+	memset(identify_x, 0, 512);
+
+	if (dev->sata_dev.command_set == ATA_COMMAND_SET) {
+		dev->sata_dev.identify_device = identify_x;
+		command = ATA_IDENTIFY_DEV;
+	} else {
+		dev->sata_dev.identify_packet_device = identify_x;
+		command = ATA_IDENTIFY_PACKET_DEV;
+	}
+
+	res = sas_issue_ata_cmd(dev, command, 0, identify_x, 512,
+				PCI_DMA_FROMDEVICE);
+	if (res)
+		goto out_err;
+
+	/* lives on the media? */
+	if (le16_to_cpu(identify_x[0]) & 4) {
+		/* incomplete response */
+		SAS_DPRINTK("sending SET FEATURE/PUP_STBY_SPIN_UP to "
+			    "dev %llx\n", SAS_ADDR(dev->sas_addr));
+		if (!le16_to_cpu(identify_x[83] & (1<<6)))
+			goto cont1;
+		res = sas_issue_ata_cmd(dev, ATA_SET_FEATURES,
+					ATA_FEATURE_PUP_STBY_SPIN_UP,
+					NULL, 0, PCI_DMA_NONE);
+		if (res)
+			goto cont1;
+
+		schedule_timeout_interruptible(5*HZ); /* More time? */
+		res = sas_issue_ata_cmd(dev, command, 0, identify_x, 512,
+					PCI_DMA_FROMDEVICE);
+		if (res)
+			goto out_err;
+	}
+cont1:
+	/* Get WWN */
+	if (dev->port->oob_mode != SATA_OOB_MODE) {
+		memcpy(dev->sas_addr, dev->sata_dev.rps_resp.rps.stp_sas_addr,
+		       SAS_ADDR_SIZE);
+	} else if (dev->sata_dev.command_set == ATA_COMMAND_SET &&
+		   (le16_to_cpu(dev->sata_dev.identify_device[108]) & 0xF000)
+		   == 0x5000) {
+		int i;
+
+		for (i = 0; i < 4; i++) {
+			dev->sas_addr[2*i] =
+	     (le16_to_cpu(dev->sata_dev.identify_device[108+i]) & 0xFF00) >> 8;
+			dev->sas_addr[2*i+1] =
+	      le16_to_cpu(dev->sata_dev.identify_device[108+i]) & 0x00FF;
+		}
+	}
+	sas_hash_addr(dev->hashed_sas_addr, dev->sas_addr);
+	if (!dev->parent)
+		sas_sata_propagate_sas_addr(dev);
+
+	return 0;
+out_err:
+	dev->sata_dev.identify_packet_device = NULL;
+	dev->sata_dev.identify_device = NULL;
+	kfree(identify_x);
+	return res;
+}
+
+static int sas_discover_sata_pm(struct domain_device *dev)
+{
+	return -ENODEV;
+}
+
+/* ---------- SAS end devices ---------- */
+
+static int sas_get_itnl_timeout(struct domain_device *dev)
+{
+	static const u8 mode_sense_6[16]  = { 0x1a, };
+	static const u8 mode_sense_10[16] = { 0x5a, };
+
+	int res = -ENOMEM;
+	struct sas_task *task;
+	u8 *buffer, *__buf;
+	const int buffer_size = 12;
+
+	task = sas_alloc_task(GFP_KERNEL);
+	if (!task)
+		return -ENOMEM;
+	buffer = kmalloc(buffer_size, GFP_KERNEL);
+	if (!buffer)
+		goto out;
+	__buf = buffer;
+
+	task->dev = dev;
+
+	task->ssp_task.retry_count = 1;
+	memcpy(task->ssp_task.cdb, mode_sense_6, 16);
+	task->ssp_task.cdb[1] |= (1 << 3);
+	task->ssp_task.cdb[2] = 0x19;
+	task->ssp_task.cdb[4] = buffer_size;
+
+	res = sas_execute_task(task, buffer, buffer_size, PCI_DMA_FROMDEVICE);
+	if (res) {
+		SAS_DPRINTK("task to device %llx returned stat 0x%x for "
+			    "MODE SENSE 6\n",
+			    SAS_ADDR(dev->sas_addr), task->task_status.stat);
+		memcpy(task->ssp_task.cdb, mode_sense_10, 16);
+		task->ssp_task.cdb[1] |= (1 << 3);
+		task->ssp_task.cdb[2] = 0x19;
+		task->ssp_task.cdb[8] = buffer_size;
+
+		res = sas_execute_task(task, buffer, buffer_size,
+				       PCI_DMA_FROMDEVICE);
+		if (res) {
+			SAS_DPRINTK("task to device %llx returned stat 0x%x "
+				    "for MODE SENSE 10\n",
+				    SAS_ADDR(dev->sas_addr),
+				    task->task_status.stat);
+			goto out_buf;
+		}
+		dev->end_dev.ms_10 = 1;
+		buffer += 4;
+	}
+
+	buffer += 4;		  /* skip mode parameter header */
+
+	dev->end_dev.ready_led_meaning = (buffer[2] & (1<<4)) ? 1 : 0;
+ 	dev->end_dev.itnl_timeout = be16_to_cpu(*(__be16 *)(buffer+4));
+	dev->end_dev.iresp_timeout= be16_to_cpu(*(__be16 *)(buffer+6));
+
+	res = 0;
+
+out_buf:
+	kfree(__buf);
+out:
+	sas_free_task(task);
+	return res;
+}
+
+static int sas_get_report_luns(struct domain_device *dev, u8 **buffer,
+			       int *size)
+{
+	static const u8 report_luns[16] = { 0xA0, };
+	static const u8 RL_WLUN[8] = { 0xC1, 0x01, };
+
+	int res = -ENOMEM;
+	struct sas_task *task;
+	u8 *buf;
+	int buffer_size = 16;
+	u32 len;
+
+	*buffer = kmalloc(buffer_size, GFP_KERNEL);
+	if (!*buffer)
+		return -ENOMEM;
+	buf = *buffer;
+
+	task = sas_alloc_task(GFP_KERNEL);
+	if (!task)
+		goto out_err;
+
+	task->dev = dev;
+	task->ssp_task.retry_count = 1;
+	memcpy(task->ssp_task.cdb, report_luns, 16);
+	*(__be32 *)(&task->ssp_task.cdb[6]) = cpu_to_be32(buffer_size);
+
+	res = sas_execute_task(task, buf, buffer_size, PCI_DMA_FROMDEVICE);
+	if (res) {
+		SAS_DPRINTK("REPORT LUNS to LUN0 failed for device %llx "
+			    "with status:0x%x\n",
+			    SAS_ADDR(dev->sas_addr), task->task_status.stat);
+		memcpy(task->ssp_task.LUN, RL_WLUN, 8);
+		res = sas_execute_task(task, buf, buffer_size,
+				       PCI_DMA_FROMDEVICE);
+		if (res) {
+			SAS_DPRINTK("REPORT LUNS to REPORT LUNS W-LUN failed "
+				    "for device %llx with status:0x%x\n",
+				    SAS_ADDR(dev->sas_addr),
+				    task->task_status.stat);
+			goto out_err_task;
+		}
+		dev->end_dev.rl_wlun = 1;
+	}
+
+	len = be32_to_cpu(*(__be32 *)buf);
+	if (len + 8 > buffer_size) {
+		SAS_DPRINTK("need bigger buffer for REPORT LUNS\n");
+		buffer_size = len + 8;
+		res = -ENOMEM;
+		buf = kmalloc(buffer_size, GFP_KERNEL);
+		if (!buf)
+			goto out_err_task;
+		kfree(*buffer);
+		*buffer = buf;
+		if (dev->end_dev.rl_wlun)
+			memcpy(task->ssp_task.LUN, RL_WLUN, 8);
+		else
+			memset(task->ssp_task.LUN, 0, 8);
+		res = sas_execute_task(task, buf, buffer_size,
+				       PCI_DMA_FROMDEVICE);
+		if (res) {
+			SAS_DPRINTK("2nd REPORT LUNS to %s failed "
+				    "for device %llx with status:0x%x\n",
+				    dev->end_dev.rl_wlun ? "REPORT LUNS W-LUN"
+				    : "LUN0",
+				    SAS_ADDR(dev->sas_addr),
+				    task->task_status.stat);
+			goto out_err_task;
+		}
+	}
+
+	*size = len+8;
+	sas_free_task(task);
+	return 0;
+
+out_err_task:
+	sas_free_task(task);
+out_err:
+	kfree(*buffer);
+	*buffer = NULL;
+	size = 0;
+	return res;
+}
+
+static int sas_get_inquiry(struct LU *lu)
+{
+	static const u8 inquiry_cmd[16] = { 0x12, };
+	struct sas_task *task;
+	int res;
+
+	task = sas_alloc_task(GFP_KERNEL);
+	if (!task)
+		return -ENOMEM;
+
+	task->dev = lu->parent;
+	task->ssp_task.retry_count = 1;
+	memcpy(task->ssp_task.LUN, lu->LUN, 8);
+	memcpy(task->ssp_task.cdb, inquiry_cmd, 16);
+	*(__be16 *)(task->ssp_task.cdb+3) = cpu_to_be16(SAS_INQUIRY_DATA_LEN);
+
+	res = sas_execute_task(task, lu->inquiry_data, SAS_INQUIRY_DATA_LEN,
+			       PCI_DMA_FROMDEVICE);
+	if (!res)
+		lu->inquiry_valid_data_len = min(SAS_INQUIRY_DATA_LEN,
+						 lu->inquiry_data[4]+5);
+	sas_free_task(task);
+	return res;
+}
+
+static struct LU *sas_alloc_lu(void)
+{
+	struct LU *lu = kmalloc(sizeof(*lu), GFP_KERNEL);
+	if (lu) {
+		memset(lu, 0, sizeof(*lu));
+		INIT_LIST_HEAD(&lu->list);
+	}
+	return lu;
+}
+
+static int sas_register_lu(struct domain_device *dev, u8 *buf, int size)
+{
+	int res;
+
+	for (buf = buf+8, size -= 8; size > 0; size -= 8, buf += 8) {
+		struct LU *lu = sas_alloc_lu();
+
+		SAS_DPRINTK("%016llx probing LUN:%016llx\n",
+			    SAS_ADDR(dev->sas_addr),
+			    be64_to_cpu(*(__be64 *)buf));
+		if (lu) {
+			lu->parent = dev;
+			memcpy(lu->LUN, buf, 8);
+			res = sas_get_inquiry(lu);
+			if (res) {
+				SAS_DPRINTK("dev %llx LUN %016llx didn't reply"
+					    " to INQUIRY, forgotten\n",
+					    SAS_ADDR(dev->sas_addr),
+					    SAS_ADDR(lu->LUN));
+				kfree(lu);
+				continue;
+			}
+			lu->lu_obj.kset = &dev->end_dev.LU_kset;
+			kobject_set_name(&lu->lu_obj, "%016llx",
+					 SAS_ADDR(lu->LUN));
+			lu->lu_obj.ktype = dev->end_dev.LU_kset.ktype;
+			memcpy(lu->LUN, buf, 8);
+			list_add_tail(&lu->list, &dev->end_dev.LU_list);
+		}
+	}
+
+	return list_empty(&dev->end_dev.LU_list) ? -ENODEV : 0;
+}
+
+/**
+ * sas_do_lu_discovery -- Discover LUs of a SCSI device
+ * @dev: pointer to a domain device of interest
+ *
+ * Discover logical units present in the SCSI device.  I'd like this
+ * to be moved to SCSI Core, but SCSI Core has no concept of a "SCSI
+ * device with a SCSI Target port".  A SCSI device with a SCSI Target
+ * port is a device which the _transport_ found, but other than that,
+ * the transport has little or _no_ knowledge about the device.
+ * Ideally, a LLDD would register a "SCSI device with a SCSI Target
+ * port" with SCSI Core and then SCSI Core would do LU discovery of
+ * that device.
+ *
+ * REPORT LUNS is mandatory.  If a device doesn't support it,
+ * it is broken and you should return it.  Nevertheless, we
+ * assume (optimistically) that the link hasn't been severed and
+ * that maybe we can get to the device anyhow.
+ */
+static int sas_do_lu_discovery(struct domain_device *dev)
+{
+	int  res;
+	u8  *buffer;
+	int  size;
+
+	res = sas_get_report_luns(dev, &buffer, &size);
+	if (res) {
+		SAS_DPRINTK("dev %llx didn't reply to REPORT LUNS, trying "
+			    "LUN 0 anyway\n",
+			    SAS_ADDR(dev->sas_addr));
+		size = 16;
+		buffer = kmalloc(size, GFP_KERNEL);
+		memset(buffer, 0, size);
+	}
+
+	res = sas_register_lu(dev, buffer, size);
+	if (res) {
+		SAS_DPRINTK("dev %llx didn't report any LUs\n",
+			    SAS_ADDR(dev->sas_addr));
+		res = 0;
+	}
+
+	kfree(buffer);
+	return res;
+}
+
+/* ---------- Common/dispatchers ---------- */
+
+void sas_kobj_set(struct domain_device *dev)
+{
+	if (!dev->parent) {
+		/* device directly attached to the host adapter */
+		dev->dev_obj.kset = &dev->port->dev_kset;
+	} else {
+		/* parent is an expander */
+		dev->dev_obj.parent = &dev->parent->dev_obj;
+		dev->port = dev->parent->port;
+	}
+
+	list_add_tail(&dev->dev_list_node, &dev->port->dev_list);
+	kobject_set_name(&dev->dev_obj, "%016llx", SAS_ADDR(dev->sas_addr));
+	dev->dev_obj.ktype = dev_ktype[dev->dev_type];
+}
+
+/**
+ * sas_discover_sata -- discover an STP/SATA domain device
+ * @dev: pointer to struct domain_device of interest
+ *
+ * First we notify the LLDD of this device, so we can send frames to
+ * it.  Then depending on the type of device we call the appropriate
+ * discover functions.  Once device discover is done, we notify the
+ * LLDD so that it can fine-tune its parameters for the device, by
+ * removing it and then adding it.  That is, the second time around,
+ * the driver would have certain fields, that it is looking at, set.
+ * Finally we initialize the kobj so that the device can be added to
+ * the system at registration time.  Devices directly attached to a HA
+ * port, have no parents.  All other devices do, and should have their
+ * "parent" pointer set appropriately before calling this function.
+ */
+int sas_discover_sata(struct domain_device *dev)
+{
+	int res;
+
+	sas_get_ata_command_set(dev);
+
+	res = sas_notify_lldd_dev_found(dev);
+	if (res)
+		return res;
+
+	switch (dev->dev_type) {
+	case SATA_DEV:
+		res = sas_discover_sata_dev(dev);
+		break;
+	case SATA_PM:
+		res = sas_discover_sata_pm(dev);
+		break;
+	default:
+		break;
+	}
+
+	sas_notify_lldd_dev_gone(dev);
+	if (!res) {
+		sas_notify_lldd_dev_found(dev);
+		sas_kobj_set(dev);
+	}
+	return res;
+}
+
+/**
+ * sas_discover_end_dev -- discover an end device (SSP, etc)
+ * @end: pointer to domain device of interest
+ *
+ * See comment in sas_discover_sata().
+ *
+ * Get the ITNL timeout only for domain root devices.
+ * If we couldn't get it, either the link was severed or
+ * this is a device which doesn't support MODE SENSE.
+ */
+int sas_discover_end_dev(struct domain_device *dev)
+{
+	int res;
+
+	res = sas_notify_lldd_dev_found(dev);
+	if (res)
+		return res;
+
+	res = sas_get_itnl_timeout(dev);
+	if (!res) {
+		sas_notify_lldd_dev_gone(dev);
+		sas_notify_lldd_dev_found(dev);
+	}
+
+	dev->end_dev.LU_kset.kobj.parent = &dev->dev_obj;
+	dev->end_dev.LU_kset.ktype  = &lu_dev_ktype;
+
+	res = sas_do_lu_discovery(dev);
+	if (res)
+		goto out_err;
+
+	kobject_set_name(&dev->end_dev.LU_kset.kobj, "%s", "LUNS");
+
+	sas_kobj_set(dev);
+	return 0;
+
+out_err:
+	sas_notify_lldd_dev_gone(dev);
+	return res;
+}
+
+/* ---------- Device registration and unregistration ---------- */
+
+static inline void sas_unregister_common_dev(struct domain_device *dev)
+{
+	sas_notify_lldd_dev_gone(dev);
+	if (!dev->parent)
+		dev->port->port_dev = NULL;
+	else
+		list_del_init(&dev->siblings);
+	list_del_init(&dev->dev_list_node);
+	kobject_unregister(&dev->dev_obj);
+}
+
+static int sas_register_end_dev(struct domain_device *dev)
+{
+	struct LU *lu;
+
+	kobject_register(&dev->dev_obj);
+	kset_register(&dev->end_dev.LU_kset);
+
+	list_for_each_entry(lu, &dev->end_dev.LU_list, list) {
+		kobject_register(&lu->lu_obj);
+   		sas_register_with_scsi(lu);
+	}
+
+	return 0;
+}
+
+static void sas_unregister_end_dev(struct domain_device *dev)
+{
+	struct LU *lu, *n;
+
+	list_for_each_entry_safe(lu, n, &dev->end_dev.LU_list, list) {
+   		sas_unregister_with_scsi(lu);
+		list_del_init(&lu->list);
+		kobject_unregister(&lu->lu_obj);
+	}
+	kset_unregister(&dev->end_dev.LU_kset);
+	sas_unregister_common_dev(dev);
+}
+
+static int sas_register_sata(struct domain_device *dev)
+{
+	kobject_register(&dev->dev_obj);
+	return 0;
+}
+
+static void sas_unregister_sata(struct domain_device *dev)
+{
+	sas_unregister_common_dev(dev);
+}
+
+/**
+ * sas_register_ex_dev -- Register this expander
+ * @ex: pointer to domain device
+ *
+ * It is imperative that this is done breadth-first.  Other parts of
+ * the code rely on that.
+ */
+static int sas_register_ex_dev(struct domain_device *dev)
+{
+	kobject_register(&dev->dev_obj);
+	sysfs_create_bin_file(&dev->dev_obj, &dev->ex_dev.smp_bin_attr);
+	return 0;
+}
+
+static void sas_unregister_ex_dev(struct domain_device *dev)
+{
+	BUG_ON(!list_empty(&dev->ex_dev.children));
+	sysfs_remove_bin_file(&dev->dev_obj, &dev->ex_dev.smp_bin_attr);
+	sas_unregister_common_dev(dev);
+}
+
+/**
+ * sas_register_domain_devs -- register the domain devices with sysfs
+ * @port: the port to the domain
+ *
+ * This function registers the domain devices with sysfs and with
+ * the SCSI subsystem.
+ */
+static int sas_register_domain_devs(struct sas_port *port)
+{
+	struct domain_device *dev;
+
+	list_for_each_entry(dev, &port->dev_list, dev_list_node) {
+		if (dev->dev_obj.dentry)
+			continue;
+		switch (dev->dev_type) {
+		case SAS_END_DEV:
+			sas_register_end_dev(dev);
+			break;
+		case EDGE_DEV:
+		case FANOUT_DEV:
+			sas_register_ex_dev(dev);
+			break;
+		case SATA_DEV:
+		case SATA_PM:
+			sas_register_sata(dev);
+			break;
+		default:
+			SAS_DPRINTK("%s: unknown device type %d\n",
+				    __FUNCTION__, dev->dev_type);
+			break;
+		}
+	}
+
+	return 0;
+}
+
+void sas_unregister_dev(struct domain_device *dev)
+{
+	switch (dev->dev_type) {
+	case SAS_END_DEV:
+		sas_unregister_end_dev(dev);
+		break;
+	case EDGE_DEV:
+	case FANOUT_DEV:
+		sas_unregister_ex_dev(dev);
+		break;
+	case SATA_DEV:
+	case SATA_PM:
+		sas_unregister_sata(dev);
+		break;
+	default:
+		SAS_DPRINTK("%s: unknown device type %d\n",
+			    __FUNCTION__, dev->dev_type);
+		BUG_ON(dev);
+		break;
+	}
+}
+
+static void sas_unregister_domain_devices(struct sas_port *port)
+{
+	struct domain_device *dev, *n;
+
+	list_for_each_entry_reverse_safe(dev,n,&port->dev_list,dev_list_node)
+		sas_unregister_dev(dev);
+}
+
+/* ---------- Discovery and Revalidation ---------- */
+
+/**
+ * sas_discover_domain -- discover the domain
+ * @port: port to the domain of interest
+ *
+ * NOTE: this process _must_ quit (return) as soon as any connection
+ * errors are encountered.  Connection recovery is done elsewhere.
+ * Discover process only interrogates devices in order to discover the
+ * domain.
+ */
+static int sas_discover_domain(struct sas_port *port)
+{
+	int error = 0;
+
+
+	if (port->port_dev)
+		return 0;
+	else {
+		error = sas_get_port_device(port);
+		if (error)
+			return error;
+	}
+
+	SAS_DPRINTK("DOING DISCOVERY on port %d, pid:%d\n", port->id,
+		    current->pid);
+
+	switch (port->port_dev->dev_type) {
+	case SAS_END_DEV:
+		error = sas_discover_end_dev(port->port_dev);
+		break;
+	case EDGE_DEV:
+	case FANOUT_DEV:
+		error = sas_discover_root_expander(port->port_dev);
+		break;
+	case SATA_DEV:
+	case SATA_PM:
+		error = sas_discover_sata(port->port_dev);
+		break;
+	default:
+		SAS_DPRINTK("unhandled device %d\n", port->port_dev->dev_type);
+		break;
+	}
+
+	if (error) {
+		kfree(port->port_dev); /* not kobject_register-ed yet */
+		port->port_dev = NULL;
+	} else
+		sas_register_domain_devs(port);
+
+	SAS_DPRINTK("DONE DISCOVERY on port %d, pid:%d, result:%d\n", port->id,
+		    current->pid, error);
+
+	return error;
+}
+
+static int sas_revalidate_domain(struct sas_port *port)
+{
+	int res = 0;
+
+	SAS_DPRINTK("REVALIDATING DOMAIN on port %d, pid:%d\n", port->id,
+		    current->pid);
+	if (port->port_dev) {
+		res = sas_ex_revalidate_domain(port->port_dev);
+		if (!res)
+			sas_register_domain_devs(port);
+	}
+	SAS_DPRINTK("done REVALIDATING DOMAIN on port %d, pid:%d, res 0x%x\n",
+		    port->id, current->pid, res);
+	return res;
+}
+
+/* ---------- Threads and events ---------- */
+
+static DECLARE_COMPLETION(disc_comp_start);
+
+static int sas_discover_thread(void *_sas_port)
+{
+	struct sas_port *port = _sas_port;
+	struct sas_ha_struct *sas_ha = port->ha;
+	struct sas_discovery *disc = &port->disc;
+
+	daemonize("sas_disc_h%dp%d", sas_ha->core.shost->host_no, port->id);
+
+	spin_lock(&disc->disc_event_lock);
+	disc->disc_thread = current;
+	complete(&disc_comp_start);
+	while (!disc->disc_thread_quit && !list_empty(&disc->disc_event_list)){
+		struct list_head *head = disc->disc_event_list.next;
+		enum discover_event disc_ev = container_of(head,
+							   struct sas_event,
+							   el)->event;
+		list_del_init(head);
+		spin_unlock(&disc->disc_event_lock);
+
+		switch (disc_ev) {
+		case DISCE_DISCOVER_DOMAIN:
+			sas_discover_domain(port);
+			break;
+		case DISCE_REVALIDATE_DOMAIN:
+			sas_revalidate_domain(port);
+			break;
+		case DISCE_PORT_GONE:
+			sas_unregister_domain_devices(port);
+			complete(&port->port_gone_completion);
+			break;
+		}
+		spin_lock(&disc->disc_event_lock);
+	}
+	INIT_LIST_HEAD(&disc->disc_event_list);
+	disc->disc_thread = NULL;
+	spin_unlock(&disc->disc_event_lock);
+	up(&disc->disc_sema);
+
+	return 0;
+}
+
+static int sas_create_discover_thread(struct sas_port *port)
+{
+	int i;
+
+	init_completion(&disc_comp_start);
+	i = kernel_thread(sas_discover_thread, port, 0);
+	if (i >= 0)
+		wait_for_completion(&disc_comp_start);
+
+	return i < 0 ? i : 0;
+}
+
+int sas_discover_event(struct sas_port *port, enum discover_event ev)
+{
+	int res;
+	struct sas_discovery *disc = &port->disc;
+
+	spin_lock(&disc->disc_event_lock);
+	list_move_tail(&disc->disc_events[ev].el,
+		       &disc->disc_event_list);
+	if (disc->disc_thread) {
+		spin_unlock(&disc->disc_event_lock);
+		return 0;
+	}
+	down_interruptible(&disc->disc_sema);
+	disc->disc_thread_quit = 0;
+	spin_unlock(&disc->disc_event_lock);
+
+	/* The event thread (caller) is single threaded so this is safe. */
+	res = sas_create_discover_thread(port);
+	if (res) {
+		SAS_DPRINTK("sas port%d: couldn't create discovery thread!\n",
+			    port->id);
+		up(&disc->disc_sema);
+	}
+	return res;
+}
+
+void sas_kill_disc_thread(struct sas_port *port)
+{
+	struct sas_discovery *disc = &port->disc;
+
+	spin_lock(&disc->disc_event_lock);
+	disc->disc_thread_quit = 1;
+	if (disc->disc_thread) {
+		wake_up_process(disc->disc_thread);
+		spin_unlock(&disc->disc_event_lock);
+		down_interruptible(&disc->disc_sema);
+		return;
+	}
+	spin_unlock(&disc->disc_event_lock);
+}
+
+/**
+ * sas_init_disc -- initialize the discovery struct in the port
+ * @port: pointer to struct port
+ *
+ * Called when the ports are being initialized.
+ */
+void sas_init_disc(struct sas_discovery *disc, struct sas_port *port)
+{
+	int i;
+
+	if (!end_dev_attrs[0]) {
+		for (i = 0; i < ARRAY_SIZE(dev_attrs)-1; i++)
+			end_dev_attrs[i] = &dev_attrs[i].attr;
+		end_dev_attrs[i] = NULL;
+		sas_init_ex_attr();
+		for (i = 0; i < ARRAY_SIZE(sata_attrs)-1; i++)
+			sata_dev_attrs[i] = &sata_attrs[i].attr;
+		sata_dev_attrs[i] = NULL;
+		for (i = 0; i < ARRAY_SIZE(lu_attrs)-1; i++)
+			lu_dev_attrs[i] = &lu_attrs[i].attr;
+		lu_dev_attrs[i] = NULL;
+	}
+
+	spin_lock_init(&disc->disc_event_lock);
+	INIT_LIST_HEAD(&disc->disc_event_list);
+	init_MUTEX(&disc->disc_sema);
+	disc->disc_thread = NULL;
+	disc->disc_thread_quit = 0;
+
+	for (i = 0; i < DISC_NUM_EVENTS; i++) {
+		struct sas_event *ev = &disc->disc_events[i];
+		ev->event = i;
+		INIT_LIST_HEAD(&ev->el);
+	}
+}
+
+void sas_unregister_devices(struct sas_ha_struct *sas_ha)
+{
+	int i;
+
+	for (i = 0; i < sas_ha->num_phys; i++)
+		sas_unregister_domain_devices(sas_ha->sas_port[i]);
+}
diff -puN /dev/null drivers/scsi/sas-class/sas_dump.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ devel-akpm/drivers/scsi/sas-class/sas_dump.c	2005-09-09 19:06:37.000000000 -0700
@@ -0,0 +1,77 @@
+/*
+ * Serial Attached SCSI (SAS) Dump/Debugging routines
+ *
+ * Copyright (C) 2005 Adaptec, Inc.  All rights reserved.
+ * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
+ *
+ * This file is licensed under GPLv2.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * $Id: //depot/sas-class/sas_dump.c#4 $
+ */
+
+#include "sas_dump.h"
+
+#ifdef SAS_DEBUG
+
+static const char *sas_hae_str[] = {
+	[0] = "HAE_RESET",
+};
+
+static const char *sas_porte_str[] = {
+	[0] = "PORTE_BYTES_DMAED",
+	[1] = "PORTE_BROADCAST_RCVD",
+	[2] = "PORTE_LINK_RESET_ERR",
+	[3] = "PORTE_TIMER_EVENT",
+	[4] = "PORTE_HARD_RESET",
+};
+
+static const char *sas_phye_str[] = {
+	[0] = "PHYE_LOSS_OF_SIGNAL",
+	[1] = "PHYE_OOB_DONE",
+	[2] = "PHYE_OOB_ERROR",
+	[3] = "PHYE_SPINUP_HOLD",
+};
+
+void sas_dprint_porte(int phyid, enum port_event pe)
+{
+	SAS_DPRINTK("phy%d: port event: %s\n", phyid, sas_porte_str[pe]);
+}
+void sas_dprint_phye(int phyid, enum phy_event pe)
+{
+	SAS_DPRINTK("phy%d: phy event: %s\n", phyid, sas_phye_str[pe]);
+}
+
+void sas_dprint_hae(struct sas_ha_struct *sas_ha, enum ha_event he)
+{
+	SAS_DPRINTK("ha %s: %s event\n", pci_name(sas_ha->pcidev),
+		    sas_hae_str[he]);
+}
+
+void sas_dump_port(struct sas_port *port)
+{
+	SAS_DPRINTK("port%d: class:0x%x\n", port->id, port->class);
+	SAS_DPRINTK("port%d: sas_addr:%llx\n", port->id,
+		    SAS_ADDR(port->sas_addr));
+	SAS_DPRINTK("port%d: attached_sas_addr:%llx\n", port->id,
+		    SAS_ADDR(port->attached_sas_addr));
+	SAS_DPRINTK("port%d: iproto:0x%x\n", port->id, port->iproto);
+	SAS_DPRINTK("port%d: tproto:0x%x\n", port->id, port->tproto);
+	SAS_DPRINTK("port%d: oob_mode:0x%x\n", port->id, port->oob_mode);
+	SAS_DPRINTK("port%d: num_phys:%d\n", port->id, port->num_phys);
+}
+
+#endif /* SAS_DEBUG */
diff -puN /dev/null drivers/scsi/sas-class/sas_dump.h
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ devel-akpm/drivers/scsi/sas-class/sas_dump.h	2005-09-09 19:06:37.000000000 -0700
@@ -0,0 +1,43 @@
+/*
+ * Serial Attached SCSI (SAS) Dump/Debugging routines header file
+ *
+ * Copyright (C) 2005 Adaptec, Inc.  All rights reserved.
+ * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
+ *
+ * This file is licensed under GPLv2.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * $Id: //depot/sas-class/sas_dump.h#2 $
+ */
+
+#include "sas_internal.h"
+
+#ifdef SAS_DEBUG
+
+void sas_dprint_porte(int phyid, enum port_event pe);
+void sas_dprint_phye(int phyid, enum phy_event pe);
+void sas_dprint_hae(struct sas_ha_struct *sas_ha, enum ha_event he);
+void sas_dump_port(struct sas_port *port);
+
+#else /* SAS_DEBUG */
+
+static inline void sas_dprint_porte(int phyid, enum port_event pe) { }
+static inline void sas_dprint_phye(int phyid, enum phy_event pe) { }
+static inline void sas_dprint_hae(struct sas_ha_struct *sas_ha,
+				  enum ha_event he) { }
+static inline void sas_dump_port(struct sas_port *port) { }
+
+#endif /* SAS_DEBUG */
diff -puN /dev/null drivers/scsi/sas-class/sas_event.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ devel-akpm/drivers/scsi/sas-class/sas_event.c	2005-09-09 19:06:37.000000000 -0700
@@ -0,0 +1,294 @@
+/*
+ * Serial Attached SCSI (SAS) Event processing
+ *
+ * Copyright (C) 2005 Adaptec, Inc.  All rights reserved.
+ * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
+ *
+ * This file is licensed under GPLv2.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * $Id: //depot/sas-class/sas_event.c#25 $
+ */
+
+/**
+ * Implementation Of Priority Queue Without Duplication
+ * Luben Tuikov 2005/07/11
+ *
+ * The SAS class implements priority queue without duplication for
+ * handling ha/port/phy/discover events.  That is, we want to process
+ * the last N unique/non-duplicating events, in the order they arrived.
+ *
+ * The requirement is that insertion is O(1), and ordered removal is O(1).
+ *
+ * Suppose events are identified by integers.  Then what is required
+ * is that for a given sequence of any random integers R, to find a
+ * sorted sequence E, where
+ *     a) |E| <= |R|.  If the number of types of events is bounded,
+ *        then E is also bounded by that number, from b).
+ *     b) For all i and k, E[i] != E[k], except when i == k,
+ *        this gives us uniqueness/non duplication.
+ *     c) For all i < k, Order(E[i]) < Order(E[k]), this gives us
+ *        ordering.
+ *     d) If T(R) = E, then O(T) <= |R|, this ensures that insertion
+ *        is O(1), and ordered removal is O(1) trivially, since we
+ *        remove at the head of E.
+ *
+ * Example:
+ * If R = {4, 5, 1, 2, 5, 3, 3, 4, 4, 3, 1}, then
+ *    E = {2, 5, 4, 3, 1}.
+ *
+ * The algorithm, T, makes use of an array of list elements, indexed
+ * by event type, and an event list head which is a linked list of the
+ * elements of the array.  When the next event arrives, we index the
+ * array by the event, and add that event to the tail of the event
+ * list head, deleting it from its previous list position (if it had
+ * one).
+ *
+ * Clearly insertion is O(1).
+ *
+ * E is given by the elements of the event list, traversed from head
+ * to tail.
+ */
+
+#include <scsi/scsi_host.h>
+#include "sas_internal.h"
+#include "sas_dump.h"
+#include <scsi/sas/sas_discover.h>
+
+static void sas_process_phy_event(struct sas_phy *phy)
+{
+	unsigned long flags;
+	struct sas_ha_struct *sas_ha = phy->ha;
+	enum phy_event phy_event;
+
+	spin_lock_irqsave(&sas_ha->event_lock, flags);
+	while (!list_empty(&phy->phy_event_list)) {
+		struct list_head *head = phy->phy_event_list.next;
+		phy_event = container_of(head, struct sas_event, el)->event;
+		list_del_init(head);
+		spin_unlock_irqrestore(&sas_ha->event_lock, flags);
+
+		sas_dprint_phye(phy->id, phy_event);
+
+		switch(phy_event) {
+		case PHYE_LOSS_OF_SIGNAL:
+			sas_phye_loss_of_signal(phy);
+			break;
+		case PHYE_OOB_DONE:
+			sas_phye_oob_done(phy);
+			break;
+		case PHYE_OOB_ERROR:
+			sas_phye_oob_error(phy);
+			break;
+		case PHYE_SPINUP_HOLD:
+			sas_phye_spinup_hold(phy);
+			break;
+		}
+		spin_lock_irqsave(&sas_ha->event_lock, flags);
+	}
+	/* Clear the bit in case we received events in due time. */
+	sas_ha->phye_mask &= ~(1 << phy->id);
+	spin_unlock_irqrestore(&sas_ha->event_lock, flags);
+}
+
+static void sas_process_port_event(struct sas_phy *phy)
+{
+	unsigned long flags;
+	struct sas_ha_struct *sas_ha = phy->ha;
+	enum port_event port_event;
+
+	spin_lock_irqsave(&sas_ha->event_lock, flags);
+	while (!list_empty(&phy->port_event_list)) {
+		struct list_head *head = phy->port_event_list.next;
+		port_event = container_of(head, struct sas_event, el)->event;
+		list_del_init(head);
+		spin_unlock_irqrestore(&sas_ha->event_lock, flags);
+
+		sas_dprint_porte(phy->id, port_event);
+
+		switch (port_event) {
+		case PORTE_BYTES_DMAED:
+			sas_porte_bytes_dmaed(phy);
+			break;
+		case PORTE_BROADCAST_RCVD:
+			sas_porte_broadcast_rcvd(phy);
+			break;
+		case PORTE_LINK_RESET_ERR:
+			sas_porte_link_reset_err(phy);
+			break;
+		case PORTE_TIMER_EVENT:
+			sas_porte_timer_event(phy);
+			break;
+		case PORTE_HARD_RESET:
+			sas_porte_hard_reset(phy);
+			break;
+		}
+		spin_lock_irqsave(&sas_ha->event_lock, flags);
+	}
+	/* Clear the bit in case we received events in due time. */
+	sas_ha->porte_mask &= ~(1 << phy->id);
+	spin_unlock_irqrestore(&sas_ha->event_lock, flags);
+}
+
+static void sas_process_ha_event(struct sas_ha_struct *sas_ha)
+{
+	unsigned long flags;
+	enum ha_event ha_event;
+
+	spin_lock_irqsave(&sas_ha->event_lock, flags);
+	while (!list_empty(&sas_ha->ha_event_list)) {
+		struct list_head *head = sas_ha->ha_event_list.next;
+		ha_event = container_of(head, struct sas_event, el)->event;
+		list_del_init(head);
+		spin_unlock_irqrestore(&sas_ha->event_lock, flags);
+
+		sas_dprint_hae(sas_ha, ha_event);
+
+		switch (ha_event) {
+		case HAE_RESET:
+			sas_hae_reset(sas_ha);
+			break;
+		}
+		spin_lock_irqsave(&sas_ha->event_lock, flags);
+	}
+	spin_unlock_irqrestore(&sas_ha->event_lock, flags);
+}
+
+static void sas_process_events(struct sas_ha_struct *sas_ha)
+{
+	unsigned long flags;
+	u32 porte_mask, phye_mask;
+	int p;
+
+	spin_lock_irqsave(&sas_ha->event_lock, flags);
+	phye_mask = sas_ha->phye_mask;
+	sas_ha->phye_mask = 0;
+	spin_unlock_irqrestore(&sas_ha->event_lock, flags);
+
+	for (p = 0; phye_mask != 0; phye_mask >>= 1, p++)
+		if (phye_mask & 01)
+			sas_process_phy_event(sas_ha->sas_phy[p]);
+
+	spin_lock_irqsave(&sas_ha->event_lock, flags);
+	porte_mask = sas_ha->porte_mask;
+	sas_ha->porte_mask = 0;
+	spin_unlock_irqrestore(&sas_ha->event_lock, flags);
+
+	for (p = 0; porte_mask != 0; porte_mask >>= 1, p++)
+		if (porte_mask & 01)
+			sas_process_port_event(sas_ha->sas_phy[p]);
+
+	sas_process_ha_event(sas_ha);
+}
+
+static void notify_ha_event(struct sas_ha_struct *sas_ha, enum ha_event event)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&sas_ha->event_lock, flags);
+	list_move_tail(&sas_ha->ha_events[event].el, &sas_ha->ha_event_list);
+	up(&sas_ha->event_sema);
+	spin_unlock_irqrestore(&sas_ha->event_lock, flags);
+}
+
+static void notify_port_event(struct sas_phy *phy, enum port_event event)
+{
+	struct sas_ha_struct *ha = phy->ha;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ha->event_lock, flags);
+	list_move_tail(&phy->port_events[event].el, &phy->port_event_list);
+	ha->porte_mask |= (1 << phy->id);
+	up(&ha->event_sema);
+	spin_unlock_irqrestore(&ha->event_lock, flags);
+}
+
+static void notify_phy_event(struct sas_phy *phy, enum phy_event event)
+{
+	struct sas_ha_struct *ha = phy->ha;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ha->event_lock, flags);
+	list_move_tail(&phy->phy_events[event].el, &phy->phy_event_list);
+	ha->phye_mask |= (1 << phy->id);
+	up(&ha->event_sema);
+	spin_unlock_irqrestore(&ha->event_lock, flags);
+}
+
+static DECLARE_COMPLETION(event_th_comp);
+
+static int sas_event_thread(void *_sas_ha)
+{
+	struct sas_ha_struct *sas_ha = _sas_ha;
+
+	daemonize("sas_event_%d", sas_ha->core.shost->host_no);
+	current->flags |= PF_NOFREEZE;
+
+	complete(&event_th_comp);
+
+	while (1) {
+		down_interruptible(&sas_ha->event_sema);
+		if (sas_ha->event_thread_kill)
+			break;
+		sas_process_events(sas_ha);
+	}
+
+	complete(&event_th_comp);
+
+	return 0;
+}
+
+int sas_start_event_thread(struct sas_ha_struct *sas_ha)
+{
+	int i;
+
+	init_MUTEX_LOCKED(&sas_ha->event_sema);
+	sas_ha->event_thread_kill = 0;
+
+	spin_lock_init(&sas_ha->event_lock);
+	INIT_LIST_HEAD(&sas_ha->ha_event_list);
+	sas_ha->porte_mask = 0;
+	sas_ha->phye_mask = 0;
+
+	for (i = 0; i < HA_NUM_EVENTS; i++) {
+		struct sas_event *ev = &sas_ha->ha_events[i];
+		ev->event = i;
+		INIT_LIST_HEAD(&ev->el);
+	}
+
+	sas_ha->notify_ha_event = notify_ha_event;
+	sas_ha->notify_port_event = notify_port_event;
+	sas_ha->notify_phy_event = notify_phy_event;
+
+	i = kernel_thread(sas_event_thread, sas_ha, 0);
+	if (i >= 0)
+		wait_for_completion(&event_th_comp);
+
+	return i < 0 ? i : 0;
+}
+
+void sas_kill_event_thread(struct sas_ha_struct *sas_ha)
+{
+	int i;
+
+	init_completion(&event_th_comp);
+	sas_ha->event_thread_kill = 1;
+	up(&sas_ha->event_sema);
+	wait_for_completion(&event_th_comp);
+
+	for (i = 0; i < sas_ha->num_phys; i++)
+		sas_kill_disc_thread(sas_ha->sas_port[i]);
+}
diff -puN /dev/null drivers/scsi/sas-class/sas_expander.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ devel-akpm/drivers/scsi/sas-class/sas_expander.c	2005-09-09 19:06:37.000000000 -0700
@@ -0,0 +1,1829 @@
+/*
+ * Serial Attached SCSI (SAS) Expander discovery and configuration
+ *
+ * Copyright (C) 2005 Adaptec, Inc.  All rights reserved.
+ * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
+ *
+ * This file is licensed under GPLv2.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * $Id: //depot/sas-class/sas_expander.c#55 $
+ */
+
+#include <linux/pci.h>
+#include <linux/scatterlist.h>
+
+#include "sas_internal.h"
+#include <scsi/sas/sas_task.h>
+#include <scsi/sas/sas_discover.h>
+
+static int sas_discover_expander(struct domain_device *dev);
+static int sas_configure_routing(struct domain_device *dev, u8 *sas_addr);
+static int sas_disable_routing(struct domain_device *dev,  u8 *sas_addr);
+
+static ssize_t smp_portal_read(struct kobject *, char *, loff_t, size_t);
+static ssize_t smp_portal_write(struct kobject *, char *, loff_t, size_t);
+
+/* ---------- Expander attributes ---------- */
+
+static ssize_t ex_show_change_count(struct domain_device *dev, char *page)
+{
+	return sprintf(page, "%d\n", dev->ex_dev.ex_change_count);
+}
+
+static ssize_t ex_show_max_route_indexes(struct domain_device *dev, char *page)
+{
+	return sprintf(page, "%d\n", dev->ex_dev.max_route_indexes);
+}
+
+static ssize_t ex_show_num_phys(struct domain_device *dev, char *page)
+{
+	return sprintf(page, "%d\n", dev->ex_dev.num_phys);
+}
+
+static ssize_t ex_show_enclosure_logical_id(struct domain_device *dev,
+					    char *page)
+{
+	return sprintf(page, "%llx\n",
+		       SAS_ADDR(dev->ex_dev.enclosure_logical_id));
+}
+
+static ssize_t ex_show_vendor_id(struct domain_device *dev, char *page)
+{
+	return sprintf(page, "%s\n", dev->ex_dev.vendor_id);
+}
+
+static ssize_t ex_show_product_id(struct domain_device *dev, char *page)
+{
+	return sprintf(page, "%s\n", dev->ex_dev.product_id);
+}
+
+static ssize_t ex_show_product_rev(struct domain_device *dev, char *page)
+{
+	return sprintf(page, "%s\n", dev->ex_dev.product_rev);
+}
+
+static ssize_t ex_show_component_vendor_id(struct domain_device *dev,
+					   char *page)
+{
+	return sprintf(page, "%s\n", dev->ex_dev.component_vendor_id);
+}
+
+static ssize_t ex_show_component_id(struct domain_device *dev, char *page)
+{
+	return sprintf(page, "%d\n", dev->ex_dev.component_id);
+}
+
+static ssize_t ex_show_component_revision_id(struct domain_device *dev,
+					     char *page)
+{
+	return sprintf(page, "%d\n", dev->ex_dev.component_revision_id);
+}
+
+static ssize_t ex_show_conf_route_table(struct domain_device *dev,
+					char *page)
+{
+	return sprintf(page, "%d\n", dev->ex_dev.conf_route_table);
+}
+
+static ssize_t ex_show_configuring(struct domain_device *dev,
+				   char *page)
+{
+	return sprintf(page, "%d\n", dev->ex_dev.configuring);
+}
+
+static struct domain_dev_attribute ex_attrs[] = {
+	__ATTR(dev_type, 0444, dev_show_type, NULL),
+	__ATTR(iproto, 0444, dev_show_iproto, NULL),
+	__ATTR(tproto, 0444, dev_show_tproto, NULL),
+	__ATTR(sas_addr, 0444, dev_show_sas_addr, NULL),
+	__ATTR(linkrate, 0444, dev_show_linkrate, NULL),
+	__ATTR(min_linkrate, 0444, dev_show_min_linkrate, NULL),
+	__ATTR(max_linkrate, 0444, dev_show_max_linkrate, NULL),
+	__ATTR(pathways, 0444, dev_show_pathways, NULL),
+	__ATTR(change_count, 0444, ex_show_change_count, NULL),
+	__ATTR(max_route_indexes, 0444, ex_show_max_route_indexes, NULL),
+	__ATTR(num_phys, 0444, ex_show_num_phys, NULL),
+	__ATTR(enclosure_logical_id, 0444, ex_show_enclosure_logical_id, NULL),
+	__ATTR(vendor_id, 0444, ex_show_vendor_id, NULL),
+	__ATTR(product_id, 0444, ex_show_product_id, NULL),
+	__ATTR(product_rev, 0444, ex_show_product_rev, NULL),
+	__ATTR(component_vendor_id, 0444, ex_show_component_vendor_id, NULL),
+	__ATTR(component_id, 0444, ex_show_component_id, NULL),
+	__ATTR(component_revision_id, 0444,ex_show_component_revision_id,NULL),
+	__ATTR(conf_route_table, 0444, ex_show_conf_route_table, NULL),
+	__ATTR(configuring, 0444, ex_show_configuring, NULL),
+	__ATTR_NULL,
+};
+
+static struct attribute *ex_dev_attrs[ARRAY_SIZE(ex_attrs)];
+
+static void ex_dev_release(struct kobject *obj)
+{
+	struct domain_device *dev = to_dom_device(obj);
+	SAS_DPRINTK("freeing dev %016llx\n", SAS_ADDR(dev->sas_addr));
+	kfree(dev->ex_dev.ex_phy);
+	kfree(dev->ex_dev.smp_req);
+	kfree(dev);
+}
+
+struct kobj_type ex_dev_ktype = {
+	.release = ex_dev_release,
+	.sysfs_ops = &dev_sysfs_ops,
+	.default_attrs = ex_dev_attrs,
+};
+
+/* ---------- SMP task management ---------- */
+
+static void smp_task_timedout(unsigned long _task)
+{
+	struct sas_task *task = (void *) _task;
+	unsigned long flags;
+
+	spin_lock_irqsave(&task->task_state_lock, flags);
+	if (!(task->task_state_flags & SAS_TASK_STATE_DONE))
+		task->task_state_flags |= SAS_TASK_STATE_ABORTED;
+	spin_unlock_irqrestore(&task->task_state_lock, flags);
+
+	complete(&task->completion);
+}
+
+static void smp_task_done(struct sas_task *task)
+{
+	if (!del_timer(&task->timer))
+		return;
+	complete(&task->completion);
+}
+
+/* Give it some long enough timeout. In seconds. */
+#define SMP_TIMEOUT 10
+
+static int smp_execute_task(struct domain_device *dev, void *req, int req_size,
+			    void *resp, int resp_size)
+{
+	int res;
+	struct sas_task *task = sas_alloc_task(GFP_KERNEL);
+
+	if (!task)
+		return -ENOMEM;
+
+	task->dev = dev;
+	task->task_proto = dev->tproto;
+	sg_init_one(&task->smp_task.smp_req, req, req_size);
+	sg_init_one(&task->smp_task.smp_resp, resp, resp_size);
+
+	task->task_done = smp_task_done;
+
+	task->timer.data = (unsigned long) task;
+	task->timer.function = smp_task_timedout;
+	task->timer.expires = jiffies + SMP_TIMEOUT*HZ;
+	add_timer(&task->timer);
+
+	res = task->dev->port->ha->lldd_execute_task(task, 1, GFP_KERNEL);
+
+	if (res) {
+		del_timer(&task->timer);
+		SAS_DPRINTK("executing SMP task failed:%d\n", res);
+		goto ex_err;
+	}
+
+	wait_for_completion(&task->completion);
+	res = -ETASK;
+	if ((task->task_state_flags & SAS_TASK_STATE_ABORTED)) {
+		SAS_DPRINTK("smp task timed out or aborted\n");
+		task->dev->port->ha->lldd_abort_task(task);
+		if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) {
+			SAS_DPRINTK("SMP task aborted and not done\n");
+			goto ex_err;
+		}
+	}
+	if (task->task_status.resp == SAS_TASK_COMPLETE &&
+	    task->task_status.stat == SAM_GOOD)
+		res = 0;
+	else
+		SAS_DPRINTK("%s: task to dev %016llx response: 0x%x "
+			    "status 0x%x\n", __FUNCTION__,
+			    SAS_ADDR(dev->sas_addr),
+			    task->task_status.resp,
+			    task->task_status.stat);
+ex_err:
+	sas_free_task(task);
+	return res;
+}
+
+/* ---------- Allocations ---------- */
+
+static inline void *alloc_smp_req(int size)
+{
+	u8 *p = kmalloc(size, GFP_KERNEL);
+	if (p) {
+		memset(p, 0, size);
+		p[0] = SMP_REQUEST;
+	}
+	return p;
+}
+
+static inline void *alloc_smp_resp(int size)
+{
+	u8 *p = kmalloc(size, GFP_KERNEL);
+	if (p)
+		memset(p, 0, size);
+	return p;
+}
+
+/* ---------- Expander configuration ---------- */
+
+static void sas_set_ex_phy(struct domain_device *dev, int phy_id,
+			   void *disc_resp)
+{
+	struct expander_device *ex = &dev->ex_dev;
+	struct ex_phy *phy = &ex->ex_phy[phy_id];
+	struct smp_resp *resp = disc_resp;
+	struct discover_resp *dr = &resp->disc;
+
+	switch (resp->result) {
+	case SMP_RESP_PHY_VACANT:
+		phy->phy_state = PHY_VACANT;
+		return;
+	default:
+		phy->phy_state = PHY_NOT_PRESENT;
+		return;
+	case SMP_RESP_FUNC_ACC:
+		phy->phy_state = PHY_EMPTY; /* do not know yet */
+		break;
+	}
+
+	phy->phy_id = phy_id;
+	phy->attached_dev_type = dr->attached_dev_type;
+	phy->linkrate = dr->linkrate;
+	phy->attached_sata_host = dr->attached_sata_host;
+	phy->attached_sata_dev  = dr->attached_sata_dev;
+	phy->attached_sata_ps   = dr->attached_sata_ps;
+	phy->attached_iproto = dr->iproto << 1;
+	phy->attached_tproto = dr->tproto << 1;
+	memcpy(phy->attached_sas_addr, dr->attached_sas_addr, SAS_ADDR_SIZE);
+	phy->attached_phy_id = dr->attached_phy_id;
+	phy->phy_change_count = dr->change_count;
+	phy->routing_attr = dr->routing_attr;
+	phy->virtual = dr->virtual;
+	phy->last_da_index = -1;
+
+	SAS_DPRINTK("ex %016llx phy%02d:%c attached: %016llx\n",
+		    SAS_ADDR(dev->sas_addr), phy->phy_id,
+		    phy->routing_attr == TABLE_ROUTING ? 'T' :
+		    phy->routing_attr == DIRECT_ROUTING ? 'D' :
+		    phy->routing_attr == SUBTRACTIVE_ROUTING ? 'S' : '?',
+		    SAS_ADDR(phy->attached_sas_addr));
+
+	return;
+}
+
+#define DISCOVER_REQ_SIZE  16
+#define DISCOVER_RESP_SIZE 56
+
+static int sas_ex_phy_discover(struct domain_device *dev, int single)
+{
+	struct expander_device *ex = &dev->ex_dev;
+	int  res = 0;
+	u8   *disc_req;
+	u8   *disc_resp;
+
+	disc_req = alloc_smp_req(DISCOVER_REQ_SIZE);
+	if (!disc_req)
+		return -ENOMEM;
+
+	disc_resp = alloc_smp_req(DISCOVER_RESP_SIZE);
+	if (!disc_resp) {
+		kfree(disc_req);
+		return -ENOMEM;
+	}
+
+	disc_req[1] = SMP_DISCOVER;
+
+	if (0 <= single && single < ex->num_phys) {
+		disc_req[9] = single;
+		res = smp_execute_task(dev, disc_req, DISCOVER_REQ_SIZE,
+				       disc_resp, DISCOVER_RESP_SIZE);
+		if (res)
+			goto out_err;
+		sas_set_ex_phy(dev, single, disc_resp);
+	} else {
+		int i;
+
+		for (i = 0; i < ex->num_phys; i++) {
+			disc_req[9] = i;
+			res = smp_execute_task(dev, disc_req,
+					       DISCOVER_REQ_SIZE, disc_resp,
+					       DISCOVER_RESP_SIZE);
+			if (res)
+				goto out_err;
+			sas_set_ex_phy(dev, i, disc_resp);
+		}
+	}
+out_err:
+	kfree(disc_resp);
+	kfree(disc_req);
+	return res;
+}
+
+static int sas_expander_discover(struct domain_device *dev)
+{
+	struct expander_device *ex = &dev->ex_dev;
+	int res;
+
+	ex->ex_phy = kmalloc(sizeof(*ex->ex_phy)*ex->num_phys, GFP_KERNEL);
+	if (!ex->ex_phy)
+		return -ENOMEM;
+	memset(ex->ex_phy, 0, sizeof(*ex->ex_phy)*ex->num_phys);
+
+	res = sas_ex_phy_discover(dev, -1);
+	if (res)
+		goto out_err;
+
+	return 0;
+out_err:
+	kfree(ex->ex_phy);
+	ex->ex_phy = NULL;
+	return res;
+}
+
+#define MAX_EXPANDER_PHYS 128
+
+static inline void ex_assign_report_general(struct domain_device *dev,
+					    struct smp_resp *resp)
+{
+	struct report_general_resp *rg = &resp->rg;
+
+	dev->ex_dev.ex_change_count = be16_to_cpu(rg->change_count);
+	dev->ex_dev.max_route_indexes = be16_to_cpu(rg->route_indexes);
+	dev->ex_dev.num_phys = min(rg->num_phys, (u8)MAX_EXPANDER_PHYS);
+	dev->ex_dev.conf_route_table = rg->conf_route_table;
+	dev->ex_dev.configuring = rg->configuring;
+	memcpy(dev->ex_dev.enclosure_logical_id, rg->enclosure_logical_id, 8);
+}
+
+#define RG_REQ_SIZE   8
+#define RG_RESP_SIZE 32
+
+static int sas_ex_general(struct domain_device *dev)
+{
+	u8 *rg_req;
+	struct smp_resp *rg_resp;
+	int res;
+	int i;
+
+	rg_req = alloc_smp_req(RG_REQ_SIZE);
+	if (!rg_req)
+		return -ENOMEM;
+
+	rg_resp = alloc_smp_resp(RG_RESP_SIZE);
+	if (!rg_resp) {
+		kfree(rg_req);
+		return -ENOMEM;
+	}
+
+	rg_req[1] = SMP_REPORT_GENERAL;
+
+	for (i = 0; i < 5; i++) {
+		res = smp_execute_task(dev, rg_req, RG_REQ_SIZE, rg_resp,
+				       RG_RESP_SIZE);
+
+		if (res) {
+			SAS_DPRINTK("RG to ex %016llx failed:0x%x\n",
+				    SAS_ADDR(dev->sas_addr), res);
+			goto out;
+		} else if (rg_resp->result != SMP_RESP_FUNC_ACC) {
+			SAS_DPRINTK("RG:ex %016llx returned SMP result:0x%x\n",
+				    SAS_ADDR(dev->sas_addr), rg_resp->result);
+			res = rg_resp->result;
+			goto out;
+		}
+
+		ex_assign_report_general(dev, rg_resp);
+
+		if (dev->ex_dev.configuring) {
+			SAS_DPRINTK("RG: ex %llx self-configuring...\n",
+				    SAS_ADDR(dev->sas_addr));
+			schedule_timeout_interruptible(5*HZ);
+		} else
+			break;
+	}
+out:
+	kfree(rg_req);
+	kfree(rg_resp);
+	return res;
+}
+
+static inline void ex_assign_manuf_info(struct domain_device *dev, void
+					*_mi_resp)
+{
+	u8 *mi_resp = _mi_resp;
+
+	memcpy(dev->ex_dev.vendor_id, mi_resp + 12, 8);
+	memcpy(dev->ex_dev.product_id, mi_resp + 20, 16);
+	memcpy(dev->ex_dev.product_rev, mi_resp + 36, 4);
+
+	if (mi_resp[8] & 1) {
+		memcpy(dev->ex_dev.component_vendor_id, mi_resp + 40, 8);
+		dev->ex_dev.component_id =
+			be16_to_cpu(*(__be16 *)(mi_resp + 48));
+		dev->ex_dev.component_revision_id = mi_resp[50];
+	}
+}
+
+#define MI_REQ_SIZE   8
+#define MI_RESP_SIZE 64
+
+static int sas_ex_manuf_info(struct domain_device *dev)
+{
+	u8 *mi_req;
+	u8 *mi_resp;
+	int res;
+
+	mi_req = alloc_smp_req(MI_REQ_SIZE);
+	if (!mi_req)
+		return -ENOMEM;
+
+	mi_resp = alloc_smp_resp(MI_RESP_SIZE);
+	if (!mi_resp) {
+		kfree(mi_req);
+		return -ENOMEM;
+	}
+
+	mi_req[1] = SMP_REPORT_MANUF_INFO;
+
+	res = smp_execute_task(dev, mi_req, MI_REQ_SIZE, mi_resp,MI_RESP_SIZE);
+	if (res) {
+		SAS_DPRINTK("MI: ex %016llx failed:0x%x\n",
+			    SAS_ADDR(dev->sas_addr), res);
+		goto out;
+	} else if (mi_resp[2] != SMP_RESP_FUNC_ACC) {
+		SAS_DPRINTK("MI ex %016llx returned SMP result:0x%x\n",
+			    SAS_ADDR(dev->sas_addr), mi_resp[2]);
+		goto out;
+	}
+
+	ex_assign_manuf_info(dev, mi_resp);
+out:
+	kfree(mi_req);
+	kfree(mi_resp);
+	return res;
+}
+
+#define PC_REQ_SIZE  44
+#define PC_RESP_SIZE 8
+
+static int smp_phy_control(struct domain_device *dev, int phy_id,
+			   enum phy_func phy_func)
+{
+	u8 *pc_req;
+	u8 *pc_resp;
+	int res;
+
+	pc_req = alloc_smp_req(PC_REQ_SIZE);
+	if (!pc_req)
+		return -ENOMEM;
+
+	pc_resp = alloc_smp_resp(PC_RESP_SIZE);
+	if (!pc_resp) {
+		kfree(pc_req);
+		return -ENOMEM;
+	}
+
+	pc_req[1] = SMP_PHY_CONTROL;
+	pc_req[9] = phy_id;
+	pc_req[10]= phy_func;
+
+	res = smp_execute_task(dev, pc_req, PC_REQ_SIZE, pc_resp,PC_RESP_SIZE);
+
+	kfree(pc_resp);
+	kfree(pc_req);
+	return res;
+}
+
+static inline void sas_ex_disable_phy(struct domain_device *dev, int phy_id)
+{
+	struct expander_device *ex = &dev->ex_dev;
+	struct ex_phy *phy = &ex->ex_phy[phy_id];
+
+	smp_phy_control(dev, phy_id, PHY_FUNC_DISABLE);
+	phy->linkrate = PHY_DISABLED;
+}
+
+static inline void sas_ex_disable_port(struct domain_device *dev, u8 *sas_addr)
+{
+	struct expander_device *ex = &dev->ex_dev;
+	int i;
+
+	for (i = 0; i < ex->num_phys; i++) {
+		struct ex_phy *phy = &ex->ex_phy[i];
+
+		if (phy->phy_state == PHY_VACANT ||
+		    phy->phy_state == PHY_NOT_PRESENT)
+			continue;
+
+		if (SAS_ADDR(phy->attached_sas_addr) == SAS_ADDR(sas_addr))
+			sas_ex_disable_phy(dev, i);
+	}
+}
+
+static inline int sas_dev_present_in_domain(struct sas_port *port,
+					    u8 *sas_addr)
+{
+	struct domain_device *dev;
+
+	if (SAS_ADDR(port->sas_addr) == SAS_ADDR(sas_addr))
+		return 1;
+	list_for_each_entry(dev, &port->dev_list, dev_list_node) {
+		if (SAS_ADDR(dev->sas_addr) == SAS_ADDR(sas_addr))
+			return 1;
+	}
+	return 0;
+}
+
+#define RPS_REQ_SIZE  16
+#define RPS_RESP_SIZE 60
+
+static inline int sas_get_report_phy_sata(struct domain_device *dev,
+					  int phy_id,
+					  struct smp_resp *rps_resp)
+{
+	int res;
+	u8 *rps_req = alloc_smp_req(RPS_REQ_SIZE);
+
+	if (!rps_req)
+		return -ENOMEM;
+
+	rps_req[1] = SMP_REPORT_PHY_SATA;
+	rps_req[9] = phy_id;
+
+	res = smp_execute_task(dev, rps_req, RPS_REQ_SIZE,
+			            rps_resp, RPS_RESP_SIZE);
+
+	kfree(rps_req);
+	return 0;
+}
+
+static inline void sas_ex_get_linkrate(struct domain_device *parent,
+				       struct domain_device *child,
+				       struct ex_phy *parent_phy)
+{
+	struct expander_device *parent_ex = &parent->ex_dev;
+	int i;
+
+	child->pathways = 0;
+
+	for (i = 0; i < parent_ex->num_phys; i++) {
+		struct ex_phy *phy = &parent_ex->ex_phy[i];
+
+		if (phy->phy_state == PHY_VACANT ||
+		    phy->phy_state == PHY_NOT_PRESENT)
+			continue;
+
+		if (SAS_ADDR(phy->attached_sas_addr) ==
+		    SAS_ADDR(child->sas_addr)) {
+
+			child->min_linkrate = min(parent->min_linkrate,
+						  phy->linkrate);
+			child->max_linkrate = max(parent->max_linkrate,
+						  phy->linkrate);
+			child->pathways++;
+		}
+	}
+	child->linkrate = min(parent_phy->linkrate, child->max_linkrate);
+	child->pathways = min(child->pathways, parent->pathways);
+}
+
+static struct domain_device *sas_ex_discover_end_dev(
+	struct domain_device *parent, int phy_id)
+{
+	struct expander_device *parent_ex = &parent->ex_dev;
+	struct ex_phy *phy = &parent_ex->ex_phy[phy_id];
+	struct domain_device *child = NULL;
+	int res;
+
+	if (phy->attached_sata_host || phy->attached_sata_ps)
+		return NULL;
+
+	child = kmalloc(sizeof(*child), GFP_KERNEL);
+	if (!child)
+		return NULL;
+	memset(child, 0, sizeof(*child));
+
+	child->parent = parent;
+	child->port   = parent->port;
+	child->iproto = phy->attached_iproto;
+	memcpy(child->sas_addr, phy->attached_sas_addr, SAS_ADDR_SIZE);
+	sas_hash_addr(child->hashed_sas_addr, child->sas_addr);
+	sas_ex_get_linkrate(parent, child, phy);
+
+	if ((phy->attached_tproto & SAS_PROTO_STP) || phy->attached_sata_dev) {
+		child->dev_type = SATA_DEV;
+		if (phy->attached_tproto & SAS_PROTO_STP)
+			child->tproto = phy->attached_tproto;
+		if (phy->attached_sata_dev)
+			child->tproto |= SATA_DEV;
+		res = sas_get_report_phy_sata(parent, phy_id,
+					      &child->sata_dev.rps_resp);
+		if (res) {
+			SAS_DPRINTK("report phy sata to %016llx:0x%x returned "
+				    "0x%x\n", SAS_ADDR(parent->sas_addr),
+				    phy_id, res);
+			kfree(child);
+			return NULL;
+		}
+		memcpy(child->frame_rcvd, &child->sata_dev.rps_resp.rps.fis,
+		       sizeof(struct dev_to_host_fis));
+		sas_init_dev(child);
+		res = sas_discover_sata(child);
+		if (res) {
+			SAS_DPRINTK("sas_discover_sata() for device %16llx at "
+				    "%016llx:0x%x returned 0x%x\n",
+				    SAS_ADDR(child->sas_addr),
+				    SAS_ADDR(parent->sas_addr), phy_id, res);
+			kfree(child);
+			return NULL;
+		}
+	} else if (phy->attached_tproto & SAS_PROTO_SSP) {
+		child->dev_type = SAS_END_DEV;
+		child->tproto = phy->attached_tproto;
+		sas_init_dev(child);
+		res = sas_discover_end_dev(child);
+		if (res) {
+			SAS_DPRINTK("sas_discover_end_dev() for device %16llx "
+				    "at %016llx:0x%x returned 0x%x\n",
+				    SAS_ADDR(child->sas_addr),
+				    SAS_ADDR(parent->sas_addr), phy_id, res);
+			kfree(child);
+			return NULL;
+		}
+	} else {
+		SAS_DPRINTK("target proto 0x%x at %016llx:0x%x not handled\n",
+			    phy->attached_tproto, SAS_ADDR(parent->sas_addr),
+			    phy_id);
+	}
+	list_add_tail(&child->siblings, &parent_ex->children);
+	return child;
+}
+
+static struct domain_device *sas_ex_discover_expander(
+	struct domain_device *parent, int phy_id)
+{
+	struct expander_device *parent_ex = &parent->ex_dev;
+	struct ex_phy *phy = &parent_ex->ex_phy[phy_id];
+	struct domain_device *child = NULL;
+	int res;
+
+	if (phy->routing_attr == DIRECT_ROUTING) {
+		SAS_DPRINTK("ex %016llx:0x%x:D <--> ex %016llx:0x%x is not "
+			    "allowed\n",
+			    SAS_ADDR(parent->sas_addr), phy_id,
+			    SAS_ADDR(phy->attached_sas_addr),
+			    phy->attached_phy_id);
+		return NULL;
+	}
+	child = kmalloc(sizeof(*child), GFP_KERNEL);
+	if (!child)
+		return NULL;
+	memset(child, 0, sizeof(*child));
+	child->dev_type = phy->attached_dev_type;
+	child->parent = parent;
+	child->port = parent->port;
+	child->iproto = phy->attached_iproto;
+	child->tproto = phy->attached_tproto;
+	memcpy(child->sas_addr, phy->attached_sas_addr, SAS_ADDR_SIZE);
+	sas_hash_addr(child->hashed_sas_addr, child->sas_addr);
+	sas_ex_get_linkrate(parent, child, phy);
+	child->ex_dev.level = parent_ex->level + 1;
+	parent->port->disc.max_level = max(parent->port->disc.max_level,
+					   child->ex_dev.level);
+	sas_init_dev(child);
+	res = sas_discover_expander(child);
+	if (res) {
+		kfree(child);
+		return NULL;
+	}
+	list_add_tail(&child->siblings, &parent_ex->children);
+	return child;
+}
+
+static int sas_ex_discover_dev(struct domain_device *dev, int phy_id)
+{
+	struct expander_device *ex = &dev->ex_dev;
+	struct ex_phy *ex_phy = &ex->ex_phy[phy_id];
+	struct domain_device *child = NULL;
+	int res = 0;
+
+	/* Phy state */
+	if (ex_phy->linkrate == PHY_SPINUP_HOLD) {
+		if (!smp_phy_control(dev, phy_id, PHY_FUNC_LINK_RESET))
+			res = sas_ex_phy_discover(dev, phy_id);
+		if (res)
+			return res;
+	}
+
+	/* Parent and domain coherency */
+	if (!dev->parent && (SAS_ADDR(ex_phy->attached_sas_addr) ==
+			     SAS_ADDR(dev->port->sas_addr)))
+		return 0;
+	if (dev->parent && (SAS_ADDR(ex_phy->attached_sas_addr) ==
+			    SAS_ADDR(dev->parent->sas_addr)))
+		return 0;
+	if (sas_dev_present_in_domain(dev->port, ex_phy->attached_sas_addr))
+		sas_ex_disable_port(dev, ex_phy->attached_sas_addr);
+
+	if (ex_phy->attached_dev_type == NO_DEVICE) {
+		if (ex_phy->routing_attr == DIRECT_ROUTING) {
+			memset(ex_phy->attached_sas_addr, 0, SAS_ADDR_SIZE);
+			sas_configure_routing(dev, ex_phy->attached_sas_addr);
+		}
+		return 0;
+	} else if (ex_phy->linkrate == PHY_LINKRATE_UNKNOWN)
+		return 0;
+
+	if (ex_phy->attached_dev_type != SAS_END_DEV &&
+	    ex_phy->attached_dev_type != FANOUT_DEV &&
+	    ex_phy->attached_dev_type != EDGE_DEV) {
+		SAS_DPRINTK("unknown device type(0x%x) attached to ex %016llx "
+			    "phy 0x%x\n", ex_phy->attached_dev_type,
+			    SAS_ADDR(dev->sas_addr),
+			    phy_id);
+		return 0;
+	}
+
+	res = sas_configure_routing(dev, ex_phy->attached_sas_addr);
+	if (res) {
+		SAS_DPRINTK("configure routing for dev %016llx "
+			    "reported 0x%x. Forgotten\n",
+			    SAS_ADDR(ex_phy->attached_sas_addr), res);
+		sas_disable_routing(dev, ex_phy->attached_sas_addr);
+		return res;
+	}
+
+	switch (ex_phy->attached_dev_type) {
+	case SAS_END_DEV:
+		child = sas_ex_discover_end_dev(dev, phy_id);
+		break;
+	case FANOUT_DEV:
+		if (SAS_ADDR(dev->port->disc.fanout_sas_addr)) {
+			SAS_DPRINTK("second fanout expander %016llx phy 0x%x "
+				    "attached to ex %016llx phy 0x%x\n",
+				    SAS_ADDR(ex_phy->attached_sas_addr),
+				    ex_phy->attached_phy_id,
+				    SAS_ADDR(dev->sas_addr),
+				    phy_id);
+			sas_ex_disable_phy(dev, phy_id);
+			break;
+		} else
+			memcpy(dev->port->disc.fanout_sas_addr,
+			       ex_phy->attached_sas_addr, SAS_ADDR_SIZE);
+		/* fallthrough */
+	case EDGE_DEV:
+		child = sas_ex_discover_expander(dev, phy_id);
+		break;
+	default:
+		break;
+	}
+
+	if (child) {
+		int i;
+
+		for (i = 0; i < ex->num_phys; i++) {
+			if (ex->ex_phy[i].phy_state == PHY_VACANT ||
+			    ex->ex_phy[i].phy_state == PHY_NOT_PRESENT)
+				continue;
+
+			if (SAS_ADDR(ex->ex_phy[i].attached_sas_addr) ==
+			    SAS_ADDR(child->sas_addr))
+				ex->ex_phy[i].phy_state= PHY_DEVICE_DISCOVERED;
+		}
+	}
+
+	return res;
+}
+
+static inline int sas_find_sub_addr(struct domain_device *dev, u8 *sub_addr)
+{
+	struct expander_device *ex = &dev->ex_dev;
+	int i;
+
+	for (i = 0; i < ex->num_phys; i++) {
+		struct ex_phy *phy = &ex->ex_phy[i];
+
+		if (phy->phy_state == PHY_VACANT ||
+		    phy->phy_state == PHY_NOT_PRESENT)
+			continue;
+
+		if ((phy->attached_dev_type == EDGE_DEV ||
+		     phy->attached_dev_type == FANOUT_DEV) &&
+		    phy->routing_attr == SUBTRACTIVE_ROUTING) {
+
+			memcpy(sub_addr, phy->attached_sas_addr,SAS_ADDR_SIZE);
+
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static int sas_check_level_subtractive_boundary(struct domain_device *dev)
+{
+	struct expander_device *ex = &dev->ex_dev;
+	struct domain_device *child;
+	u8 sub_addr[8] = {0, };
+
+	list_for_each_entry(child, &ex->children, siblings) {
+		if (child->dev_type != EDGE_DEV &&
+		    child->dev_type != FANOUT_DEV)
+			continue;
+		if (sub_addr[0] == 0) {
+			sas_find_sub_addr(child, sub_addr);
+			continue;
+		} else {
+			u8 s2[8];
+
+			if (sas_find_sub_addr(child, s2) &&
+			    (SAS_ADDR(sub_addr) != SAS_ADDR(s2))) {
+
+				SAS_DPRINTK("ex %016llx->%016llx-?->%016llx "
+					    "diverges from subtractive "
+					    "boundary %016llx\n",
+					    SAS_ADDR(dev->sas_addr),
+					    SAS_ADDR(child->sas_addr),
+					    SAS_ADDR(s2),
+					    SAS_ADDR(sub_addr));
+
+				sas_ex_disable_port(child, s2);
+			}
+		}
+	}
+	return 0;
+}
+/**
+ * sas_ex_discover_devices -- discover devices attached to this expander
+ * dev: pointer to the expander domain device
+ * single: if you want to do a single phy, else set to -1;
+ *
+ * Configure this expander for use with its devices and register the
+ * devices of this expander.
+ */
+static int sas_ex_discover_devices(struct domain_device *dev, int single)
+{
+	struct expander_device *ex = &dev->ex_dev;
+	int i = 0, end = ex->num_phys;
+	int res = 0;
+
+	if (0 <= single && single < end) {
+		i = single;
+		end = i+1;
+	}
+
+	for ( ; i < end; i++) {
+		struct ex_phy *ex_phy = &ex->ex_phy[i];
+
+		if (ex_phy->phy_state == PHY_VACANT ||
+		    ex_phy->phy_state == PHY_NOT_PRESENT ||
+		    ex_phy->phy_state == PHY_DEVICE_DISCOVERED)
+			continue;
+
+		switch (ex_phy->linkrate) {
+		case PHY_DISABLED:
+		case PHY_RESET_PROBLEM:
+		case PHY_PORT_SELECTOR:
+			continue;
+		default:
+			res = sas_ex_discover_dev(dev, i);
+			if (res)
+				break;
+			continue;
+		}
+	}
+
+	if (!res)
+		sas_check_level_subtractive_boundary(dev);
+
+	return res;
+}
+
+static int sas_check_ex_subtractive_boundary(struct domain_device *dev)
+{
+	struct expander_device *ex = &dev->ex_dev;
+	int i;
+	u8  *sub_sas_addr = NULL;
+
+	if (dev->dev_type != EDGE_DEV)
+		return 0;
+
+	for (i = 0; i < ex->num_phys; i++) {
+		struct ex_phy *phy = &ex->ex_phy[i];
+
+		if (phy->phy_state == PHY_VACANT ||
+		    phy->phy_state == PHY_NOT_PRESENT)
+			continue;
+
+		if ((phy->attached_dev_type == FANOUT_DEV ||
+		     phy->attached_dev_type == EDGE_DEV) &&
+		    phy->routing_attr == SUBTRACTIVE_ROUTING) {
+
+			if (!sub_sas_addr)
+				sub_sas_addr = &phy->attached_sas_addr[0];
+			else if (SAS_ADDR(sub_sas_addr) !=
+				 SAS_ADDR(phy->attached_sas_addr)) {
+
+				SAS_DPRINTK("ex %016llx phy 0x%x "
+					    "diverges(%016llx) on subtractive "
+					    "boundary(%016llx). Disabled\n",
+					    SAS_ADDR(dev->sas_addr), i,
+					    SAS_ADDR(phy->attached_sas_addr),
+					    SAS_ADDR(sub_sas_addr));
+				sas_ex_disable_phy(dev, i);
+			}
+		}
+	}
+	return 0;
+}
+
+static inline void sas_print_parent_topology_bug(struct domain_device *child,
+						 struct ex_phy *parent_phy,
+						 struct ex_phy *child_phy)
+{
+	static const char ra_char[] = {
+		[DIRECT_ROUTING] = 'D',
+		[SUBTRACTIVE_ROUTING] = 'S',
+		[TABLE_ROUTING] = 'T',
+	};
+	static const char *ex_type[] = {
+		[EDGE_DEV] = "edge",
+		[FANOUT_DEV] = "fanout",
+	};
+	struct domain_device *parent = child->parent;
+
+	sas_printk("%s ex %016llx phy 0x%x <--> %s ex %016llx phy 0x%x "
+		   "has %c:%c routing link!\n",
+
+		   ex_type[parent->dev_type],
+		   SAS_ADDR(parent->sas_addr),
+		   parent_phy->phy_id,
+
+		   ex_type[child->dev_type],
+		   SAS_ADDR(child->sas_addr),
+		   child_phy->phy_id,
+
+		   ra_char[parent_phy->routing_attr],
+		   ra_char[child_phy->routing_attr]);
+}
+
+static inline int sas_check_eeds(struct domain_device *child,
+				 struct ex_phy *parent_phy,
+				 struct ex_phy *child_phy)
+{
+	int res = 0;
+	struct domain_device *parent = child->parent;
+
+	if (SAS_ADDR(parent->port->disc.fanout_sas_addr) != 0) {
+		res = -ENODEV;
+		SAS_DPRINTK("edge ex %016llx phy S:0x%x <--> edge ex %016llx "
+			    "phy S:0x%x, while there is a fanout ex %016llx\n",
+			    SAS_ADDR(parent->sas_addr),
+			    parent_phy->phy_id,
+			    SAS_ADDR(child->sas_addr),
+			    child_phy->phy_id,
+			    SAS_ADDR(parent->port->disc.fanout_sas_addr));
+	} else if (SAS_ADDR(parent->port->disc.eeds_a) == 0) {
+		memcpy(parent->port->disc.eeds_a, parent->sas_addr,
+		       SAS_ADDR_SIZE);
+		memcpy(parent->port->disc.eeds_b, child->sas_addr,
+		       SAS_ADDR_SIZE);
+	} else if (((SAS_ADDR(parent->port->disc.eeds_a) ==
+		    SAS_ADDR(parent->sas_addr)) ||
+		   (SAS_ADDR(parent->port->disc.eeds_a) ==
+		    SAS_ADDR(child->sas_addr)))
+		   &&
+		   ((SAS_ADDR(parent->port->disc.eeds_b) ==
+		     SAS_ADDR(parent->sas_addr)) ||
+		    (SAS_ADDR(parent->port->disc.eeds_b) ==
+		     SAS_ADDR(child->sas_addr))))
+		;
+	else {
+		res = -ENODEV;
+		SAS_DPRINTK("edge ex %016llx phy 0x%x <--> edge ex %016llx "
+			    "phy 0x%x link forms a third EEDS!\n",
+			    SAS_ADDR(parent->sas_addr),
+			    parent_phy->phy_id,
+			    SAS_ADDR(child->sas_addr),
+			    child_phy->phy_id);
+	}
+
+	return res;
+}
+
+/* Here we spill over 80 columns.  It is intentional.
+ */
+static int sas_check_parent_topology(struct domain_device *child)
+{
+	struct expander_device *child_ex = &child->ex_dev;
+	struct expander_device *parent_ex;
+	int i;
+	int res = 0;
+
+	if (!child->parent)
+		return 0;
+
+	if (child->parent->dev_type != EDGE_DEV &&
+	    child->parent->dev_type != FANOUT_DEV)
+		return 0;
+
+	parent_ex = &child->parent->ex_dev;
+
+	for (i = 0; i < parent_ex->num_phys; i++) {
+		struct ex_phy *parent_phy = &parent_ex->ex_phy[i];
+		struct ex_phy *child_phy;
+
+		if (parent_phy->phy_state == PHY_VACANT ||
+		    parent_phy->phy_state == PHY_NOT_PRESENT)
+			continue;
+
+		if (SAS_ADDR(parent_phy->attached_sas_addr) != SAS_ADDR(child->sas_addr))
+			continue;
+
+		child_phy = &child_ex->ex_phy[parent_phy->attached_phy_id];
+
+		switch (child->parent->dev_type) {
+		case EDGE_DEV:
+			if (child->dev_type == FANOUT_DEV) {
+				if (parent_phy->routing_attr != SUBTRACTIVE_ROUTING ||
+				    child_phy->routing_attr != TABLE_ROUTING) {
+					sas_print_parent_topology_bug(child, parent_phy, child_phy);
+					res = -ENODEV;
+				}
+			} else if (parent_phy->routing_attr == SUBTRACTIVE_ROUTING) {
+				if (child_phy->routing_attr == SUBTRACTIVE_ROUTING) {
+					res = sas_check_eeds(child, parent_phy, child_phy);
+				} else if (child_phy->routing_attr != TABLE_ROUTING) {
+					sas_print_parent_topology_bug(child, parent_phy, child_phy);
+					res = -ENODEV;
+				}
+			} else if (parent_phy->routing_attr == TABLE_ROUTING &&
+				   child_phy->routing_attr != SUBTRACTIVE_ROUTING) {
+				sas_print_parent_topology_bug(child, parent_phy, child_phy);
+				res = -ENODEV;
+			}
+			break;
+		case FANOUT_DEV:
+			if (parent_phy->routing_attr != TABLE_ROUTING ||
+			    child_phy->routing_attr != SUBTRACTIVE_ROUTING) {
+				sas_print_parent_topology_bug(child, parent_phy, child_phy);
+				res = -ENODEV;
+			}
+			break;
+		default:
+			break;
+		}
+	}
+
+	return res;
+}
+
+#define RRI_REQ_SIZE  16
+#define RRI_RESP_SIZE 44
+
+static int sas_configure_present(struct domain_device *dev, int phy_id,
+				 u8 *sas_addr, int *index, int *present)
+{
+	int i, res = 0;
+	struct expander_device *ex = &dev->ex_dev;
+	struct ex_phy *phy = &ex->ex_phy[phy_id];
+	u8 *rri_req;
+	u8 *rri_resp;
+
+	*present = 0;
+	*index = 0;
+
+	rri_req = alloc_smp_req(RRI_REQ_SIZE);
+	if (!rri_req)
+		return -ENOMEM;
+
+	rri_resp = alloc_smp_resp(RRI_RESP_SIZE);
+	if (!rri_resp) {
+		kfree(rri_req);
+		return -ENOMEM;
+	}
+
+	rri_req[1] = SMP_REPORT_ROUTE_INFO;
+	rri_req[9] = phy_id;
+
+	for (i = 0; i < ex->max_route_indexes ; i++) {
+		*(__be16 *)(rri_req+6) = cpu_to_be16(i);
+		res = smp_execute_task(dev, rri_req, RRI_REQ_SIZE, rri_resp,
+				       RRI_RESP_SIZE);
+		if (res)
+			goto out;
+		res = rri_resp[2];
+		if (res == SMP_RESP_NO_INDEX) {
+			SAS_DPRINTK("overflow of indexes: dev %016llx "
+				    "phy 0x%x index 0x%x\n",
+				    SAS_ADDR(dev->sas_addr), phy_id, i);
+			goto out;
+		} else if (res != SMP_RESP_FUNC_ACC) {
+			SAS_DPRINTK("%s: dev %016llx phy 0x%x index 0x%x "
+				    "result 0x%x\n", __FUNCTION__,
+				    SAS_ADDR(dev->sas_addr), phy_id, i, res);
+			goto out;
+		}
+		if (SAS_ADDR(sas_addr) != 0) {
+			if (SAS_ADDR(rri_resp+16) == SAS_ADDR(sas_addr)) {
+				*index = i;
+				if ((rri_resp[12] & 0x80) == 0x80)
+					*present = 0;
+				else
+					*present = 1;
+				goto out;
+			} else if (SAS_ADDR(rri_resp+16) == 0) {
+				*index = i;
+				*present = 0;
+				goto out;
+			}
+		} else if (SAS_ADDR(rri_resp+16) == 0 &&
+			   phy->last_da_index < i) {
+			phy->last_da_index = i;
+			*index = i;
+			*present = 0;
+			goto out;
+		}
+	}
+	res = -1;
+out:
+	kfree(rri_req);
+	kfree(rri_resp);
+	return res;
+}
+
+#define CRI_REQ_SIZE  44
+#define CRI_RESP_SIZE  8
+
+static int sas_configure_set(struct domain_device *dev, int phy_id,
+			     u8 *sas_addr, int index, int include)
+{
+	int res;
+	u8 *cri_req;
+	u8 *cri_resp;
+
+	cri_req = alloc_smp_req(CRI_REQ_SIZE);
+	if (!cri_req)
+		return -ENOMEM;
+
+	cri_resp = alloc_smp_resp(CRI_RESP_SIZE);
+	if (!cri_resp) {
+		kfree(cri_req);
+		return -ENOMEM;
+	}
+
+	cri_req[1] = SMP_CONF_ROUTE_INFO;
+	*(__be16 *)(cri_req+6) = cpu_to_be16(index);
+	cri_req[9] = phy_id;
+	if (SAS_ADDR(sas_addr) == 0 || !include)
+		cri_req[12] |= 0x80;
+	memcpy(cri_req+16, sas_addr, SAS_ADDR_SIZE);
+
+	res = smp_execute_task(dev, cri_req, CRI_REQ_SIZE, cri_resp,
+			       CRI_RESP_SIZE);
+	if (res)
+		goto out;
+	res = cri_resp[2];
+	if (res == SMP_RESP_NO_INDEX) {
+		SAS_DPRINTK("overflow of indexes: dev %016llx phy 0x%x "
+			    "index 0x%x\n",
+			    SAS_ADDR(dev->sas_addr), phy_id, index);
+	}
+out:
+	kfree(cri_req);
+	kfree(cri_resp);
+	return res;
+}
+
+static inline int sas_configure_phy(struct domain_device *dev, int phy_id,
+				    u8 *sas_addr, int include)
+{
+	int index;
+	int present;
+	int res;
+
+	res = sas_configure_present(dev, phy_id, sas_addr, &index, &present);
+	if (res)
+		return res;
+	if (include ^ present)
+		return sas_configure_set(dev, phy_id, sas_addr, index,include);
+
+	return res;
+}
+
+/**
+ * sas_configure_parent -- configure routing table of parent
+ * parent: parent expander
+ * child: child expander
+ * sas_addr: SAS port identifier of device directly attached to child
+ */
+static int sas_configure_parent(struct domain_device *parent,
+				struct domain_device *child,
+				u8 *sas_addr, int include)
+{
+	struct expander_device *ex_parent = &parent->ex_dev;
+	int res = 0;
+	int i;
+
+
+	if (parent->parent) {
+		res = sas_configure_parent(parent->parent, parent, sas_addr,
+					   include);
+		if (res)
+			return res;
+	}
+
+	if (ex_parent->conf_route_table == 0) {
+		SAS_DPRINTK("ex %016llx has self-configuring routing table\n",
+			    SAS_ADDR(parent->sas_addr));
+		return 0;
+	}
+
+	for (i = 0; i < ex_parent->num_phys; i++) {
+		struct ex_phy *phy = &ex_parent->ex_phy[i];
+
+		if ((phy->routing_attr == TABLE_ROUTING) &&
+		    (SAS_ADDR(phy->attached_sas_addr) ==
+		     SAS_ADDR(child->sas_addr))) {
+			res = sas_configure_phy(parent, i, sas_addr, include);
+			if (res)
+				return res;
+		}
+	}
+
+	return res;
+}
+
+/**
+ * sas_configure_routing -- configure routing
+ * dev: expander device
+ * sas_addr: port identifier of device directly attached to the expander device
+ */
+static int sas_configure_routing(struct domain_device *dev, u8 *sas_addr)
+{
+	if (dev->parent)
+		return sas_configure_parent(dev->parent, dev, sas_addr, 1);
+	return 0;
+}
+
+static int sas_disable_routing(struct domain_device *dev,  u8 *sas_addr)
+{
+	if (dev->parent)
+		return sas_configure_parent(dev->parent, dev, sas_addr, 0);
+	return 0;
+}
+
+#define SMP_BIN_ATTR_NAME "smp_portal"
+
+static void sas_ex_smp_hook(struct domain_device *dev)
+{
+	struct expander_device *ex_dev = &dev->ex_dev;
+	struct bin_attribute *bin_attr = &ex_dev->smp_bin_attr;
+
+	memset(bin_attr, 0, sizeof(*bin_attr));
+
+	bin_attr->attr.name = SMP_BIN_ATTR_NAME;
+	bin_attr->attr.owner = THIS_MODULE;
+	bin_attr->attr.mode = 0600;
+
+	bin_attr->size = 0;
+	bin_attr->private = NULL;
+	bin_attr->read = smp_portal_read;
+	bin_attr->write= smp_portal_write;
+	bin_attr->mmap = NULL;
+
+	ex_dev->smp_portal_pid = -1;
+	init_MUTEX(&ex_dev->smp_sema);
+}
+
+/**
+ * sas_discover_expander -- expander discovery
+ * @ex: pointer to expander domain device
+ *
+ * See comment in sas_discover_sata().
+ */
+static int sas_discover_expander(struct domain_device *dev)
+{
+	int res;
+
+	res = sas_notify_lldd_dev_found(dev);
+	if (res)
+		return res;
+
+	res = sas_ex_general(dev);
+	if (res)
+		goto out_err;
+	res = sas_ex_manuf_info(dev);
+	if (res)
+		goto out_err;
+
+	res = sas_expander_discover(dev);
+	if (res) {
+		SAS_DPRINTK("expander %016llx discovery failed(0x%x)\n",
+			    SAS_ADDR(dev->sas_addr), res);
+		goto out_err;
+	}
+
+	sas_check_ex_subtractive_boundary(dev);
+	res = sas_check_parent_topology(dev);
+	if (res)
+		goto out_err;
+	sas_ex_smp_hook(dev);
+	sas_kobj_set(dev);
+	return 0;
+out_err:
+	sas_notify_lldd_dev_gone(dev);
+	return res;
+}
+
+static int sas_ex_level_discovery(struct sas_port *port, const int level)
+{
+	int res = 0;
+	struct domain_device *dev;
+
+	list_for_each_entry(dev, &port->dev_list, dev_list_node) {
+		if (dev->dev_type == EDGE_DEV ||
+		    dev->dev_type == FANOUT_DEV) {
+			struct expander_device *ex = &dev->ex_dev;
+
+			if (level == ex->level)
+				res = sas_ex_discover_devices(dev, -1);
+		}
+	}
+
+	return res;
+}
+
+static int sas_ex_bfs_disc(struct sas_port *port)
+{
+	int res;
+	int level;
+
+	do {
+		level = port->disc.max_level;
+		res = sas_ex_level_discovery(port, level);
+		mb();
+	} while (level < port->disc.max_level);
+
+	return res;
+}
+
+int sas_discover_root_expander(struct domain_device *dev)
+{
+	int res;
+
+	dev->ex_dev.level = dev->port->disc.max_level; /* 0 */
+	res = sas_discover_expander(dev);
+	if (!res)
+		sas_ex_bfs_disc(dev->port);
+
+	return res;
+}
+
+void sas_init_ex_attr(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ex_attrs)-1; i++)
+		ex_dev_attrs[i] = &ex_attrs[i].attr;
+	ex_dev_attrs[i] = NULL;
+}
+
+/* ---------- Domain revalidation ---------- */
+
+static int sas_get_phy_discover(struct domain_device *dev,
+				int phy_id, struct smp_resp *disc_resp)
+{
+	int res;
+	u8 *disc_req;
+
+	disc_req = alloc_smp_req(DISCOVER_REQ_SIZE);
+	if (!disc_req)
+		return -ENOMEM;
+
+	disc_req[1] = SMP_DISCOVER;
+	disc_req[9] = phy_id;
+
+	res = smp_execute_task(dev, disc_req, DISCOVER_REQ_SIZE,
+			       disc_resp, DISCOVER_RESP_SIZE);
+	if (res)
+		goto out;
+	else if (disc_resp->result != SMP_RESP_FUNC_ACC) {
+		res = disc_resp->result;
+		goto out;
+	}
+out:
+	kfree(disc_req);
+	return res;
+}
+
+static int sas_get_phy_change_count(struct domain_device *dev,
+				    int phy_id, int *pcc)
+{
+	int res;
+	struct smp_resp *disc_resp;
+
+	disc_resp = alloc_smp_resp(DISCOVER_RESP_SIZE);
+	if (!disc_resp)
+		return -ENOMEM;
+
+	res = sas_get_phy_discover(dev, phy_id, disc_resp);
+	if (!res)
+		*pcc = disc_resp->disc.change_count;
+
+	kfree(disc_resp);
+	return res;
+}
+
+static int sas_get_phy_attached_sas_addr(struct domain_device *dev,
+					 int phy_id, u8 *attached_sas_addr)
+{
+	int res;
+	struct smp_resp *disc_resp;
+
+	disc_resp = alloc_smp_resp(DISCOVER_RESP_SIZE);
+	if (!disc_resp)
+		return -ENOMEM;
+
+	res = sas_get_phy_discover(dev, phy_id, disc_resp);
+	if (!res)
+		memcpy(attached_sas_addr,disc_resp->disc.attached_sas_addr,8);
+
+	kfree(disc_resp);
+	return res;
+}
+
+static int sas_find_bcast_phy(struct domain_device *dev, int *phy_id,
+			      int from_phy)
+{
+	struct expander_device *ex = &dev->ex_dev;
+	int res = 0;
+	int i;
+
+	for (i = from_phy; i < ex->num_phys; i++) {
+		int phy_change_count;
+
+		res = sas_get_phy_change_count(dev, i, &phy_change_count);
+		if (res)
+			goto out;
+		else if (phy_change_count != ex->ex_phy[i].phy_change_count) {
+			ex->ex_phy[i].phy_change_count = phy_change_count;
+			*phy_id = i;
+			return 0;
+		}
+	}
+out:
+	return res;
+}
+
+static int sas_get_ex_change_count(struct domain_device *dev, int *ecc)
+{
+	int res;
+	u8  *rg_req;
+	struct smp_resp  *rg_resp;
+
+	rg_req = alloc_smp_req(RG_REQ_SIZE);
+	if (!rg_req)
+		return -ENOMEM;
+
+	rg_resp = alloc_smp_resp(RG_RESP_SIZE);
+	if (!rg_resp) {
+		kfree(rg_req);
+		return -ENOMEM;
+	}
+
+	rg_req[1] = SMP_REPORT_GENERAL;
+
+	res = smp_execute_task(dev, rg_req, RG_REQ_SIZE, rg_resp,
+			       RG_RESP_SIZE);
+	if (res)
+		goto out;
+	if (rg_resp->result != SMP_RESP_FUNC_ACC) {
+		res = rg_resp->result;
+		goto out;
+	}
+
+	*ecc = be16_to_cpu(rg_resp->rg.change_count);
+out:
+	kfree(rg_resp);
+	kfree(rg_req);
+	return res;
+}
+
+static int sas_find_bcast_dev(struct domain_device *dev,
+			      struct domain_device **src_dev)
+{
+	struct expander_device *ex = &dev->ex_dev;
+	int ex_change_count = -1;
+	int res;
+
+	res = sas_get_ex_change_count(dev, &ex_change_count);
+	if (res)
+		goto out;
+	if (ex_change_count != -1 &&
+	    ex_change_count != ex->ex_change_count) {
+		*src_dev = dev;
+		ex->ex_change_count = ex_change_count;
+	} else {
+		struct domain_device *ch;
+
+		list_for_each_entry(ch, &ex->children, siblings) {
+			if (ch->dev_type == EDGE_DEV ||
+			    ch->dev_type == FANOUT_DEV) {
+				res = sas_find_bcast_dev(ch, src_dev);
+				if (src_dev)
+					return res;
+			}
+		}
+	}
+out:
+	return res;
+}
+
+static void sas_unregister_ex_tree(struct domain_device *dev)
+{
+	struct expander_device *ex = &dev->ex_dev;
+	struct domain_device *child, *n;
+
+	list_for_each_entry_safe(child, n, &ex->children, siblings) {
+		if (child->dev_type == EDGE_DEV ||
+		    child->dev_type == FANOUT_DEV)
+			sas_unregister_ex_tree(child);
+		else
+			sas_unregister_dev(child);
+	}
+	sas_unregister_dev(dev);
+}
+
+static void sas_unregister_devs_sas_addr(struct domain_device *parent,
+					 int phy_id)
+{
+	struct expander_device *ex_dev = &parent->ex_dev;
+	struct ex_phy *phy = &ex_dev->ex_phy[phy_id];
+	struct domain_device *child, *n;
+
+	list_for_each_entry_safe(child, n, &ex_dev->children, siblings) {
+		if (SAS_ADDR(child->sas_addr) ==
+		    SAS_ADDR(phy->attached_sas_addr)) {
+			if (child->dev_type == EDGE_DEV ||
+			    child->dev_type == FANOUT_DEV)
+				sas_unregister_ex_tree(child);
+			else
+				sas_unregister_dev(child);
+			break;
+		}
+	}
+	sas_disable_routing(parent, phy->attached_sas_addr);
+	memset(phy->attached_sas_addr, 0, SAS_ADDR_SIZE);
+}
+
+static int sas_discover_bfs_by_root_level(struct domain_device *root,
+					  const int level)
+{
+	struct expander_device *ex_root = &root->ex_dev;
+	struct domain_device *child;
+	int res = 0;
+
+	list_for_each_entry(child, &ex_root->children, siblings) {
+		if (child->dev_type == EDGE_DEV ||
+		    child->dev_type == FANOUT_DEV) {
+			struct expander_device *ex = &child->ex_dev;
+
+			if (level > ex->level)
+				res = sas_discover_bfs_by_root_level(child,
+								     level);
+			else if (level == ex->level)
+				res = sas_ex_discover_devices(child, -1);
+		}
+	}
+	return res;
+}
+
+static int sas_discover_bfs_by_root(struct domain_device *dev)
+{
+	int res;
+	int level = dev->ex_dev.level+1;
+
+	res = sas_ex_discover_devices(dev, -1);
+	if (res)
+		goto out;
+	do {
+		res = sas_discover_bfs_by_root_level(dev, level);
+		mb();
+		level += 1;
+	} while (level <= dev->port->disc.max_level);
+out:
+	return res;
+}
+
+static inline int sas_discover_new(struct domain_device *dev, int phy_id)
+{
+	struct ex_phy *ex_phy = &dev->ex_dev.ex_phy[phy_id];
+	struct domain_device *child;
+	int res;
+
+	SAS_DPRINTK("ex %016llx phy%d new device attached\n",
+		    SAS_ADDR(dev->sas_addr), phy_id);
+	res = sas_ex_phy_discover(dev, phy_id);
+	if (res)
+		goto out;
+	res = sas_ex_discover_devices(dev, phy_id);
+	if (res)
+		goto out;
+	list_for_each_entry(child, &dev->ex_dev.children, siblings) {
+		if (SAS_ADDR(child->sas_addr) ==
+		    SAS_ADDR(ex_phy->attached_sas_addr)) {
+			if (child->dev_type == EDGE_DEV ||
+			    child->dev_type == FANOUT_DEV)
+				res = sas_discover_bfs_by_root(child);
+			break;
+		}
+	}
+out:
+	return res;
+}
+
+static int sas_rediscover_dev(struct domain_device *dev, int phy_id)
+{
+	struct expander_device *ex = &dev->ex_dev;
+	struct ex_phy *phy = &ex->ex_phy[phy_id];
+	u8 attached_sas_addr[8];
+	int res;
+
+	res = sas_get_phy_attached_sas_addr(dev, phy_id, attached_sas_addr);
+	switch (res) {
+	case SMP_RESP_NO_PHY:
+		phy->phy_state = PHY_NOT_PRESENT;
+		sas_unregister_devs_sas_addr(dev, phy_id);
+		goto out; break;
+	case SMP_RESP_PHY_VACANT:
+		phy->phy_state = PHY_VACANT;
+		sas_unregister_devs_sas_addr(dev, phy_id);
+		goto out; break;
+	case SMP_RESP_FUNC_ACC:
+		break;
+	}
+
+	if (SAS_ADDR(attached_sas_addr) == 0) {
+		phy->phy_state = PHY_EMPTY;
+		sas_unregister_devs_sas_addr(dev, phy_id);
+	} else if (SAS_ADDR(attached_sas_addr) ==
+		   SAS_ADDR(phy->attached_sas_addr)) {
+		SAS_DPRINTK("ex %016llx phy 0x%x broadcast flutter\n",
+			    SAS_ADDR(dev->sas_addr), phy_id);
+	} else
+		res = sas_discover_new(dev, phy_id);
+out:
+	return res;
+}
+
+static int sas_rediscover(struct domain_device *dev, const int phy_id)
+{
+	struct expander_device *ex = &dev->ex_dev;
+	struct ex_phy *changed_phy = &ex->ex_phy[phy_id];
+	int res = 0;
+	int i;
+
+	SAS_DPRINTK("ex %016llx phy%d originated BROADCAST(CHANGE)\n",
+		    SAS_ADDR(dev->sas_addr), phy_id);
+
+	if (SAS_ADDR(changed_phy->attached_sas_addr) != 0) {
+		for (i = 0; i < ex->num_phys; i++) {
+			struct ex_phy *phy = &ex->ex_phy[i];
+
+			if (i == phy_id)
+				continue;
+			if (SAS_ADDR(phy->attached_sas_addr) ==
+			    SAS_ADDR(changed_phy->attached_sas_addr)) {
+				SAS_DPRINTK("phy%d part of wide port with "
+					    "phy%d\n", phy_id, i);
+				goto out;
+			}
+		}
+		res = sas_rediscover_dev(dev, phy_id);
+	} else
+		res = sas_discover_new(dev, phy_id);
+out:
+	return res;
+}
+
+/**
+ * sas_revalidate_domain -- revalidate the domain
+ * @port: port to the domain of interest
+ *
+ * NOTE: this process _must_ quit (return) as soon as any connection
+ * errors are encountered.  Connection recovery is done elsewhere.
+ * Discover process only interrogates devices in order to discover the
+ * domain.
+ */
+int sas_ex_revalidate_domain(struct domain_device *port_dev)
+{
+	int res;
+	struct domain_device *dev = NULL;
+
+	res = sas_find_bcast_dev(port_dev, &dev);
+	if (res)
+		goto out;
+	if (dev) {
+		struct expander_device *ex = &dev->ex_dev;
+		int i = 0, phy_id;
+
+		do {
+			phy_id = -1;
+			res = sas_find_bcast_phy(dev, &phy_id, i);
+			if (phy_id == -1)
+				break;
+			res = sas_rediscover(dev, phy_id);
+			i = phy_id + 1;
+		} while (i < ex->num_phys);
+	}
+out:
+	return res;
+}
+
+/* ---------- SMP portal ---------- */
+
+static ssize_t smp_portal_write(struct kobject *kobj, char *buf, loff_t offs,
+				size_t size)
+{
+	struct domain_device *dev = to_dom_device(kobj);
+	struct expander_device *ex = &dev->ex_dev;
+
+	if (offs != 0)
+		return -EFBIG;
+	else if (size == 0)
+		return 0;
+
+	down_interruptible(&ex->smp_sema);
+	if (ex->smp_req)
+		kfree(ex->smp_req);
+	ex->smp_req = kmalloc(size, GFP_USER);
+	if (!ex->smp_req)
+		return -ENOMEM;
+	memcpy(ex->smp_req, buf, size);
+	ex->smp_req_size = size;
+	ex->smp_portal_pid = current->pid;
+	up(&ex->smp_sema);
+
+	return size;
+}
+
+static ssize_t smp_portal_read(struct kobject *kobj, char *buf, loff_t offs,
+			       size_t size)
+{
+	struct domain_device *dev = to_dom_device(kobj);
+	struct expander_device *ex = &dev->ex_dev;
+	u8 *smp_resp;
+	int res = -EINVAL;
+
+	/* XXX: sysfs gives us an offset of 0x10 or 0x8 while in fact
+	 *  it should be 0.
+	 */
+
+	down_interruptible(&ex->smp_sema);
+	if (!ex->smp_req || ex->smp_portal_pid != current->pid)
+		goto out;
+
+	res = 0;
+	if (size == 0)
+		goto out;
+
+	res = -ENOMEM;
+	smp_resp = alloc_smp_resp(size);
+	if (!smp_resp)
+		goto out;
+	res = smp_execute_task(dev, ex->smp_req, ex->smp_req_size,
+			       smp_resp, size);
+	if (!res) {
+		memcpy(buf, smp_resp, size);
+		res = size;
+	}
+
+	kfree(smp_resp);
+out:
+	kfree(ex->smp_req);
+	ex->smp_req = NULL;
+	ex->smp_req_size = 0;
+	ex->smp_portal_pid = -1;
+	up(&ex->smp_sema);
+	return res;
+}
diff -puN /dev/null drivers/scsi/sas-class/sas_init.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ devel-akpm/drivers/scsi/sas-class/sas_init.c	2005-09-09 19:06:37.000000000 -0700
@@ -0,0 +1,229 @@
+/*
+ * Serial Attached SCSI (SAS) Transport Layer initialization
+ *
+ * Copyright (C) 2005 Adaptec, Inc.  All rights reserved.
+ * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
+ *
+ * This file is licensed under GPLv2.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * $Id: //depot/sas-class/sas_init.c#44 $
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <scsi/scsi_host.h>
+
+#include "sas_internal.h"
+#include <scsi/sas/sas_task.h>
+
+kmem_cache_t *sas_task_cache;
+
+/* ---------- HA events ---------- */
+
+void sas_hae_reset(struct sas_ha_struct *sas_ha)
+{
+	;
+}
+
+/* ---------- HA attributes ---------- */
+
+static ssize_t sas_ha_name_show(struct sas_ha_struct *sas_ha, char *buf)
+{
+	if (sas_ha->sas_ha_name)
+		return sprintf(buf, "%s\n", sas_ha->sas_ha_name);
+	return 0;
+}
+
+static ssize_t sas_ha_addr_show(struct sas_ha_struct *sas_ha, char *buf)
+{
+	return sprintf(buf, "%llx\n", SAS_ADDR(sas_ha->sas_addr));
+}
+
+/* ---------- SAS HA Class ---------- */
+
+#define to_sas_ha(_obj) container_of(to_kset(_obj),struct sas_ha_struct,ha_kset)
+#define to_ha_attr(_attr) container_of(_attr, struct ha_attribute, attr)
+
+struct ha_attribute {
+	struct attribute attr;
+	ssize_t (*show)(struct sas_ha_struct *sas_ha, char *);
+	ssize_t (*store)(struct sas_ha_struct *sas_ha,const char *,size_t len);
+};
+
+static ssize_t ha_show_attr(struct kobject *kobj,
+		     struct attribute *attr,
+		     char *page)
+{
+	ssize_t ret = 0;
+	struct sas_ha_struct *sas_ha = to_sas_ha(kobj);
+	struct ha_attribute *ha_attr = to_ha_attr(attr);
+
+	if (ha_attr->show)
+		ret = ha_attr->show(sas_ha, page);
+	return ret;
+}
+
+static struct ha_attribute ha_attrs[] = {
+	__ATTR(ha_name, 0444, sas_ha_name_show, NULL),
+	__ATTR(device_name, 0444, sas_ha_addr_show, NULL),
+	__ATTR_NULL,
+};
+
+static struct attribute *def_attrs[ARRAY_SIZE(ha_attrs)];
+
+static struct sysfs_ops ha_sysfs_ops = {
+	.show = ha_show_attr,
+};
+
+static struct kobj_type ha_ktype = {
+	.sysfs_ops = &ha_sysfs_ops,
+	.default_attrs = def_attrs,
+};
+
+/* This is our "root". */
+static struct kset sas_kset = {
+	.kobj = { .name = "sas" },
+	.ktype = &ha_ktype,	  /* children are of this type */
+};
+
+int sas_register_ha(struct sas_ha_struct *sas_ha)
+{
+	int i, error = 0;
+
+	spin_lock_init(&sas_ha->phy_port_lock);
+	sas_hash_addr(sas_ha->hashed_sas_addr, sas_ha->sas_addr);
+
+	if (sas_ha->lldd_queue_size == 0)
+		sas_ha->lldd_queue_size = 1;
+	else if (sas_ha->lldd_queue_size == -1)
+		sas_ha->lldd_queue_size = 128; /* Sanity */
+
+	error = sas_register_scsi_host(sas_ha);
+	if (error) {
+		printk(KERN_NOTICE "couldn't register scsi host\n");
+		return error;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(def_attrs)-1; i++)
+		def_attrs[i] = &ha_attrs[i].attr;
+	def_attrs[i] = NULL;
+
+	/* make sas/ appear */
+	sas_kset.kobj.parent = &sas_ha->core.shost->shost_gendev.kobj;
+	kset_register(&sas_kset);
+
+	/* make sas/ha/ appear */
+	kobject_set_name(&sas_ha->ha_kset.kobj, "%s", "ha");
+	sas_ha->ha_kset.kobj.kset = &sas_kset; /* parent */
+	sas_ha->ha_kset.kobj.ktype = sas_kset.ktype;
+	kset_register(&sas_ha->ha_kset);
+
+	error = sas_register_phys(sas_ha);
+	if (error) {
+		printk(KERN_NOTICE "couldn't register sas phys:%d\n", error);
+		goto Undo;
+	}
+
+	error = sas_register_ports(sas_ha);
+ 	if (error) {
+ 		printk(KERN_NOTICE "couldn't register sas ports:%d\n", error);
+ 		goto Undo_phys;
+ 	}
+
+	error = sas_start_event_thread(sas_ha);
+	if (error) {
+		printk(KERN_NOTICE "couldn't start event thread:%d\n", error);
+		goto Undo_ports;
+	}
+
+	if (sas_ha->lldd_max_execute_num > 1) {
+		error = sas_init_queue(sas_ha);
+		if (!error)
+			kobject_register(&sas_ha->core.scsi_core_obj);
+		else {
+			printk(KERN_NOTICE "couldn't start queue thread:%d, "
+			       "running in direct mode\n", error);
+			sas_ha->lldd_max_execute_num = 1;
+		}
+	}
+
+	return 0;
+
+Undo_ports:
+	sas_unregister_ports(sas_ha);
+Undo_phys:
+	sas_unregister_phys(sas_ha);
+Undo:
+	kset_unregister(&sas_ha->ha_kset);
+	kset_unregister(&sas_kset);
+	sas_unregister_scsi_host(sas_ha);
+
+	return error;
+}
+
+int sas_unregister_ha(struct sas_ha_struct *sas_ha)
+{
+	sas_unregister_devices(sas_ha);
+
+	if (sas_ha->lldd_max_execute_num > 1) {
+		kobject_unregister(&sas_ha->core.scsi_core_obj);
+		sas_shutdown_queue(sas_ha);
+	}
+
+	sas_kill_event_thread(sas_ha);
+
+	sas_unregister_ports(sas_ha);
+	sas_unregister_phys(sas_ha);
+
+	kset_unregister(&sas_ha->ha_kset);
+	kset_unregister(&sas_kset);
+
+	sas_unregister_scsi_host(sas_ha);
+
+	return 0;
+}
+
+/* ---------- SAS Class register/unregister ---------- */
+
+static int __init sas_class_init(void)
+{
+	sas_task_cache = kmem_cache_create("sas_task", sizeof(struct sas_task),
+					   0, SLAB_HWCACHE_ALIGN, NULL, NULL);
+	if (!sas_task_cache)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static void __exit sas_class_exit(void)
+{
+	if (sas_task_cache)
+		kmem_cache_destroy(sas_task_cache);
+}
+
+MODULE_AUTHOR("Luben Tuikov <luben_tuikov@adaptec.com>");
+MODULE_DESCRIPTION("SAS Transport Layer");
+MODULE_LICENSE("GPL v2");
+
+module_init(sas_class_init);
+module_exit(sas_class_exit);
+
+EXPORT_SYMBOL_GPL(sas_register_ha);
+EXPORT_SYMBOL_GPL(sas_unregister_ha);
diff -puN /dev/null drivers/scsi/sas-class/sas_internal.h
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ devel-akpm/drivers/scsi/sas-class/sas_internal.h	2005-09-09 19:06:37.000000000 -0700
@@ -0,0 +1,79 @@
+/*
+ * Serial Attached SCSI (SAS) class internal header file
+ *
+ * Copyright (C) 2005 Adaptec, Inc.  All rights reserved.
+ * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
+ *
+ * This file is licensed under GPLv2.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * $Id: //depot/sas-class/sas_internal.h#35 $
+ */
+
+#ifndef _SAS_INTERNAL_H_
+#define _SAS_INTERNAL_H_
+
+#include <scsi/sas/sas_class.h>
+
+#define sas_printk(fmt, ...) printk(KERN_NOTICE "sas: " fmt, ## __VA_ARGS__)
+
+#ifdef SAS_DEBUG
+#define SAS_DPRINTK(fmt, ...) printk(KERN_NOTICE "sas: " fmt, ## __VA_ARGS__)
+#else
+#define SAS_DPRINTK(fmt, ...)
+#endif
+
+int sas_show_class(enum sas_class class, char *buf);
+int sas_show_proto(enum sas_proto proto, char *buf);
+int sas_show_linkrate(enum sas_phy_linkrate linkrate, char *buf);
+int sas_show_oob_mode(enum sas_oob_mode oob_mode, char *buf);
+
+int  sas_register_phys(struct sas_ha_struct *sas_ha);
+void sas_unregister_phys(struct sas_ha_struct *sas_ha);
+
+int  sas_register_ports(struct sas_ha_struct *sas_ha);
+void sas_unregister_ports(struct sas_ha_struct *sas_ha);
+
+int  sas_register_scsi_host(struct sas_ha_struct *sas_ha);
+void sas_unregister_scsi_host(struct sas_ha_struct *sas_ha);
+
+int  sas_start_event_thread(struct sas_ha_struct *sas_ha);
+void sas_kill_event_thread(struct sas_ha_struct *sas_ha);
+
+int  sas_init_queue(struct sas_ha_struct *sas_ha);
+void sas_shutdown_queue(struct sas_ha_struct *sas_ha);
+
+void sas_phye_loss_of_signal(struct sas_phy *phy);
+void sas_phye_oob_done(struct sas_phy *phy);
+void sas_phye_oob_error(struct sas_phy *phy);
+void sas_phye_spinup_hold(struct sas_phy *phy);
+
+void sas_deform_port(struct sas_phy *phy);
+
+void sas_porte_bytes_dmaed(struct sas_phy *phy);
+void sas_porte_broadcast_rcvd(struct sas_phy *phy);
+void sas_porte_link_reset_err(struct sas_phy *phy);
+void sas_porte_timer_event(struct sas_phy *phy);
+void sas_porte_hard_reset(struct sas_phy *phy);
+
+int  sas_reserve_free_id(struct sas_port *port);
+void sas_reserve_scsi_id(struct sas_port *port, int id);
+void sas_release_scsi_id(struct sas_port *port, int id);
+
+void sas_hae_reset(struct sas_ha_struct *sas_ha);
+
+#endif /* _SAS_INTERNAL_H_ */
diff -puN /dev/null drivers/scsi/sas-class/sas_phy.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ devel-akpm/drivers/scsi/sas-class/sas_phy.c	2005-09-09 19:06:37.000000000 -0700
@@ -0,0 +1,307 @@
+/*
+ * Serial Attached SCSI (SAS) Phy class
+ *
+ * Copyright (C) 2005 Adaptec, Inc.  All rights reserved.
+ * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
+ *
+ * This file is licensed under GPLv2.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * $Id: //depot/sas-class/sas_phy.c#37 $
+ */
+
+#include "sas_internal.h"
+
+/* ---------- Phy events ---------- */
+
+void sas_phye_loss_of_signal(struct sas_phy *phy)
+{
+	phy->error = 0;
+	sas_deform_port(phy);
+}
+
+void sas_phye_oob_done(struct sas_phy *phy)
+{
+	phy->error = 0;
+}
+
+void sas_phye_oob_error(struct sas_phy *phy)
+{
+	struct sas_ha_struct *sas_ha = phy->ha;
+	struct sas_port *port = phy->port;
+
+	sas_deform_port(phy);
+
+	if (!port && phy->enabled && sas_ha->lldd_control_phy) {
+		phy->error++;
+		switch (phy->error) {
+		case 1:
+		case 2:
+			sas_ha->lldd_control_phy(phy, PHY_FUNC_HARD_RESET);
+			break;
+		case 3:
+		default:
+			phy->error = 0;
+			phy->enabled = 0;
+			sas_ha->lldd_control_phy(phy, PHY_FUNC_DISABLE);
+			break;
+		}
+	}
+}
+
+void sas_phye_spinup_hold(struct sas_phy *phy)
+{
+	struct sas_ha_struct *sas_ha = phy->ha;
+
+	phy->error = 0;
+	sas_ha->lldd_control_phy(phy, PHY_FUNC_RELEASE_SPINUP_HOLD);
+}
+
+/* ---------- Phy attributes ---------- */
+
+static ssize_t sas_phy_id_show(struct sas_phy *phy, char *buf)
+{
+	return sprintf(buf, "%d\n", phy->id);
+}
+
+static ssize_t sas_phy_enabled_show(struct sas_phy *phy, char *buf)
+{
+	return sprintf(buf, "%d\n", phy->enabled);
+}
+
+static ssize_t sas_phy_enabled_store(struct sas_phy *phy, const char *buf,
+				     size_t size)
+{
+	if (size > 0) {
+		if (buf[0] == '1')
+			phy->ha->lldd_control_phy(phy, PHY_FUNC_LINK_RESET);
+	}
+	return size;
+}
+
+static ssize_t sas_phy_class_show(struct sas_phy *phy, char *buf)
+{
+	if (!phy->enabled)
+		return 0;
+	return sas_show_class(phy->class, buf);
+}
+
+static ssize_t sas_phy_iproto_show(struct sas_phy *phy, char *page)
+{
+	if (!phy->enabled)
+		return 0;
+	return sas_show_proto(phy->iproto, page);
+}
+
+static ssize_t sas_phy_tproto_show(struct sas_phy *phy, char *page)
+{
+	if (!phy->enabled)
+		return 0;
+	return sas_show_proto(phy->tproto, page);
+}
+
+static ssize_t sas_phy_type_show(struct sas_phy *phy, char *buf)
+{
+	static const char *phy_type_str[] = {
+		[PHY_TYPE_PHYSICAL] = "physical",
+		[PHY_TYPE_VIRTUAL] = "virtual",
+	};
+	if (!phy->enabled)
+		return 0;
+	return sprintf(buf, "%s\n", phy_type_str[phy->type]);
+}
+
+static ssize_t sas_phy_role_show(struct sas_phy *phy, char *page)
+{
+	static const char *phy_role_str[] = {
+		[PHY_ROLE_NONE] = "none",
+		[PHY_ROLE_TARGET] = "target",
+		[PHY_ROLE_INITIATOR] = "initiator",
+	};
+	int  v;
+	char *buf = page;
+
+	if (!phy->enabled)
+		return 0;
+
+	if (phy->role == PHY_ROLE_NONE)
+		return sprintf(buf, "%s\n", phy_role_str[PHY_ROLE_NONE]);
+
+	for (v = 1; v <= PHY_ROLE_INITIATOR; v <<= 1) {
+		if (v & phy->role) {
+			buf += sprintf(buf, "%s", phy_role_str[v]);
+			if (phy->role & ~((v<<1)-1))
+				buf += sprintf(buf, "|");
+			else
+				buf += sprintf(buf, "\n");
+		}
+	}
+	return buf-page;
+}
+
+static ssize_t sas_phy_linkrate_show(struct sas_phy *phy, char *buf)
+{
+	if (!phy->enabled)
+		return 0;
+	return sas_show_linkrate(phy->linkrate, buf);
+}
+
+static ssize_t sas_phy_addr_show(struct sas_phy *phy, char *buf)
+{
+	if (!phy->enabled)
+		return 0;
+	return sprintf(buf, "%llx\n", SAS_ADDR(phy->sas_addr));
+}
+
+static ssize_t sas_phy_oob_mode_show(struct sas_phy *phy, char *buf)
+{
+	if (!phy->enabled)
+		return 0;
+	return sas_show_oob_mode(phy->oob_mode, buf);
+}
+
+struct phy_attribute {
+	struct attribute attr;
+	ssize_t (*show)(struct sas_phy *phy, char *);
+	ssize_t (*store)(struct sas_phy *phy, const char *, size_t);
+};
+
+static struct phy_attribute phy_attrs[] = {
+	/* port is a symlink */
+	__ATTR(id, 0444, sas_phy_id_show, NULL),
+	__ATTR(enabled, 0644, sas_phy_enabled_show, sas_phy_enabled_store),
+	__ATTR(class, 0444, sas_phy_class_show, NULL),
+	__ATTR(iproto, 0444, sas_phy_iproto_show, NULL),
+	__ATTR(tproto, 0444, sas_phy_tproto_show, NULL),
+	__ATTR(type, 0444, sas_phy_type_show, NULL),
+	__ATTR(role, 0444, sas_phy_role_show, NULL),
+	__ATTR(linkrate, 0444, sas_phy_linkrate_show, NULL),
+	__ATTR(sas_addr, 0444, sas_phy_addr_show, NULL),
+	__ATTR(oob_mode, 0444, sas_phy_oob_mode_show, NULL),
+	__ATTR_NULL,
+};
+
+static struct attribute *def_attrs[ARRAY_SIZE(phy_attrs)];
+
+#define to_sas_phy(_obj) container_of(_obj, struct sas_phy, phy_kobj)
+#define to_phy_attr(_attr) container_of(_attr, struct phy_attribute, attr)
+
+static ssize_t phy_show_attr(struct kobject *kobj,
+			     struct attribute *attr,
+			     char *page)
+{
+	ssize_t ret = 0;
+	struct sas_phy *phy = to_sas_phy(kobj);
+	struct phy_attribute *phy_attr = to_phy_attr(attr);
+
+	if (phy_attr->show)
+		ret = phy_attr->show(phy, page);
+	return ret;
+}
+
+static ssize_t phy_store_attr(struct kobject *kobj,
+			      struct attribute *attr,
+			      const char *page, size_t size)
+{
+	ssize_t ret = 0;
+	struct sas_phy *phy = to_sas_phy(kobj);
+	struct phy_attribute *phy_attr = to_phy_attr(attr);
+
+	if (phy_attr->store)
+		ret = phy_attr->store(phy, page, size);
+	return ret;
+}
+
+static struct sysfs_ops phy_sysfs_ops = {
+	.show = phy_show_attr,
+	.store = phy_store_attr,
+};
+
+static struct kobj_type phy_ktype = {
+	.sysfs_ops = &phy_sysfs_ops,
+	.default_attrs = def_attrs,
+};
+
+/* ---------- Phy class registration ---------- */
+
+int sas_register_phys(struct sas_ha_struct *sas_ha)
+{
+	int i, error;
+
+	for (i = 0; i < ARRAY_SIZE(def_attrs)-1; i++)
+		def_attrs[i] = &phy_attrs[i].attr;
+	def_attrs[i] = NULL;
+
+	/* make sas/ha/phys/ appear */
+	kobject_set_name(&sas_ha->phy_kset.kobj, "%s", "phys");
+	sas_ha->phy_kset.kobj.kset = &sas_ha->ha_kset; /* parent */
+	/* we do not inherit the type of the parent */
+	sas_ha->phy_kset.kobj.ktype = NULL;
+	sas_ha->phy_kset.ktype = &phy_ktype;
+	error = kset_register(&sas_ha->phy_kset);
+	if (error)
+		return error;
+
+	/* Now register the phys. */
+	for (i = 0; i < sas_ha->num_phys; i++) {
+		int k;
+		struct sas_phy *phy = sas_ha->sas_phy[i];
+
+		phy->error = 0;
+		INIT_LIST_HEAD(&phy->port_phy_el);
+		INIT_LIST_HEAD(&phy->port_event_list);
+		INIT_LIST_HEAD(&phy->phy_event_list);
+		for (k = 0; k < PORT_NUM_EVENTS; k++) {
+			struct sas_event *ev = &phy->port_events[k];
+			ev->event = k;
+			INIT_LIST_HEAD(&ev->el);
+		}
+		for (k = 0; k < PHY_NUM_EVENTS; k++) {
+			struct sas_event *ev = &phy->phy_events[k];
+			ev->event = k;
+			INIT_LIST_HEAD(&ev->el);
+		}
+		phy->port = NULL;
+		phy->ha = sas_ha;
+		spin_lock_init(&phy->frame_rcvd_lock);
+		spin_lock_init(&phy->sas_prim_lock);
+		phy->frame_rcvd_size = 0;
+
+		kobject_set_name(&phy->phy_kobj, "%d", i);
+		phy->phy_kobj.kset = &sas_ha->phy_kset; /* parent */
+		phy->phy_kobj.ktype = sas_ha->phy_kset.ktype;
+		error = kobject_register(&phy->phy_kobj);
+		if (error)
+			goto unroll;
+	}
+
+	return 0;
+unroll:
+	for (i--; i >= 0; i--)
+		kobject_unregister(&sas_ha->sas_phy[i]->phy_kobj);
+
+	return error;
+}
+
+void sas_unregister_phys(struct sas_ha_struct *sas_ha)
+{
+	int i;
+
+	for (i = 0; i < sas_ha->num_phys; i++)
+		kobject_unregister(&sas_ha->sas_phy[i]->phy_kobj);
+
+	kset_unregister(&sas_ha->phy_kset);
+}
diff -puN /dev/null drivers/scsi/sas-class/sas_port.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ devel-akpm/drivers/scsi/sas-class/sas_port.c	2005-09-09 19:06:37.000000000 -0700
@@ -0,0 +1,430 @@
+/*
+ * Serial Attached SCSI (SAS) Port class
+ *
+ * Copyright (C) 2005 Adaptec, Inc.  All rights reserved.
+ * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
+ *
+ * This file is licensed under GPLv2.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * $Id: //depot/sas-class/sas_port.c#41 $
+ */
+
+#include "sas_internal.h"
+#include <scsi/sas/sas_discover.h>
+
+/* called only when num_phys increments, afterwards */
+static void sas_create_port_sysfs_links(struct sas_phy *phy)
+{
+	struct sas_port *port = phy->port;
+
+	if (port->num_phys == 1) {
+		kobject_register(&port->port_kobj);
+		kset_register(&port->phy_kset);
+		kset_register(&port->dev_kset);
+	}
+	/* add port->phy link */
+	sysfs_create_link(&port->phy_kset.kobj, &phy->phy_kobj,
+			  kobject_name(&phy->phy_kobj));
+	/* add phy->port link */
+	sysfs_create_link(&phy->phy_kobj, &port->port_kobj, "port");
+}
+
+/* called only when num_phys decrements, just before it does */
+static void sas_remove_port_sysfs_links(struct sas_phy *phy)
+{
+	struct sas_port *port = phy->port;
+
+	/* remove phy->port link */
+	sysfs_remove_link(&phy->phy_kobj, "port");
+	/* remove port to phy link */
+	sysfs_remove_link(&port->phy_kset.kobj, kobject_name(&phy->phy_kobj));
+
+	if (port->num_phys == 1) {
+		kset_unregister(&port->dev_kset);
+		kset_unregister(&port->phy_kset);
+		kobject_unregister(&port->port_kobj);
+	}
+}
+
+/**
+ * sas_form_port -- add this phy to a port
+ * @phy: the phy of interest
+ *
+ * This function adds this phy to an existing port, thus creating a wide
+ * port, or it creates a port and adds the phy to the port.
+ */
+static void sas_form_port(struct sas_phy *phy)
+{
+	int i;
+	struct sas_ha_struct *sas_ha = phy->ha;
+	struct sas_port *port = phy->port;
+
+	if (port) {
+		if (memcmp(port->attached_sas_addr, phy->attached_sas_addr,
+			   SAS_ADDR_SIZE) == 0)
+			sas_deform_port(phy);
+		else {
+			SAS_DPRINTK("%s: phy%d belongs to port%d already(%d)!\n",
+				    __FUNCTION__, phy->id, phy->port->id,
+				    phy->port->num_phys);
+			return;
+		}
+	}
+
+	/* find a port */
+	spin_lock(&sas_ha->phy_port_lock);
+	for (i = 0; i < sas_ha->num_phys; i++) {
+		port = sas_ha->sas_port[i];
+		spin_lock(&port->phy_list_lock);
+		if (*(u64 *) port->sas_addr &&
+		    memcmp(port->attached_sas_addr,
+			   phy->attached_sas_addr, SAS_ADDR_SIZE) == 0 &&
+		    port->num_phys > 0) {
+			/* wide port */
+			SAS_DPRINTK("phy%d matched wide port%d\n", phy->id,
+				    port->id);
+			break;
+		} else if (*(u64 *) port->sas_addr == 0 && port->num_phys==0) {
+			memcpy(port->sas_addr, phy->sas_addr, SAS_ADDR_SIZE);
+			break;
+		}
+		spin_unlock(&port->phy_list_lock);
+	}
+
+	if (i >= sas_ha->num_phys) {
+		printk(KERN_NOTICE "%s: couldn't find a free port, bug?\n",
+		       __FUNCTION__);
+		spin_unlock(&sas_ha->phy_port_lock);
+		return;
+	}
+
+	/* add the phy to the port */
+	list_add_tail(&phy->port_phy_el, &port->phy_list);
+	phy->port = port;
+	port->num_phys++;
+	port->phy_mask |= (1U << phy->id);
+
+	SAS_DPRINTK("phy%d added to port%d, phy_mask:0x%x\n", phy->id,
+		    port->id, port->phy_mask);
+
+	if (*(u64 *)port->attached_sas_addr == 0) {
+		port->class = phy->class;
+		memcpy(port->attached_sas_addr, phy->attached_sas_addr,
+		       SAS_ADDR_SIZE);
+		port->iproto = phy->iproto;
+		port->tproto = phy->tproto;
+		port->oob_mode = phy->oob_mode;
+		port->linkrate = phy->linkrate;
+	} else
+		port->linkrate = max(port->linkrate, phy->linkrate);
+	spin_unlock(&port->phy_list_lock);
+	spin_unlock(&sas_ha->phy_port_lock);
+
+	if (port->port_dev)
+		port->port_dev->pathways = port->num_phys;
+
+	sas_create_port_sysfs_links(phy);
+	/* Tell the LLDD about this port formation. */
+	if (sas_ha->lldd_port_formed)
+		sas_ha->lldd_port_formed(phy);
+
+	sas_discover_event(phy->port, DISCE_DISCOVER_DOMAIN);
+}
+
+/**
+ * sas_deform_port -- remove this phy from the port it belongs to
+ * @phy: the phy of interest
+ *
+ * This is called when the physical link to the other phy has been
+ * lost (on this phy), in Event thread context. We cannot delay here.
+ */
+void sas_deform_port(struct sas_phy *phy)
+{
+	struct sas_ha_struct *sas_ha = phy->ha;
+	struct sas_port *port = phy->port;
+
+	if (!port)
+		return;		  /* done by a phy event */
+
+	if (port->port_dev)
+		port->port_dev->pathways--;
+
+	if (port->num_phys == 1) {
+		init_completion(&port->port_gone_completion);
+		sas_discover_event(port, DISCE_PORT_GONE);
+		wait_for_completion(&port->port_gone_completion);
+	}
+
+	if (sas_ha->lldd_port_deformed)
+		sas_ha->lldd_port_deformed(phy);
+
+	sas_remove_port_sysfs_links(phy);
+
+	spin_lock(&sas_ha->phy_port_lock);
+	spin_lock(&port->phy_list_lock);
+
+	list_del_init(&phy->port_phy_el);
+	phy->port = NULL;
+	port->num_phys--;
+	port->phy_mask &= ~(1U << phy->id);
+
+	if (port->num_phys == 0) {
+		INIT_LIST_HEAD(&port->phy_list);
+		memset(port->sas_addr, 0, SAS_ADDR_SIZE);
+		memset(port->attached_sas_addr, 0, SAS_ADDR_SIZE);
+		port->class = 0;
+		port->iproto = 0;
+		port->tproto = 0;
+		port->oob_mode = 0;
+		port->phy_mask = 0;
+	}
+	spin_unlock(&port->phy_list_lock);
+	spin_unlock(&sas_ha->phy_port_lock);
+
+	return;
+}
+
+/* ---------- SAS port events ---------- */
+
+void sas_porte_bytes_dmaed(struct sas_phy *phy)
+{
+	sas_form_port(phy);
+}
+
+void sas_porte_broadcast_rcvd(struct sas_phy *phy)
+{
+	unsigned long flags;
+	u32 prim;
+
+	spin_lock_irqsave(&phy->sas_prim_lock, flags);
+	prim = phy->sas_prim;
+	spin_unlock_irqrestore(&phy->sas_prim_lock, flags);
+
+	SAS_DPRINTK("broadcast received: %d\n", prim);
+	sas_discover_event(phy->port, DISCE_REVALIDATE_DOMAIN);
+}
+
+void sas_porte_link_reset_err(struct sas_phy *phy)
+{
+	sas_deform_port(phy);
+}
+
+void sas_porte_timer_event(struct sas_phy *phy)
+{
+	sas_deform_port(phy);
+}
+
+void sas_porte_hard_reset(struct sas_phy *phy)
+{
+	sas_deform_port(phy);
+}
+
+/* ---------- SAS port attributes ---------- */
+
+static ssize_t sas_port_id_show(struct sas_port *port, char *buf)
+{
+	return sprintf(buf, "%d\n", port->id);
+}
+
+static ssize_t sas_port_class_show(struct sas_port *port, char *buf)
+{
+	return sas_show_class(port->class, buf);
+}
+
+static ssize_t sas_port_sas_addr_show(struct sas_port *port, char *buf)
+{
+	return sprintf(buf, "%llx\n", SAS_ADDR(port->sas_addr));
+}
+
+static ssize_t sas_port_attached_sas_addr_show(struct sas_port *port,char *buf)
+{
+	return sprintf(buf, "%llx\n", SAS_ADDR(port->attached_sas_addr));
+}
+
+static ssize_t sas_port_iproto_show(struct sas_port *port, char *buf)
+{
+	return sas_show_proto(port->iproto, buf);
+}
+
+static ssize_t sas_port_tproto_show(struct sas_port *port, char *buf)
+{
+	return sas_show_proto(port->tproto, buf);
+}
+
+static ssize_t sas_port_oob_mode_show(struct sas_port *port, char *buf)
+{
+	return sas_show_oob_mode(port->oob_mode, buf);
+}
+
+struct port_attribute {
+	struct attribute attr;
+	ssize_t (*show)(struct sas_port *port, char *);
+	ssize_t (*store)(struct sas_port *port, const char *, size_t);
+};
+
+static struct port_attribute port_attrs[] = {
+	__ATTR(id, 0444, sas_port_id_show, NULL),
+	__ATTR(class, 0444, sas_port_class_show, NULL),
+	__ATTR(port_identifier, 0444, sas_port_sas_addr_show, NULL),
+	__ATTR(attached_port_identifier, 0444, sas_port_attached_sas_addr_show, NULL),
+	__ATTR(iproto, 0444, sas_port_iproto_show, NULL),
+	__ATTR(tproto, 0444, sas_port_tproto_show, NULL),
+	__ATTR(oob_mode, 0444, sas_port_oob_mode_show, NULL),
+	__ATTR_NULL,
+};
+
+static struct attribute *def_attrs[ARRAY_SIZE(port_attrs)];
+
+#define to_sas_port(_obj) container_of(_obj, struct sas_port, port_kobj)
+#define to_port_attr(_attr) container_of(_attr, struct port_attribute, attr)
+
+static ssize_t port_show_attr(struct kobject *kobj, struct attribute *attr,
+			      char *page)
+{
+	ssize_t ret = 0;
+	struct sas_port *port = to_sas_port(kobj);
+	struct port_attribute *port_attr = to_port_attr(attr);
+
+	if (port_attr->show)
+		ret = port_attr->show(port, page);
+	return ret;
+}
+
+static struct sysfs_ops port_sysfs_ops = {
+	.show = port_show_attr,
+};
+
+static struct kobj_type port_type = {
+	.sysfs_ops = &port_sysfs_ops,
+	.default_attrs = def_attrs,
+};
+
+/* ---------- SAS port registration ---------- */
+
+static void sas_init_port(struct sas_port *port,
+			  struct sas_ha_struct *sas_ha, int i,
+			  struct kset *parent_kset)
+{
+	port->id = i;
+	INIT_LIST_HEAD(&port->dev_list);
+	spin_lock_init(&port->phy_list_lock);
+	INIT_LIST_HEAD(&port->phy_list);
+	port->num_phys = 0;
+	port->phy_mask = 0;
+	port->ha = sas_ha;
+
+	memset(&port->port_kobj, 0, sizeof(port->port_kobj));
+	memset(&port->phy_kset, 0, sizeof(port->phy_kset));
+	memset(&port->dev_kset, 0, sizeof(port->dev_kset));
+
+	kobject_set_name(&port->port_kobj, "%d", port->id);
+	port->port_kobj.kset = parent_kset;
+	port->port_kobj.ktype= parent_kset->ktype;
+
+	kobject_set_name(&port->phy_kset.kobj, "%s", "phys");
+	port->phy_kset.kobj.parent = &port->port_kobj;
+	port->phy_kset.ktype = NULL;
+
+	kobject_set_name(&port->dev_kset.kobj, "%s", "domain");
+	port->dev_kset.kobj.parent = &port->port_kobj;
+	port->dev_kset.ktype = NULL;
+
+	port->id_map.max_ids = 128;
+	port->id_map.id_bitmap_size =
+		BITS_TO_LONGS(port->id_map.max_ids)*sizeof(long);
+	port->id_map.id_bitmap = kmalloc(port->id_map.id_bitmap_size,
+					 GFP_KERNEL);
+	memset(port->id_map.id_bitmap, 0, port->id_map.id_bitmap_size);
+	spin_lock_init(&port->id_map.id_bitmap_lock);
+}
+
+int sas_register_ports(struct sas_ha_struct *sas_ha)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(def_attrs)-1; i++)
+		def_attrs[i] = &port_attrs[i].attr;
+	def_attrs[i] = NULL;
+
+	/* make sas/ha/ports/ appear */
+	kobject_set_name(&sas_ha->port_kset.kobj, "%s", "ports");
+	sas_ha->port_kset.kobj.kset = &sas_ha->ha_kset; /* parent */
+	/* no type inheritance */
+	sas_ha->port_kset.kobj.ktype = NULL;
+	sas_ha->port_kset.ktype = &port_type; /* children are of this type */
+
+	/* initialize the ports and discovery */
+	for (i = 0; i < sas_ha->num_phys; i++) {
+		struct sas_port *port = sas_ha->sas_port[i];
+
+		sas_init_port(port, sas_ha, i, &sas_ha->port_kset);
+		sas_init_disc(&port->disc, port);
+	}
+
+	return kset_register(&sas_ha->port_kset);
+}
+
+void sas_unregister_ports(struct sas_ha_struct *sas_ha)
+{
+	int i;
+
+	for (i = 0; i < sas_ha->num_phys; i++)
+		if (sas_ha->sas_phy[i]->port)
+			sas_deform_port(sas_ha->sas_phy[i]);
+
+	for (i = 0; i < sas_ha->num_phys; i++) {
+		kfree(sas_ha->sas_port[i]->id_map.id_bitmap);
+		sas_ha->sas_port[i]->id_map.id_bitmap = NULL;
+	}
+
+	kset_unregister(&sas_ha->port_kset);
+}
+
+int sas_reserve_free_id(struct sas_port *port)
+{
+	int id;
+
+	spin_lock(&port->id_map.id_bitmap_lock);
+	id = find_first_zero_bit(port->id_map.id_bitmap, port->id_map.max_ids);
+	if (id >= port->id_map.max_ids) {
+		id = -ENOMEM;
+		spin_unlock(&port->id_map.id_bitmap_lock);
+		goto out;
+	}
+	set_bit(id, port->id_map.id_bitmap);
+	spin_unlock(&port->id_map.id_bitmap_lock);
+out:
+	return id;
+}
+
+void sas_reserve_scsi_id(struct sas_port *port, int id)
+{
+	if (0 > id || id >= port->id_map.max_ids)
+		return;
+	spin_lock(&port->id_map.id_bitmap_lock);
+	set_bit(id, port->id_map.id_bitmap);
+	spin_unlock(&port->id_map.id_bitmap_lock);
+}
+
+void sas_release_scsi_id(struct sas_port *port, int id)
+{
+	if (0 > id || id >= port->id_map.max_ids)
+		return;
+	spin_lock(&port->id_map.id_bitmap_lock);
+	clear_bit(id, port->id_map.id_bitmap);
+	spin_unlock(&port->id_map.id_bitmap_lock);
+}
diff -puN /dev/null drivers/scsi/sas-class/sas_scsi_host.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ devel-akpm/drivers/scsi/sas-class/sas_scsi_host.c	2005-09-09 19:06:37.000000000 -0700
@@ -0,0 +1,991 @@
+/*
+ * Serial Attached SCSI (SAS) class SCSI Host glue.
+ *
+ * Copyright (C) 2005 Adaptec, Inc.  All rights reserved.
+ * Copyright (C) 2005 Luben Tuikov <luben_tuikov@adaptec.com>
+ *
+ * This file is licensed under GPLv2.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ *
+ * $Id: //depot/sas-class/sas_scsi_host.c#55 $
+ */
+
+#include "sas_internal.h"
+#include <scsi/sas/sas_discover.h>
+#include <scsi/sas/sas_task.h>
+
+#include <scsi/scsi_host.h>
+#include <scsi/scsi_device.h>
+#include <scsi/scsi_tcq.h>
+#include <scsi/scsi.h>
+
+#include <linux/err.h>
+#include <linux/blkdev.h>
+#include <linux/kobject.h>
+#include <linux/scatterlist.h>
+
+/* The SAM LUN structure should be _completely_ opaque to SCSI Core.
+ * This is why this macro here, and not using the broken
+ * scsilun_to_int().  Ideally, a SCSI LUN should be communicated in
+ * its entirety, and not as an integer.  For some unknown to myself
+ * reason, SCSI Core thinks that SCSI LUNs can be interpreted as
+ * integers.
+ */
+#define SCSI_LUN(_sam_lun)   ((unsigned int)be32_to_cpu(*(__be32 *)_sam_lun))
+
+/* ---------- SCSI Core device registration ---------- */
+
+int  sas_register_with_scsi(struct LU *lu)
+{
+	struct scsi_device *scsi_dev;
+
+	lu->map.channel = lu->parent->port->id;
+	lu->map.id      = sas_reserve_free_id(lu->parent->port);
+	if (lu->map.id == -ENOMEM)
+		return -ENOMEM;
+
+	scsi_dev = scsi_add_device(lu->parent->port->ha->core.shost,
+				   lu->map.channel, lu->map.id,
+				   SCSI_LUN(lu->LUN));
+	if (IS_ERR(scsi_dev))
+		return PTR_ERR(scsi_dev);
+
+	return 0;
+}
+
+void sas_unregister_with_scsi(struct LU *lu)
+{
+	if (lu->uldd_dev) {
+		struct scsi_device *scsi_dev = lu->uldd_dev;
+		scsi_remove_device(scsi_dev);
+	}
+}
+
+/* ---------- SCSI Host glue ---------- */
+
+#define TO_SAS_TASK(_scsi_cmd)  ((void *)(_scsi_cmd)->host_scribble)
+#define ASSIGN_SAS_TASK(_sc, _t) do { (_sc)->host_scribble = (void *) _t; } while (0)
+
+static void sas_scsi_task_done(struct sas_task *task)
+{
+	struct task_status_struct *ts = &task->task_status;
+	struct scsi_cmnd *sc = task->uldd_task;
+	unsigned ts_flags = task->task_state_flags;
+	int hs = 0, stat = 0;
+
+	if (unlikely(!sc)) {
+		SAS_DPRINTK("task_done called with non existing SCSI cmnd!\n");
+		list_del_init(&task->list);
+		sas_free_task(task);
+		return;
+	}
+
+	if (ts->resp == SAS_TASK_UNDELIVERED) {
+		/* transport error */
+		hs = DID_NO_CONNECT;
+	} else { /* ts->resp == SAS_TASK_COMPLETE */
+		/* task delivered, what happened afterwards? */
+		switch (ts->stat) {
+		case SAS_DEV_NO_RESPONSE:
+		case SAS_INTERRUPTED:
+		case SAS_PHY_DOWN:
+		case SAS_NAK_R_ERR:
+		case SAS_OPEN_TO:
+			hs = DID_NO_CONNECT;
+			break;
+		case SAS_DATA_UNDERRUN:
+			sc->resid = ts->residual;
+			if (sc->request_bufflen - sc->resid < sc->underflow)
+				hs = DID_ERROR;
+			break;
+		case SAS_DATA_OVERRUN:
+			hs = DID_ERROR;
+			break;
+		case SAS_QUEUE_FULL:
+			hs = DID_SOFT_ERROR; /* retry */
+			break;
+		case SAS_DEVICE_UNKNOWN:
+			hs = DID_BAD_TARGET;
+			break;
+		case SAS_SG_ERR:
+			hs = DID_PARITY;
+			break;
+		case SAS_OPEN_REJECT:
+			if (ts->open_rej_reason == SAS_OREJ_RSVD_RETRY)
+				hs = DID_SOFT_ERROR; /* retry */
+			else
+				hs = DID_ERROR;
+			break;
+		case SAS_PROTO_RESPONSE:
+			SAS_DPRINTK("LLDD:%s sent SAS_PROTO_RESP for an SSP "
+				    "task; please report this\n",
+				    task->dev->port->ha->sas_ha_name);
+			break;
+		case SAS_ABORTED_TASK:
+			hs = DID_ABORT;
+			break;
+		case SAM_CHECK_COND:
+			memcpy(sc->sense_buffer, ts->buf,
+			       max(SCSI_SENSE_BUFFERSIZE, ts->buf_valid_size));
+			stat = SAM_CHECK_COND;
+			break;
+		default:
+			stat = ts->stat;
+			break;
+		}
+	}
+	ASSIGN_SAS_TASK(sc, NULL);
+	sc->result = (hs << 16) | stat;
+	list_del_init(&task->list);
+	sas_free_task(task);
+	/* This is very ugly but this is how SCSI Core works. */
+	if (ts_flags & SAS_TASK_STATE_ABORTED)
+		scsi_finish_command(sc);
+	else
+		sc->scsi_done(sc);
+}
+
+static inline enum task_attribute sas_scsi_get_task_attr(struct scsi_cmnd *cmd)
+{
+	enum task_attribute ta = TASK_ATTR_SIMPLE;
+	if (cmd->request && blk_rq_tagged(cmd->request)) {
+		if (cmd->device->ordered_tags &&
+		    (cmd->request->flags & REQ_HARDBARRIER))
+			ta = TASK_ATTR_HOQ;
+	}
+	return ta;
+}
+
+static inline struct sas_task *sas_create_task(struct scsi_cmnd *cmd,
+					       struct LU *lu,
+					       unsigned long gfp_flags)
+{
+	struct sas_task *task = sas_alloc_task(gfp_flags);
+
+	if (!task)
+		return NULL;
+
+	*(u32 *)cmd->sense_buffer = 0;
+	task->uldd_task = cmd;
+	ASSIGN_SAS_TASK(cmd, task);
+
+	task->dev = lu->parent;
+	task->task_proto = task->dev->tproto; /* BUG_ON(!SSP) */
+
+	task->ssp_task.retry_count = 1;
+	memcpy(task->ssp_task.LUN, lu->LUN, 8);
+	task->ssp_task.task_attr = sas_scsi_get_task_attr(cmd);
+	memcpy(task->ssp_task.cdb, cmd->cmnd, 16);
+
+	task->scatter = cmd->request_buffer;
+	task->num_scatter = cmd->use_sg;
+	task->total_xfer_len = cmd->request_bufflen;
+	task->data_dir = cmd->sc_data_direction;
+
+	task->task_done = sas_scsi_task_done;
+
+	return task;
+}
+
+static inline int sas_queue_up(struct sas_task *task)
+{
+	struct sas_ha_struct *sas_ha = task->dev->port->ha;
+	struct scsi_core *core = &sas_ha->core;
+	unsigned long flags;
+	LIST_HEAD(list);
+
+	spin_lock_irqsave(&core->task_queue_lock, flags);
+	if (sas_ha->lldd_queue_size < core->task_queue_size + 1) {
+		spin_unlock_irqrestore(&core->task_queue_lock, flags);
+		return -SAS_QUEUE_FULL;
+	}
+	list_add_tail(&task->list, &core->task_queue);
+	core->task_queue_size += 1;
+	spin_unlock_irqrestore(&core->task_queue_lock, flags);
+	up(&core->queue_thread_sema);
+
+	return 0;
+}
+
+/**
+ * sas_queuecommand -- Enqueue a command for processing
+ * @parameters: See SCSI Core documentation
+ *
+ * Note: XXX: Remove the host unlock/lock pair when SCSI Core can
+ * call us without holding an IRQ spinlock...
+ */
+static int sas_queuecommand(struct scsi_cmnd *cmd,
+			    void (*scsi_done)(struct scsi_cmnd *))
+{
+	int res = 0;
+	struct LU *lu = cmd->device->hostdata;
+	struct Scsi_Host *host = cmd->device->host;
+
+	spin_unlock_irq(host->host_lock);
+	if (!lu) {
+		SAS_DPRINTK("scsi cmd 0x%p sent to non existing LU\n",
+			    cmd);
+		cmd->result = DID_BAD_TARGET << 16;
+		scsi_done(cmd);
+		goto out;
+	} else {
+		struct sas_ha_struct *sas_ha = lu->parent->port->ha;
+		struct sas_task *task;
+
+		res = -ENOMEM;
+		task = sas_create_task(cmd, lu, GFP_ATOMIC);
+		if (!task)
+			goto out;
+
+		cmd->scsi_done = scsi_done;
+		/* Queue up, Direct Mode or Task Collector Mode. */
+		if (sas_ha->lldd_max_execute_num < 2)
+			res = sas_ha->lldd_execute_task(task, 1, GFP_ATOMIC);
+		else
+			res = sas_queue_up(task);
+
+		/* Examine */
+		if (res) {
+			SAS_DPRINTK("lldd_execute_task returned: %d\n", res);
+			ASSIGN_SAS_TASK(cmd, NULL);
+			sas_free_task(task);
+			if (res == -SAS_QUEUE_FULL) {
+				cmd->result = DID_SOFT_ERROR << 16; /* retry */
+				res = 0;
+				scsi_done(cmd);
+			}
+			goto out;
+		}
+	}
+out:
+	spin_lock_irq(host->host_lock);
+	return res;
+}
+
+static void sas_scsi_clear_queue_lu(struct list_head *error_q, struct LU *lu)
+{
+	struct scsi_cmnd *cmd, *n;
+
+	list_for_each_entry_safe(cmd, n, error_q, eh_entry) {
+		struct LU *x = cmd->device->hostdata;
+
+		if (x == lu)
+			list_del_init(&cmd->eh_entry);
+	}
+}
+
+static void sas_scsi_clear_queue_I_T(struct list_head *error_q,
+				     struct domain_device *dev)
+{
+	struct scsi_cmnd *cmd, *n;
+
+	list_for_each_entry_safe(cmd, n, error_q, eh_entry) {
+		struct LU *y = cmd->device->hostdata;
+		struct domain_device *x = y->parent;
+
+		if (x == dev)
+			list_del_init(&cmd->eh_entry);
+	}
+}
+
+static void sas_scsi_clear_queue_port(struct list_head *error_q,
+				      struct sas_port *port)
+{
+	struct scsi_cmnd *cmd, *n;
+
+	list_for_each_entry_safe(cmd, n, error_q, eh_entry) {
+		struct LU *y = cmd->device->hostdata;
+		struct sas_port *x = y->parent->port;
+
+		if (x == port)
+			list_del_init(&cmd->eh_entry);
+	}
+}
+
+enum task_disposition {
+	TASK_IS_DONE,
+	TASK_IS_ABORTED,
+	TASK_IS_AT_LU,
+	TASK_IS_NOT_AT_LU,
+};
+
+static enum task_disposition sas_scsi_find_task(struct sas_task *task)
+{
+	struct sas_ha_struct *ha = task->dev->port->ha;
+	unsigned long flags;
+	int i, res;
+
+	if (ha->lldd_max_execute_num > 1) {
+		struct scsi_core *core = &ha->core;
+		struct sas_task *t, *n;
+
+		spin_lock_irqsave(&core->task_queue_lock, flags);
+		list_for_each_entry_safe(t, n, &core->task_queue, list) {
+			if (task == t) {
+				list_del_init(&t->list);
+				spin_unlock_irqrestore(&core->task_queue_lock,
+						       flags);
+				SAS_DPRINTK("%s: task 0x%p aborted from "
+					    "task_queue\n",
+					    __FUNCTION__, task);
+				return TASK_IS_ABORTED;
+			}
+		}
+		spin_unlock_irqrestore(&core->task_queue_lock, flags);
+	}
+
+	for (i = 0; i < 5; i++) {
+		SAS_DPRINTK("%s: aborting task 0x%p\n", __FUNCTION__, task);
+		res = task->dev->port->ha->lldd_abort_task(task);
+
+		spin_lock_irqsave(&task->task_state_lock, flags);
+		if (task->task_state_flags & SAS_TASK_STATE_DONE) {
+			spin_unlock_irqrestore(&task->task_state_lock, flags);
+			SAS_DPRINTK("%s: task 0x%p is done\n", __FUNCTION__,
+				    task);
+			return TASK_IS_DONE;
+		}
+		spin_unlock_irqrestore(&task->task_state_lock, flags);
+
+		if (res == TMF_RESP_FUNC_COMPLETE) {
+			SAS_DPRINTK("%s: task 0x%p is aborted\n",
+				    __FUNCTION__, task);
+			return TASK_IS_ABORTED;
+		} else if (ha->lldd_query_task) {
+			SAS_DPRINTK("%s: querying task 0x%p\n",
+				    __FUNCTION__, task);
+			res = ha->lldd_query_task(task);
+			if (res == TMF_RESP_FUNC_SUCC) {
+				SAS_DPRINTK("%s: task 0x%p at LU\n",
+					    __FUNCTION__, task);
+				return TASK_IS_AT_LU;
+			} else if (res == TMF_RESP_FUNC_COMPLETE) {
+				SAS_DPRINTK("%s: task 0x%p not at LU\n",
+					    __FUNCTION__, task);
+				return TASK_IS_NOT_AT_LU;
+			}
+		}
+	}
+	return res;
+}
+
+static int sas_recover_lu(struct domain_device *dev, struct LU *lu)
+{
+	struct sas_ha_struct *ha = dev->port->ha;
+	int res = TMF_RESP_FUNC_FAILED;
+
+	SAS_DPRINTK("eh: device %llx LUN %llx has the task\n",
+		    SAS_ADDR(dev->sas_addr),
+		    SAS_ADDR(lu->LUN));
+
+	if (ha->lldd_abort_task_set)
+		res = ha->lldd_abort_task_set(dev, lu->LUN);
+
+	if (res == TMF_RESP_FUNC_FAILED) {
+		if (ha->lldd_clear_task_set)
+			res = ha->lldd_clear_task_set(dev, lu->LUN);
+	}
+
+	if (res == TMF_RESP_FUNC_FAILED) {
+		if (ha->lldd_lu_reset)
+			res = ha->lldd_lu_reset(dev, lu->LUN);
+	}
+
+	return res;
+}
+
+static int sas_recover_I_T(struct domain_device *dev)
+{
+	struct sas_ha_struct *ha = dev->port->ha;
+	int res = TMF_RESP_FUNC_FAILED;
+
+	SAS_DPRINTK("I_T nexus reset for dev %016llx\n",
+		    SAS_ADDR(dev->sas_addr));
+
+	if (ha->lldd_I_T_nexus_reset)
+		res = ha->lldd_I_T_nexus_reset(dev);
+
+	return res;
+}
+
+static int sas_scsi_recover_host(struct Scsi_Host *shost)
+{
+	struct sas_ha_struct *ha = SHOST_TO_SAS_HA(shost);
+	unsigned long flags;
+	LIST_HEAD(error_q);
+	struct scsi_cmnd *cmd, *n;
+	enum task_disposition res = TASK_IS_DONE;
+	int tmf_resp;
+
+	spin_lock_irqsave(shost->host_lock, flags);
+	list_splice_init(&shost->eh_cmd_q, &error_q);
+	spin_unlock_irqrestore(shost->host_lock, flags);
+
+	SAS_DPRINTK("Enter %s\n", __FUNCTION__);
+
+	/* All tasks on this list were marked SAS_TASK_STATE_ABORTED
+	 * by sas_scsi_timed_out() callback.
+	 */
+Again:
+	SAS_DPRINTK("going over list...\n");
+	list_for_each_entry_safe(cmd, n, &error_q, eh_entry) {
+		struct sas_task *task = TO_SAS_TASK(cmd);
+		struct LU *lu = cmd->device->hostdata;
+
+		SAS_DPRINTK("trying to find task 0x%p\n", task);
+		list_del_init(&cmd->eh_entry);
+		res = sas_scsi_find_task(task);
+
+		cmd->eh_eflags = 0;
+		shost->host_failed--;
+
+		switch (res) {
+		case TASK_IS_DONE:
+			SAS_DPRINTK("%s: task 0x%p is done\n", __FUNCTION__,
+				    task);
+			task->task_done(task);
+			continue;
+		case TASK_IS_ABORTED:
+			SAS_DPRINTK("%s: task 0x%p is aborted\n",
+				    __FUNCTION__, task);
+			task->task_done(task);
+			continue;
+		case TASK_IS_AT_LU:
+			SAS_DPRINTK("task 0x%p is at LU: lu recover\n", task);
+			tmf_resp = sas_recover_lu(task->dev, lu);
+			if (tmf_resp == TMF_RESP_FUNC_COMPLETE) {
+				SAS_DPRINTK("dev %016llx LU %016llx is "
+					    "recovered\n",
+					    SAS_ADDR(task->dev),
+					    SAS_ADDR(lu->LUN));
+				task->task_done(task);
+				sas_scsi_clear_queue_lu(&error_q, lu);
+				goto Again;
+			}
+			/* fallthrough */
+		case TASK_IS_NOT_AT_LU:
+			SAS_DPRINTK("task 0x%p is not at LU: I_T recover\n",
+				    task);
+			tmf_resp = sas_recover_I_T(task->dev);
+			if (tmf_resp == TMF_RESP_FUNC_COMPLETE) {
+				SAS_DPRINTK("I_T %016llx recovered\n",
+					    SAS_ADDR(task->dev->sas_addr));
+				task->task_done(task);
+				sas_scsi_clear_queue_I_T(&error_q, task->dev);
+				goto Again;
+			}
+			/* Hammer time :-) */
+			if (ha->lldd_clear_nexus_port) {
+				struct sas_port *port = task->dev->port;
+				SAS_DPRINTK("clearing nexus for port:%d\n",
+					    port->id);
+				res = ha->lldd_clear_nexus_port(port);
+				if (res == TMF_RESP_FUNC_COMPLETE) {
+					SAS_DPRINTK("clear nexus port:%d "
+						    "succeeded\n", port->id);
+					task->task_done(task);
+					sas_scsi_clear_queue_port(&error_q,
+								  port);
+					goto Again;
+				}
+			}
+			if (ha->lldd_clear_nexus_ha) {
+				SAS_DPRINTK("clear nexus ha\n");
+				res = ha->lldd_clear_nexus_ha(ha);
+				if (res == TMF_RESP_FUNC_COMPLETE) {
+					SAS_DPRINTK("clear nexus ha "
+						    "succeeded\n");
+					task->task_done(task);
+					goto out;
+				}
+			}
+			/* If we are here -- this means that no amount
+			 * of effort could recover from errors.  Quite
+			 * possibly the HA just disappeared.
+			 */
+			SAS_DPRINTK("error from  device %llx, LUN %llx "
+				    "couldn't be recovered in any way\n",
+				    SAS_ADDR(task->dev->sas_addr),
+				    SAS_ADDR(lu->LUN));
+
+			task->task_done(task);
+			goto clear_q;
+		}
+	}
+out:
+	SAS_DPRINTK("--- Exit %s\n", __FUNCTION__);
+	return 0;
+clear_q:
+	SAS_DPRINTK("--- Exit %s -- clear_q\n", __FUNCTION__);
+	list_for_each_entry_safe(cmd, n, &error_q, eh_entry) {
+		struct sas_task *task = TO_SAS_TASK(cmd);
+		list_del_init(&cmd->eh_entry);
+		task->task_done(task);
+	}
+	return 0;
+}
+
+static enum scsi_eh_timer_return sas_scsi_timed_out(struct scsi_cmnd *cmd)
+{
+	struct sas_task *task = TO_SAS_TASK(cmd);
+	unsigned long flags;
+
+	if (!task) {
+		SAS_DPRINTK("command 0x%p, task 0x%p, timed out: EH_HANDLED\n",
+			    cmd, task);
+		return EH_HANDLED;
+	}
+
+	spin_lock_irqsave(&task->task_state_lock, flags);
+	if (task->task_state_flags & SAS_TASK_STATE_DONE) {
+		spin_unlock_irqrestore(&task->task_state_lock, flags);
+		SAS_DPRINTK("command 0x%p, task 0x%p, timed out: EH_HANDLED\n",
+			    cmd, task);
+		return EH_HANDLED;
+	}
+	task->task_state_flags |= SAS_TASK_STATE_ABORTED;
+	spin_unlock_irqrestore(&task->task_state_lock, flags);
+
+	SAS_DPRINTK("command 0x%p, task 0x%p, timed out: EH_NOT_HANDLED\n",
+		    cmd, task);
+
+	return EH_NOT_HANDLED;
+}
+
+/**
+ * sas_slave_alloc -- configure an LU which SCSI Core wants to poke at
+ * @scsi_dev: pointer to scsi device
+ *
+ * The kludge here is that the only token we have to go by in order to
+ * identify which device SCSI Core has just found about, is channel,
+ * id and lun/2.  Of course this is 1) incredibly broken and 2)
+ * leftover from when SCSI Core was SPI-centric.  A solution would be
+ * to pass an opaque token to scsi_add_device, which SCSI Core treats
+ * as that, an opaque token, which it sets inside scsi_dev, so we can
+ * find out which device SCSI Core is talking about.  That is, how
+ * SCSI Core is _addressing_ the device is not the business of LLDD
+ * and vice versa.  An event _better_ solution is if SCSI Core knew
+ * about a "SCSI device with Target ports" so we can register only the
+ * targets, and then it would do its own LU discovery...  See comment
+ * in sas_do_lu_discovery().
+ */
+static int sas_slave_alloc(struct scsi_device *scsi_dev)
+{
+	struct sas_ha_struct *sas_ha = SHOST_TO_SAS_HA(scsi_dev->host);
+	struct sas_port *port = sas_ha->sas_port[scsi_dev->channel];
+        unsigned id = scsi_dev->id;
+	unsigned lun = scsi_dev->lun;
+
+	struct domain_device *dev = NULL;
+	struct LU *lu = NULL;
+
+	scsi_dev->hostdata = NULL;
+
+	list_for_each_entry(dev, &port->dev_list, dev_list_node) {
+		if (dev->dev_type == SAS_END_DEV) {
+			list_for_each_entry(lu, &dev->end_dev.LU_list, list) {
+				if (lu->map.id == id &&
+				    SCSI_LUN(lu->LUN) == lun) {
+					scsi_dev->hostdata = lu;
+					lu->uldd_dev = scsi_dev;
+					goto out_loop;
+				}
+			}
+		}
+	}
+out_loop:
+	if (!scsi_dev->hostdata) {
+		SAS_DPRINTK("sas device not found! How is this possible?\n");
+		return -ENODEV;
+	}
+	kobject_get(&lu->lu_obj);
+	return 0;
+}
+
+#define SAS_DEF_QD 32
+#define SAS_MAX_QD 64
+
+static int sas_slave_configure(struct scsi_device *scsi_dev)
+{
+	struct LU *lu = scsi_dev->hostdata;
+	struct domain_device *dev;
+	struct sas_ha_struct *sas_ha;
+
+	if (!lu) {
+		SAS_DPRINTK("slave configure and no LU?!\n");
+		return -ENODEV;
+	}
+
+	dev = lu->parent;
+	sas_ha = dev->port->ha;
+
+	if (scsi_dev->inquiry_len > 7) {
+		u8 bq = (scsi_dev->inquiry[6] & 0x80) ? 1 : 0;
+		u8 cq = (scsi_dev->inquiry[7] & 0x02) ? 1 : 0;
+
+		if (bq ^ cq) {
+			lu->tm_type = (bq<<1) | cq;
+			scsi_dev->tagged_supported = 1;
+			if (cq)
+				scsi_set_tag_type(scsi_dev, MSG_ORDERED_TAG);
+			else
+				scsi_set_tag_type(scsi_dev, MSG_SIMPLE_TAG);
+			scsi_activate_tcq(scsi_dev, SAS_DEF_QD);
+		} else {
+			SAS_DPRINTK("device %llx, LUN %llx doesn't support "
+				    "TCQ\n", SAS_ADDR(dev->sas_addr),
+				    SAS_ADDR(lu->LUN));
+			scsi_dev->tagged_supported = 0;
+			scsi_set_tag_type(scsi_dev, 0);
+			scsi_deactivate_tcq(scsi_dev, 1);
+		}
+	}
+
+	if (dev->end_dev.itnl_timeout > 0)
+		scsi_dev->timeout = HZ +
+			msecs_to_jiffies(dev->end_dev.itnl_timeout);
+
+	return 0;
+}
+
+static void sas_slave_destroy(struct scsi_device *scsi_dev)
+{
+	struct LU *lu = scsi_dev->hostdata;
+
+	if (lu) {
+		scsi_dev->hostdata = NULL;
+		lu->uldd_dev = NULL;
+		kobject_put(&lu->lu_obj);
+	}
+	scsi_device_put(scsi_dev);
+}
+
+static int sas_change_queue_depth(struct scsi_device *scsi_dev, int new_depth)
+{
+	int res = min(new_depth, SAS_MAX_QD);
+
+	if (scsi_dev->tagged_supported)
+		scsi_adjust_queue_depth(scsi_dev, scsi_get_tag_type(scsi_dev),
+					res);
+	else {
+		struct LU *lu = scsi_dev->hostdata;
+		sas_printk("device %llx LUN %llx queue depth changed to 1\n",
+			   SAS_ADDR(lu->parent->sas_addr),
+			   SAS_ADDR(lu->LUN));
+		scsi_adjust_queue_depth(scsi_dev, 0, 1);
+		res = 1;
+	}
+
+	return res;
+}
+
+static int sas_change_queue_type(struct scsi_device *scsi_dev, int qt)
+{
+	struct LU *lu = scsi_dev->hostdata;
+
+	if (!scsi_dev->tagged_supported)
+		return 0;
+
+	scsi_deactivate_tcq(scsi_dev, 1);
+
+	switch (qt) {
+	case MSG_ORDERED_TAG:
+		if (lu->tm_type != TASK_MANAGEMENT_FULL)
+			qt = MSG_SIMPLE_TAG;
+		break;
+	case MSG_SIMPLE_TAG:
+	default:
+		;
+	}
+
+	scsi_set_tag_type(scsi_dev, qt);
+	scsi_activate_tcq(scsi_dev, scsi_dev->queue_depth);
+
+	return qt;
+}
+
+static int sas_bios_param(struct scsi_device *scsi_dev,
+			  struct block_device *bdev,
+			  sector_t capacity, int *hsc)
+{
+	hsc[0] = 255;
+	hsc[1] = 63;
+	sector_div(capacity, 255*63);
+	hsc[2] = capacity;
+
+	return 0;
+}
+
+static const struct scsi_host_template sas_host_template = {
+	.module = THIS_MODULE,
+	/* .name is initialized */
+	.name = "",
+	.queuecommand = sas_queuecommand,
+	.eh_strategy_handler = sas_scsi_recover_host,
+	.eh_timed_out = sas_scsi_timed_out,
+	.slave_alloc = sas_slave_alloc,
+	.slave_configure = sas_slave_configure,
+	.slave_destroy = sas_slave_destroy,
+	.change_queue_depth = sas_change_queue_depth,
+	.change_queue_type = sas_change_queue_type,
+	.bios_param = sas_bios_param,
+	/* .can_queue is initialized */
+	.this_id = -1,
+	.sg_tablesize = SG_ALL,
+	.max_sectors = SCSI_DEFAULT_MAX_SECTORS,
+	/* .cmd_per_lun is initilized to .can_queue */
+	.use_clustering = ENABLE_CLUSTERING,
+};
+
+static inline void sas_init_host_template(struct sas_ha_struct *sas_ha)
+{
+	struct scsi_host_template *sht = sas_ha->core.sht;
+
+	*sht = sas_host_template;
+
+	sht->name = sas_ha->sas_ha_name;
+	sht->can_queue = sas_ha->lldd_queue_size;
+	sht->cmd_per_lun = sht->can_queue;
+}
+
+int sas_register_scsi_host(struct sas_ha_struct *sas_ha)
+{
+	int err = -ENOMEM;
+
+	sas_ha->core.sht = kmalloc(sizeof(*sas_ha->core.sht), GFP_KERNEL);
+	if (!sas_ha->core.sht)
+		return -ENOMEM;
+	memset(sas_ha->core.sht, 0, sizeof(*sas_ha->core.sht));
+
+	sas_init_host_template(sas_ha);
+
+	sas_ha->core.shost = scsi_host_alloc(sas_ha->core.sht, sizeof(void *));
+	if (!sas_ha->core.shost) {
+		printk(KERN_NOTICE "couldn't allocate scsi host\n");
+		goto out_err;
+	}
+ 	SHOST_TO_SAS_HA(sas_ha->core.shost) = sas_ha;
+
+	/* XXX: SCSI Core should really fix this (max vs. num of) */
+	sas_ha->core.shost->max_channel = sas_ha->num_phys - 1;
+	sas_ha->core.shost->max_id = ~0 - 1;
+	sas_ha->core.shost->max_lun = ~0 - 1;
+
+	sas_ha->core.shost->max_cmd_len = 16;
+
+	err = scsi_add_host(sas_ha->core.shost, &sas_ha->pcidev->dev);
+	if (err) {
+		scsi_host_put(sas_ha->core.shost);
+		sas_ha->core.shost = NULL;
+		goto out_err;
+	}
+	return 0;
+
+out_err:
+	kfree(sas_ha->core.sht);
+	sas_ha->core.sht = NULL;
+	return err;
+}
+
+void sas_unregister_scsi_host(struct sas_ha_struct *sas_ha)
+{
+	scsi_remove_host(sas_ha->core.shost);
+	scsi_host_put(sas_ha->core.shost);
+	sas_ha->core.shost = NULL;
+	kfree(sas_ha->core.sht);
+	sas_ha->core.sht = NULL;
+}
+
+/* ---------- Task Collector Thread implementation ---------- */
+
+static void sas_queue(struct sas_ha_struct *sas_ha)
+{
+	struct scsi_core *core = &sas_ha->core;
+	unsigned long flags;
+	LIST_HEAD(q);
+	int can_queue;
+	int res;
+
+	spin_lock_irqsave(&core->task_queue_lock, flags);
+	while (!core->queue_thread_kill &&
+	       !list_empty(&core->task_queue)) {
+
+		can_queue = sas_ha->lldd_queue_size - core->task_queue_size;
+		if (can_queue >= 0) {
+			can_queue = core->task_queue_size;
+			list_splice_init(&core->task_queue, &q);
+		} else {
+			struct list_head *a, *n;
+
+			can_queue = sas_ha->lldd_queue_size;
+			list_for_each_safe(a, n, &core->task_queue) {
+				list_move_tail(a, &q);
+				if (--can_queue == 0)
+					break;
+			}
+			can_queue = sas_ha->lldd_queue_size;
+		}
+		core->task_queue_size -= can_queue;
+		spin_unlock_irqrestore(&core->task_queue_lock, flags);
+		{
+			struct sas_task *task = list_entry(q.next,
+							   struct sas_task,
+							   list);
+			list_del_init(&q);
+			res = sas_ha->lldd_execute_task(task, can_queue,
+							GFP_KERNEL);
+			if (unlikely(res))
+				__list_add(&q, task->list.prev, &task->list);
+		}
+		spin_lock_irqsave(&core->task_queue_lock, flags);
+		if (res) {
+			list_splice_init(&q, &core->task_queue); /*at head*/
+			core->task_queue_size += can_queue;
+		}
+	}
+	spin_unlock_irqrestore(&core->task_queue_lock, flags);
+}
+
+static DECLARE_COMPLETION(queue_th_comp);
+
+/**
+ * sas_queue_thread -- The Task Collector thread
+ * @_sas_ha: pointer to struct sas_ha
+ */
+static int sas_queue_thread(void *_sas_ha)
+{
+	struct sas_ha_struct *sas_ha = _sas_ha;
+	struct scsi_core *core = &sas_ha->core;
+
+	daemonize("sas_queue_%d", core->shost->host_no);
+	current->flags |= PF_NOFREEZE;
+
+	complete(&queue_th_comp);
+
+	while (1) {
+		down_interruptible(&core->queue_thread_sema);
+		sas_queue(sas_ha);
+		if (core->queue_thread_kill)
+			break;
+	}
+
+	complete(&queue_th_comp);
+
+	return 0;
+}
+
+/* ---------- SCSI Core struct attributes ---------- */
+
+static ssize_t show_task_queue_size(struct scsi_core *core, char *page)
+{
+	return sprintf(page, "%d\n", core->task_queue_size);
+}
+
+struct scsi_core_attribute {
+	struct attribute attr;
+	ssize_t (*show)(struct scsi_core *, char *);
+	ssize_t (*store)(struct scsi_core *, const char *, size_t len);
+};
+
+#define to_scsi_core(_obj) container_of((_obj), struct scsi_core, \
+					scsi_core_obj)
+#define to_sc_attr(_attr) container_of((_attr), struct scsi_core_attribute,\
+				       attr)
+
+static ssize_t sc_show_attr(struct kobject *kobj, struct attribute *attr,
+			    char *page)
+{
+	ssize_t ret = 0;
+	struct scsi_core *core = to_scsi_core(kobj);
+	struct scsi_core_attribute *sc_attr = to_sc_attr(attr);
+
+	if (sc_attr->show)
+		ret = sc_attr->show(core, page);
+	return ret;
+}
+
+static struct scsi_core_attribute sc_attrs[] = {
+	__ATTR(task_queue_size, 0444, show_task_queue_size, NULL),
+	__ATTR_NULL,
+};
+
+static struct attribute *sc_def_attrs[ARRAY_SIZE(sc_attrs)];
+
+static struct sysfs_ops sc_sysfs_ops = {
+	.show = sc_show_attr,
+};
+
+static struct kobj_type scsi_core_ktype = {
+	.sysfs_ops = &sc_sysfs_ops,
+	.default_attrs = sc_def_attrs,
+};
+
+int sas_init_queue(struct sas_ha_struct *sas_ha)
+{
+	int res;
+	struct scsi_core *core = &sas_ha->core;
+
+	spin_lock_init(&core->task_queue_lock);
+	core->task_queue_size = 0;
+	INIT_LIST_HEAD(&core->task_queue);
+	init_MUTEX_LOCKED(&core->queue_thread_sema);
+
+	res = kernel_thread(sas_queue_thread, sas_ha, 0);
+	if (res >= 0) {
+		int i;
+		wait_for_completion(&queue_th_comp);
+
+		for (i = 0; i < ARRAY_SIZE(sc_attrs)-1; i++)
+			sc_def_attrs[i] = &sc_attrs[i].attr;
+		sc_def_attrs[i] = NULL;
+
+		core->scsi_core_obj.kset = &sas_ha->ha_kset;
+		kobject_set_name(&core->scsi_core_obj, "%s", "scsi_core");
+		core->scsi_core_obj.ktype = &scsi_core_ktype;
+	}
+
+	return res < 0 ? res : 0;
+}
+
+void sas_shutdown_queue(struct sas_ha_struct *sas_ha)
+{
+	unsigned long flags;
+	struct scsi_core *core = &sas_ha->core;
+	struct sas_task *task, *n;
+
+	init_completion(&queue_th_comp);
+	core->queue_thread_kill = 1;
+	up(&core->queue_thread_sema);
+	wait_for_completion(&queue_th_comp);
+
+	if (!list_empty(&core->task_queue))
+		SAS_DPRINTK("HA: %llx: scsi core task queue is NOT empty!?\n",
+			    SAS_ADDR(sas_ha->sas_addr));
+
+	spin_lock_irqsave(&core->task_queue_lock, flags);
+	list_for_each_entry_safe(task, n, &core->task_queue, list) {
+		struct scsi_cmnd *cmd = task->uldd_task;
+
+		list_del_init(&task->list);
+
+		ASSIGN_SAS_TASK(cmd, NULL);
+		sas_free_task(task);
+		cmd->result = DID_ABORT << 16;
+		cmd->scsi_done(cmd);
+	}
+	spin_unlock_irqrestore(&core->task_queue_lock, flags);
+}
_