linux-nvme.lists.infradead.org archive mirror
 help / color / mirror / Atom feed
* [RFC PATCH 3/5] nvme: to add support for nvme reset functions
@ 2019-12-11  2:34 tsutomu.owa
  0 siblings, 0 replies; only message in thread
From: tsutomu.owa @ 2019-12-11  2:34 UTC (permalink / raw)
  To: linux-nvme, sagi, kbusch, hch; +Cc: tsutomu.owa

This patch adds support for nvme reset functions described in NVMe spec
1.3c "7.3.2 Controller Level Reset" (conventional hot reset,
link down reset and function level reset).

Signed-off-by: Tsutomu OWA <tsutomu.owa@kioxia.com>
---
 drivers/nvme/host/core.c        | 122 ++++++++++++++++++++++++++++++++
 drivers/nvme/host/nvme.h        |   4 ++
 drivers/nvme/host/pci.c         |  11 +++
 include/linux/nvme.h            |   4 ++
 include/uapi/linux/nvme_ioctl.h |  43 +++++++++++
 5 files changed, 184 insertions(+)

diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c
index dfe37a525..65e3ef820 100644
--- a/drivers/nvme/host/core.c
+++ b/drivers/nvme/host/core.c
@@ -94,6 +94,113 @@ static void nvme_put_subsystem(struct nvme_subsystem *subsys);
 static void nvme_remove_invalid_namespaces(struct nvme_ctrl *ctrl,
 					   unsigned nsid);
 
