scsi: hisi_sas: Fix losing directly attached disk when hot-plug
authorXiaofei Tan <tanxiaofei@huawei.com>
Fri, 25 Jan 2019 14:22:35 +0000 (22:22 +0800)
committerMartin K. Petersen <martin.petersen@oracle.com>
Tue, 29 Jan 2019 06:41:20 +0000 (01:41 -0500)
Hot-plugging SAS wire of direct hard disk backplane may cause disk lost. We
have done this test with several types of SATA disk from different venders,
and only two models from Seagate has this problem, ST4000NM0035-1V4107 and
ST3000VM002-1ET166.

The root cause is that the disk doesn't send D2H frame after OOB finished.
SAS controller will issue phyup interrupt only when D2H frame is received,
otherwise, will be waiting there all the time.

When this issue happen, we can find the disk again with link reset.  To fix
this issue, we setup an timer after OOB finished. If the PHY is not up in
20s, do link reset. Notes: the 20s is an experience value.

Signed-off-by: Xiaofei Tan <tanxiaofei@huawei.com>
Signed-off-by: John Garry <john.garry@huawei.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
drivers/scsi/hisi_sas/hisi_sas.h
drivers/scsi/hisi_sas/hisi_sas_main.c
drivers/scsi/hisi_sas/hisi_sas_v2_hw.c
drivers/scsi/hisi_sas/hisi_sas_v3_hw.c

index 235b81998c9abdfdedf0942a6a0a65e63d2e2f33..9b35f84b5029411b0e88e2332cea6f8e6108dd24 100644 (file)
@@ -76,6 +76,8 @@
 
 #define HISI_SAS_PROT_MASK (HISI_SAS_DIF_PROT_MASK)
 
