[PATCH 5/5] nvme: ahci remap support

Dan Williams dan.j.williams at intel.com
Fri Oct 21 17:25:49 PDT 2016


Provide a platform driver for the nvme resources that may be hidden /
remapped behind an ahci bar. The implementation is the standard baseline
nvme driver with callouts to "ahci_nvme" specific routines to replace
"pci-express" functionality.

Signed-off-by: Dan Williams <dan.j.williams at intel.com>
---
 drivers/ata/Kconfig        |    1 
 drivers/nvme/host/Makefile |    2 
 drivers/nvme/host/ahci.c   |  198 ++++++++++++++++++++++++++++++++++++++++++++
 drivers/nvme/host/pci.c    |   19 +++-
 drivers/nvme/host/pci.h    |    9 ++
 5 files changed, 222 insertions(+), 7 deletions(-)
 create mode 100644 drivers/nvme/host/ahci.c

diff --git a/drivers/ata/Kconfig b/drivers/ata/Kconfig
index b9e46f2c69c1..189adbcfe4d3 100644
--- a/drivers/ata/Kconfig
+++ b/drivers/ata/Kconfig
@@ -92,6 +92,7 @@ config SATA_AHCI
 
 config SATA_AHCI_NVME
 	tristate "AHCI: Remapped NVMe support"
+	default SATA_AHCI
 	depends on SATA_AHCI
 	depends on BLK_DEV_NVME
 	help
diff --git a/drivers/nvme/host/Makefile b/drivers/nvme/host/Makefile
index 47abcec23514..e665b6232a10 100644
--- a/drivers/nvme/host/Makefile
+++ b/drivers/nvme/host/Makefile
@@ -2,12 +2,14 @@ obj-$(CONFIG_NVME_CORE)			+= nvme-core.o
 obj-$(CONFIG_BLK_DEV_NVME)		+= nvme.o
 obj-$(CONFIG_NVME_FABRICS)		+= nvme-fabrics.o
 obj-$(CONFIG_NVME_RDMA)			+= nvme-rdma.o
+obj-$(CONFIG_SATA_AHCI_NVME)		+= ahci-nvme.o
 
 nvme-core-y				:= core.o
 nvme-core-$(CONFIG_BLK_DEV_NVME_SCSI)	+= scsi.o
 nvme-core-$(CONFIG_NVM)			+= lightnvm.o
 
 nvme-y					+= pci.o
+ahci-nvme-y				+= ahci.o
 
 nvme-fabrics-y				+= fabrics.o
 
