From: Luben Tuikov This is The SAS Layer with suggestions by Nish and Alexey, http://www.geocities.com/ltuikov/mm2/sas-class.patch.gz Cc: James Bottomley Signed-off-by: Andrew Morton --- 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 \n", prog); + printf("\tWhere 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 +# +# 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 +# +# 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 + * + * 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 +#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 + * + * 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 +#include +#include +#include +#include "sas_internal.h" +#include +#include + +/* ---------- 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 + * + * 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 + * + * 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 + * + * 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 +#include "sas_internal.h" +#include "sas_dump.h" +#include + +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 + * + * 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 +#include + +#include "sas_internal.h" +#include +#include + +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 + * + * 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 +#include +#include +#include +#include + +#include "sas_internal.h" +#include + +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 "); +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 + * + * 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 + +#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 + * + * 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 + * + * 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 + +/* 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 + * + * 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 +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +/* 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); +} _