Commit 8fa9a7bd authored by Xiang Chen's avatar Xiang Chen Committed by Martin K. Petersen
Browse files

scsi: hisi_sas: use wait_for_completion_timeout() when clearing ITCT

When injecting 2bit ecc errors, it will cause confusion inside SAS
controller which needs host reset to recover it. If a device is gone at the
same times inject 2bit ecc errors, we may not receive the ITCT interrupt so
it will wait for completion in clear_itct_v3_hw() all the time. And host
reset will also not occur because it can't require hisi_hba->sem, so the
system will be suspended.

To solve the issue, use wait_for_completion_timeout() instead of
wait_for_completion(), and also don't mark the gone device as
SAS_PHY_UNUSED when device gone.

Link: https://lore.kernel.org/r/1571926105-74636-4-git-send-email-john.garry@huawei.com


Signed-off-by: default avatarXiang Chen <chenxiang66@hisilicon.com>
Signed-off-by: default avatarJohn Garry <john.garry@huawei.com>
Signed-off-by: default avatarMartin K. Petersen <martin.petersen@oracle.com>
parent 65a3b8bd
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -84,6 +84,7 @@
#define HISI_SAS_PROT_MASK (HISI_SAS_DIF_PROT_MASK | HISI_SAS_DIX_PROT_MASK)

#define HISI_SAS_WAIT_PHYUP_TIMEOUT 20
#define CLEAR_ITCT_TIMEOUT	20

struct hisi_hba;

@@ -296,7 +297,7 @@ struct hisi_sas_hw {
	void (*phy_set_linkrate)(struct hisi_hba *hisi_hba, int phy_no,
			struct sas_phy_linkrates *linkrates);
	enum sas_linkrate (*phy_get_max_linkrate)(void);
	void (*clear_itct)(struct hisi_hba *hisi_hba,
	int (*clear_itct)(struct hisi_hba *hisi_hba,
			  struct hisi_sas_device *dev);
	void (*free_device)(struct hisi_sas_device *sas_dev);
	int (*get_wideport_bitmap)(struct hisi_hba *hisi_hba, int port_id);
+6 −2
Original line number Diff line number Diff line
@@ -1045,6 +1045,7 @@ static void hisi_sas_dev_gone(struct domain_device *device)
	struct hisi_sas_device *sas_dev = device->lldd_dev;
	struct hisi_hba *hisi_hba = dev_to_hisi_hba(device);
	struct device *dev = hisi_hba->dev;
	int ret = 0;

	dev_info(dev, "dev[%d:%x] is gone\n",
		 sas_dev->device_id, sas_dev->dev_type);
@@ -1056,12 +1057,15 @@ static void hisi_sas_dev_gone(struct domain_device *device)

		hisi_sas_dereg_device(hisi_hba, device);

		hisi_hba->hw->clear_itct(hisi_hba, sas_dev);
		ret = hisi_hba->hw->clear_itct(hisi_hba, sas_dev);
		device->lldd_dev = NULL;
	}

	if (hisi_hba->hw->free_device)
		hisi_hba->hw->free_device(sas_dev);

	/* Don't mark it as SAS_PHY_UNUSED if failed to clear ITCT */
	if (!ret)
		sas_dev->dev_type = SAS_PHY_UNUSED;
	sas_dev->sas_device = NULL;
	up(&hisi_hba->sem);
+4 −2
Original line number Diff line number Diff line
@@ -531,7 +531,7 @@ static void setup_itct_v1_hw(struct hisi_hba *hisi_hba,
				(0xff00ULL << ITCT_HDR_REJ_OPEN_TL_OFF));
}