+#ifdef CONFIG_NVME_PCI_RESET
+static int nvme_conventional_hot_reset(struct pci_dev *bridge)
+{
+	return pci_bridge_secondary_bus_reset(bridge);
+}
+
+#define PCIE_RESET_READY_POLL_MS	60000
+static int nvme_link_down_reset(struct pci_dev *pdev, struct pci_dev *bridge)
+{
+	/* link down */
+	pcie_capability_set_word(bridge, PCI_EXP_LNKCTL, PCI_EXP_LNKCTL_LD);
+
+	msleep(100);
+
+	/* link up */
+	pcie_capability_clear_word(bridge, PCI_EXP_LNKCTL, PCI_EXP_LNKCTL_LD);
+
+	return pci_dev_wait(pdev, "LNK", PCIE_RESET_READY_POLL_MS);
+}
+
+static int __pci_reset_dev_locked(struct nvme_ctrl *ctrl,
+					struct pci_dev *pdev, unsigned int cmd)
+{
+	int rc;
+	struct pci_dev *bridge = pci_upstream_bridge(pdev);
+
+	might_sleep();
+
+	if (!pci_is_pcie(pdev) || !bridge || !pci_is_pcie(bridge)) {
+		dev_err(ctrl->device,
+				"  no bridge found (or not PCIe dev)\n");
+		return -ENOTTY;
+	}
+
+	if (!pci_wait_for_pending_transaction(pdev))
+		dev_warn(ctrl->device,
+			"timed out waiting for pending transaction\n");
+	switch (cmd) {
+	case NVME_IOCTL_CONVENTIONAL_HOT_RESET:
+		rc = nvme_conventional_hot_reset(bridge);
+		break;
+	case NVME_IOCTL_LINK_DOWN_RESET:
+		rc = nvme_link_down_reset(pdev, bridge);
+		break;
+	default:
+		dev_err(ctrl->device,
+			"%s- unknown reset (%u)\n", __func__, cmd);
+		rc = -ENOTTY;
+	}
+
+	if (rc) {
+		dev_err(ctrl->device,
+			"%s: reset (%s) failed. rc=%d\n",
+			__func__, get_nvme_ioctl_name(cmd), rc);
+		return rc;
+	}
+
+	/* to reset mps (and misc.) */
+	pci_configure_device(pdev);
+
+	return rc;
+}
+
+static int nvme_pci_reset_dev(struct nvme_ctrl *ctrl,
+					struct pci_dev *pdev, unsigned int cmd)
+{
+	int rc;
+
+	pci_dev_lock(pdev);
+
+	/*
+	 * do not disable the device
+	 * as pci_dev_save_and_disable() calls nvme_disable_device().
+	 */
+	ctrl->ctrl_config |= NVME_CC_NO_ENABLE;
+
+	pci_dev_save_and_disable(pdev);		/* to save pcie status */
+
+	rc = __pci_reset_dev_locked(ctrl, pdev, cmd);
+
+	pci_dev_restore(pdev);
+
+	/* play safe */
+	ctrl->ctrl_config &= ~NVME_CC_NO_ENABLE;
+	pci_dev_unlock(pdev);
+
+	return rc;
+}
+
+static int nvme_pci_reset_common(struct nvme_ctrl *ctrl, unsigned int cmd)
+{
+	struct pci_dev *pdev;
+
+	dev_info(ctrl->device, "reset %s\n", get_nvme_ioctl_name(cmd));
+
+	if (!ctrl->ops->pci_dev)
+		return -ENOTTY;
+
+	pdev = ctrl->ops->pci_dev(ctrl);
+	if (cmd == NVME_IOCTL_FUNCTION_LEVEL_RESET)
+		return pci_reset_function(pdev);
+	else
+		return nvme_pci_reset_dev(ctrl, pdev, cmd);
+}
+
+#endif /* CONFIG_NVME_PCI_RESET */
+
 static void nvme_set_queue_dying(struct nvme_ns *ns)
 {
 	/*
@@ -2097,6 +2204,15 @@ int nvme_disable_ctrl(struct nvme_ctrl *ctrl)
 	ctrl->ctrl_config &= ~NVME_CC_SHN_MASK;
 	ctrl->ctrl_config &= ~NVME_CC_ENABLE;
 
+#ifdef CONFIG_NVME_PCI_RESET
+	if (ctrl->ctrl_config & NVME_CC_NO_ENABLE) {
+		dev_dbg(ctrl->device,
+				"%s: NVME_CC_NO_ENABLE set\n", __func__);
+		ctrl->ctrl_config &= ~NVME_CC_NO_ENABLE;
+		return 0;
+	}
+#endif /* CONFIG_NVME_PCI_RESET */
+
 	ret = ctrl->ops->reg_write32(ctrl, NVME_REG_CC, ctrl->ctrl_config);
 	if (ret)
 		return ret;
@@ -2979,6 +3095,12 @@ static long nvme_dev_ioctl(struct file *file, unsigned int cmd,
 	case NVME_IOCTL_RESCAN:
 		nvme_queue_scan(ctrl);
 		return 0;
+#ifdef CONFIG_NVME_PCI_RESET
+	case NVME_IOCTL_CONVENTIONAL_HOT_RESET:		/* fall thru */
+	case NVME_IOCTL_LINK_DOWN_RESET:
+	case NVME_IOCTL_FUNCTION_LEVEL_RESET:
+		return nvme_pci_reset_common(ctrl, cmd);
+#endif /* CONFIG_NVME_PCI_RESET */
 	default:
 		return -ENOTTY;
 	}
diff --git a/drivers/nvme/host/nvme.h b/drivers/nvme/host/nvme.h
index 3b9cbe066..0b5f59b50 100644
--- a/drivers/nvme/host/nvme.h
+++ b/drivers/nvme/host/nvme.h
@@ -401,6 +401,10 @@ struct nvme_ctrl_ops {
 	void (*submit_async_event)(struct nvme_ctrl *ctrl);
 	void (*delete_ctrl)(struct nvme_ctrl *ctrl);
 	int (*get_address)(struct nvme_ctrl *ctrl, char *buf, int size);
+
+#ifdef CONFIG_NVME_PCI_RESET
+	struct pci_dev *(*pci_dev)(struct nvme_ctrl *ctrl);
+#endif /* CONFIG_NVME_PCI_RESET */
 };
 
 #ifdef CONFIG_FAULT_INJECTION_DEBUG_FS
diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c
index dcaad5831..5a78165ec 100644
--- a/drivers/nvme/host/pci.c
+++ b/drivers/nvme/host/pci.c
@@ -2694,6 +2694,13 @@ static int nvme_pci_get_address(struct nvme_ctrl *ctrl, char *buf, int size)
 	return snprintf(buf, size, "%s", dev_name(&pdev->dev));
 }
 
