sg: rename sg_cmd_done sg_rq_end_io
[safe/jmp/linux-2.6] / drivers / scsi / libsas / sas_expander.c
index 30b8014..3da02e4 100644 (file)
@@ -22,8 +22,8 @@
  *
  */
 
-#include <linux/pci.h>
 #include <linux/scatterlist.h>
+#include <linux/blkdev.h>
 
 #include "sas_internal.h"
 
@@ -37,12 +37,6 @@ static int sas_configure_phy(struct domain_device *dev, int phy_id,
                             u8 *sas_addr, int include);
 static int sas_disable_routing(struct domain_device *dev,  u8 *sas_addr);
 
-#if 0
-/* FIXME: smp needs to migrate into the sas class */
-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);
-#endif
-
 /* ---------- SMP task management ---------- */
 
 static void smp_task_timedout(unsigned long _task)
@@ -71,55 +65,75 @@ static void smp_task_done(struct sas_task *task)
 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);
+       int res, retry;
+       struct sas_task *task = NULL;
        struct sas_internal *i =
                to_sas_internal(dev->port->ha->core.shost->transportt);
 
-       if (!task)
-               return -ENOMEM;
+       for (retry = 0; retry < 3; retry++) {
+               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->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->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);
+               task->timer.data = (unsigned long) task;
+               task->timer.function = smp_task_timedout;
+               task->timer.expires = jiffies + SMP_TIMEOUT*HZ;
+               add_timer(&task->timer);
 
-       res = i->dft->lldd_execute_task(task, 1, GFP_KERNEL);
+               res = i->dft->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");
-               i->dft->lldd_abort_task(task);
-               if (!(task->task_state_flags & SAS_TASK_STATE_DONE)) {
-                       SAS_DPRINTK("SMP task aborted and not done\n");
+               if (res) {
+                       del_timer(&task->timer);
+                       SAS_DPRINTK("executing SMP task failed:%d\n", res);
                        goto ex_err;
                }
+
+               wait_for_completion(&task->completion);
+               res = -ECOMM;
+               if ((task->task_state_flags & SAS_TASK_STATE_ABORTED)) {
+                       SAS_DPRINTK("smp task timed out or aborted\n");
+                       i->dft->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;
+                       break;
+               } if (task->task_status.resp == SAS_TASK_COMPLETE &&
+                     task->task_status.stat == SAS_DATA_UNDERRUN) {
+                       /* no error, but return the number of bytes of
+                        * underrun */
+                       res = task->task_status.residual;
+                       break;
+               } if (task->task_status.resp == SAS_TASK_COMPLETE &&
+                     task->task_status.stat == SAS_DATA_OVERRUN) {
+                       res = -EMSGSIZE;
+                       break;
+               } else {
+                       SAS_DPRINTK("%s: task to dev %016llx response: 0x%x "
+                                   "status 0x%x\n", __func__,
+                                   SAS_ADDR(dev->sas_addr),
+                                   task->task_status.resp,
+                                   task->task_status.stat);
+                       sas_free_task(task);
+                       task = NULL;
+               }
        }
-       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);
+       BUG_ON(retry == 3 && task != NULL);
+       if (task != NULL) {
+               sas_free_task(task);
+       }
        return res;
 }
 
@@ -209,6 +223,36 @@ static void sas_set_ex_phy(struct domain_device *dev, int phy_id,
 #define DISCOVER_REQ_SIZE  16
 #define DISCOVER_RESP_SIZE 56
 
+static int sas_ex_phy_discover_helper(struct domain_device *dev, u8 *disc_req,
+                                     u8 *disc_resp, int single)
+{
+       int i, res;
+
+       disc_req[9] = single;
+       for (i = 1 ; i < 3; i++) {
+               struct discover_resp *dr;
+
+               res = smp_execute_task(dev, disc_req, DISCOVER_REQ_SIZE,
+                                      disc_resp, DISCOVER_RESP_SIZE);
+               if (res)
+                       return res;
+               /* This is detecting a failure to transmit inital
+                * dev to host FIS as described in section G.5 of
+                * sas-2 r 04b */
+               dr = &((struct smp_resp *)disc_resp)->disc;
+               if (!(dr->attached_dev_type == 0 &&
+                     dr->attached_sata_dev))
+                       break;
+               /* In order to generate the dev to host FIS, we
+                * send a link reset to the expander port */
+               sas_smp_phy_control(dev, single, PHY_FUNC_LINK_RESET, NULL);
+               /* Wait for the reset to trigger the negotiation */
+               msleep(500);
+       }
+       sas_set_ex_phy(dev, single, disc_resp);
+       return 0;
+}
+
 static int sas_ex_phy_discover(struct domain_device *dev, int single)
 {
        struct expander_device *ex = &dev->ex_dev;
@@ -229,23 +273,15 @@ static int sas_ex_phy_discover(struct domain_device *dev, int single)
        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);
+               res = sas_ex_phy_discover_helper(dev, disc_req, disc_resp, single);
        } 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);