static void clear_itct_v1_hw(struct hisi_hba *hisi_hba,
static int clear_itct_v1_hw(struct hisi_hba *hisi_hba,
			    struct hisi_sas_device *sas_dev)
{
	u64 dev_id = sas_dev->device_id;
@@ -551,6 +551,8 @@ static void clear_itct_v1_hw(struct hisi_hba *hisi_hba,
	qw0 = le64_to_cpu(itct->qw0);
	qw0 &= ~ITCT_HDR_VALID_MSK;
	itct->qw0 = cpu_to_le64(qw0);

	return 0;
}

static int reset_hw_v1_hw(struct hisi_hba *hisi_hba)
+10 −3
Original line number Diff line number Diff line
@@ -974,13 +974,14 @@ static void setup_itct_v2_hw(struct hisi_hba *hisi_hba,
					(0x1ULL << ITCT_HDR_RTOLT_OFF));
}

static void clear_itct_v2_hw(struct hisi_hba *hisi_hba,
static int clear_itct_v2_hw(struct hisi_hba *hisi_hba,
			    struct hisi_sas_device *sas_dev)
{
	DECLARE_COMPLETION_ONSTACK(completion);
	u64 dev_id = sas_dev->device_id;
	struct hisi_sas_itct *itct = &hisi_hba->itct[dev_id];
	u32 reg_val = hisi_sas_read32(hisi_hba, ENT_INT_SRC3);
	struct device *dev = hisi_hba->dev;
	int i;

	sas_dev->completion = &completion;
@@ -990,13 +991,19 @@ static void clear_itct_v2_hw(struct hisi_hba *hisi_hba,
		hisi_sas_write32(hisi_hba, ENT_INT_SRC3,
				 ENT_INT_SRC3_ITC_INT_MSK);

	/* need to set register twice to clear ITCT for v2 hw */
	for (i = 0; i < 2; i++) {
		reg_val = ITCT_CLR_EN_MSK | (dev_id & ITCT_DEV_MSK);
		hisi_sas_write32(hisi_hba, ITCT_CLR, reg_val);
		wait_for_completion(sas_dev->completion);
		if (!wait_for_completion_timeout(sas_dev->completion,
						 CLEAR_ITCT_TIMEOUT * HZ)) {
			dev_warn(dev, "failed to clear ITCT\n");
			return -ETIMEDOUT;
		}

		memset(itct, 0, sizeof(struct hisi_sas_itct));
	}
	return 0;
}

static void free_device_v2_hw(struct hisi_sas_device *sas_dev)
+10 −3
Original line number Diff line number Diff line
@@ -795,13 +795,14 @@ static void setup_itct_v3_hw(struct hisi_hba *hisi_hba,
					(0x1ULL << ITCT_HDR_RTOLT_OFF));
}

static void clear_itct_v3_hw(struct hisi_hba *hisi_hba,
static int clear_itct_v3_hw(struct hisi_hba *hisi_hba,
			    struct hisi_sas_device *sas_dev)
{
	DECLARE_COMPLETION_ONSTACK(completion);
	u64 dev_id = sas_dev->device_id;
	struct hisi_sas_itct *itct = &hisi_hba->itct[dev_id];
	u32 reg_val = hisi_sas_read32(hisi_hba, ENT_INT_SRC3);
	struct device *dev = hisi_hba->dev;

	sas_dev->completion = &completion;

@@ -814,8 +815,14 @@ static void clear_itct_v3_hw(struct hisi_hba *hisi_hba,
	reg_val = ITCT_CLR_EN_MSK | (dev_id & ITCT_DEV_MSK);
	hisi_sas_write32(hisi_hba, ITCT_CLR, reg_val);

	wait_for_completion(sas_dev->completion);
	if (!wait_for_completion_timeout(sas_dev->completion,
					 CLEAR_ITCT_TIMEOUT * HZ)) {
		dev_warn(dev, "failed to clear ITCT\n");
		return -ETIMEDOUT;
	}

	memset(itct, 0, sizeof(struct hisi_sas_itct));
	return 0;
}

static void dereg_device_v3_hw(struct hisi_hba *hisi_hba,