+#ifdef CONFIG_NVME_PCI_RESET
+static struct pci_dev *nvme_pci_to_pci_dev(struct nvme_ctrl *ctrl)
+{
+	return to_pci_dev(to_nvme_dev(ctrl)->dev);
+}
+#endif /* CONFIG_NVME_PCI_RESET */
+
 static const struct nvme_ctrl_ops nvme_pci_ctrl_ops = {
 	.name			= "pcie",
 	.module			= THIS_MODULE,
@@ -2705,6 +2712,10 @@ static const struct nvme_ctrl_ops nvme_pci_ctrl_ops = {
 	.free_ctrl		= nvme_pci_free_ctrl,
 	.submit_async_event	= nvme_pci_submit_async_event,
 	.get_address		= nvme_pci_get_address,
+
+#ifdef CONFIG_NVME_PCI_RESET
+	.pci_dev		= nvme_pci_to_pci_dev,
+#endif /* CONFIG_NVME_PCI_RESET */
 };
 
 static int nvme_dev_map(struct nvme_dev *dev)
diff --git a/include/linux/nvme.h b/include/linux/nvme.h
index 3d5189f46..5c2ce7374 100644
--- a/include/linux/nvme.h
+++ b/include/linux/nvme.h
@@ -160,6 +160,10 @@ enum {
 
 enum {
 	NVME_CC_ENABLE		= 1 << 0,
+#ifdef CONFIG_NVME_PCI_RESET
+	NVME_CC_NO_ENABLE	= 1 << 30,	/* conventional hot reset or */
+						/* link down reset is going. */
+#endif /* CONFIG_NVME_PCI_RESET */
 	NVME_CC_CSS_NVM		= 0 << 4,
 	NVME_CC_EN_SHIFT	= 0,
 	NVME_CC_CSS_SHIFT	= 4,
diff --git a/include/uapi/linux/nvme_ioctl.h b/include/uapi/linux/nvme_ioctl.h
index d99b5a772..36a77ff75 100644
--- a/include/uapi/linux/nvme_ioctl.h
+++ b/include/uapi/linux/nvme_ioctl.h
@@ -79,4 +79,47 @@ struct nvme_passthru_cmd64 {
 #define NVME_IOCTL_ADMIN64_CMD	_IOWR('N', 0x47, struct nvme_passthru_cmd64)
 #define NVME_IOCTL_IO64_CMD	_IOWR('N', 0x48, struct nvme_passthru_cmd64)
 
+#ifdef CONFIG_NVME_PCI_RESET
+#define NVME_IOCTL_CONVENTIONAL_HOT_RESET	_IO('N', 0x49)
+#define NVME_IOCTL_LINK_DOWN_RESET		_IO('N', 0x4a)
+#define NVME_IOCTL_FUNCTION_LEVEL_RESET		_IO('N', 0x4b)
+#endif /* CONFIG_NVME_PCI_RESET */
+
+static inline char *get_nvme_ioctl_name(unsigned int cmd)
+{
+	switch (cmd) {
+	case NVME_IOCTL_ID:
+		return "id";
+	case NVME_IOCTL_ADMIN_CMD:
+		return "admin cmd";
+	case NVME_IOCTL_SUBMIT_IO:
+		return "submit io";
+	case NVME_IOCTL_IO_CMD:
+		return "io cmd";
+	case NVME_IOCTL_RESET:
+		return "controller reset";
+	case NVME_IOCTL_SUBSYS_RESET:
+		return "subsystem reset";
+	case NVME_IOCTL_RESCAN:
+		return "rescan";
+	case NVME_IOCTL_ADMIN64_CMD:
+		return "admin64 cmd";
+	case NVME_IOCTL_IO64_CMD:
+		return "io64 cmd";
+#ifdef CONFIG_NVME_PCI_RESET
+	case NVME_IOCTL_CONVENTIONAL_HOT_RESET:
+		return "conventional hot reset";
+	case NVME_IOCTL_LINK_DOWN_RESET:
+		return "link down reset";
+	case NVME_IOCTL_FUNCTION_LEVEL_RESET:
+		return "function level reset";
+#endif /* CONFIG_NVME_PCI_RESET */
+	default:
+		return "Unknown";
+	}
+
+	// should not reach here
+	return "Unknown";
+}
+
 #endif /* _UAPI_LINUX_NVME_IOCTL_H */
-- 
2.17.1


_______________________________________________
linux-nvme mailing list
linux-nvme@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-nvme

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2019-12-11  2:35 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-12-11  2:34 [RFC PATCH 3/5] nvme: to add support for nvme reset functions tsutomu.owa

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).