[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