+#define HISI_SAS_WAIT_PHYUP_TIMEOUT 20
+
 struct hisi_hba;
 
 enum {
@@ -141,6 +143,7 @@ struct hisi_sas_phy {
        struct asd_sas_phy      sas_phy;
        struct sas_identify     identify;
        struct completion *reset_completion;
+       struct timer_list timer;
        spinlock_t lock;
        u64             port_id; /* from hw */
        u64             frame_rcvd_size;
@@ -522,6 +525,7 @@ extern void hisi_sas_init_mem(struct hisi_hba *hisi_hba);
 extern void hisi_sas_rst_work_handler(struct work_struct *work);
 extern void hisi_sas_sync_rst_work_handler(struct work_struct *work);
 extern void hisi_sas_kill_tasklets(struct hisi_hba *hisi_hba);
+extern void hisi_sas_phy_oob_ready(struct hisi_hba *hisi_hba, int phy_no);
 extern bool hisi_sas_notify_phy_event(struct hisi_sas_phy *phy,
                                enum hisi_sas_phy_event event);
 extern void hisi_sas_release_tasks(struct hisi_hba *hisi_hba);
index e44d2cd69de788a86661867aafce1f4df56a83e0..98ea029e5a35a1537007adb8489967f94c881fb6 100644 (file)
@@ -808,6 +808,30 @@ bool hisi_sas_notify_phy_event(struct hisi_sas_phy *phy,
 }
 EXPORT_SYMBOL_GPL(hisi_sas_notify_phy_event);
 
+static void hisi_sas_wait_phyup_timedout(struct timer_list *t)
+{
+       struct hisi_sas_phy *phy = from_timer(phy, t, timer);
+       struct hisi_hba *hisi_hba = phy->hisi_hba;
+       struct device *dev = hisi_hba->dev;
+       int phy_no = phy->sas_phy.id;
+
+       dev_warn(dev, "phy%d wait phyup timeout, issuing link reset\n", phy_no);
+       hisi_sas_notify_phy_event(phy, HISI_PHYE_LINK_RESET);
+}
+
+void hisi_sas_phy_oob_ready(struct hisi_hba *hisi_hba, int phy_no)
+{
+       struct hisi_sas_phy *phy = &hisi_hba->phy[phy_no];
+       struct device *dev = hisi_hba->dev;
+
+       if (!timer_pending(&phy->timer)) {
+               dev_dbg(dev, "phy%d OOB ready\n", phy_no);
+               phy->timer.expires = jiffies + HISI_SAS_WAIT_PHYUP_TIMEOUT * HZ;
+               add_timer(&phy->timer);
+       }
+}
+EXPORT_SYMBOL_GPL(hisi_sas_phy_oob_ready);
+
 static void hisi_sas_phy_init(struct hisi_hba *hisi_hba, int phy_no)
 {
        struct hisi_sas_phy *phy = &hisi_hba->phy[phy_no];
@@ -836,6 +860,8 @@ static void hisi_sas_phy_init(struct hisi_hba *hisi_hba, int phy_no)
                INIT_WORK(&phy->works[i], hisi_sas_phye_fns[i]);
 
        spin_lock_init(&phy->lock);
+
+       timer_setup(&phy->timer, hisi_sas_wait_phyup_timedout, 0);
 }
 
 static void hisi_sas_port_notify_formed(struct asd_sas_phy *sas_phy)
index 8a2a24bde8a5f7425a2005938d22d8eccf125b84..aa90f17a1cacd1f12dcf4df9c4fd29188ddf43c6 100644 (file)
@@ -2676,6 +2676,8 @@ static int phy_up_v2_hw(int phy_no, struct hisi_hba *hisi_hba)
        if (is_sata_phy_v2_hw(hisi_hba, phy_no))
                goto end;
 
+       del_timer(&phy->timer);
+
        if (phy_no == 8) {
                u32 port_state = hisi_sas_read32(hisi_hba, PORT_STATE);
 
@@ -2755,6 +2757,7 @@ static int phy_down_v2_hw(int phy_no, struct hisi_hba *hisi_hba)
        struct hisi_sas_port *port = phy->port;
        struct device *dev = hisi_hba->dev;
 
+       del_timer(&phy->timer);
        hisi_sas_phy_write32(hisi_hba, phy_no, PHYCTRL_NOT_RDY_MSK, 1);
 
        phy_state = hisi_sas_read32(hisi_hba, PHY_STATE);
@@ -2943,6 +2946,9 @@ static irqreturn_t int_chnl_int_v2_hw(int irq_no, void *p)
                        if (irq_value0 & CHL_INT0_SL_RX_BCST_ACK_MSK)
                                phy_bcast_v2_hw(phy_no, hisi_hba);
 
+                       if (irq_value0 & CHL_INT0_PHY_RDY_MSK)
+                               hisi_sas_phy_oob_ready(hisi_hba, phy_no);
+
                        hisi_sas_phy_write32(hisi_hba, phy_no,
                                        CHL_INT0, irq_value0
                                        & (~CHL_INT0_HOTPLUG_TOUT_MSK)
@@ -3226,6 +3232,8 @@ static irqreturn_t sata_int_v2_hw(int irq_no, void *p)
        unsigned long flags;
        int phy_no, offset;
 
+       del_timer(&phy->timer);
+
        phy_no = sas_phy->id;
        initial_fis = &hisi_hba->initial_fis[phy_no];
        fis = &initial_fis->fis;
index 4b628c20447d2483497ef93189c135d6ab52f37e..f1009e27d9bcbdecb471c56a04c954422d42146c 100644 (file)
@@ -1290,6 +1290,7 @@ static irqreturn_t phy_up_v3_hw(int phy_no, struct hisi_hba *hisi_hba)
        struct device *dev = hisi_hba->dev;
        unsigned long flags;
 
+       del_timer(&phy->timer);
        hisi_sas_phy_write32(hisi_hba, phy_no, PHYCTRL_PHY_ENA_MSK, 1);
 
        port_id = hisi_sas_read32(hisi_hba, PHY_PORT_NUM_MA);
@@ -1383,9 +1384,11 @@ end:
 
 static irqreturn_t phy_down_v3_hw(int phy_no, struct hisi_hba *hisi_hba)
 {
+       struct hisi_sas_phy *phy = &hisi_hba->phy[phy_no];
        u32 phy_state, sl_ctrl, txid_auto;
        struct device *dev = hisi_hba->dev;
 
+       del_timer(&phy->timer);
        hisi_sas_phy_write32(hisi_hba, phy_no, PHYCTRL_NOT_RDY_MSK, 1);
 
        phy_state = hisi_sas_read32(hisi_hba, PHY_STATE);
@@ -1554,6 +1557,19 @@ static void handle_chl_int2_v3_hw(struct hisi_hba *hisi_hba, int phy_no)
        hisi_sas_phy_write32(hisi_hba, phy_no, CHL_INT2, irq_value);
 }
 
+static void handle_chl_int0_v3_hw(struct hisi_hba *hisi_hba, int phy_no)
+{
+       u32 irq_value0 = hisi_sas_phy_read32(hisi_hba, phy_no, CHL_INT0);
+
+       if (irq_value0 & CHL_INT0_PHY_RDY_MSK)
+               hisi_sas_phy_oob_ready(hisi_hba, phy_no);
+
+       hisi_sas_phy_write32(hisi_hba, phy_no, CHL_INT0,
+                            irq_value0 & (~CHL_INT0_SL_RX_BCST_ACK_MSK)
+                            & (~CHL_INT0_SL_PHY_ENABLE_MSK)
+                            & (~CHL_INT0_NOT_RDY_MSK));
+}
+
 static irqreturn_t int_chnl_int_v3_hw(int irq_no, void *p)
 {
        struct hisi_hba *hisi_hba = p;
@@ -1564,8 +1580,8 @@ static irqreturn_t int_chnl_int_v3_hw(int irq_no, void *p)
                                & 0xeeeeeeee;
 
        while (irq_msk) {
-               u32 irq_value0 = hisi_sas_phy_read32(hisi_hba, phy_no,
-                                                    CHL_INT0);
+               if (irq_msk & (2 << (phy_no * 4)))
+                       handle_chl_int0_v3_hw(hisi_hba, phy_no);
 
                if (irq_msk & (4 << (phy_no * 4)))
                        handle_chl_int1_v3_hw(hisi_hba, phy_no);
@@ -1573,13 +1589,6 @@ static irqreturn_t int_chnl_int_v3_hw(int irq_no, void *p)
                if (irq_msk & (8 << (phy_no * 4)))
                        handle_chl_int2_v3_hw(hisi_hba, phy_no);
 
-               if (irq_msk & (2 << (phy_no * 4)) && irq_value0) {
-                       hisi_sas_phy_write32(hisi_hba, phy_no,
-                                       CHL_INT0, irq_value0
-                                       & (~CHL_INT0_SL_RX_BCST_ACK_MSK)
-                                       & (~CHL_INT0_SL_PHY_ENABLE_MSK)
-                                       & (~CHL_INT0_NOT_RDY_MSK));
-               }
                irq_msk &= ~(0xe << (phy_no * 4));
                phy_no++;
        }