Merge branch 'master' into for_paulus
[linux-drm-fsl-dcu.git] / drivers / message / fusion / mptsas.c
index a8df06c422bd41693f2275dc2505b135022a5f08..84b8b485e95bd8b57b6a11685a2cf64cd317d704 100644 (file)
@@ -4,7 +4,7 @@
  *      running LSI Logic Fusion MPT (Message Passing Technology) firmware.
  *
  *  Copyright (c) 1999-2007 LSI Logic Corporation
- *  (mailto:mpt_linux_developer@lsil.com)
+ *  (mailto:mpt_linux_developer@lsi.com)
  *  Copyright (c) 2005-2007 Dell
  */
 /*=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=*/
@@ -96,6 +96,12 @@ static int   mptsasMgmtCtx = -1;
 
 static void mptsas_hotplug_work(struct work_struct *work);
 
+struct mptsas_target_reset_event {
+       struct list_head        list;
+       EVENT_DATA_SAS_DEVICE_STATUS_CHANGE sas_event_data;
+       u8      target_reset_issued;
+};
+
 enum mptsas_hotplug_action {
        MPTSAS_ADD_DEVICE,
        MPTSAS_DEL_DEVICE,
@@ -163,6 +169,7 @@ struct mptsas_portinfo_details{
 };
 
 struct mptsas_phyinfo {
+       u16     handle;                 /* unique id to address this */
        u8      phy_id;                 /* phy index */
        u8      port_id;                /* firmware port identifier */
        u8      negotiated_link_rate;   /* nego'd link rate for this phy */
@@ -178,7 +185,6 @@ struct mptsas_phyinfo {
 
 struct mptsas_portinfo {
        struct list_head list;
-       u16             handle;         /* unique id to address this */
        u16             num_phys;       /* number of phys */
        struct mptsas_phyinfo *phy_info;
 };
@@ -571,22 +577,273 @@ mptsas_setup_wide_ports(MPT_ADAPTER *ioc, struct mptsas_portinfo *port_info)
        mutex_unlock(&ioc->sas_topology_mutex);
 }
 
+/**
+ * csmisas_find_vtarget
+ *
+ * @ioc
+ * @volume_id
+ * @volume_bus
+ *
+ **/
+static VirtTarget *
+mptsas_find_vtarget(MPT_ADAPTER *ioc, u8 channel, u8 id)
+{
+       struct scsi_device              *sdev;
+       VirtDevice                      *vdev;
+       VirtTarget                      *vtarget = NULL;
+
+       shost_for_each_device(sdev, ioc->sh) {
+               if ((vdev = sdev->hostdata) == NULL)
+                       continue;
+               if (vdev->vtarget->id == id &&
+                   vdev->vtarget->channel == channel)
+                       vtarget = vdev->vtarget;
+       }
+       return vtarget;
+}
+
+/**
+ * mptsas_target_reset
+ *
+ * Issues TARGET_RESET to end device using handshaking method
+ *
+ * @ioc
+ * @channel
+ * @id
+ *
+ * Returns (1) success
+ *         (0) failure
+ *
+ **/
+static int
+mptsas_target_reset(MPT_ADAPTER *ioc, u8 channel, u8 id)
+{
+       MPT_FRAME_HDR   *mf;
+       SCSITaskMgmt_t  *pScsiTm;
+
+       if ((mf = mpt_get_msg_frame(ioc->TaskCtx, ioc)) == NULL) {
+               dfailprintk((MYIOC_s_WARN_FMT "%s, no msg frames @%d!!\n",
+                   ioc->name,__FUNCTION__, __LINE__));
+               return 0;
+       }
+
+       /* Format the Request
+        */
+       pScsiTm = (SCSITaskMgmt_t *) mf;
+       memset (pScsiTm, 0, sizeof(SCSITaskMgmt_t));
+       pScsiTm->TargetID = id;
+       pScsiTm->Bus = channel;
+       pScsiTm->Function = MPI_FUNCTION_SCSI_TASK_MGMT;
+       pScsiTm->TaskType = MPI_SCSITASKMGMT_TASKTYPE_TARGET_RESET;
+       pScsiTm->MsgFlags = MPI_SCSITASKMGMT_MSGFLAGS_LIPRESET_RESET_OPTION;
+
+       DBG_DUMP_TM_REQUEST_FRAME(mf);
+
+       if (mpt_send_handshake_request(ioc->TaskCtx, ioc,
+           sizeof(SCSITaskMgmt_t), (u32 *)mf, NO_SLEEP)) {
+               mpt_free_msg_frame(ioc, mf);
+               dfailprintk((MYIOC_s_WARN_FMT "%s, tm handshake failed @%d!!\n",
+                   ioc->name,__FUNCTION__, __LINE__));
+               return 0;
+       }
+
+       return 1;
+}
+
+/**
+ * mptsas_target_reset_queue
+ *
+ * Receive request for TARGET_RESET after recieving an firmware
+ * event NOT_RESPONDING_EVENT, then put command in link list
+ * and queue if task_queue already in use.
+ *
+ * @ioc
+ * @sas_event_data
+ *
+ **/
 static void
-mptsas_target_reset(MPT_ADAPTER *ioc, VirtTarget * vtarget)
+mptsas_target_reset_queue(MPT_ADAPTER *ioc,
+    EVENT_DATA_SAS_DEVICE_STATUS_CHANGE *sas_event_data)
 {
-       MPT_SCSI_HOST           *hd = (MPT_SCSI_HOST *)ioc->sh->hostdata;
+       MPT_SCSI_HOST   *hd = (MPT_SCSI_HOST *)ioc->sh->hostdata;
+       VirtTarget *vtarget = NULL;
+       struct mptsas_target_reset_event *target_reset_list;
+       u8              id, channel;
 
-       if (mptscsih_TMHandler(hd,
-            MPI_SCSITASKMGMT_TASKTYPE_TARGET_RESET,
-            vtarget->channel, vtarget->id, 0, 0, 5) < 0) {
-               hd->tmPending = 0;
-               hd->tmState = TM_STATE_NONE;
-               printk(MYIOC_s_WARN_FMT
-              "Error processing TaskMgmt id=%d TARGET_RESET\n",
-                       ioc->name, vtarget->id);
+       id = sas_event_data->TargetID;
+       channel = sas_event_data->Bus;
+
+       if (!(vtarget = mptsas_find_vtarget(ioc, channel, id)))
+               return;
+
+       vtarget->deleted = 1; /* block IO */
+
+       target_reset_list = kzalloc(sizeof(*target_reset_list),
+           GFP_ATOMIC);
+       if (!target_reset_list) {
+               dfailprintk((MYIOC_s_WARN_FMT "%s, failed to allocate mem @%d..!!\n",
+                   ioc->name,__FUNCTION__, __LINE__));
+               return;
+       }
+
+       memcpy(&target_reset_list->sas_event_data, sas_event_data,
+               sizeof(*sas_event_data));
+       list_add_tail(&target_reset_list->list, &hd->target_reset_list);
+
+       if (hd->resetPending)
+               return;
+
+       if (mptsas_target_reset(ioc, channel, id)) {
+               target_reset_list->target_reset_issued = 1;
+               hd->resetPending = 1;
        }
 }
 
+/**
+ * mptsas_dev_reset_complete
+ *
+ * Completion for TARGET_RESET after NOT_RESPONDING_EVENT,
+ * enable work queue to finish off removing device from upper layers.
+ * then send next TARGET_RESET in the queue.
+ *
+ * @ioc
+ *
+ **/
+static void
+mptsas_dev_reset_complete(MPT_ADAPTER *ioc)
+{
+       MPT_SCSI_HOST   *hd = (MPT_SCSI_HOST *)ioc->sh->hostdata;
+        struct list_head *head = &hd->target_reset_list;
+       struct mptsas_target_reset_event *target_reset_list;
+       struct mptsas_hotplug_event *ev;
+       EVENT_DATA_SAS_DEVICE_STATUS_CHANGE *sas_event_data;
+       u8              id, channel;
+       __le64          sas_address;
+
+       if (list_empty(head))
+               return;
+
+       target_reset_list = list_entry(head->next, struct mptsas_target_reset_event, list);
+
+       sas_event_data = &target_reset_list->sas_event_data;
+       id = sas_event_data->TargetID;
+       channel = sas_event_data->Bus;
+       hd->resetPending = 0;
+
+       /*
+        * retry target reset
+        */
+       if (!target_reset_list->target_reset_issued) {
+               if (mptsas_target_reset(ioc, channel, id)) {
+                       target_reset_list->target_reset_issued = 1;
+                       hd->resetPending = 1;
+               }
+               return;
+       }
+
+       /*
+        * enable work queue to remove device from upper layers
+        */
+       list_del(&target_reset_list->list);
+
+       ev = kzalloc(sizeof(*ev), GFP_ATOMIC);
+       if (!ev) {
+               dfailprintk((MYIOC_s_WARN_FMT "%s, failed to allocate mem @%d..!!\n",
+                   ioc->name,__FUNCTION__, __LINE__));
+               return;
+       }
+
+       INIT_WORK(&ev->work, mptsas_hotplug_work);
+       ev->ioc = ioc;
+       ev->handle = le16_to_cpu(sas_event_data->DevHandle);
+       ev->parent_handle =
+           le16_to_cpu(sas_event_data->ParentDevHandle);
+       ev->channel = channel;
+       ev->id =id;
+       ev->phy_id = sas_event_data->PhyNum;
+       memcpy(&sas_address, &sas_event_data->SASAddress,
+           sizeof(__le64));
+       ev->sas_address = le64_to_cpu(sas_address);
+       ev->device_info = le32_to_cpu(sas_event_data->DeviceInfo);
+       ev->event_type = MPTSAS_DEL_DEVICE;
+       schedule_work(&ev->work);
+       kfree(target_reset_list);
+
+       /*
+        * issue target reset to next device in the queue
+        */
+
+       head = &hd->target_reset_list;
+       if (list_empty(head))
+               return;
+
+       target_reset_list = list_entry(head->next, struct mptsas_target_reset_event,
+           list);
+
+       sas_event_data = &target_reset_list->sas_event_data;
+       id = sas_event_data->TargetID;
+       channel = sas_event_data->Bus;
+
+       if (mptsas_target_reset(ioc, channel, id)) {
+               target_reset_list->target_reset_issued = 1;
+               hd->resetPending = 1;
+       }
+}
+
+/**
+ * mptsas_taskmgmt_complete
+ *
+ * @ioc
+ * @mf
+ * @mr
+ *
+ **/
+static int
+mptsas_taskmgmt_complete(MPT_ADAPTER *ioc, MPT_FRAME_HDR *mf, MPT_FRAME_HDR *mr)
+{
+       mptsas_dev_reset_complete(ioc);
+       return mptscsih_taskmgmt_complete(ioc, mf, mr);
+}
+
+/**
+ * mptscsih_ioc_reset
+ *
+ * @ioc
+ * @reset_phase
+ *
+ **/
+static int
+mptsas_ioc_reset(MPT_ADAPTER *ioc, int reset_phase)
+{
+       MPT_SCSI_HOST   *hd = (MPT_SCSI_HOST *)ioc->sh->hostdata;
+       struct mptsas_target_reset_event *target_reset_list, *n;
+       int rc;
+
+       rc = mptscsih_ioc_reset(ioc, reset_phase);
+
+       if (ioc->bus_type != SAS)
+               goto out;
+
+       if (reset_phase != MPT_IOC_POST_RESET)
+               goto out;
+
+       if (!hd || !hd->ioc)
+               goto out;
+
+       if (list_empty(&hd->target_reset_list))
+               goto out;
+
+       /* flush the target_reset_list */
+       list_for_each_entry_safe(target_reset_list, n,
+           &hd->target_reset_list, list) {
+               list_del(&target_reset_list->list);
+               kfree(target_reset_list);
+       }
+
+ out:
+       return rc;
+}
+
 static int
 mptsas_sas_enclosure_pg0(MPT_ADAPTER *ioc, struct mptsas_enclosure *enclosure,
                u32 form, u32 form_specific)
@@ -1130,9 +1387,6 @@ mptsas_sas_io_unit_pg0(MPT_ADAPTER *ioc, struct mptsas_portinfo *port_info)
                goto out_free_consistent;
        }
 
-       if (port_info->num_phys)
-               port_info->handle =
-                   le16_to_cpu(buffer->PhyData[0].ControllerDevHandle);
        for (i = 0; i < port_info->num_phys; i++) {
                mptsas_print_phy_data(&buffer->PhyData[i]);
                port_info->phy_info[i].phy_id = i;
@@ -1141,6 +1395,8 @@ mptsas_sas_io_unit_pg0(MPT_ADAPTER *ioc, struct mptsas_portinfo *port_info)
                port_info->phy_info[i].negotiated_link_rate =
                    buffer->PhyData[i].NegotiatedLinkRate;
                port_info->phy_info[i].portinfo = port_info;
+               port_info->phy_info[i].handle =
+                   le16_to_cpu(buffer->PhyData[i].ControllerDevHandle);
        }
 
  out_free_consistent:
@@ -1342,7 +1598,6 @@ mptsas_sas_expander_pg0(MPT_ADAPTER *ioc, struct mptsas_portinfo *port_info,
 
        /* save config data */
        port_info->num_phys = buffer->NumPhys;
-       port_info->handle = le16_to_cpu(buffer->DevHandle);
        port_info->phy_info = kcalloc(port_info->num_phys,
                sizeof(*port_info->phy_info),GFP_KERNEL);
        if (!port_info->phy_info) {
@@ -1350,8 +1605,11 @@ mptsas_sas_expander_pg0(MPT_ADAPTER *ioc, struct mptsas_portinfo *port_info,
                goto out_free_consistent;
        }
 
-       for (i = 0; i < port_info->num_phys; i++)
+       for (i = 0; i < port_info->num_phys; i++) {
                port_info->phy_info[i].portinfo = port_info;
+               port_info->phy_info[i].handle =
+                   le16_to_cpu(buffer->DevHandle);
+       }
 
  out_free_consistent:
        pci_free_consistent(ioc->pcidev, hdr.ExtPageLength * 4,
@@ -1719,7 +1977,6 @@ static int
 mptsas_probe_hba_phys(MPT_ADAPTER *ioc)
 {
        struct mptsas_portinfo *port_info, *hba;
-       u32 handle = 0xFFFF;
        int error = -ENOMEM, i;
 
        hba = kzalloc(sizeof(*port_info), GFP_KERNEL);
@@ -1731,34 +1988,36 @@ mptsas_probe_hba_phys(MPT_ADAPTER *ioc)
                goto out_free_port_info;
 
        mutex_lock(&ioc->sas_topology_mutex);
-       ioc->handle = hba->handle;
-       port_info = mptsas_find_portinfo_by_handle(ioc, hba->handle);
+       ioc->handle = hba->phy_info[0].handle;
+       port_info = mptsas_find_portinfo_by_handle(ioc, ioc->handle);
        if (!port_info) {
                port_info = hba;
                list_add_tail(&port_info->list, &ioc->sas_topology);
        } else {
-               port_info->handle = hba->handle;
-               for (i = 0; i < hba->num_phys; i++)
+               for (i = 0; i < hba->num_phys; i++) {
                        port_info->phy_info[i].negotiated_link_rate =
                                hba->phy_info[i].negotiated_link_rate;
+                       port_info->phy_info[i].handle =
+                               hba->phy_info[i].handle;
+                       port_info->phy_info[i].port_id =
+                               hba->phy_info[i].port_id;
+               }
                kfree(hba->phy_info);
                kfree(hba);
                hba = NULL;
        }
        mutex_unlock(&ioc->sas_topology_mutex);
-
        for (i = 0; i < port_info->num_phys; i++) {
                mptsas_sas_phy_pg0(ioc, &port_info->phy_info[i],
                        (MPI_SAS_PHY_PGAD_FORM_PHY_NUMBER <<
                         MPI_SAS_PHY_PGAD_FORM_SHIFT), i);
 
                mptsas_sas_device_pg0(ioc, &port_info->phy_info[i].identify,
-                       (MPI_SAS_DEVICE_PGAD_FORM_GET_NEXT_HANDLE <<
-                        MPI_SAS_DEVICE_PGAD_FORM_SHIFT), handle);
+                       (MPI_SAS_DEVICE_PGAD_FORM_HANDLE <<
+                        MPI_SAS_DEVICE_PGAD_FORM_SHIFT),
+                        port_info->phy_info[i].handle);
                port_info->phy_info[i].identify.phy_id =
-                   port_info->phy_info[i].phy_id;
-               handle = port_info->phy_info[i].identify.handle;
-
+                   port_info->phy_info[i].phy_id = i;
                if (port_info->phy_info[i].attached.handle)
                        mptsas_sas_device_pg0(ioc,
                                &port_info->phy_info[i].attached,
@@ -1794,12 +2053,12 @@ mptsas_probe_expander_phys(MPT_ADAPTER *ioc, u32 *handle)
                goto out;
 
        error = mptsas_sas_expander_pg0(ioc, ex,
-               (MPI_SAS_EXPAND_PGAD_FORM_GET_NEXT_HANDLE <<
-                MPI_SAS_EXPAND_PGAD_FORM_SHIFT), *handle);
+           (MPI_SAS_EXPAND_PGAD_FORM_GET_NEXT_HANDLE <<
+            MPI_SAS_EXPAND_PGAD_FORM_SHIFT), *handle);
        if (error)
                goto out_free_port_info;
 
-       *handle = ex->handle;
+       *handle = ex->phy_info[0].handle;
 
        mutex_lock(&ioc->sas_topology_mutex);
        port_info = mptsas_find_portinfo_by_handle(ioc, *handle);
@@ -1807,7 +2066,12 @@ mptsas_probe_expander_phys(MPT_ADAPTER *ioc, u32 *handle)
                port_info = ex;
                list_add_tail(&port_info->list, &ioc->sas_topology);
        } else {
-               port_info->handle = ex->handle;
+               for (i = 0; i < ex->num_phys; i++) {
+                       port_info->phy_info[i].handle =
+                               ex->phy_info[i].handle;
+                       port_info->phy_info[i].port_id =
+                               ex->phy_info[i].port_id;
+               }
                kfree(ex->phy_info);
                kfree(ex);
                ex = NULL;
@@ -1885,8 +2149,6 @@ mptsas_delete_expander_phys(MPT_ADAPTER *ioc)
        struct mptsas_portinfo buffer;
        struct mptsas_portinfo *port_info, *n, *parent;
        struct mptsas_phyinfo *phy_info;
-       struct scsi_target * starget;
-       VirtTarget * vtarget;
        struct sas_port * port;
        int i;
        u64     expander_sas_address;
@@ -1901,26 +2163,8 @@ mptsas_delete_expander_phys(MPT_ADAPTER *ioc)
 
                if (mptsas_sas_expander_pg0(ioc, &buffer,
                     (MPI_SAS_EXPAND_PGAD_FORM_HANDLE <<
-                    MPI_SAS_EXPAND_PGAD_FORM_SHIFT), port_info->handle)) {
-
-                       /*
-                        * Issue target reset to all child end devices
-                        * then mark them deleted to prevent further
-                        * IO going to them.
-                        */
-                       phy_info = port_info->phy_info;
-                       for (i = 0; i < port_info->num_phys; i++, phy_info++) {
-                               starget = mptsas_get_starget(phy_info);
-                               if (!starget)
-                                       continue;
-                               vtarget = starget->hostdata;
-                               if(vtarget->deleted)
-                                       continue;
-                               vtarget->deleted = 1;
-                               mptsas_target_reset(ioc, vtarget);
-                               sas_port_delete(mptsas_get_port(phy_info));
-                               mptsas_port_delete(phy_info->port_details);
-                       }
+                    MPI_SAS_EXPAND_PGAD_FORM_SHIFT),
+                    port_info->phy_info[0].handle)) {
 
                        /*
                         * Obtain the port_info instance to the parent port
@@ -2319,9 +2563,6 @@ mptsas_hotplug_work(struct work_struct *work)
                                    ev->phys_disk_num;
                        break;
                        }
-
-                       vtarget->deleted = 1;
-                       mptsas_target_reset(ioc, vtarget);
                }
 
                if (phy_info->attached.device_info &
@@ -2474,8 +2715,6 @@ mptsas_hotplug_work(struct work_struct *work)
                printk(MYIOC_s_INFO_FMT
                       "removing raid volume, channel %d, id %d\n",
                       ioc->name, MPTSAS_RAID_CHANNEL, ev->id);
-               vdevice->vtarget->deleted = 1;
-               mptsas_target_reset(ioc, vdevice->vtarget);
                vdevice = sdev->hostdata;
                scsi_remove_device(sdev);
                scsi_device_put(sdev);
@@ -2509,8 +2748,12 @@ mptsas_send_sas_event(MPT_ADAPTER *ioc,
                return;
 
        switch (sas_event_data->ReasonCode) {
-       case MPI_EVENT_SAS_DEV_STAT_RC_ADDED:
        case MPI_EVENT_SAS_DEV_STAT_RC_NOT_RESPONDING:
+
+               mptsas_target_reset_queue(ioc, sas_event_data);
+               break;
+
+       case MPI_EVENT_SAS_DEV_STAT_RC_ADDED:
                ev = kzalloc(sizeof(*ev), GFP_ATOMIC);
                if (!ev) {
                        printk(KERN_WARNING "mptsas: lost hotplug event\n");
@@ -2871,8 +3114,6 @@ mptsas_probe(struct pci_dev *pdev, const struct pci_device_id *id)
                sh->sg_tablesize = numSGE;
        }
 
-       spin_unlock_irqrestore(&ioc->FreeQlock, flags);
-
        hd = (MPT_SCSI_HOST *) sh->hostdata;
        hd->ioc = ioc;
 
@@ -2912,15 +3153,17 @@ mptsas_probe(struct pci_dev *pdev, const struct pci_device_id *id)
 
        ioc->sas_data.ptClear = mpt_pt_clear;
 
+       init_waitqueue_head(&hd->scandv_waitq);
+       hd->scandv_wait_done = 0;
+       hd->last_queue_full = 0;
+       INIT_LIST_HEAD(&hd->target_reset_list);
+       spin_unlock_irqrestore(&ioc->FreeQlock, flags);
+
        if (ioc->sas_data.ptClear==1) {
                mptbase_sas_persist_operation(
                    ioc, MPI_SAS_OP_CLEAR_ALL_PERSISTENT);
        }
 
-       init_waitqueue_head(&hd->scandv_waitq);
-       hd->scandv_wait_done = 0;
-       hd->last_queue_full = 0;
-
        error = scsi_add_host(sh, &ioc->pcidev->dev);
        if (error) {
                dprintk((KERN_ERR MYNAM
@@ -2999,7 +3242,7 @@ mptsas_init(void)
                return -ENODEV;
 
        mptsasDoneCtx = mpt_register(mptscsih_io_done, MPTSAS_DRIVER);
-       mptsasTaskCtx = mpt_register(mptscsih_taskmgmt_complete, MPTSAS_DRIVER);
+       mptsasTaskCtx = mpt_register(mptsas_taskmgmt_complete, MPTSAS_DRIVER);
        mptsasInternalCtx =
                mpt_register(mptscsih_scandv_complete, MPTSAS_DRIVER);
        mptsasMgmtCtx = mpt_register(mptsas_mgmt_done, MPTSAS_DRIVER);
@@ -3009,7 +3252,7 @@ mptsas_init(void)
                  ": Registered for IOC event notifications\n"));
        }
 
-       if (mpt_reset_register(mptsasDoneCtx, mptscsih_ioc_reset) == 0) {
+       if (mpt_reset_register(mptsasDoneCtx, mptsas_ioc_reset) == 0) {
                dprintk((KERN_INFO MYNAM
                  ": Registered for IOC reset notifications\n"));
        }