Add support for SATA PM so that host can find devices that is
attached to PM, and add PM hotplug event support.

Signed-off-by: Xiangliang Yu <yxlr...@gmail.com>
---
 drivers/ata/libata-scsi.c           |   48 ++++-
 drivers/scsi/libsas/sas_ata.c       |  358 ++++++++++++++++++++++++++++++++++-
 drivers/scsi/libsas/sas_discover.c  |   25 +++-
 drivers/scsi/libsas/sas_internal.h  |    7 +
 drivers/scsi/libsas/sas_phy.c       |    1 +
 drivers/scsi/libsas/sas_port.c      |   12 ++
 drivers/scsi/libsas/sas_scsi_host.c |   12 +-
 include/scsi/libsas.h               |   13 ++-
 include/scsi/sas_ata.h              |    5 +
 9 files changed, 458 insertions(+), 23 deletions(-)

diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c
index ef8567d..969b3bb 100644
--- a/drivers/ata/libata-scsi.c
+++ b/drivers/ata/libata-scsi.c
@@ -4091,7 +4091,8 @@ int ata_sas_port_start(struct ata_port *ap)
         */
        if (!ap->ops->error_handler)
                ap->pflags &= ~ATA_PFLAG_FROZEN;
-       return 0;
+
+       return ata_tport_add(ap->dev, ap);
 }
 EXPORT_SYMBOL_GPL(ata_sas_port_start);
 
@@ -4107,6 +4108,15 @@ EXPORT_SYMBOL_GPL(ata_sas_port_start);
 
 void ata_sas_port_stop(struct ata_port *ap)
 {
+       int i = 0;
+
+       /* delete pmp link */
+       if (ap->pmp_link) {
+               for (i = 0; i < ap->nr_pmp_links; i++)
+                       ata_tlink_delete(&ap->pmp_link[i]);
+       }
+
+       ata_tport_delete(ap);
 }
 EXPORT_SYMBOL_GPL(ata_sas_port_stop);
 
@@ -4143,12 +4153,8 @@ EXPORT_SYMBOL_GPL(ata_sas_sync_probe);
 
 int ata_sas_port_init(struct ata_port *ap)
 {
-       int rc = ap->ops->port_start(ap);
-
-       if (rc)
-               return rc;
        ap->print_id = atomic_inc_return(&ata_print_id);
-       return 0;
+       return ap->ops->port_start(ap);
 }
 EXPORT_SYMBOL_GPL(ata_sas_port_init);
 
@@ -4166,6 +4172,23 @@ void ata_sas_port_destroy(struct ata_port *ap)
 }
 EXPORT_SYMBOL_GPL(ata_sas_port_destroy);
 
