[RFC PATCH 19/22] PCI: endpoint: Add EP function driver to provide VHOST interface

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Add a new endpoint function driver to register VHOST device and
provide interface for the VHOST driver to access virtqueues
created by the remote host (using VIRTIO).

Signed-off-by: Kishon Vijay Abraham I <kishon@xxxxxx>
---
 drivers/pci/endpoint/functions/Kconfig        |   11 +
 drivers/pci/endpoint/functions/Makefile       |    1 +
 .../pci/endpoint/functions/pci-epf-vhost.c    | 1144 +++++++++++++++++
 drivers/vhost/vhost_cfs.c                     |   13 -
 include/linux/vhost.h                         |   14 +
 5 files changed, 1170 insertions(+), 13 deletions(-)
 create mode 100644 drivers/pci/endpoint/functions/pci-epf-vhost.c

diff --git a/drivers/pci/endpoint/functions/Kconfig b/drivers/pci/endpoint/functions/Kconfig
index 55ac7bb2d469..21830576e1f4 100644
--- a/drivers/pci/endpoint/functions/Kconfig
+++ b/drivers/pci/endpoint/functions/Kconfig
@@ -24,3 +24,14 @@ config PCI_EPF_NTB
 	   device tree.
 
 	   If in doubt, say "N" to disable Endpoint NTB driver.
+
+config PCI_EPF_VHOST
+	tristate "PCI Endpoint VHOST driver"
+	depends on PCI_ENDPOINT
+	help
+	   Select this configuration option to enable the VHOST driver
+	   for PCI Endpoint. EPF VHOST driver implements VIRTIO backend
+	   for EPF and uses the VHOST framework to bind any VHOST driver
+	   to the VHOST device created by this driver.
+
+	   If in doubt, say "N" to disable Endpoint VHOST driver.
diff --git a/drivers/pci/endpoint/functions/Makefile b/drivers/pci/endpoint/functions/Makefile
index 96ab932a537a..39d4f9daf63a 100644
--- a/drivers/pci/endpoint/functions/Makefile
+++ b/drivers/pci/endpoint/functions/Makefile
@@ -5,3 +5,4 @@
 
 obj-$(CONFIG_PCI_EPF_TEST)		+= pci-epf-test.o
 obj-$(CONFIG_PCI_EPF_NTB)		+= pci-epf-ntb.o