diff --git a/drivers/nvme/host/ahci.c b/drivers/nvme/host/ahci.c
new file mode 100644
index 000000000000..ee2aaadc5213
--- /dev/null
+++ b/drivers/nvme/host/ahci.c
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2011-2016, Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include "pci.h"
+
+struct ahci_nvme_data {
+	atomic_t enabled;
+};
+
+static struct ahci_nvme_data *to_ahci_nvme_data(struct nvme_dev *dev)
+{
+	return dev->dev->platform_data;
+}
+
+static int ahci_nvme_enable(struct nvme_dev *dev)
+{
+	int rc;
+	struct resource *res;
+	struct device *ddev = dev->dev;
+	struct device *parent = ddev->parent;
+	struct ahci_nvme_data *adata = to_ahci_nvme_data(dev);
+	struct platform_device *pdev = to_platform_device(ddev);
+
+	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (!res)
+		return -ENXIO;
+
+	/* parent ahci device determines the dma mask */
+	if (dma_supported(parent, DMA_BIT_MASK(64)))
+		rc = dma_coerce_mask_and_coherent(ddev, DMA_BIT_MASK(64));
+	else if (dma_supported(parent, DMA_BIT_MASK(32)))
+		rc = dma_coerce_mask_and_coherent(ddev, DMA_BIT_MASK(32));
+	else
+		rc = -ENXIO;
+	if (rc)
+		return rc;
+
+	rc = nvme_enable(dev);
+	if (rc)
+		return rc;
+
+	atomic_inc(&adata->enabled);
+
+	return 0;
+}
+
+static int ahci_nvme_is_enabled(struct nvme_dev *dev)
+{
+	struct ahci_nvme_data *adata = to_ahci_nvme_data(dev);
+
+	return atomic_read(&adata->enabled) > 0;
+}
+
+static void ahci_nvme_disable(struct nvme_dev *dev)
+{
+	struct ahci_nvme_data *adata = to_ahci_nvme_data(dev);
+
+	/*
+	 * bug compatible with nvme_pci_disable() which also has this
+	 * potential disable race
+	 */
+	if (ahci_nvme_is_enabled(dev))
+		atomic_dec(&adata->enabled);
+}
+
+static int ahci_nvme_is_offline(struct nvme_dev *dev)
+{
+	return 0;
+}
+
+static bool ahci_nvme_is_present(struct nvme_dev *dev)
+{
+	return true;
+}
+
+static int ahci_nvme_map_irq(struct nvme_dev *dev, int nr_io_queues)
+{
+	struct platform_device *pdev = to_platform_device(dev->dev);
+	struct nvme_queue *adminq = dev->queues[0];
+	struct resource *res;
+
+	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (!res)
+		return -ENXIO;
+
+	/* Deregister the admin queue's interrupt */
+	free_irq(res->start, adminq);
+
+	return min_t(int, resource_size(res), nr_io_queues);
+}
+
+static int ahci_nvme_q_irq(struct nvme_queue *nvmeq)
+{
+	struct resource *res;
+	struct nvme_dev *dev = nvmeq->dev;
+	struct platform_device *pdev = to_platform_device(dev->dev);
+
+	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (!res)
+		return -ENXIO;
+
+	if (resource_size(res) > nvmeq->qid)
+		return res->start + nvmeq->qid;
+	return res->start;
+}
+
+static const struct nvme_dev_ops ahci_nvme_dev_ops = {
+	.enable			= ahci_nvme_enable,
+	.disable		= ahci_nvme_disable,
+	.map_irq		= ahci_nvme_map_irq,
+	.q_irq			= ahci_nvme_q_irq,
+	.is_enabled		= ahci_nvme_is_enabled,
+	.is_offline		= ahci_nvme_is_offline,
+	.is_present		= ahci_nvme_is_present,
+};
+
+static void ahci_nvme_shutdown(struct platform_device *pdev)
+{
+	struct nvme_dev *dev = platform_get_drvdata(pdev);
+
+	nvme_dev_disable(dev, true);
+}
+
+static int ahci_nvme_remove(struct platform_device *pdev)
+{
+	nvme_remove(&pdev->dev);
+	pdev->dev.platform_data = NULL;
+	return 0;
+}
+
+static struct platform_device_id ahci_nvme_id_table[] = {
+	{ .name = "ahci_nvme", },
+	{},
+};
+
+static int ahci_nvme_probe(struct platform_device *pdev)
+{
+	struct device *ddev = &pdev->dev;
+	struct ahci_nvme_data *adata;
+	struct resource *res;
+
+	adata = devm_kzalloc(ddev, sizeof(*adata), GFP_KERNEL);
+	if (!adata)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENXIO;
+
+	if (!devm_request_mem_region(ddev, res->start, resource_size(res),
+				dev_name(ddev)))
+		return -EBUSY;
+
+	ddev->platform_data = adata;
+	return nvme_probe(ddev, res, &ahci_nvme_dev_ops, 0);
+}
+
+static SIMPLE_DEV_PM_OPS(ahci_nvme_dev_pm_ops, nvme_suspend, nvme_resume);
+
+static struct platform_driver ahci_nvme_driver = {
+	.driver		= {
+		.name	= "ahci_nvme",
+		.pm     = &ahci_nvme_dev_pm_ops,
+	},
+	.id_table	= ahci_nvme_id_table,
+	.probe		= ahci_nvme_probe,
+	.remove		= ahci_nvme_remove,
+	.shutdown	= ahci_nvme_shutdown,
+};
+
+static __init int ahci_nvme_init(void)
+{
+	return platform_driver_register(&ahci_nvme_driver);
+}
+
+static __exit void ahci_nvme_exit(void)
+{
+	platform_driver_unregister(&ahci_nvme_driver);
+}
+
+MODULE_DEVICE_TABLE(platform, ahci_nvme_id_table);
+module_init(ahci_nvme_init);
+module_exit(ahci_nvme_exit);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Intel Corporation");
diff --git a/drivers/nvme/host/pci.c b/drivers/nvme/host/pci.c
index 418ccc1c0cf7..2814caba9e2e 100644
--- a/drivers/nvme/host/pci.c
+++ b/drivers/nvme/host/pci.c
@@ -71,7 +71,6 @@ struct nvme_queue;
 
 static int nvme_reset(struct nvme_dev *dev);
 static void nvme_process_cq(struct nvme_queue *nvmeq);
-static void nvme_dev_disable(struct nvme_dev *dev, bool shutdown);
 
 static inline struct nvme_dev *to_nvme_dev(struct nvme_ctrl *ctrl)
 {
@@ -1525,7 +1524,7 @@ static int nvme_dev_add(struct nvme_dev *dev)
 	return 0;
 }
 
-static int nvme_enable(struct nvme_dev *dev)
+int nvme_enable(struct nvme_dev *dev)
 {
 	u64 cap;
 
@@ -1540,6 +1539,7 @@ static int nvme_enable(struct nvme_dev *dev)
 
 	return 0;
 }