+static struct ata_device *ata_sas_find_dev(struct scsi_device *sdev,
+               struct ata_port *ap)
+{
+       int devno = 0;
+
+       if (!sata_pmp_attached(ap)) {
+               if (unlikely(sdev->channel || sdev->lun))
+                       return NULL;
+       } else {
+               if (unlikely(sdev->lun))
+                       return NULL;
+               devno = sdev->channel;
+       }
+
+       return ata_find_dev(ap, devno);
+}
+
 /**
  *     ata_sas_slave_configure - Default slave_config routine for libata 
devices
  *     @sdev: SCSI device to configure
@@ -4177,8 +4200,11 @@ EXPORT_SYMBOL_GPL(ata_sas_port_destroy);
 
 int ata_sas_slave_configure(struct scsi_device *sdev, struct ata_port *ap)
 {
+       struct ata_device *dev = ata_sas_find_dev(sdev, ap);
+       BUG_ON(!dev);
+
        ata_scsi_sdev_config(sdev);
-       ata_scsi_dev_config(sdev, ap->link.device);
+       ata_scsi_dev_config(sdev, dev);
        return 0;
 }
 EXPORT_SYMBOL_GPL(ata_sas_slave_configure);
@@ -4196,11 +4222,15 @@ EXPORT_SYMBOL_GPL(ata_sas_slave_configure);
 int ata_sas_queuecmd(struct scsi_cmnd *cmd, struct ata_port *ap)
 {
        int rc = 0;
+       struct ata_device *dev;
+       struct scsi_device *scsidev = cmd->device;
 
        ata_scsi_dump_cdb(ap, cmd);
 
-       if (likely(ata_dev_enabled(ap->link.device)))
-               rc = __ata_scsi_queuecmd(cmd, ap->link.device);
+       dev = ata_sas_find_dev(scsidev, ap);
+
+       if (likely(dev && ata_dev_enabled(dev)))
+               rc = __ata_scsi_queuecmd(cmd, dev);
        else {
                cmd->result = (DID_BAD_TARGET << 16);
                cmd->scsi_done(cmd);
diff --git a/drivers/scsi/libsas/sas_ata.c b/drivers/scsi/libsas/sas_ata.c
index 29a19fd..c820042 100644
--- a/drivers/scsi/libsas/sas_ata.c
+++ b/drivers/scsi/libsas/sas_ata.c
@@ -201,6 +201,17 @@ static unsigned int sas_ata_qc_issue(struct ata_queued_cmd 
*qc)
        task = sas_alloc_task(GFP_ATOMIC);
        if (!task)
                goto out;
+
+       if (sata_pmp_attached(ap)) {
+               struct ata_device *adev = qc->dev;
+
+               if (!list_empty(&dev->children) &&
+                       !ata_is_host_link(adev->link)) {
+                       dev = adev->private_data;
+                       BUG_ON(!dev);
+               }
+       }
+
        task->dev = dev;
        task->task_proto = SAS_PROTOCOL_STP;
        task->task_done = sas_ata_task_done;
@@ -662,6 +673,31 @@ error:
        return ret;
 }
 
+int sas_pm_revalidate_domain(struct domain_device *dev)
+{
+       struct ata_port *ap = NULL;
+       unsigned long flag;
+
+       ap = dev->sata_dev.ap;
+       if (!ap) {
+               SAS_DPRINTK("ap is null.\n");
+               return -1;
+       }
+
+       spin_lock_irqsave(ap->lock, flag);
+
+       /* avoid calling ata_scsi_hotplug function */
+       ap->pflags |= ATA_PFLAG_LOADING;
+
+       ata_port_schedule_eh(ap);
+
+       spin_unlock_irqrestore(ap->lock, flag);
+
+       sas_ata_wait_eh(dev);
+
+       return 0;
+}
+
 /*
  * notify the lldd to forget the sas_task for this internal ata command
  * that bypasses scsi-eh
@@ -801,6 +837,7 @@ int sas_ata_init(struct domain_device *found_dev)
 {
        struct sas_ha_struct *ha = found_dev->port->ha;
        struct Scsi_Host *shost = ha->core.shost;
+       struct sas_internal *i = dev_to_sas_internal(found_dev);
        struct ata_port *ap;
        int rc;
 
@@ -816,6 +853,7 @@ int sas_ata_init(struct domain_device *found_dev)
        ap->private_data = found_dev;
        ap->cbl = ATA_CBL_SATA;
        ap->scsi_host = shost;
+
        rc = ata_sas_port_init(ap);
        if (rc) {
                ata_sas_port_destroy(ap);
@@ -823,6 +861,10 @@ int sas_ata_init(struct domain_device *found_dev)
        }
        found_dev->sata_dev.ap = ap;
 
+       /* ata port support setting */
+       if (i->dft->lldd_dev_set)
+               i->dft->lldd_dev_set(found_dev);
+
        return 0;
 }
 
@@ -868,7 +910,13 @@ static void sas_get_ata_command_set(struct domain_device 
*dev)
             fis->lbal         == 0 &&
             fis->lbam         == 0xCE &&
             fis->lbah         == 0xAA &&
-            (fis->device & ~0x10) == 0))
+            (fis->device & ~0x10) == 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 = ATA_COMMAND_SET;
 
@@ -884,18 +932,293 @@ static void sas_get_ata_command_set(struct domain_device 
*dev)
                  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))
+                 fis->device       == 0))
 
                /* Treat it as a superset? */
                dev->sata_dev.command_set = ATAPI_COMMAND_SET;
 }
 