+obj-$(CONFIG_PCI_EPF_VHOST)		+= pci-epf-vhost.o
diff --git a/drivers/pci/endpoint/functions/pci-epf-vhost.c b/drivers/pci/endpoint/functions/pci-epf-vhost.c
new file mode 100644
index 000000000000..d090e5e88575
--- /dev/null
+++ b/drivers/pci/endpoint/functions/pci-epf-vhost.c
@@ -0,0 +1,1144 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * Endpoint Function Driver to implement VHOST functionality
+ *
+ * Copyright (C) 2020 Texas Instruments
+ * Author: Kishon Vijay Abraham I <kishon@xxxxxx>
+ */
+
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/vhost.h>
+#include <linux/vringh.h>
+
+#include <linux/pci-epc.h>
+#include <linux/pci-epf.h>
+
+#include <uapi/linux/virtio_pci.h>
+
+#define MAX_VQS		8
+
+#define VHOST_QUEUE_STATUS_ENABLE	BIT(0)
+
+#define VHOST_DEVICE_CONFIG_SIZE	1024
+#define EPF_VHOST_MAX_INTERRUPTS	(MAX_VQS + 1)
+
+static struct workqueue_struct *kpcivhost_workqueue;
+
+struct epf_vhost_queue {
+	struct delayed_work cmd_handler;
+	struct vhost_virtqueue *vq;
+	struct epf_vhost *vhost;
+	phys_addr_t phys_addr;
+	void __iomem *addr;
+	unsigned int size;
+};
+
+struct epf_vhost {
+	const struct pci_epc_features *epc_features;
+	struct epf_vhost_queue vqueue[MAX_VQS];
+	struct delayed_work cmd_handler;
+	struct delayed_work cfs_work;
+	struct epf_vhost_reg *reg;
+	struct config_group group;
+	size_t msix_table_offset;
+	struct vhost_dev vdev;
+	struct pci_epf *epf;
+	struct vring vring;
+	int msix_bar;
+};
+
+static inline struct epf_vhost *to_epf_vhost_from_ci(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct epf_vhost, group);
+}
+
+#define to_epf_vhost(v) container_of((v), struct epf_vhost, vdev)
+
+struct epf_vhost_reg_queue {
+	u8 cmd;
+	u8 cmd_status;
+	u16 status;
+	u16 num_buffers;
+	u16 msix_vector;
+	u64 queue_addr;
+} __packed;
+
+enum queue_cmd {
+	QUEUE_CMD_NONE,
+	QUEUE_CMD_ACTIVATE,
+	QUEUE_CMD_DEACTIVATE,
+	QUEUE_CMD_NOTIFY,
+};
+
+enum queue_cmd_status {
+	QUEUE_CMD_STATUS_NONE,
+	QUEUE_CMD_STATUS_OKAY,
+	QUEUE_CMD_STATUS_ERROR,
+};
+
+struct epf_vhost_reg {
+	u64 host_features;
+	u64 guest_features;
+	u16 msix_config;
+	u16 num_queues;
+	u8 device_status;
+	u8 config_generation;
+	u32 isr;
+	u8 cmd;
+	u8 cmd_status;
+	struct epf_vhost_reg_queue vq[MAX_VQS];
+} __packed;
+
+enum host_cmd {
+	HOST_CMD_NONE,
+	HOST_CMD_SET_STATUS,
+	HOST_CMD_FINALIZE_FEATURES,
+	HOST_CMD_RESET,
+};
+
+enum host_cmd_status {
+	HOST_CMD_STATUS_NONE,
+	HOST_CMD_STATUS_OKAY,
+	HOST_CMD_STATUS_ERROR,
+};
+
+static struct pci_epf_header epf_vhost_header = {
+	.vendorid	= PCI_ANY_ID,
+	.deviceid	= PCI_ANY_ID,
+	.baseclass_code	= PCI_CLASS_OTHERS,
+	.interrupt_pin	= PCI_INTERRUPT_INTA,
+};
+
+/* pci_epf_vhost_cmd_handler - Handle commands from remote EPF virtio driver
+ * @work: The work_struct holding the pci_epf_vhost_cmd_handler() function that
+ *   is scheduled
+ *
+ * Handle commands from the remote EPF virtio driver and sends notification to
+ * the vhost client driver. The remote EPF virtio driver sends commands when the
+ * virtio driver status is updated or when the feature negotiation is complete or
+ * if the virtio driver wants to reset the device.
+ */
+static void pci_epf_vhost_cmd_handler(struct work_struct *work)
+{
+	struct epf_vhost_reg *reg;
+	struct epf_vhost *vhost;
+	struct vhost_dev *vdev;
+	struct device *dev;
+	u8 command;
+
+	vhost = container_of(work, struct epf_vhost, cmd_handler.work);
+	vdev = &vhost->vdev;
+	dev = &vhost->epf->dev;
+	reg = vhost->reg;
+
+	command = reg->cmd;
+	if (!command)
+		goto reset_handler;
+
+	reg->cmd = 0;
+
+	switch (command) {
+	case HOST_CMD_SET_STATUS:
+		blocking_notifier_call_chain(&vdev->notifier, NOTIFY_SET_STATUS,
+					     NULL);
+		reg->cmd_status = HOST_CMD_STATUS_OKAY;
+		break;
+	case HOST_CMD_FINALIZE_FEATURES:
+		vdev->features = reg->guest_features;
+		blocking_notifier_call_chain(&vdev->notifier,
+					     NOTIFY_FINALIZE_FEATURES, 0);
+		reg->cmd_status = HOST_CMD_STATUS_OKAY;
+		break;
+	case HOST_CMD_RESET:
+		blocking_notifier_call_chain(&vdev->notifier, NOTIFY_RESET, 0);
+		reg->cmd_status = HOST_CMD_STATUS_OKAY;
+		break;
+	default:
+		dev_err(dev, "UNKNOWN command: %d\n", command);
+		break;
+	}
+
+reset_handler:
+	queue_delayed_work(kpcivhost_workqueue, &vhost->cmd_handler,
+			   msecs_to_jiffies(1));
+}
+
+/* pci_epf_vhost_queue_activate - Map virtqueue local address to remote
+ *   virtqueue address provided by EPF virtio
+ * @vqueue: struct epf_vhost_queue holding the local virtqueue address
+ *
+ * In order for the local system to access the remote virtqueue, the address
+ * reserved in local system should be mapped to the remote virtqueue address.
+ * Map local virtqueue address to remote virtqueue address here.
+ */
+static int pci_epf_vhost_queue_activate(struct epf_vhost_queue *vqueue)
+{
+	struct epf_vhost_reg_queue *reg_queue;
+	struct vhost_virtqueue *vq;
+	struct epf_vhost_reg *reg;
+	phys_addr_t vq_phys_addr;
+	struct epf_vhost *vhost;
+	struct pci_epf *epf;
+	struct pci_epc *epc;
+	struct device *dev;
+	u64 vq_remote_addr;
+	size_t vq_size;
+	u8 func_no;
+	int ret;
+
+	vhost = vqueue->vhost;
+	epf = vhost->epf;
+	dev = &epf->dev;
+	epc = epf->epc;
+	func_no = epf->func_no;
+
+	vq = vqueue->vq;
+	reg = vhost->reg;
+	reg_queue = &reg->vq[vq->index];
+	vq_phys_addr = vqueue->phys_addr;
+	vq_remote_addr = reg_queue->queue_addr;
+	vq_size = vqueue->size;
+
+	ret = pci_epc_map_addr(epc, func_no, vq_phys_addr, vq_remote_addr,
+			       vq_size);
+	if (ret) {
+		dev_err(dev, "Failed to map outbound address\n");
+		return ret;
+	}
+
+	reg_queue->status |= VHOST_QUEUE_STATUS_ENABLE;
+
+	return 0;
+}
+
+/* pci_epf_vhost_queue_deactivate - Unmap virtqueue local address from remote
+ *   virtqueue address
+ * @vqueue: struct epf_vhost_queue holding the local virtqueue address
+ *
+ * Unmap virtqueue local address from remote virtqueue address.
+ */
+static void pci_epf_vhost_queue_deactivate(struct epf_vhost_queue *vqueue)
+{
+	struct epf_vhost_reg_queue *reg_queue;
+	struct vhost_virtqueue *vq;
+	struct epf_vhost_reg *reg;
+	phys_addr_t vq_phys_addr;
+	struct epf_vhost *vhost;
+	struct pci_epf *epf;
+	struct pci_epc *epc;
+	u8 func_no;
+
+	vhost = vqueue->vhost;
+
+	epf = vhost->epf;
+	epc = epf->epc;
+	func_no = epf->func_no;
+	vq_phys_addr = vqueue->phys_addr;
+
+	pci_epc_unmap_addr(epc, func_no, vq_phys_addr);
+
+	reg = vhost->reg;
+	vq = vqueue->vq;
+	reg_queue = &reg->vq[vq->index];
+	reg_queue->status &= ~VHOST_QUEUE_STATUS_ENABLE;
+}
+
+/* pci_epf_vhost_queue_cmd_handler - Handle commands from remote EPF virtio
+ *   driver sent for a particular virtqueue
+ * @work: The work_struct holding the pci_epf_vhost_queue_cmd_handler()
+ *   function that is scheduled
+ *
+ * Handle commands from the remote EPF virtio driver sent for a particular
+ * virtqueue to activate/de-activate a virtqueue or to send notification to
+ * the vhost client driver.
+ */
+static void pci_epf_vhost_queue_cmd_handler(struct work_struct *work)
+{
+	struct epf_vhost_reg_queue *reg_queue;
+	struct epf_vhost_queue *vqueue;
+	struct vhost_virtqueue *vq;
+	struct epf_vhost_reg *reg;
+	struct epf_vhost *vhost;
+	struct device *dev;
+	u8 command;
+	int ret;
+
+	vqueue = container_of(work, struct epf_vhost_queue, cmd_handler.work);
+	vhost = vqueue->vhost;
+	reg = vhost->reg;
+	vq = vqueue->vq;
+	reg_queue = &reg->vq[vq->index];
+	dev = &vhost->epf->dev;
+
+	command = reg_queue->cmd;
+	if (!command)
+		goto reset_handler;
+
+	reg_queue->cmd = 0;
+	vq = vqueue->vq;
+
+	switch (command) {
+	case QUEUE_CMD_ACTIVATE:
+		ret = pci_epf_vhost_queue_activate(vqueue);
+		if (ret)
+			reg_queue->cmd_status = QUEUE_CMD_STATUS_ERROR;
+		else
+			reg_queue->cmd_status = QUEUE_CMD_STATUS_OKAY;
+		break;
+	case QUEUE_CMD_DEACTIVATE:
+		pci_epf_vhost_queue_deactivate(vqueue);
+		reg_queue->cmd_status = QUEUE_CMD_STATUS_OKAY;
+		break;
+	case QUEUE_CMD_NOTIFY:
+		vhost_virtqueue_callback(vqueue->vq);
+		reg_queue->cmd_status = QUEUE_CMD_STATUS_OKAY;
+		break;
+	default:
+		dev_err(dev, "UNKNOWN QUEUE command: %d\n", command);
+		break;
+}
+
+reset_handler:
+	queue_delayed_work(kpcivhost_workqueue, &vqueue->cmd_handler,
+			   msecs_to_jiffies(1));
+}
+
+/* pci_epf_vhost_write - Write data to buffer provided by remote virtio driver
+ * @vdev: Vhost device that communicates with remove virtio device
+ * @dst: Buffer address present in the memory of the remote system to which
+ *   data should be written
+ * @src: Buffer address in the local device provided by the vhost client driver
+ * @len: Length of the data to be copied from @src to @dst
+ *
+ * Write data to buffer provided by remote virtio driver from buffer provided
+ * by vhost client driver.
+ */
+static int pci_epf_vhost_write(struct vhost_dev *vdev, u64 dst, void *src, int len)
+{
+	const struct pci_epc_features *epc_features;
+	struct epf_vhost *vhost;
+	phys_addr_t phys_addr;
+	struct pci_epf *epf;
+	struct pci_epc *epc;
+	void __iomem *addr;
+	struct device *dev;
+	int offset, ret;
+	u64 dst_addr;
+	size_t align;
+	u8 func_no;
+
+	vhost = to_epf_vhost(vdev);
+	epf = vhost->epf;
+	dev = &epf->dev;
+	epc = epf->epc;
+	func_no = epf->func_no;
+	epc_features = vhost->epc_features;
+	align = epc_features->align;
+
+	offset = dst & (align - 1);
+	dst_addr = dst & ~(align - 1);
+
+	addr = pci_epc_mem_alloc_addr(epc, &phys_addr, len);
+	if (!addr) {
+		dev_err(dev, "Failed to allocate outbound address\n");
+		return -ENOMEM;
+	}
+
+	ret = pci_epc_map_addr(epc, func_no, phys_addr, dst_addr, len);
+	if (ret) {
+		dev_err(dev, "Failed to map outbound address\n");
+		goto ret;
+	}
+
+	memcpy_toio(addr + offset, src, len);
+
+	pci_epc_unmap_addr(epc, func_no, phys_addr);
+
+ret:
+	pci_epc_mem_free_addr(epc, phys_addr, addr, len);
+
+	return ret;
+}
+
+/* ntb_vhost_read - Read data from buffer provided by remote virtio driver
+ * @vdev: Vhost device that communicates with remove virtio device
+ * @dst: Buffer address in the local device provided by the vhost client driver
+ * @src: Buffer address in the remote device provided by the remote virtio
+ *   driver
+ * @len: Length of the data to be copied from @src to @dst
+ *
+ * Read data from buffer provided by remote virtio driver to address provided
+ * by vhost client driver.
+ */
+static int pci_epf_vhost_read(struct vhost_dev *vdev, void *dst, u64 src, int len)
+{
+	const struct pci_epc_features *epc_features;
+	struct epf_vhost *vhost;
+	phys_addr_t phys_addr;
+	struct pci_epf *epf;
+	struct pci_epc *epc;
+	void __iomem *addr;
+	struct device *dev;
+	int offset, ret;
+	u64 src_addr;
+	size_t align;
+	u8 func_no;
+
+	vhost = to_epf_vhost(vdev);
+	epf = vhost->epf;
+	dev = &epf->dev;
+	epc = epf->epc;
+	func_no = epf->func_no;
+	epc_features = vhost->epc_features;
+	align = epc_features->align;
+
+	offset = src & (align - 1);
+	src_addr = src & ~(align - 1);
+
+	addr = pci_epc_mem_alloc_addr(epc, &phys_addr, len);
+	if (!addr) {
+		dev_err(dev, "Failed to allocate outbound address\n");
+		return -ENOMEM;
+	}
+
+	ret = pci_epc_map_addr(epc, func_no, phys_addr, src_addr, len);
+	if (ret) {
+		dev_err(dev, "Failed to map outbound address\n");
+		goto ret;
+	}
+
+	memcpy_fromio(dst, addr + offset, len);
+
+	pci_epc_unmap_addr(epc, func_no, phys_addr);
+
+ret:
+	pci_epc_mem_free_addr(epc, phys_addr, addr, len);
+
+	return ret;
+}
+
+/* pci_epf_vhost_notify - Send notification to the remote virtqueue
+ * @vq: The local vhost virtqueue corresponding to the remote virtio virtqueue
+ *
+ * Use endpoint core framework to raise MSI-X interrupt to notify the remote
+ * virtqueue.
+ */
+static void  pci_epf_vhost_notify(struct vhost_virtqueue *vq)
+{
+	struct epf_vhost_reg_queue *reg_queue;
+	struct epf_vhost_reg *reg;
+	struct epf_vhost *vhost;
+	struct vhost_dev *vdev;
+	struct pci_epf *epf;
+	struct pci_epc *epc;
+	u8 func_no;
+
+	vdev = vq->dev;
+	vhost = to_epf_vhost(vdev);
+	epf = vhost->epf;
+	func_no = epf->func_no;
+	epc = epf->epc;
+	reg = vhost->reg;
+	reg_queue = &reg->vq[vq->index];
+
+	pci_epc_raise_irq(epc, func_no, PCI_EPC_IRQ_MSIX,
+			  reg_queue->msix_vector + 1);
+}
+
+/* pci_epf_vhost_del_vqs - Delete all the vqs associated with the vhost device
+ * @vdev: Vhost device that communicates with remove virtio device
+ *
+ * Delete all the vqs associated with the vhost device and free the memory
+ * address reserved for accessing the remote virtqueue.
+ */
+static void pci_epf_vhost_del_vqs(struct vhost_dev *vdev)
+{
+	struct epf_vhost_queue *vqueue;
+	struct vhost_virtqueue *vq;
+	phys_addr_t vq_phys_addr;
+	struct epf_vhost *vhost;
+	void __iomem *vq_addr;
+	unsigned int vq_size;
+	struct pci_epf *epf;
+	struct pci_epc *epc;
+	int i;
+
+	vhost = to_epf_vhost(vdev);
+	epf = vhost->epf;
+	epc = epf->epc;
+
+	for (i = 0; i < vdev->nvqs; i++) {
+		vq = vdev->vqs[i];
+		if (IS_ERR_OR_NULL(vq))
+			continue;
+
+		vqueue = &vhost->vqueue[i];
+		vq_phys_addr = vqueue->phys_addr;
+		vq_addr = vqueue->addr;
+		vq_size = vqueue->size;
+		pci_epc_mem_free_addr(epc, vq_phys_addr, vq_addr, vq_size);
+		kfree(vq);
+	}
+}
+
+/* pci_epf_vhost_create_vq - Create a new vhost virtqueue
+ * @vdev: Vhost device that communicates with remove virtio device
+ * @index: Index of the vhost virtqueue
+ * @num_bufs: The number of buffers that should be supported by the vhost
+ *   virtqueue (number of descriptors in the vhost virtqueue)
+ * @callback: Callback function associated with the virtqueue
+ *
+ * Create a new vhost virtqueue which can be used by the vhost client driver
+ * to access the remote virtio. This sets up the local address of the vhost
+ * virtqueue but shouldn't be accessed until the virtio sets the status to
+ * VIRTIO_CONFIG_S_DRIVER_OK.
+ */
+static struct vhost_virtqueue *
+pci_epf_vhost_create_vq(struct vhost_dev *vdev, int index,
+			unsigned int num_bufs,
+			void (*callback)(struct vhost_virtqueue *))
+{
+	struct epf_vhost_reg_queue *reg_queue;
+	struct epf_vhost_queue *vqueue;
+	struct epf_vhost_reg *reg;
+	struct vhost_virtqueue *vq;
+	phys_addr_t vq_phys_addr;
+	struct epf_vhost *vhost;
+	struct vringh *vringh;
+	void __iomem *vq_addr;
+	unsigned int vq_size;
+	struct vring *vring;
+	struct pci_epf *epf;
+	struct pci_epc *epc;
+	struct device *dev;
+	int ret;
+
+	vhost = to_epf_vhost(vdev);
+	vqueue = &vhost->vqueue[index];
+	reg = vhost->reg;
+	reg_queue = &reg->vq[index];
+	epf = vhost->epf;
+	epc = epf->epc;
+	dev = &epf->dev;
+
+	vq = kzalloc(sizeof(*vq), GFP_KERNEL);
+	if (!vq)
+		return ERR_PTR(-ENOMEM);
+
+	vq->dev = vdev;
+	vq->callback = callback;
+	vq->num = num_bufs;
+	vq->index = index;
+	vq->notify = pci_epf_vhost_notify;
+	vq->type = VHOST_TYPE_MMIO;
+
+	vqueue->vq = vq;
+	vqueue->vhost = vhost;
+
+	vringh = &vq->vringh;
+	vring = &vringh->vring;
+	reg_queue->num_buffers = num_bufs;
+
+	vq_size = vring_size(num_bufs, VIRTIO_PCI_VRING_ALIGN);
+	vq_addr = pci_epc_mem_alloc_addr(epc, &vq_phys_addr, vq_size);
+	if (!vq_addr) {
+		dev_err(dev, "Failed to allocate virtqueue address\n");
+		ret = -ENOMEM;
+		goto err_mem_alloc_addr;
+	}
+
+	vring_init(vring, num_bufs, vq_addr, VIRTIO_PCI_VRING_ALIGN);
+	ret = vringh_init_mmio(vringh, 0, num_bufs, false, vring->desc,
+			       vring->avail, vring->used);
+	if (ret) {
+		dev_err(dev, "Failed to init vringh\n");
+		goto err_init_mmio;
+	}
+
+	vqueue->phys_addr = vq_phys_addr;
+	vqueue->addr = vq_addr;
+	vqueue->size = vq_size;
+
+	INIT_DELAYED_WORK(&vqueue->cmd_handler, pci_epf_vhost_queue_cmd_handler);
+	queue_work(kpcivhost_workqueue, &vqueue->cmd_handler.work);
+
+	return vq;
+
+err_init_mmio:
+	pci_epc_mem_free_addr(epc, vq_phys_addr, vq_addr, vq_size);
+
+err_mem_alloc_addr:
+	kfree(vq);
+
+	return ERR_PTR(ret);
+}
+
+/* pci_epf_vhost_create_vqs - Create vhost virtqueues for vhost device
+ * @vdev: Vhost device that communicates with the remote virtio device
+ * @nvqs: Number of vhost virtqueues to be created
+ * @num_bufs: The number of buffers that should be supported by the vhost
+ *   virtqueue (number of descriptors in the vhost virtqueue)
+ * @vqs: Pointers to all the created vhost virtqueues
+ * @callback: Callback function associated with the virtqueue
+ * @names: Names associated with each virtqueue
+ *
+ * Create vhost virtqueues for vhost device. This acts as a wrapper to
+ * pci_epf_vhost_create_vq() which creates individual vhost virtqueue.
+ */
+static int pci_epf_vhost_create_vqs(struct vhost_dev *vdev, unsigned int nvqs,
+				    unsigned int num_bufs,
+				    struct vhost_virtqueue *vqs[],
+				    vhost_vq_callback_t *callbacks[],
+				    const char * const names[])
+{
+	struct epf_vhost *vhost;
+	struct pci_epf *epf;
+	struct device *dev;
+	int ret, i;
+
+	vhost = to_epf_vhost(vdev);
+	epf = vhost->epf;
+	dev = &epf->dev;
+
+	for (i = 0; i < nvqs; i++) {
+		vqs[i] = pci_epf_vhost_create_vq(vdev, i, num_bufs,
+						 callbacks[i]);
+		if (IS_ERR_OR_NULL(vqs[i])) {
+			ret = PTR_ERR(vqs[i]);
+			dev_err(dev, "Failed to create virtqueue\n");
+			goto err;
+		}
+	}
+
+	vdev->nvqs = nvqs;
+	vdev->vqs = vqs;
+
+	return 0;
+
+err:
+	pci_epf_vhost_del_vqs(vdev);
+	return ret;
+}
+
+/* pci_epf_vhost_set_features - vhost_config_ops to set vhost device features
+ * @vdev: Vhost device that communicates with the remote virtio device
+ * @features: Features supported by the vhost client driver
+ *
+ * vhost_config_ops invoked by the vhost client driver to set vhost device
+ * features.
+ */
+static int pci_epf_vhost_set_features(struct vhost_dev *vdev, u64 features)
+{
+	struct epf_vhost_reg *reg;
+	struct epf_vhost *vhost;
+
+	vhost = to_epf_vhost(vdev);
+	reg = vhost->reg;
+
+	reg->host_features = features;
+
+	return 0;
+}
+
+/* ntb_vhost_set_status - vhost_config_ops to set vhost device status
+ * @vdev: Vhost device that communicates with the remote virtio device
+ * @status: Vhost device status configured by vhost client driver
+ *
+ * vhost_config_ops invoked by the vhost client driver to set vhost device
+ * status.
+ */
+static int pci_epf_vhost_set_status(struct vhost_dev *vdev, u8 status)
+{
+	struct epf_vhost_reg *reg;
+	struct epf_vhost *vhost;
+
+	vhost = to_epf_vhost(vdev);
+	reg = vhost->reg;
+
+	reg->device_status = status;
+
+	return 0;
+}
+
+/* ntb_vhost_get_status - vhost_config_ops to get vhost device status
+ * @vdev: Vhost device that communicates with the remote virtio device
+ *
+ * vhost_config_ops invoked by the vhost client driver to get vhost device
+ * status set by the remote virtio driver.
+ */
+static u8 pci_epf_vhost_get_status(struct vhost_dev *vdev)
+{
+	struct epf_vhost_reg *reg;
+	struct epf_vhost *vhost;
+
+	vhost = to_epf_vhost(vdev);
+	reg = vhost->reg;
+
+	return reg->device_status;
+}
+
+static const struct vhost_config_ops pci_epf_vhost_ops = {
+	.create_vqs	= pci_epf_vhost_create_vqs,
+	.del_vqs	= pci_epf_vhost_del_vqs,
+	.write		= pci_epf_vhost_write,
+	.read		= pci_epf_vhost_read,
+	.set_features	= pci_epf_vhost_set_features,
+	.set_status	= pci_epf_vhost_set_status,
+	.get_status	= pci_epf_vhost_get_status,
+};
+
+/* pci_epf_vhost_write_header - Write to PCIe standard configuration space
+ *   header
+ * @vhost: EPF vhost containing the vhost device that communicates with the
+ *   remote virtio device
+ *
+ * Invokes endpoint core framework's pci_epc_write_header() to write to the
+ * standard configuration space header.
+ */
+static int pci_epf_vhost_write_header(struct epf_vhost *vhost)
+{
+	struct pci_epf_header *header;
+	struct vhost_dev *vdev;
+	struct pci_epc *epc;
+	struct pci_epf *epf;
+	struct device *dev;
+	u8 func_no;
+	int ret;
+
+	vdev = &vhost->vdev;
+	epf = vhost->epf;
+	dev = &epf->dev;
+	epc = epf->epc;
+	func_no = epf->func_no;
+	header = epf->header;
+
+	ret = pci_epc_write_header(epc, func_no, header);
+	if (ret) {
+		dev_err(dev, "Configuration header write failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+/* pci_epf_vhost_release_dev - Callback function to free device
+ * @dev: Device in vhost_dev that has to be freed
+ *
+ * Callback function from device core invoked to free the device after
+ * all references have been removed. This frees the allocated memory for
+ * struct ntb_vhost.
+ */
+static void pci_epf_vhost_release_dev(struct device *dev)
+{
+	struct epf_vhost *vhost;
+	struct vhost_dev *vdev;
+
+	vdev = to_vhost_dev(dev);
+	vhost = to_epf_vhost(vdev);
+
+	kfree(vhost);
+}
+
+/* pci_epf_vhost_register - Register a vhost device
+ * @vhost: EPF vhost containing the vhost device that communicates with the
+ *   remote virtio device
+ *
+ * Invoked vhost_register_device() to register a vhost device after populating
+ * the deviceID and vendorID of the vhost device.
+ */
+static int pci_epf_vhost_register(struct epf_vhost *vhost)
+{
+	struct vhost_dev *vdev;
+	struct pci_epf *epf;
+	struct device *dev;
+	int ret;
+
+	vdev = &vhost->vdev;
+	epf = vhost->epf;
+	dev = &epf->dev;
+
+	vdev->dev.parent = dev;
+	vdev->dev.release = pci_epf_vhost_release_dev;
+	vdev->id.device = vhost->epf->header->subsys_id;
+	vdev->id.vendor = vhost->epf->header->subsys_vendor_id;
+	vdev->ops = &pci_epf_vhost_ops;
+
+	ret = vhost_register_device(vdev);
+	if (ret) {
+		dev_err(dev, "Failed to register vhost device\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+/* pci_epf_vhost_configure_bar - Configure BAR of EPF device
+ * @vhost: EPF vhost containing the vhost device that communicates with the
+ *   remote virtio device
+ *
+ * Allocate memory for the standard virtio configuration space and map it to
+ * the first free BAR.
+ */
+static int pci_epf_vhost_configure_bar(struct epf_vhost *vhost)
+{
+	size_t msix_table_size = 0, pba_size = 0, align, bar_size;
+	const struct pci_epc_features *epc_features;
+	struct pci_epf_bar *epf_bar;
+	struct vhost_dev *vdev;
+	struct pci_epf *epf;
+	struct pci_epc *epc;
+	struct device *dev;
+	bool msix_capable;
+	u32 config_size;
+	int barno, ret;
+	void *base;
+	u64 size;
+
+	vdev = &vhost->vdev;
+	epf = vhost->epf;
+	dev = &epf->dev;
+	epc = epf->epc;
+
+	epc_features = vhost->epc_features;
+	barno = pci_epc_get_first_free_bar(epc_features);
+	if (barno < 0) {
+		dev_err(dev, "Failed to get free BAR\n");
+		return barno;
+	}
+
+	size = epc_features->bar_fixed_size[barno];
+	align = epc_features->align;
+	/* Check if epc_features is populated incorrectly */
+	if ((!IS_ALIGNED(size, align)))
+		return -EINVAL;
+
+	config_size = sizeof(struct epf_vhost_reg) + VHOST_DEVICE_CONFIG_SIZE;
+	config_size = ALIGN(config_size, 8);
+
+	msix_capable = epc_features->msix_capable;
+	if (msix_capable) {
+		msix_table_size = PCI_MSIX_ENTRY_SIZE * epf->msix_interrupts;
+		vhost->msix_table_offset = config_size;
+		vhost->msix_bar = barno;
+		/* Align to QWORD or 8 Bytes */
+		pba_size = ALIGN(DIV_ROUND_UP(epf->msix_interrupts, 8), 8);
+	}
+
+	bar_size = config_size + msix_table_size + pba_size;
+
+	if (!align)
+		bar_size = roundup_pow_of_two(bar_size);
+	else
+		bar_size = ALIGN(bar_size, align);
+
+	if (!size)
+		size = bar_size;
+	else if (size < bar_size)
+		return -EINVAL;
+
+	base = pci_epf_alloc_space(epf, size, barno, align,
+				   PRIMARY_INTERFACE);
+	if (!base) {
+		dev_err(dev, "Failed to allocate configuration region\n");
+		return -ENOMEM;
+	}
+
+	epf_bar = &epf->bar[barno];
+	ret = pci_epc_set_bar(epc, epf->func_no, epf_bar);
+	if (ret) {
+		dev_err(dev, "Failed to set BAR: %d\n", barno);
+		goto err_set_bar;
+	}
+
+	vhost->reg = base;
+
+	return 0;
+
+err_set_bar:
+	pci_epf_free_space(epf, base, barno, PRIMARY_INTERFACE);
+
+	return ret;
+}
+
+/* pci_epf_vhost_configure_interrupts - Configure MSI/MSI-X capability of EPF
+ *   device
+ * @vhost: EPF vhost containing the vhost device that communicates with the
+ *   remote virtio device
+ *
+ * Configure MSI/MSI-X capability of EPF device. This will be used to interrupt
+ * the vhost virtqueue.
+ */
+static int pci_epf_vhost_configure_interrupts(struct epf_vhost *vhost)
+{
+	const struct pci_epc_features *epc_features;
+	struct pci_epf *epf;
+	struct pci_epc *epc;
+	struct device *dev;
+	int ret;
+
+	epc_features = vhost->epc_features;
+	epf = vhost->epf;
+	dev = &epf->dev;
+	epc = epf->epc;
+
+	if (epc_features->msi_capable) {
+		ret = pci_epc_set_msi(epc, epf->func_no,
+				      EPF_VHOST_MAX_INTERRUPTS);
+		if (ret) {
+			dev_err(dev, "MSI configuration failed\n");
+			return ret;
+		}
+	}
+
+	if (epc_features->msix_capable) {
+		ret = pci_epc_set_msix(epc, epf->func_no,
+				       EPF_VHOST_MAX_INTERRUPTS,
+				       vhost->msix_bar,
+				       vhost->msix_table_offset);
+		if (ret) {
+			dev_err(dev, "MSI-X configuration failed\n");
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+/* pci_epf_vhost_cfs_link - Link vhost client driver with EPF vhost to get
+ *   the deviceID and driverID to be writtent to the PCIe config space
+ * @epf_vhost_item: Config item representing the EPF vhost created by this
+ *   driver
+ * @epf: Endpoint function device that is bound to the endpoint controller
+ *
+ * This is invoked when the user creates a softlink between the vhost client
+ * to the EPF vhost. This gets the deviceID and vendorID data from the vhost
+ * client and copies it to the subys_id and subsys_vendor_id of the EPF
+ * header. This will be used by the remote virtio to bind a virtio client
+ * driver.
+ */
+static int pci_epf_vhost_cfs_link(struct config_item *epf_vhost_item,
+				  struct config_item *driver_item)
+{
+	struct vhost_driver_item *vdriver_item;
+	struct epf_vhost *vhost;
+
+	vdriver_item = to_vhost_driver_item(driver_item);
+	vhost = to_epf_vhost_from_ci(epf_vhost_item);
+
+	vhost->epf->header->subsys_id = vdriver_item->device;
+	vhost->epf->header->subsys_vendor_id = vdriver_item->vendor;
+
+	return 0;
+}
+
+static struct configfs_item_operations pci_epf_vhost_cfs_ops = {
+	.allow_link	= pci_epf_vhost_cfs_link,
+};
+
+static const struct config_item_type pci_epf_vhost_cfs_type = {
+	.ct_item_ops	= &pci_epf_vhost_cfs_ops,
+	.ct_owner	= THIS_MODULE,
+};
+
+/* pci_epf_vhost_cfs_work - Delayed work function to create configfs directory
+ *   to perform EPF vhost specific initializations
+ * @work: The work_struct holding the pci_epf_vhost_cfs_work() function that
+ *   is scheduled
+ *
+ * This is a delayed work function to create configfs directory to perform EPF
+ * vhost specific initializations. This configfs directory will be a
+ * sub-directory to the directory created by the user to create pci_epf device.
+ */
+static void pci_epf_vhost_cfs_work(struct work_struct *work)
+{
+	struct epf_vhost *vhost = container_of(work, struct epf_vhost,
+					       cfs_work.work);
+	struct pci_epf *epf = vhost->epf;
+	struct device *dev = &epf->dev;
+	struct config_group *group;
+	struct vhost_dev *vdev;
+	int ret;
+
+	if (!epf->group) {
+		queue_delayed_work(kpcivhost_workqueue, &vhost->cfs_work,
+				   msecs_to_jiffies(50));
+		return;
+	}
+
+	vdev = &vhost->vdev;
+	group = &vhost->group;
+	config_group_init_type_name(group, dev_name(dev), &pci_epf_vhost_cfs_type);
+	ret = configfs_register_group(epf->group, group);
+	if (ret) {
+		dev_err(dev, "Failed to register configfs group %s\n", dev_name(dev));
+		return;
+	}
+}
+
+/* pci_epf_vhost_probe - Initialize struct epf_vhost when a new EPF device is
+ *   created
+ * @epf: Endpoint function device that is bound to this driver
+ *
+ * Probe function to initialize struct epf_vhost when a new EPF device is
+ * created.
+ */
+static int pci_epf_vhost_probe(struct pci_epf *epf)
+{
+	struct epf_vhost *vhost;
+
+	vhost = kzalloc(sizeof(*vhost), GFP_KERNEL);
+	if (!vhost)
+		return -ENOMEM;
+
+	epf->header = &epf_vhost_header;
+	vhost->epf = epf;
+
+	epf_set_drvdata(epf, vhost);
+	INIT_DELAYED_WORK(&vhost->cmd_handler, pci_epf_vhost_cmd_handler);
+	INIT_DELAYED_WORK(&vhost->cfs_work, pci_epf_vhost_cfs_work);
+	queue_delayed_work(kpcivhost_workqueue, &vhost->cfs_work,
+			   msecs_to_jiffies(50));
+
+	return 0;
+}
+
+/* pci_epf_vhost_remove - Free the initializations performed by
+ *   pci_epf_vhost_probe()
+ * @epf: Endpoint function device that is bound to this driver
+ *
+ * Free the initializations performed by pci_epf_vhost_probe().
+ */
+static int pci_epf_vhost_remove(struct pci_epf *epf)
+{
+	struct epf_vhost *vhost;
+
+	vhost = epf_get_drvdata(epf);
+	cancel_delayed_work_sync(&vhost->cfs_work);
+
+	return 0;
+}
+
+/* pci_epf_vhost_bind - Bind callback to initialize the PCIe EP controller
+ * @epf: Endpoint function device that is bound to the endpoint controller
+ *
+ * pci_epf_vhost_bind() is invoked when an endpoint controller is bound to
+ * endpoint function. This function initializes the endpoint controller
+ * with vhost endpoint function specific data.
+ */
+static int pci_epf_vhost_bind(struct pci_epf *epf)
+{
+	const struct pci_epc_features *epc_features;
+	struct epf_vhost *vhost;
+	struct pci_epc *epc;
+	struct device *dev;
+	int ret;
+
+	vhost = epf_get_drvdata(epf);
+	dev = &epf->dev;
+	epc = epf->epc;
+
+	epc_features = pci_epc_get_features(epc, epf->func_no);
+	if (!epc_features) {
+		dev_err(dev, "Fail to get EPC features\n");
+		return -EINVAL;
+	}
+	vhost->epc_features = epc_features;
+
+	ret = pci_epf_vhost_write_header(vhost);
+	if (ret) {
+		dev_err(dev, "Failed to bind VHOST config header\n");
+		return ret;
+	}
+
+	ret = pci_epf_vhost_configure_bar(vhost);
+	if (ret) {
+		dev_err(dev, "Failed to configure BAR\n");
+		return ret;
+	}
+
+	ret = pci_epf_vhost_configure_interrupts(vhost);
+	if (ret) {
+		dev_err(dev, "Failed to configure BAR\n");
+		return ret;
+	}
+
+	ret = pci_epf_vhost_register(vhost);
+	if (ret) {
+		dev_err(dev, "Failed to bind VHOST config header\n");
+		return ret;
+	}
+
+	queue_work(kpcivhost_workqueue, &vhost->cmd_handler.work);
+
+	return ret;
+}
+
+/* pci_epf_vhost_unbind - Inbind callback to cleanup the PCIe EP controller
+ * @epf: Endpoint function device that is bound to the endpoint controller
+ *
+ * pci_epf_vhost_unbind() is invoked when the binding between endpoint
+ * controller is removed from endpoint function. This will unregister vhost
+ * device and cancel pending cmd_handler work.
+ */
+static void pci_epf_vhost_unbind(struct pci_epf *epf)
+{
+	struct epf_vhost *vhost;
+	struct vhost_dev *vdev;
+
+	vhost = epf_get_drvdata(epf);
+	vdev = &vhost->vdev;
+
+	cancel_delayed_work_sync(&vhost->cmd_handler);
+	if (device_is_registered(&vdev->dev))
+		vhost_unregister_device(vdev);
+}
+
+static struct pci_epf_ops epf_ops = {
+	.bind	= pci_epf_vhost_bind,
+	.unbind	= pci_epf_vhost_unbind,
+};
+
+static const struct pci_epf_device_id pci_epf_vhost_ids[] = {
+	{
+		.name = "pci-epf-vhost",
+	},
+	{ },
+};
+
+static struct pci_epf_driver epf_vhost_driver = {
+	.driver.name	= "pci_epf_vhost",
+	.probe		= pci_epf_vhost_probe,
+	.remove		= pci_epf_vhost_remove,
+	.id_table	= pci_epf_vhost_ids,
+	.ops		= &epf_ops,
+	.owner		= THIS_MODULE,
+};
+
+static int __init pci_epf_vhost_init(void)
+{
+	int ret;
+
+	kpcivhost_workqueue = alloc_workqueue("kpcivhost", WQ_MEM_RECLAIM |
+					      WQ_HIGHPRI, 0);
+	ret = pci_epf_register_driver(&epf_vhost_driver);
+	if (ret) {
+		pr_err("Failed to register pci epf vhost driver --> %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+module_init(pci_epf_vhost_init);
+
+static void __exit pci_epf_vhost_exit(void)
+{
+	pci_epf_unregister_driver(&epf_vhost_driver);
+}
+module_exit(pci_epf_vhost_exit);
+
+MODULE_DESCRIPTION("PCI EPF VHOST DRIVER");
+MODULE_AUTHOR("Kishon Vijay Abraham I <kishon@xxxxxx>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/vhost/vhost_cfs.c b/drivers/vhost/vhost_cfs.c
index ae46e71968f1..ab0393289200 100644
--- a/drivers/vhost/vhost_cfs.c
+++ b/drivers/vhost/vhost_cfs.c
@@ -18,12 +18,6 @@ static struct config_group *vhost_driver_group;
 /* VHOST device like PCIe EP, NTB etc., */
 static struct config_group *vhost_device_group;
 
-struct vhost_driver_item {
-	struct config_group group;
-	u32 vendor;
-	u32 device;
-};
-
 struct vhost_driver_group {
 	struct config_group group;
 };
@@ -33,13 +27,6 @@ struct vhost_device_item {
 	struct vhost_dev *vdev;
 };
 
-static inline
-struct vhost_driver_item *to_vhost_driver_item(struct config_item *item)
-{
-	return container_of(to_config_group(item), struct vhost_driver_item,
-			    group);
-}
-
 static inline
 struct vhost_device_item *to_vhost_device_item(struct config_item *item)
 {
diff --git a/include/linux/vhost.h b/include/linux/vhost.h
index be9341ffd266..640650311310 100644
--- a/include/linux/vhost.h
+++ b/include/linux/vhost.h
@@ -74,6 +74,7 @@ struct vhost_virtqueue {
 	struct vhost_dev *dev;
 	enum vhost_type type;
 	struct vringh vringh;
+	int index;
 	void (*callback)(struct vhost_virtqueue *vq);
 	void (*notify)(struct vhost_virtqueue *vq);
 
@@ -148,6 +149,12 @@ struct vhost_msg_node {
   struct list_head node;
 };
 
+struct vhost_driver_item {
+	struct config_group group;
+	u32 vendor;
+	u32 device;
+};
+
 enum vhost_notify_event {
 	NOTIFY_SET_STATUS,
 	NOTIFY_FINALIZE_FEATURES,
@@ -230,6 +237,13 @@ static inline void *vhost_get_drvdata(struct vhost_dev *vdev)
 	return dev_get_drvdata(&vdev->dev);
 }
 
+static inline
+struct vhost_driver_item *to_vhost_driver_item(struct config_item *item)
+{
+	return container_of(to_config_group(item), struct vhost_driver_item,
+			    group);
+}
+
 int vhost_register_driver(struct vhost_driver *driver);
 void vhost_unregister_driver(struct vhost_driver *driver);
 int vhost_register_device(struct vhost_dev *vdev);
-- 
2.17.1




[Index of Archives]     [DMA Engine]     [Linux Coverity]     [Linux USB]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Greybus]

  Powered by Linux