+EXPORT_SYMBOL_GPL(nvme_enable);
 
 static int nvme_pci_enable(struct nvme_dev *dev)
 {
@@ -1618,7 +1618,7 @@ static bool nvme_pci_is_present(struct nvme_dev *dev)
 	return pci_device_is_present(to_pci_dev(dev->dev));
 }
 
-static void nvme_dev_disable(struct nvme_dev *dev, bool shutdown)
+void nvme_dev_disable(struct nvme_dev *dev, bool shutdown)
 {
 	int i;
 	u32 csts = -1;
@@ -1651,6 +1651,7 @@ static void nvme_dev_disable(struct nvme_dev *dev, bool shutdown)
 	blk_mq_tagset_busy_iter(&dev->admin_tagset, nvme_cancel_request, &dev->ctrl);
 	mutex_unlock(&dev->shutdown_lock);
 }
+EXPORT_SYMBOL_GPL(nvme_dev_disable);
 
 static int nvme_setup_prp_pools(struct nvme_dev *dev)
 {
@@ -1841,7 +1842,7 @@ static const struct nvme_dev_ops nvme_pci_dev_ops = {
 	.is_present		= nvme_pci_is_present,
 };
 
-static int nvme_probe(struct device *ddev, struct resource *res,
+int nvme_probe(struct device *ddev, struct resource *res,
 		const struct nvme_dev_ops *ops, unsigned long quirks)
 {
 	int node, result = -ENOMEM;
@@ -1910,6 +1911,7 @@ static int nvme_probe(struct device *ddev, struct resource *res,
 	kfree(dev);
 	return result;
 }
+EXPORT_SYMBOL_GPL(nvme_probe);
 
 static void nvme_pci_release_regions(void *data)
 {
@@ -1957,7 +1959,7 @@ static void nvme_pci_shutdown(struct pci_dev *pdev)
  * state. This function must not have any dependencies on the device state in
  * order to proceed.
  */
-static void nvme_remove(struct device *ddev)
+void nvme_remove(struct device *ddev)
 {
 	struct nvme_dev *dev = dev_get_drvdata(ddev);
 
@@ -1977,6 +1979,7 @@ static void nvme_remove(struct device *ddev)
 	nvme_release_prp_pools(dev);
 	nvme_put_ctrl(&dev->ctrl);
 }
+EXPORT_SYMBOL_GPL(nvme_remove);
 
 static void nvme_pci_remove(struct pci_dev *pdev)
 {
@@ -2002,21 +2005,23 @@ static int nvme_pci_sriov_configure(struct pci_dev *pdev, int numvfs)
 }
 
 #ifdef CONFIG_PM_SLEEP
-static int nvme_suspend(struct device *dev)
+int nvme_suspend(struct device *dev)
 {
 	struct nvme_dev *ndev = dev_get_drvdata(dev);
 
 	nvme_dev_disable(ndev, true);
 	return 0;
 }
+EXPORT_SYMBOL_GPL(nvme_suspend);
 
-static int nvme_resume(struct device *dev)
+int nvme_resume(struct device *dev)
 {
 	struct nvme_dev *ndev = dev_get_drvdata(dev);
 
 	queue_work(nvme_workq, &ndev->reset_work);
 	return 0;
 }
+EXPORT_SYMBOL_GPL(nvme_resume);
 #endif
 
 static SIMPLE_DEV_PM_OPS(nvme_dev_pm_ops, nvme_suspend, nvme_resume);
diff --git a/drivers/nvme/host/pci.h b/drivers/nvme/host/pci.h
index 62b658abb886..f1a3e928ed80 100644
--- a/drivers/nvme/host/pci.h
+++ b/drivers/nvme/host/pci.h
@@ -14,6 +14,7 @@
 #ifndef __NVME_PCI_H__
 #define __NVME_PCI_H__
 #include <linux/blk-mq.h>
+#include "nvme.h"
 
 struct nvme_queue;
 struct nvme_dev;
@@ -86,4 +87,12 @@ struct nvme_queue {
 	u8 cq_phase;
 	u8 cqe_seen;
 };
+
+int nvme_probe(struct device *ddev, struct resource *res,
+		const struct nvme_dev_ops *ops, unsigned long quirks);
+void nvme_remove(struct device *ddev);
+int nvme_enable(struct nvme_dev *dev);
+void nvme_dev_disable(struct nvme_dev *dev, bool shutdown);
+int nvme_suspend(struct device *dev);
+int nvme_resume(struct device *dev);
 #endif /* __NVME_PCI_H__ */




More information about the Linux-nvme mailing list