+/* Support unplug event of PMP attahced device*/
+void sas_ata_detach_dev(struct ata_port *ap, struct ata_device *dev)
+{
+       struct domain_device *parent = ap->private_data;
+       struct domain_device *child = NULL, *n;
+       struct sata_device *sdev = &parent->sata_dev;
+       struct ata_link *link = dev->link;
+       struct ex_phy *ephy = &sdev->ephy[link->pmp];
+
+       list_for_each_entry_safe(child, n, &parent->children, siblings) {
+
+               if (child->sata_dev.port_no == link->pmp) {
+                       sas_unregister_dev(parent->port, child);
+
+                       memset(ephy->attached_sas_addr, 0, SAS_ADDR_SIZE);
+                       if (ephy->port) {
+                               sas_port_delete_phy(ephy->port, ephy->phy);
+                               sas_device_set_phy(child, ephy->port);
+                               if (ephy->port->num_phys == 0)
+                                       sas_port_delete(ephy->port);
+                               ephy->port = NULL;
+                       }
+
+                       break;
+               }
+       }
+}
+
+static void sas_ata_alloc_sas_phy(struct domain_device *parent,
+               struct ata_device *dev)
+{
+       struct sata_device *sdev = &parent->sata_dev;
+       struct ata_link *link = dev->link;
+       struct ex_phy *ephy = &sdev->ephy[link->pmp];
+       struct sas_rphy *rphy = parent->rphy;
+       struct sas_phy *phy = NULL;
+       unsigned int num_phys = 0, phy_id;
+
+       if (ephy->phy) {
+               SAS_DPRINTK("phy is not null.\n");
+               return;
+       }
+       num_phys = parent->port->ha->num_phys;
+
+       phy_id = num_phys + (parent->phy->number + 1) * link->pmp;
+       ephy->phy = sas_phy_alloc(&rphy->dev, phy_id);
+
+       phy = ephy->phy;
+       BUG_ON(!phy);
+
+       ephy->attached_dev_type = SAS_SATA_DEV;
+       ephy->phy_id = link->pmp;
+       ephy->attached_tproto = parent->tproto;
+       ephy->attached_iproto = parent->iproto;
+
+       phy->identify.device_type = SAS_SATA_DEV;
+       phy->identify.initiator_port_protocols = SAS_PROTOCOL_SATA;
+       phy->identify.target_port_protocols = SAS_PROTOCOL_SATA;
+       phy->identify.phy_identifier = phy_id;
+
+       sas_phy_add(phy);
+}
+
+static void sas_ata_free_sas_phy(struct domain_device *parent,
+               struct ata_device *dev)
+{
+       struct sata_device *sdev = &parent->sata_dev;
+       struct ata_link *link = dev->link;
+       struct ex_phy *ephy = &sdev->ephy[link->pmp];
+       struct sas_phy *phy = ephy->phy;
+
+       sas_phy_delete(phy);
+       sas_phy_free(phy);
+
+       ephy->phy = NULL;
+}
+
+/* alloc domain device for each sata device and insert it into children list */
+static int sas_ata_alloc_ddev(struct domain_device *parent,
+               struct ata_device *dev)
+{
+       struct ata_link *link = dev->link;
+       struct domain_device *child = NULL;
+       struct sata_device *sdev = &parent->sata_dev;
+       struct ex_phy *ephy = &sdev->ephy[link->pmp];
+       struct sas_rphy *rphy = NULL;
+       int ret = -1;
+
+       child = sas_alloc_device();
+       if (!child) {
+               SAS_DPRINTK("can't alloc child device.\n");
+               goto error;
+       }
+
+       kref_get(&parent->kref);
+       child->parent = parent;
+       child->port = parent->port;
+       child->tproto = parent->tproto;
+       child->dev_type = SAS_SATA_DEV;
+       child->sata_dev.port_no = link->pmp;
+       child->sata_dev.command_set = ATA_COMMAND_SET;
+       child->sata_dev.ap = parent->sata_dev.ap;
+
+       if (!ephy->port) {
+               ephy->port = sas_port_alloc(&parent->rphy->dev,
+                               ephy->phy->number);
+               if (unlikely(!ephy->port)) {
+                       SAS_DPRINTK("fail to alloc sas port.\n");
+                       goto free;
+               }
+               if (unlikely(sas_port_add(ephy->port) != 0)) {
+                       SAS_DPRINTK("can't add sas port.\n");
+                       goto out;
+               }
+       }
+
+       BUG_ON(!ephy->phy);
+
+       sas_port_add_phy(ephy->port, ephy->phy);
+
+       rphy = sas_end_device_alloc(ephy->port);
+       if (!rphy) {
+               SAS_DPRINTK("fail to alloc end device.\n");
+               goto out;
+       }
+
+       rphy->identify.phy_identifier = parent->phy->identify.phy_identifier;
+       memcpy(child->sas_addr, parent->sas_addr, SAS_ADDR_SIZE);
+       sas_fill_in_rphy(child, rphy);
+       sas_hash_addr(child->hashed_sas_addr, child->sas_addr);
+       child->linkrate = parent->linkrate;
+       child->min_linkrate = child->linkrate;
+       child->max_linkrate = child->linkrate;
+       child->pathways = parent->pathways;
+
+       sas_device_set_phy(child, ephy->port);
+
+       child->rphy = rphy;
+       get_device(&child->rphy->dev);
+
+       list_add_tail(&child->siblings, &parent->children);
+       dev->private_data = child;
+
+       return 0;
+out:
+       sas_port_free(ephy->port);
+free:
+       ephy->port = NULL;
+       sas_put_device(child);
+error:
+       SAS_DPRINTK("error exit.\n");
+       return ret;
+}
+
+static void sas_ata_free_ddev(struct domain_device *parent,
+               struct ata_device *dev)
+{
+       struct ata_link *link = dev->link;
+       struct domain_device *child = NULL;
+       struct sata_device *sdev = &parent->sata_dev;
+       struct ex_phy *ephy = &sdev->ephy[link->pmp];
+       struct sas_rphy *rphy = NULL;
+
+       child = dev->private_data;
+       rphy = child->rphy;
+
+       list_del_init(&child->siblings);
+
+       sas_rphy_free(rphy);
+
+       sas_port_delete(ephy->port);
+       ephy->port = NULL;
+
+       sas_put_device(child);
+       dev->private_data = NULL;
+}
+
+static int sas_ata_add_ddev(struct domain_device *parent,
+               struct ata_device *dev)
+{
+       struct ata_link *link = dev->link;
+       struct domain_device *child, *n;
+       struct sas_rphy *rphy = NULL;
+       int ret = 0;
+
+       list_for_each_entry_safe(child, n, &parent->children, siblings)
+       {
+               if (child->sata_dev.port_no != link->pmp)
+                       continue;
+
+               ret = sas_notify_lldd_dev_found(child);
+               if (ret) {
+                       SAS_DPRINTK(" fail to notify lldd.\n");
+                       return ret;
+               }
+               rphy = child->rphy;
+
+               ret = sas_rphy_add(rphy);
+               if (ret) {
+                       sas_notify_lldd_dev_gone(child);
+                       SAS_DPRINTK("fail to add rphy.\n");
+                       return ret;
+               }
+
+               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);
+       }
+
+       return ret;
+}
+
+void sas_ata_scan_host(struct ata_port *ap)
+{
+       struct ata_link *link;
+       struct ata_device *dev;
+       struct domain_device *parent = ap->private_data;
+       struct sas_port *port = NULL;
+       struct sata_device *sdev = NULL;
+       int ret = 0;
+
+       if (!sata_pmp_attached(ap)) {
+               SAS_DPRINTK("ap is not pmp attached.\n");
+               return;
+       }
+
+       port = parent->port->port;
+       if (unlikely(!port->rphy)) {
+               ret = sas_rphy_add(parent->rphy);
+               if (ret) {
+                       SAS_DPRINTK("fail to add rphy .\n");
+                       sas_fail_probe(parent, __func__, ret);
+                       return;
+               }
+               list_del_init(&parent->disco_list_node);
+       }
+
+       sdev = &parent->sata_dev;
+
+       if (unlikely(!sdev->ephy)) {
+               sdev->ephy = kzalloc(sizeof(*sdev->ephy) * ap->nr_pmp_links,
+                                       GFP_KERNEL);
+               if (!sdev->ephy) {
+                       SAS_DPRINTK("failed to alloc ex_phy.\n");
+                       sas_rphy_delete(parent->rphy);
+                       sas_fail_probe(parent, __func__, -ENOMEM);
+                       return;
+               }
+       }
+
+       ata_for_each_link(link, ap, EDGE) {
+               ata_for_each_dev(dev, link, ALL) {
+
+                       if (dev->flags & ATA_DFLAG_DETACHED) {
+                               SAS_DPRINTK("unplug device.\n");
+                               sas_ata_detach_dev(ap, dev);
+                               continue;
+                       }
+
+                       if (dev->sdev || !ata_dev_enabled(dev))
+                               continue;
+
+                       sas_ata_alloc_sas_phy(parent, dev);
+
+                       ret = sas_ata_alloc_ddev(parent, dev);
+                       if (ret) {
+                               SAS_DPRINTK("fail to alloc child dev.\n");
+                               sas_ata_free_sas_phy(parent, dev);
+                               continue;
+                       }
+
+                       ret = sas_ata_add_ddev(parent, dev);
+                       if (ret) {
+                               SAS_DPRINTK("fail to add sas dev.\n");
+                               sas_ata_free_ddev(parent, dev);
+                               sas_ata_free_sas_phy(parent, dev);
+                       }
+               }
+       }
+}
+
 void sas_probe_sata(struct asd_sas_port *port)
 {
        struct domain_device *dev, *n;
@@ -996,8 +1319,16 @@ int sas_discover_sata(struct domain_device *dev)
 {
        int res;
 
-       if (dev->dev_type == SAS_SATA_PM)
-               return -ENODEV;
+       if (dev->dev_type == SAS_SATA_PM) {
+               struct ata_port *ap = dev->sata_dev.ap;
+
+               if (unlikely(!ap))
+                       BUG();
+
+               /* if lldd do not support pmp, end discover */
+               if (!(ap->flags & ATA_FLAG_PMP))
+                       return -ENODEV;
+       }
 
        sas_get_ata_command_set(dev);
        sas_fill_in_rphy(dev, dev->rphy);
@@ -1047,6 +1378,10 @@ void sas_ata_strategy_handler(struct Scsi_Host *shost)
                        if (!dev_is_sata(dev))
                                continue;
 
+                       if (dev->parent &&
+                               (dev->parent->dev_type == SAS_SATA_PM))
+                               continue;
+
                        /* hold a reference over eh since we may be
                         * racing with final remove once all commands
                         * are completed
@@ -1136,4 +1471,7 @@ void sas_ata_wait_eh(struct domain_device *dev)
 
        ap = dev->sata_dev.ap;
        ata_port_wait_eh(ap);
+
+       /* add sata device that is attached to PM */
+       sas_ata_scan_host(ap);
 }
diff --git a/drivers/scsi/libsas/sas_discover.c 
b/drivers/scsi/libsas/sas_discover.c
index 62b58d3..6d4b0bd 100644
--- a/drivers/scsi/libsas/sas_discover.c
+++ b/drivers/scsi/libsas/sas_discover.c
@@ -42,6 +42,9 @@ void sas_init_dev(struct domain_device *dev)
        case SAS_END_DEVICE:
                INIT_LIST_HEAD(&dev->ssp_dev.eh_list_node);
                break;
+       case SAS_SATA_PM:
+               INIT_LIST_HEAD(&dev->children);
+               break;
        case SAS_EDGE_EXPANDER_DEVICE:
        case SAS_FANOUT_EXPANDER_DEVICE:
                INIT_LIST_HEAD(&dev->ex_dev.children);
@@ -110,6 +113,7 @@ static int sas_get_port_device(struct asd_sas_port *port)
        dev->port = port;
        switch (dev->dev_type) {
        case SAS_SATA_DEV:
+       case SAS_SATA_PM:
                rc = sas_ata_init(dev);
                if (rc) {
                        rphy = NULL;
@@ -319,6 +323,14 @@ void sas_free_device(struct kref *kref)
                kfree(dev->ex_dev.ex_phy);
 
        if (dev_is_sata(dev) && dev->sata_dev.ap) {
+               /* PMP attached device */
+               if (dev->parent) {
+                       kfree(dev);
+                       return;
+               }
+
+               kfree(dev->sata_dev.ephy);
+
                ata_sas_port_destroy(dev->sata_dev.ap);
                dev->sata_dev.ap = NULL;
        }
@@ -500,6 +512,7 @@ static void sas_revalidate_domain(struct work_struct *work)
        struct sas_discovery_event *ev = to_sas_discovery_event(work);
        struct asd_sas_port *port = ev->port;
        struct sas_ha_struct *ha = port->ha;
+       struct domain_device *dev = NULL;
 
        /* prevent revalidation from finding sata links in recovery */
        mutex_lock(&ha->disco_mutex);
@@ -514,8 +527,16 @@ static void sas_revalidate_domain(struct work_struct *work)
        SAS_DPRINTK("REVALIDATING DOMAIN on port %d, pid:%d\n", port->id,
                    task_pid_nr(current));
 
-       if (port->port_dev)
-               res = sas_ex_revalidate_domain(port->port_dev);
+       if (port->port_dev) {
+               dev = port->port_dev;
+               /* SATA port multiplier hotplug */
+               if (dev->dev_type == SAS_SATA_PM) {
+                       mutex_unlock(&ha->disco_mutex);
+                       res = sas_pm_revalidate_domain(dev);
+                       mutex_lock(&ha->disco_mutex);
+               } else
+                       res = sas_ex_revalidate_domain(dev);
+       }
 
        SAS_DPRINTK("done REVALIDATING DOMAIN on port %d, pid:%d, res 0x%x\n",
                    port->id, task_pid_nr(current), res);
diff --git a/drivers/scsi/libsas/sas_internal.h 
b/drivers/scsi/libsas/sas_internal.h
index 7e7ba83..ba11001 100644
--- a/drivers/scsi/libsas/sas_internal.h
+++ b/drivers/scsi/libsas/sas_internal.h
@@ -77,6 +77,7 @@ void sas_deform_port(struct asd_sas_phy *phy, int gone);
 
 void sas_porte_bytes_dmaed(struct work_struct *work);
 void sas_porte_broadcast_rcvd(struct work_struct *work);
+void sas_porte_sntf_rcvd(struct work_struct *work);
 void sas_porte_link_reset_err(struct work_struct *work);
 void sas_porte_timer_event(struct work_struct *work);
 void sas_porte_hard_reset(struct work_struct *work);
@@ -131,6 +132,12 @@ static inline void sas_fill_in_rphy(struct domain_device 
*dev,
        rphy->identify.initiator_port_protocols = dev->iproto;
        rphy->identify.target_port_protocols = dev->tproto;
        switch (dev->dev_type) {
+       case SAS_SATA_PM:
+               /* SATA port multiplier don't need to scan target in
+                * sas_rphy_add function and need device type when
+                * removing module
+                */
+               rphy->identify.target_port_protocols = SAS_PROTOCOL_NONE;
        case SAS_SATA_DEV:
                /* FIXME: need sata device type */
        case SAS_END_DEVICE:
diff --git a/drivers/scsi/libsas/sas_phy.c b/drivers/scsi/libsas/sas_phy.c
index cdee446..ade4ff7 100644
--- a/drivers/scsi/libsas/sas_phy.c
+++ b/drivers/scsi/libsas/sas_phy.c
@@ -134,6 +134,7 @@ int sas_register_phys(struct sas_ha_struct *sas_ha)
                [PORTE_LINK_RESET_ERR] = sas_porte_link_reset_err,
                [PORTE_TIMER_EVENT] = sas_porte_timer_event,
                [PORTE_HARD_RESET] = sas_porte_hard_reset,
+               [PORTE_SNTF_RCVD] = sas_porte_sntf_rcvd,
        };
 
        /* Now register the phys. */
diff --git a/drivers/scsi/libsas/sas_port.c b/drivers/scsi/libsas/sas_port.c
index d3c5297..3a4c514 100644
--- a/drivers/scsi/libsas/sas_port.c
+++ b/drivers/scsi/libsas/sas_port.c
@@ -266,6 +266,18 @@ void sas_porte_bytes_dmaed(struct work_struct *work)
        sas_form_port(phy);
 }
 
+/* Support SATA port multiplier hotplug event */
+void sas_porte_sntf_rcvd(struct work_struct *work)
+{
+       struct asd_sas_event *ev = to_asd_sas_event(work);
+       struct asd_sas_phy *phy = ev->phy;
+
+       clear_bit(PORTE_SNTF_RCVD, &phy->port_events_pending);
+
+       SAS_DPRINTK("pm sntf received.\n");
+       sas_discover_event(phy->port, DISCE_REVALIDATE_DOMAIN);
+}
+
 void sas_porte_broadcast_rcvd(struct work_struct *work)
 {
        struct asd_sas_event *ev = to_asd_sas_event(work);
diff --git a/drivers/scsi/libsas/sas_scsi_host.c 
b/drivers/scsi/libsas/sas_scsi_host.c
index 25d0f12..0c3b88b 100644
--- a/drivers/scsi/libsas/sas_scsi_host.c
+++ b/drivers/scsi/libsas/sas_scsi_host.c
@@ -916,6 +916,12 @@ int sas_target_alloc(struct scsi_target *starget)
 
        kref_get(&found_dev->kref);
        starget->hostdata = found_dev;
+
+       /* find ata device through channel field */
+       if (found_dev->parent &&
+               (found_dev->parent->dev_type == SAS_SATA_PM))
+               starget->channel = found_dev->sata_dev.port_no;
+
        return 0;
 }
 
@@ -929,7 +935,11 @@ int sas_slave_configure(struct scsi_device *scsi_dev)
        BUG_ON(dev->rphy->identify.device_type != SAS_END_DEVICE);
 
        if (dev_is_sata(dev)) {
-               ata_sas_slave_configure(scsi_dev, dev->sata_dev.ap);
+               struct domain_device *parent = dev;
+               if (dev->parent && (dev->parent->dev_type == SAS_SATA_PM))
+                       parent = dev->parent;
+
+               ata_sas_slave_configure(scsi_dev, parent->sata_dev.ap);
                return 0;
        }
 
diff --git a/include/scsi/libsas.h b/include/scsi/libsas.h
index a26466a..6ba1dd3 100644
--- a/include/scsi/libsas.h
+++ b/include/scsi/libsas.h
@@ -71,7 +71,8 @@ enum port_event {
        PORTE_LINK_RESET_ERR  = 2,
        PORTE_TIMER_EVENT     = 3,
        PORTE_HARD_RESET      = 4,
-       PORT_NUM_EVENTS       = 5,
+       PORTE_SNTF_RCVD       = 5,
+       PORT_NUM_EVENTS       = 6,
 };
 
 enum phy_event {
@@ -173,6 +174,11 @@ struct sata_device {
         struct smp_resp        rps_resp; /* report_phy_sata_resp */
         u8     port_no;        /* port number, if this is a PM (Port) */
 
+       /* Allocate sas phy and port for each devcie that
+        * is attached to port multiplier.
+        */
+       struct ex_phy *ephy;
+
        struct ata_port *ap;
        struct ata_host ata_host;
        u8     fis[ATA_RESP_FIS_SIZE];
@@ -204,6 +210,7 @@ struct domain_device {
 
         struct domain_device *parent;
         struct list_head siblings; /* devices on the same level */
+       struct list_head children;
         struct asd_sas_port *port;        /* shortcut to root of the tree */
        struct sas_phy *phy;
 
@@ -695,6 +702,9 @@ struct sas_domain_function_template {
        void (*lldd_dev_thaw)(struct domain_device *);
        int (*lldd_wait_task_done)(struct sas_task *);
        int (*lldd_dev_classify)(struct domain_device *);
+
+       /* SATA Port multiplier setting */
+       void (*lldd_dev_set)(struct domain_device *);
 };
 
 extern int sas_register_ha(struct sas_ha_struct *);
@@ -725,6 +735,7 @@ int  sas_discover_root_expander(struct domain_device *);
 void sas_init_ex_attr(void);
 
 int  sas_ex_revalidate_domain(struct domain_device *);
+int  sas_pm_revalidate_domain(struct domain_device *);
 
 void sas_unregister_domain_devices(struct asd_sas_port *port, int gone);
 void sas_init_disc(struct sas_discovery *disc, struct asd_sas_port *);
diff --git a/include/scsi/sas_ata.h b/include/scsi/sas_ata.h
index 00f41ae..9a638c5 100644
--- a/include/scsi/sas_ata.h
+++ b/include/scsi/sas_ata.h
@@ -48,6 +48,7 @@ void sas_probe_sata(struct asd_sas_port *port);
 void sas_suspend_sata(struct asd_sas_port *port);
 void sas_resume_sata(struct asd_sas_port *port);
 void sas_ata_end_eh(struct ata_port *ap);
+void sas_ata_scan_host(struct ata_port *ap);
 #else
 
 
@@ -100,6 +101,10 @@ static inline int sas_get_ata_info(struct domain_device 
*dev, struct ex_phy *phy
 static inline void sas_ata_end_eh(struct ata_port *ap)
 {
 }
+
+static inline void sas_ata_scan_host(struct ata_port *ap)
+{
+}
 #endif
 
 #endif /* _SAS_ATA_H_ */
-- 
1.7.1

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to