+                       res = sas_ex_phy_discover_helper(dev, disc_req,
+                                                        disc_resp, i);
                        if (res)
                                goto out_err;
-                       sas_set_ex_phy(dev, i, disc_resp);
                }
        }
 out_err:
@@ -481,13 +517,20 @@ static int sas_dev_present_in_domain(struct asd_sas_port *port,
 int sas_smp_get_phy_events(struct sas_phy *phy)
 {
        int res;
+       u8 *req;
+       u8 *resp;
        struct sas_rphy *rphy = dev_to_rphy(phy->dev.parent);
        struct domain_device *dev = sas_find_dev_by_rphy(rphy);
-       u8 *req = alloc_smp_req(RPEL_REQ_SIZE);
-       u8 *resp = kzalloc(RPEL_RESP_SIZE, GFP_KERNEL);
 
-       if (!resp)
+       req = alloc_smp_req(RPEL_REQ_SIZE);
+       if (!req)
+               return -ENOMEM;
+
+       resp = alloc_smp_resp(RPEL_RESP_SIZE);
+       if (!resp) {
+               kfree(req);
                return -ENOMEM;
+       }
 
        req[1] = SMP_REPORT_PHY_ERR_LOG;
        req[9] = phy->number;
@@ -509,6 +552,8 @@ int sas_smp_get_phy_events(struct sas_phy *phy)
 
 }
 
+#ifdef CONFIG_SCSI_SAS_ATA
+
 #define RPS_REQ_SIZE  16
 #define RPS_RESP_SIZE 60
 
@@ -518,6 +563,7 @@ static int sas_get_report_phy_sata(struct domain_device *dev,
 {
        int res;
        u8 *rps_req = alloc_smp_req(RPS_REQ_SIZE);
+       u8 *resp = (u8 *)rps_resp;
 
        if (!rps_req)
                return -ENOMEM;
@@ -528,9 +574,30 @@ static int sas_get_report_phy_sata(struct domain_device *dev,
        res = smp_execute_task(dev, rps_req, RPS_REQ_SIZE,
                                    rps_resp, RPS_RESP_SIZE);
 
+       /* 0x34 is the FIS type for the D2H fis.  There's a potential
+        * standards cockup here.  sas-2 explicitly specifies the FIS
+        * should be encoded so that FIS type is in resp[24].
+        * However, some expanders endian reverse this.  Undo the
+        * reversal here */
+       if (!res && resp[27] == 0x34 && resp[24] != 0x34) {
+               int i;
+
+               for (i = 0; i < 5; i++) {
+                       int j = 24 + (i*4);
+                       u8 a, b;
+                       a = resp[j + 0];
+                       b = resp[j + 1];
+                       resp[j + 0] = resp[j + 3];
+                       resp[j + 1] = resp[j + 2];
+                       resp[j + 2] = b;
+                       resp[j + 3] = a;
+               }
+       }
+
        kfree(rps_req);
-       return 0;
+       return res;
 }
+#endif
 
 static void sas_ex_get_linkrate(struct domain_device *parent,
                                       struct domain_device *child,
@@ -587,15 +654,21 @@ static struct domain_device *sas_ex_discover_end_dev(
        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);
-       phy->port = sas_port_alloc(&parent->rphy->dev, phy_id);
-       BUG_ON(!phy->port);
-       /* FIXME: better error handling*/
-       BUG_ON(sas_port_add(phy->port) != 0);
+       if (!phy->port) {
+               phy->port = sas_port_alloc(&parent->rphy->dev, phy_id);
+               if (unlikely(!phy->port))
+                       goto out_err;
+               if (unlikely(sas_port_add(phy->port) != 0)) {
+                       sas_port_free(phy->port);
+                       goto out_err;
+               }
+       }
        sas_ex_get_linkrate(parent, child, phy);
 
-       if ((phy->attached_tproto & SAS_PROTO_STP) || phy->attached_sata_dev) {
+#ifdef CONFIG_SCSI_SAS_ATA
+       if ((phy->attached_tproto & SAS_PROTOCOL_STP) || phy->attached_sata_dev) {
                child->dev_type = SATA_DEV;
-               if (phy->attached_tproto & SAS_PROTO_STP)
+               if (phy->attached_tproto & SAS_PROTOCOL_STP)
                        child->tproto = phy->attached_tproto;
                if (phy->attached_sata_dev)
                        child->tproto |= SATA_DEV;
@@ -605,35 +678,48 @@ static struct domain_device *sas_ex_discover_end_dev(
                        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;
+                       goto out_free;
                }
                memcpy(child->frame_rcvd, &child->sata_dev.rps_resp.rps.fis,
                       sizeof(struct dev_to_host_fis));
+
+               rphy = sas_end_device_alloc(phy->port);
+               if (unlikely(!rphy))
+                       goto out_free;
+
                sas_init_dev(child);
+
+               child->rphy = rphy;
+
+               spin_lock_irq(&parent->port->dev_list_lock);
+               list_add_tail(&child->dev_list_node, &parent->port->dev_list);
+               spin_unlock_irq(&parent->port->dev_list_lock);
+
                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;
+                       goto out_list_del;
                }
-       } else if (phy->attached_tproto & SAS_PROTO_SSP) {
+       } else
+#endif
+         if (phy->attached_tproto & SAS_PROTOCOL_SSP) {
                child->dev_type = SAS_END_DEV;
                rphy = sas_end_device_alloc(phy->port);
                /* FIXME: error handling */
-               BUG_ON(!rphy);
+               if (unlikely(!rphy))
+                       goto out_free;
                child->tproto = phy->attached_tproto;
                sas_init_dev(child);
 
                child->rphy = rphy;
                sas_fill_in_rphy(child, rphy);
 
-               spin_lock(&parent->port->dev_list_lock);
+               spin_lock_irq(&parent->port->dev_list_lock);
                list_add_tail(&child->dev_list_node, &parent->port->dev_list);
-               spin_unlock(&parent->port->dev_list_lock);
+               spin_unlock_irq(&parent->port->dev_list_lock);
 
                res = sas_discover_end_dev(child);
                if (res) {
@@ -641,18 +727,51 @@ static struct domain_device *sas_ex_discover_end_dev(
                                    "at %016llx:0x%x returned 0x%x\n",
                                    SAS_ADDR(child->sas_addr),
                                    SAS_ADDR(parent->sas_addr), phy_id, res);
-                       /* FIXME: this kfrees list elements without removing them */
-                       //kfree(child);
-                       return NULL;
+                       goto out_list_del;
                }
        } else {
                SAS_DPRINTK("target proto 0x%x at %016llx:0x%x not handled\n",
                            phy->attached_tproto, SAS_ADDR(parent->sas_addr),
                            phy_id);
+               goto out_free;
        }
 
        list_add_tail(&child->siblings, &parent_ex->children);
        return child;
+
+ out_list_del:
+       sas_rphy_free(child->rphy);
+       child->rphy = NULL;
+       list_del(&child->dev_list_node);
+ out_free:
+       sas_port_delete(phy->port);
+ out_err:
+       phy->port = NULL;
+       kfree(child);
+       return NULL;
+}
+
+/* See if this phy is part of a wide port */
+static int sas_ex_join_wide_port(struct domain_device *parent, int phy_id)
+{
+       struct ex_phy *phy = &parent->ex_dev.ex_phy[phy_id];
+       int i;
+
+       for (i = 0; i < parent->ex_dev.num_phys; i++) {
+               struct ex_phy *ephy = &parent->ex_dev.ex_phy[i];
+
+               if (ephy == phy)
+                       continue;
+
+               if (!memcmp(phy->attached_sas_addr, ephy->attached_sas_addr,
+                           SAS_ADDR_SIZE) && ephy->port) {
+                       sas_port_add_phy(ephy->port, phy->phy);
+                       phy->phy_state = PHY_DEVICE_DISCOVERED;
+                       return 0;
+               }
+       }
+
+       return -ENODEV;
 }
 
 static struct domain_device *sas_ex_discover_expander(
@@ -714,9 +833,9 @@ static struct domain_device *sas_ex_discover_expander(
        sas_fill_in_rphy(child, rphy);
        sas_rphy_add(rphy);
 
-       spin_lock(&parent->port->dev_list_lock);
+       spin_lock_irq(&parent->port->dev_list_lock);
        list_add_tail(&child->dev_list_node, &parent->port->dev_list);
-       spin_unlock(&parent->port->dev_list_lock);
+       spin_unlock_irq(&parent->port->dev_list_lock);
 
        res = sas_discover_expander(child);
        if (res) {
@@ -787,6 +906,13 @@ static int sas_ex_discover_dev(struct domain_device *dev, int phy_id)
                return res;
        }
 
+       res = sas_ex_join_wide_port(dev, phy_id);
+       if (!res) {
+               SAS_DPRINTK("Attaching ex phy%d to wide port %016llx\n",
+                           phy_id, SAS_ADDR(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);
@@ -1153,7 +1279,7 @@ static int sas_configure_present(struct domain_device *dev, int phy_id,
                        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__,
+                                   "result 0x%x\n", __func__,
                                    SAS_ADDR(dev->sas_addr), phy_id, i, res);
                        goto out;
                }
@@ -1305,31 +1431,6 @@ static int sas_disable_routing(struct domain_device *dev,  u8 *sas_addr)
        return 0;
 }
 
-#if 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);
-}
-#endif
-
 /**
  * sas_discover_expander -- expander discovery
  * @ex: pointer to expander domain device
@@ -1409,14 +1510,23 @@ int sas_discover_root_expander(struct domain_device *dev)
        int res;
        struct sas_expander_device *ex = rphy_to_expander_device(dev->rphy);
 
-       sas_rphy_add(dev->rphy);
+       res = sas_rphy_add(dev->rphy);
+       if (res)
+               goto out_err;
 
        ex->level = dev->port->disc.max_level; /* 0 */
        res = sas_discover_expander(dev);
-       if (!res)
-               sas_ex_bfs_disc(dev->port);
+       if (res)
+               goto out_err2;
+
+       sas_ex_bfs_disc(dev->port);
 
        return res;
+
+out_err2:
+       sas_rphy_remove(dev->rphy);
+out_err:
+       return res;
 }
 
 /* ---------- Domain revalidation ---------- */
@@ -1782,74 +1892,57 @@ out:
        return res;
 }
 
-#if 0
-/* ---------- SMP portal ---------- */
-
-static ssize_t smp_portal_write(struct kobject *kobj, char *buf, loff_t offs,
-                               size_t size)
+int sas_smp_handler(struct Scsi_Host *shost, struct sas_rphy *rphy,
+                   struct request *req)
 {
-       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;
+       struct domain_device *dev;
+       int ret, type;
+       struct request *rsp = req->next_rq;
 
-       down_interruptible(&ex->smp_sema);
-       if (ex->smp_req)
-               kfree(ex->smp_req);
-       ex->smp_req = kzalloc(size, GFP_USER);
-       if (!ex->smp_req) {
-               up(&ex->smp_sema);
-               return -ENOMEM;
+       if (!rsp) {
+               printk("%s: space for a smp response is missing\n",
+                      __func__);
+               return -EINVAL;
        }
-       memcpy(ex->smp_req, buf, size);
-       ex->smp_req_size = size;
-       ex->smp_portal_pid = current->pid;
-       up(&ex->smp_sema);
 
-       return size;
-}
+       /* no rphy means no smp target support (ie aic94xx host) */
+       if (!rphy)
+               return sas_smp_host_handler(shost, req, rsp);
 
-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;
+       type = rphy->identify.device_type;
 
-       /* XXX: sysfs gives us an offset of 0x10 or 0x8 while in fact
-        *  it should be 0.
-        */
+       if (type != SAS_EDGE_EXPANDER_DEVICE &&
+           type != SAS_FANOUT_EXPANDER_DEVICE) {
+               printk("%s: can we send a smp request to a device?\n",
+                      __func__);
+               return -EINVAL;
+       }
 
-       down_interruptible(&ex->smp_sema);
-       if (!ex->smp_req || ex->smp_portal_pid != current->pid)
-               goto out;
+       dev = sas_find_dev_by_rphy(rphy);
+       if (!dev) {
+               printk("%s: fail to find a domain_device?\n", __func__);
+               return -EINVAL;
+       }
 
-       res = 0;
-       if (size == 0)
-               goto out;
+       /* do we need to support multiple segments? */
+       if (req->bio->bi_vcnt > 1 || rsp->bio->bi_vcnt > 1) {
+               printk("%s: multiple segments req %u %u, rsp %u %u\n",
+                      __func__, req->bio->bi_vcnt, req->data_len,
+                      rsp->bio->bi_vcnt, rsp->data_len);
+               return -EINVAL;
+       }
 
-       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;
+       ret = smp_execute_task(dev, bio_data(req->bio), req->data_len,
+                              bio_data(rsp->bio), rsp->data_len);
+       if (ret > 0) {
+               /* positive number is the untransferred residual */
+               rsp->data_len = ret;
+               req->data_len = 0;
+               ret = 0;
+       } else if (ret == 0) {
+               rsp->data_len = 0;
+               req->data_len = 0;
        }
 
-       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;
+       return ret;
 }
-#endif