[RFC PATCH v3 17/17] virtio: add a vbus transport
From: Gregory Haskins
Date: Tue Apr 21 2009 - 14:44:48 EST
We add a new virtio transport for accessing backends located on vbus. This
complements the existing transports for virtio-pci, virtio-s390, and
virtio-lguest that already exist.
Signed-off-by: Gregory Haskins <ghaskins@xxxxxxxxxx>
---
drivers/virtio/Kconfig | 15 +
drivers/virtio/Makefile | 1
drivers/virtio/virtio_vbus.c | 496 +++++++++++++++++++++++++++++++++
include/linux/virtio_vbus.h | 163 +++++++++++
kernel/vbus/Kconfig | 8 +
kernel/vbus/Makefile | 3
kernel/vbus/virtio.c | 627 ++++++++++++++++++++++++++++++++++++++++++
7 files changed, 1313 insertions(+), 0 deletions(-)
create mode 100644 drivers/virtio/virtio_vbus.c
create mode 100644 include/linux/virtio_vbus.h
create mode 100644 kernel/vbus/virtio.c
diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
index 3dd6294..e8562ee 100644
--- a/drivers/virtio/Kconfig
+++ b/drivers/virtio/Kconfig
@@ -23,6 +23,21 @@ config VIRTIO_PCI
If unsure, say M.
+config VIRTIO_VBUS
+ tristate "VBUS driver for virtio devices (EXPERIMENTAL)"
+ depends on VBUS_DRIVERS && EXPERIMENTAL
+ select VIRTIO
+ select VIRTIO_RING
+ ---help---
+ This drivers provides support for virtio based paravirtual device
+ drivers over VBUS. This requires that your VMM has appropriate VBUS
+ virtio backends.
+
+ Currently, the ABI is not considered stable so there is no guarantee
+ that this version of the driver will work with your VMM.
+
+ If unsure, say M.
+
config VIRTIO_BALLOON
tristate "Virtio balloon driver (EXPERIMENTAL)"
select VIRTIO
diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile
index 6738c44..0342e42 100644
--- a/drivers/virtio/Makefile
+++ b/drivers/virtio/Makefile
@@ -1,4 +1,5 @@
obj-$(CONFIG_VIRTIO) += virtio.o
obj-$(CONFIG_VIRTIO_RING) += virtio_ring.o
obj-$(CONFIG_VIRTIO_PCI) += virtio_pci.o
+obj-$(CONFIG_VIRTIO_VBUS) += virtio_vbus.o
obj-$(CONFIG_VIRTIO_BALLOON) += virtio_balloon.o
diff --git a/drivers/virtio/virtio_vbus.c b/drivers/virtio/virtio_vbus.c
new file mode 100644
index 0000000..ebefcf2
--- /dev/null
+++ b/drivers/virtio/virtio_vbus.c
@@ -0,0 +1,496 @@
+/*
+ * Virtio VBUS driver
+ *
+ * This module allows virtio devices to be used over a virtual-bus device.
+ *
+ * Copyright: Novell, 2009
+ *
+ * Authors:
+ * Gregory Haskins <ghaskins@xxxxxxxxxx>
+ *
+ * Derived from virtio-pci, written by
+ * Anthony Liguori <aliguori@xxxxxxxxxx>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/interrupt.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <linux/virtio_ring.h>
+#include <linux/virtio_vbus.h>
+#include <linux/vbus_driver.h>
+#include <linux/spinlock.h>
+
+MODULE_AUTHOR("Gregory Haskins <ghaskins@xxxxxxxxxx>");
+MODULE_DESCRIPTION("virtio-vbus");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1");
+
+struct virtio_vbus_priv {
+ struct virtio_device virtio_dev;
+ struct vbus_device_proxy *vbus_dev;
+ struct {
+ struct virtio_vbus_shm *shm;
+ struct shm_signal *signal;
+ struct shm_signal_notifier notifier;
+ } config;
+};
+
+struct vbus_virtqueue {
+ struct virtqueue *vq;
+ u64 index;
+ int num;
+ struct virtio_vbus_shm *shm;
+ size_t size;
+ struct shm_signal *signal;
+ struct shm_signal_notifier notifier;
+};
+
+static struct virtio_vbus_priv *
+virtio_to_priv(struct virtio_device *virtio_dev)
+{
+ return container_of(virtio_dev, struct virtio_vbus_priv, virtio_dev);
+}
+
+static int
+devcall(struct virtio_vbus_priv *priv, u32 func, void *data, size_t len)
+{
+ struct vbus_device_proxy *dev = priv->vbus_dev;
+
+ return dev->ops->call(dev, func, data, len, 0);
+}
+
+/*
+ * This is called whenever the host signals our config-space shm
+ */
+static void
+config_isr(struct shm_signal_notifier *notifier)
+{
+ struct virtio_vbus_priv *priv = container_of(notifier,
+ struct virtio_vbus_priv,
+ config.notifier);
+ struct virtio_driver *drv = container_of(priv->virtio_dev.dev.driver,
+ struct virtio_driver, driver);
+
+ if (drv && drv->config_changed)
+ drv->config_changed(&priv->virtio_dev);
+}
+
+/*
+ * ------------------
+ * virtio config ops
+ * ------------------
+ */
+
+static u32
+_virtio_get_features(struct virtio_device *dev)
+{
+ struct virtio_vbus_priv *priv = virtio_to_priv(dev);
+ u32 features;
+ int ret;
+
+ ret = devcall(priv, VIRTIO_VBUS_FUNC_GET_FEATURES,
+ &features, sizeof(features));
+ BUG_ON(ret < 0);
+
+ /*
+ * When someone needs more than 32 feature bits, we'll need to
+ * steal a bit to indicate that the rest are somewhere else.
+ */
+ return features;
+}
+
+static void
+_virtio_finalize_features(struct virtio_device *dev)
+{
+ struct virtio_vbus_priv *priv = virtio_to_priv(dev);
+ int ret;
+
+ /* Give virtio_ring a chance to accept features. */
+ vring_transport_features(dev);
+
+ /* We only support 32 feature bits. */
+ BUILD_BUG_ON(ARRAY_SIZE(dev->features) != 1);
+
+ ret = devcall(priv, VIRTIO_VBUS_FUNC_FINALIZE_FEATURES,
+ &dev->features[0], sizeof(dev->features[0]));
+ BUG_ON(ret < 0);
+}
+
+static void
+_virtio_get(struct virtio_device *vdev, unsigned offset,
+ void *buf, unsigned len)
+{
+ struct virtio_vbus_priv *priv = virtio_to_priv(vdev);
+
+ BUG_ON((offset + len) > VIRTIO_VBUS_CONFIGSPACE_LEN);
+ memcpy(buf, &priv->config.shm->data[offset], len);
+}
+
+static void
+_virtio_set(struct virtio_device *vdev, unsigned offset,
+ const void *buf, unsigned len)
+{
+ struct virtio_vbus_priv *priv = virtio_to_priv(vdev);
+ int ret;
+
+ BUG_ON((offset + len) > VIRTIO_VBUS_CONFIGSPACE_LEN);
+ memcpy(&priv->config.shm->data[offset], buf, len);
+
+ ret = shm_signal_inject(priv->config.signal, 0);
+ BUG_ON(ret < 0);
+}
+
+static u8
+_virtio_get_status(struct virtio_device *vdev)
+{
+ struct virtio_vbus_priv *priv = virtio_to_priv(vdev);
+ u8 data;
+ int ret;
+
+ ret = devcall(priv, VIRTIO_VBUS_FUNC_GET_STATUS, &data, sizeof(data));
+ BUG_ON(ret < 0);
+
+ return data;
+}
+
+static void
+_virtio_set_status(struct virtio_device *vdev, u8 status)
+{
+ struct virtio_vbus_priv *priv = virtio_to_priv(vdev);
+ int ret;
+
+ /* We should never be setting status to 0. */
+ BUG_ON(status == 0);
+
+ ret = devcall(priv, VIRTIO_VBUS_FUNC_SET_STATUS, &status,
+ sizeof(status));
+ BUG_ON(ret < 0);
+}
+
+static void
+_virtio_reset(struct virtio_device *vdev)
+{
+ struct virtio_vbus_priv *priv = virtio_to_priv(vdev);
+ int ret;
+
+ ret = devcall(priv, VIRTIO_VBUS_FUNC_RESET, NULL, 0);
+ BUG_ON(ret < 0);
+}
+
+/*
+ * ------------------
+ * virtqueue ops
+ * ------------------
+ */
+
+static int
+_vq_getlen(struct virtio_vbus_priv *priv, int index)
+{
+ struct virtio_vbus_queryqueue query = {
+ .index = index,
+ };
+ int ret;
+
+ ret = devcall(priv, VIRTIO_VBUS_FUNC_QUERY_QUEUE,
+ &query, sizeof(query));
+ if (ret < 0)
+ return ret;
+
+ return query.num;
+}
+
+static void
+_vq_kick(struct virtqueue *vq)
+{
+ struct vbus_virtqueue *_vq = vq->priv;
+ int ret;
+
+ ret = shm_signal_inject(_vq->signal, 0);
+ BUG_ON(ret < 0);
+}
+
+/*
+ * This is called whenever the host signals our virtqueue
+ */
+static void
+_vq_isr(struct shm_signal_notifier *notifier)
+{
+ struct vbus_virtqueue *_vq = container_of(notifier,
+ struct vbus_virtqueue,
+ notifier);
+ vring_interrupt(0, _vq->vq);
+}
+
+static struct virtqueue *
+_virtio_find_vq(struct virtio_device *vdev, unsigned index,
+ void (*callback)(struct virtqueue *vq))
+{
+ struct virtio_vbus_priv *priv = virtio_to_priv(vdev);
+ struct vbus_device_proxy *dev = priv->vbus_dev;
+ struct vbus_virtqueue *_vq;
+ struct virtqueue *vq;
+ unsigned long ringsize;
+ int num;
+ int ret;
+
+ num = _vq_getlen(priv, index);
+ if (num < 0)
+ return ERR_PTR(num);
+
+ _vq = kmalloc(sizeof(struct vbus_virtqueue), GFP_KERNEL);
+ if (!_vq)
+ return ERR_PTR(-ENOMEM);
+
+ ringsize = vring_size(num, PAGE_SIZE);
+
+ _vq->index = index;
+ _vq->num = num;
+ _vq->size = PAGE_ALIGN(sizeof(struct virtio_vbus_shm) + ringsize - 1);
+
+ _vq->shm = alloc_pages_exact(_vq->size, GFP_KERNEL|__GFP_ZERO);
+ if (!_vq->shm) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ /* initialize the shm with a ring */
+ vq = vring_new_virtqueue(_vq->num, PAGE_SIZE, vdev,
+ &_vq->shm->data[0],
+ _vq_kick,
+ callback);
+ if (!vq) {
+ ret = -ENOMEM;
+ goto out_free;
+ }
+
+ /* register the shm with an id of the vq index + RING_OFFSET */
+ ret = dev->ops->shm(dev, index + VIRTIO_VBUS_RING_OFFSET, 0,
+ _vq->shm, _vq->size,
+ &_vq->shm->signal, &_vq->signal, 0);
+ if (ret < 0)
+ goto out_free;
+
+ _vq->notifier.signal = &_vq_isr;
+ _vq->signal->notifier = &_vq->notifier;
+
+ shm_signal_enable(_vq->signal, 0);
+
+ vq->priv = _vq;
+ _vq->vq = vq;
+
+ return vq;
+
+out_free:
+ free_pages_exact(_vq->shm, _vq->size);
+out:
+ if (_vq && _vq->signal)
+ shm_signal_put(_vq->signal);
+ kfree(_vq);
+ return ERR_PTR(ret);
+}
+
+/* the config->del_vq() implementation */
+static void
+_virtio_del_vq(struct virtqueue *vq)
+{
+ struct virtio_vbus_priv *priv = virtio_to_priv(vq->vdev);
+ struct vbus_virtqueue *_vq = vq->priv;
+
+ devcall(priv, VIRTIO_VBUS_FUNC_DEL_QUEUE,
+ &_vq->index, sizeof(_vq->index));
+
+ vring_del_virtqueue(vq);
+
+ shm_signal_put(_vq->signal);
+ free_pages_exact(_vq->shm, _vq->size);
+ kfree(_vq);
+}
+
+/*
+ * ------------------
+ * general setup
+ * ------------------
+ */
+
+static struct virtio_config_ops virtio_vbus_config_ops = {
+ .get = _virtio_get,
+ .set = _virtio_set,
+ .get_status = _virtio_get_status,
+ .set_status = _virtio_set_status,
+ .reset = _virtio_reset,
+ .find_vq = _virtio_find_vq,
+ .del_vq = _virtio_del_vq,
+ .get_features = _virtio_get_features,
+ .finalize_features = _virtio_finalize_features,
+};
+
+/*
+ * Negotiate vbus transport features. This is not to be confused with the
+ * higher-level function FUNC_GET/FINALIZE_FEATURES, which is specifically
+ * for the virtio transport
+ */
+static void
+virtio_vbus_negcap(struct virtio_vbus_priv *priv)
+{
+ u64 features = 0; /* We do not have any advanced features to enable */
+ int ret;
+
+ ret = devcall(priv, VIRTIO_VBUS_FUNC_NEG_CAP,
+ &features, sizeof(features));
+ BUG_ON(ret < 0);
+}
+
+static void
+virtio_vbus_getid(struct virtio_vbus_priv *priv)
+{
+ struct virtio_vbus_id id;
+ int ret;
+
+ ret = devcall(priv, VIRTIO_VBUS_FUNC_GET_ID, &id, sizeof(id));
+ BUG_ON(ret < 0);
+
+ priv->virtio_dev.id.vendor = id.vendor;
+ priv->virtio_dev.id.device = id.device;
+}
+
+static int
+virtio_vbus_initconfig(struct virtio_vbus_priv *priv)
+{
+ struct vbus_device_proxy *vdev = priv->vbus_dev;
+ size_t len;
+ int ret;
+
+ len = sizeof(struct virtio_vbus_shm) + VIRTIO_VBUS_CONFIGSPACE_LEN - 1;
+
+ priv->config.shm = kzalloc(len, GFP_KERNEL);
+ if (!priv->config.shm)
+ return -ENOMEM;
+
+ ret = vdev->ops->shm(vdev, 0, 0,
+ &priv->config.shm, len,
+ &priv->config.shm->signal, &priv->config.signal,
+ 0);
+ BUG_ON(ret < 0);
+
+ priv->config.notifier.signal = &config_isr;
+ priv->config.signal->notifier = &priv->config.notifier;
+
+ shm_signal_enable(priv->config.signal, 0);
+
+ return 0;
+}
+
+/* the VBUS probing function */
+static int
+virtio_vbus_probe(struct vbus_device_proxy *vdev)
+{
+ struct virtio_vbus_priv *priv;
+ int ret;
+
+ printk(KERN_INFO "VIRTIO-VBUS: Found new device at %lld\n", vdev->id);
+
+ ret = vdev->ops->open(vdev, VIRTIO_VBUS_ABI_VERSION, 0);
+ if (ret < 0) {
+ printk(KERN_ERR "virtio_vbus: ABI version %d failed with: %d\n",
+ VIRTIO_VBUS_ABI_VERSION, ret);
+ return ret;
+ }
+
+ priv = kzalloc(sizeof(struct virtio_vbus_priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->virtio_dev.config = &virtio_vbus_config_ops;
+ priv->vbus_dev = vdev;
+
+ /*
+ * Negotiate for any vbus specific features
+ */
+ virtio_vbus_negcap(priv);
+
+ /*
+ * This probe occurs for any "virtio" device on the vbus, so we need
+ * to hypercall the host to figure out what specific PCI-ID type
+ * device this is
+ */
+ virtio_vbus_getid(priv);
+
+ /*
+ * Map our config-space to the device, and establish a signal-path
+ * for config-space updates
+ */
+ virtio_vbus_initconfig(priv);
+
+ /* finally register the virtio device */
+ ret = register_virtio_device(&priv->virtio_dev);
+ if (ret)
+ goto out;
+
+ vdev->priv = priv;
+
+ return 0;
+
+out:
+ kfree(priv);
+ return ret;
+}
+
+#ifdef NOTYET
+/* FIXME: wire this up */
+static void
+virtio_vbus_release(struct virtio_vbus_priv *priv)
+{
+ shm_signal_put(priv->config.signal);
+ kfree(priv->config.shm);
+ kfree(priv);
+}
+
+#endif
+
+static int
+virtio_vbus_remove(struct vbus_device_proxy *vdev)
+{
+ struct virtio_vbus_priv *priv = vdev->priv;
+
+ unregister_virtio_device(&priv->virtio_dev);
+
+ return 0;
+}
+
+/*
+ * Finally, the module stuff
+ */
+
+static struct vbus_driver_ops virtio_vbus_driver_ops = {
+ .probe = virtio_vbus_probe,
+ .remove = virtio_vbus_remove,
+};
+
+static struct vbus_driver virtio_vbus_driver = {
+ .type = "virtio",
+ .owner = THIS_MODULE,
+ .ops = &virtio_vbus_driver_ops,
+};
+
+static __init int
+virtio_vbus_init_module(void)
+{
+ printk(KERN_INFO "Virtio-VBUS: Copyright (C) 2009 Novell, Gregory Haskins\n");
+ return vbus_driver_register(&virtio_vbus_driver);
+}
+
+static __exit void
+virtio_vbus_cleanup(void)
+{
+ vbus_driver_unregister(&virtio_vbus_driver);
+}
+
+module_init(virtio_vbus_init_module);
+module_exit(virtio_vbus_cleanup);
+
diff --git a/include/linux/virtio_vbus.h b/include/linux/virtio_vbus.h
new file mode 100644
index 0000000..05791bf
--- /dev/null
+++ b/include/linux/virtio_vbus.h
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2009 Novell. All Rights Reserved.
+ *
+ * Virtio VBUS driver
+ *
+ * This module allows virtio devices to be used over a VBUS interface
+ *
+ * Author:
+ * Gregory Haskins <ghaskins@xxxxxxxxxx>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _LINUX_VIRTIO_VBUS_H
+#define _LINUX_VIRTIO_VBUS_H
+
+#include <linux/shm_signal.h>
+
+#define VIRTIO_VBUS_ABI_VERSION 1
+
+enum {
+ VIRTIO_VBUS_FUNC_NEG_CAP,
+ VIRTIO_VBUS_FUNC_GET_ID,
+ VIRTIO_VBUS_FUNC_GET_FEATURES,
+ VIRTIO_VBUS_FUNC_FINALIZE_FEATURES,
+ VIRTIO_VBUS_FUNC_GET_STATUS,
+ VIRTIO_VBUS_FUNC_SET_STATUS,
+ VIRTIO_VBUS_FUNC_RESET,
+ VIRTIO_VBUS_FUNC_QUERY_QUEUE,
+ VIRTIO_VBUS_FUNC_DEL_QUEUE,
+};
+
+struct virtio_vbus_id {
+ u16 vendor;
+ u16 device;
+};
+
+struct virtio_vbus_queryqueue {
+ u64 index; /* in: queue index */
+ u32 num; /* out: number of entries */
+ u32 pad[0];
+};
+
+#define VIRTIO_VBUS_CONFIGSPACE_LEN 1024
+#define VIRTIO_VBUS_RING_OFFSET 10000 /* shm-index where rings start */
+
+struct virtio_vbus_shm {
+ struct shm_signal_desc signal;
+ char data[1];
+};
+
+/*
+ * --------------------------------------------------
+ * Backend support - These components are only needed
+ * for interfacing a virtio-backend to the vbus-backend
+ * --------------------------------------------------
+ */
+
+#include <linux/vbus_device.h>
+
+struct virtio_device_interface;
+struct virtio_connection;
+
+struct virtio_queue_def {
+ int index;
+ int entries;
+};
+
+/*
+ * ----------------------
+ * interface
+ * ----------------------
+ */
+
+struct virtio_device_interface_ops {
+ int (*open)(struct virtio_device_interface *intf,
+ struct vbus_memctx *ctx,
+ struct virtio_connection **conn);
+ void (*release)(struct virtio_device_interface *intf);
+};
+
+struct virtio_device_interface {
+ struct virtio_vbus_id id;
+ struct virtio_device_interface_ops *ops;
+ struct virtio_queue_def *queues;
+ struct vbus_device_interface *parent;
+};
+
+/**
+ * virtio_device_interface_register() - register an interface with a bus
+ * @dev: The device context of the caller
+ * @vbus: The bus context to register with
+ * @intf: The interface context to register
+ *
+ * This function is invoked (usually in the context of a device::bus_connect()
+ * callback) to register a interface on a bus. We make this an explicit
+ * operation instead of implicit on the bus_connect() to facilitate devices
+ * that may present multiple interfaces to a bus. In those cases, a device
+ * may invoke this function multiple times (one per supported interface).
+ *
+ * Returns: success = 0, <0 = ERRNO
+ *
+ **/
+int virtio_device_interface_register(struct vbus_device *dev,
+ struct vbus *vbus,
+ struct virtio_device_interface *intf);
+
+/**
+ * virtio_device_interface_unregister() - unregister an interface with a bus
+ * @intf: The interface context to unregister
+ *
+ * This function is the converse of interface_register. It is typically
+ * invoked in the context of a device::bus_disconnect().
+ *
+ * Returns: success = 0, <0 = ERRNO
+ *
+ **/
+int virtio_device_interface_unregister(struct virtio_device_interface *intf);
+
+/*
+ * ----------------------
+ * connection
+ * ----------------------
+ */
+struct virtqueue;
+
+struct virtio_connection_ops {
+ void (*config_changed)(struct virtio_connection *vconn);
+ u8 (*get_status)(struct virtio_connection *vconn);
+ void (*set_status)(struct virtio_connection *vconn, u8 status);
+ void (*reset)(struct virtio_connection *vconn);
+ u32 (*get_features)(struct virtio_connection *vconn);
+ void (*finalize_features)(struct virtio_connection *vconn);
+ void (*add_vq)(struct virtio_connection *vconn, int index,
+ struct virtqueue *vq);
+ void (*del_vq)(struct virtio_connection *vconn, int index);
+ void (*notify_vq)(struct virtio_connection *vconn, int index);
+ void (*release)(struct virtio_connection *conn);
+};
+
+struct virtio_connection {
+ struct virtio_connection_ops *ops;
+ struct vbus_connection *parent;
+};
+
+int virtio_connection_config_get(struct virtio_connection *vconn,
+ int offset, void *buf, size_t len);
+
+int virtio_connection_config_set(struct virtio_connection *vconn,
+ int offset, void *buf, size_t len);
+
+#endif /* _LINUX_VIRTIO_VBUS_H */
diff --git a/kernel/vbus/Kconfig b/kernel/vbus/Kconfig
index b894dd1..0a4813e 100644
--- a/kernel/vbus/Kconfig
+++ b/kernel/vbus/Kconfig
@@ -14,6 +14,14 @@ config VBUS
If unsure, say N
+config VBUS_VIRTIO_BACKEND
+ tristate "Virtio VBUS Backend"
+ depends on VIRTIO_RING
+ depends on VBUS
+ default n
+ help
+ Provides backend support for virtio devices over vbus
+
config VBUS_DEVICES
bool "Virtual-Bus Devices"
depends on VBUS
diff --git a/kernel/vbus/Makefile b/kernel/vbus/Makefile
index 61d0371..c2bd140 100644
--- a/kernel/vbus/Makefile
+++ b/kernel/vbus/Makefile
@@ -1,6 +1,9 @@
obj-$(CONFIG_VBUS) += core.o devclass.o config.o attribute.o map.o client.o
obj-$(CONFIG_VBUS) += shm-ioq.o
+virtio-backend-objs += virtio.o
+obj-$(CONFIG_VBUS_VIRTIO_BACKEND) += virtio-backend.o
+
vbus-proxy-objs += proxy.o
obj-$(CONFIG_VBUS_DRIVERS) += vbus-proxy.o
diff --git a/kernel/vbus/virtio.c b/kernel/vbus/virtio.c
new file mode 100644
index 0000000..b2fe002
--- /dev/null
+++ b/kernel/vbus/virtio.c
@@ -0,0 +1,627 @@
+/*
+ * Copyright 2009 Novell. All Rights Reserved.
+ *
+ * Author:
+ * Gregory Haskins <ghaskins@xxxxxxxxxx>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/virtio.h>
+#include <linux/virtio_ring.h>
+#include <linux/virtio_vbus.h>
+
+MODULE_AUTHOR("Gregory Haskins");
+MODULE_LICENSE("GPL");
+
+#undef PDEBUG
+#ifdef VENETTAP_DEBUG
+# define PDEBUG(fmt, args...) printk(KERN_DEBUG "virtio-vbus: " fmt, ## args)
+#else
+# define PDEBUG(fmt, args...)
+#endif
+
+struct _virtio_device_interface {
+ struct virtio_device_interface *vintf;
+ struct vbus_device_interface intf;
+};
+
+struct _virtio_connection {
+ struct _virtio_device_interface *_vintf;
+ struct virtio_connection *vconn;
+ struct vbus_connection conn;
+ struct vbus_memctx *ctx;
+ struct list_head queues;
+
+ struct {
+ struct vbus_shm *shm;
+ struct shm_signal *signal;
+ struct shm_signal_notifier notifier;
+ } config;
+
+ int running:1;
+};
+
+struct _virtio_queue {
+ int index;
+ int num;
+ struct _virtio_connection *_vconn;
+ struct virtqueue *vq;
+
+ struct vbus_shm *shm;
+ struct shm_signal *signal;
+ struct shm_signal_notifier notifier;
+
+ struct list_head node;
+};
+
+static struct _virtio_device_interface *
+to_vintf(struct vbus_device_interface *intf)
+{
+ return container_of(intf, struct _virtio_device_interface, intf);
+}
+
+static struct _virtio_connection *
+to_vconn(struct vbus_connection *conn)
+{
+ return container_of(conn, struct _virtio_connection, conn);
+}
+
+int virtio_connection_config_get(struct virtio_connection *vconn,
+ int offset, void *buf, size_t len)
+{
+ struct _virtio_connection *_vconn = to_vconn(vconn->parent);
+ char *data;
+
+ if (!_vconn->config.shm)
+ return -EINVAL;
+
+ if (offset + len > _vconn->config.shm->len)
+ return -EINVAL;
+
+ data = _vconn->config.shm->ptr;
+
+ memcpy(buf, &data[offset], len);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(virtio_connection_config_get);
+
+int virtio_connection_config_set(struct virtio_connection *vconn,
+ int offset, void *buf, size_t len)
+{
+ struct _virtio_connection *_vconn = to_vconn(vconn->parent);
+ char *data;
+
+ if (!_vconn->config.shm)
+ return -EINVAL;
+
+ if (offset + len > _vconn->config.shm->len)
+ return -EINVAL;
+
+ data = _vconn->config.shm->ptr;
+
+ memcpy(&data[offset], buf, len);
+
+ if (_vconn->config.signal)
+ shm_signal_inject(_vconn->config.signal, 0);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(virtio_connection_config_set);
+
+/*
+ * Negotiate Capabilities - This function is provided so that the
+ * interface may be extended without breaking ABI compatability
+ *
+ * The caller is expected to send down any capabilities they would like
+ * to enable, and the device will OR them with capabilities that it
+ * supports. This value is then returned so that both sides may
+ * ascertain the lowest-common-denominator of features to enable
+ */
+static int
+_virtio_connection_negcap(struct _virtio_connection *_vconn,
+ void *data, unsigned long len)
+{
+ struct vbus_memctx *ctx = _vconn->ctx;
+ u64 features;
+ int ret;
+
+ if (len != sizeof(features))
+ return -EINVAL;
+
+ if (_vconn->running)
+ return -EINVAL;
+
+#ifdef NOTYET
+ ret = ctx->ops->copy_from(ctx, &features, data, sizeof(features));
+ if (ret)
+ return -EFAULT;
+#endif
+
+ /*
+ * right now we dont support any advanced features, so just clear all
+ * bits
+ */
+ features = 0;
+
+ ret = ctx->ops->copy_to(ctx, data, &features, sizeof(features));
+ if (ret)
+ return -EFAULT;
+
+ return 0;
+}
+
+static int
+_virtio_connection_getid(struct _virtio_connection *_vconn,
+ void *data, unsigned long len)
+{
+ struct vbus_memctx *ctx = _vconn->ctx;
+ struct virtio_vbus_id *id = &_vconn->_vintf->vintf->id;
+ int ret;
+
+ if (len != sizeof(*id))
+ return -EINVAL;
+
+ ret = ctx->ops->copy_to(ctx, data, id, sizeof(*id));
+ if (ret)
+ return -EFAULT;
+
+ return 0;
+}
+
+static int
+_virtio_connection_getstatus(struct _virtio_connection *_vconn,
+ void *data, unsigned long len)
+{
+ struct virtio_connection *vconn = _vconn->vconn;
+ struct vbus_memctx *ctx = _vconn->ctx;
+ u8 val = 0;
+ int ret;
+
+ if (len != sizeof(val))
+ return -EINVAL;
+
+ if (vconn->ops->get_status)
+ val = vconn->ops->get_status(vconn);
+
+ ret = ctx->ops->copy_to(ctx, data, &val, sizeof(val));
+ if (ret)
+ return -EFAULT;
+
+ return 0;
+}
+
+static int
+_virtio_connection_setstatus(struct _virtio_connection *_vconn,
+ void *data, unsigned long len)
+{
+ struct virtio_connection *vconn = _vconn->vconn;
+ struct vbus_memctx *ctx = _vconn->ctx;
+ u8 val;
+ int ret;
+
+ if (len != sizeof(val))
+ return -EINVAL;
+
+ if (!vconn->ops->set_status)
+ return 0;
+
+ ret = ctx->ops->copy_from(ctx, &val, data, sizeof(val));
+ if (ret)
+ return -EFAULT;
+
+ vconn->ops->set_status(vconn, val);
+
+ return 0;
+}
+
+static int
+_virtio_connection_getfeatures(struct _virtio_connection *_vconn,
+ void *data, unsigned long len)
+{
+ struct virtio_connection *vconn = _vconn->vconn;
+ struct vbus_memctx *ctx = _vconn->ctx;
+ u32 val = 0;
+ int ret;
+
+ if (len != sizeof(val))
+ return -EINVAL;
+
+ if (vconn->ops->get_features)
+ val = vconn->ops->get_features(vconn);
+
+ ret = ctx->ops->copy_to(ctx, data, &val, sizeof(val));
+ if (ret)
+ return -EFAULT;
+
+ return 0;
+}
+
+static int
+_virtio_connection_finalizefeatures(struct _virtio_connection *_vconn)
+{
+ struct virtio_connection *vconn = _vconn->vconn;
+
+ if (vconn->ops->finalize_features)
+ vconn->ops->finalize_features(vconn);
+
+ return 0;
+}
+
+static int
+_virtio_connection_reset(struct _virtio_connection *_vconn)
+{
+ struct virtio_connection *vconn = _vconn->vconn;
+
+ if (vconn->ops->reset)
+ vconn->ops->reset(vconn);
+
+ return 0;
+}
+
+static struct _virtio_queue *
+_virtio_find_queue(struct _virtio_connection *_vconn, int index)
+{
+ struct _virtio_queue *vq;
+
+ list_for_each_entry(vq, &_vconn->queues, node) {
+ if (vq->index == index)
+ return vq;
+ }
+
+ return NULL;
+}
+
+static int
+_virtio_connection_queryqueue(struct _virtio_connection *_vconn,
+ void *data, unsigned long len)
+{
+ struct vbus_memctx *ctx = _vconn->ctx;
+ struct virtio_vbus_queryqueue val;
+ struct _virtio_queue *vq;
+ int ret;
+
+ if (len != sizeof(val))
+ return -EINVAL;
+
+ ret = ctx->ops->copy_from(ctx, &val, data, sizeof(val));
+ if (ret)
+ return -EFAULT;
+
+ vq = _virtio_find_queue(_vconn, val.index);
+
+ if (!vq)
+ return -EINVAL;
+
+ if (vq->shm)
+ return -EEXIST;
+
+ val.num = vq->num;
+
+ ret = ctx->ops->copy_to(ctx, data, &val, sizeof(val));
+ if (ret)
+ return -EFAULT;
+
+ return 0;
+}
+
+static int
+_virtio_connection_call(struct vbus_connection *conn,
+ unsigned long func,
+ void *data,
+ unsigned long len,
+ unsigned long flags)
+{
+ struct _virtio_connection *_vconn = to_vconn(conn);
+ int ret = 0;
+
+ PDEBUG("call -> %d with %p/%d\n", func, data, len);
+
+ switch (func) {
+ case VIRTIO_VBUS_FUNC_NEG_CAP:
+ ret = _virtio_connection_negcap(_vconn, data, len);
+ break;
+ case VIRTIO_VBUS_FUNC_GET_ID:
+ ret = _virtio_connection_getid(_vconn, data, len);
+ break;
+ case VIRTIO_VBUS_FUNC_GET_FEATURES:
+ ret = _virtio_connection_getfeatures(_vconn, data, len);
+ break;
+ case VIRTIO_VBUS_FUNC_FINALIZE_FEATURES:
+ _virtio_connection_finalizefeatures(_vconn);
+ break;
+ case VIRTIO_VBUS_FUNC_GET_STATUS:
+ ret = _virtio_connection_getstatus(_vconn, data, len);
+ break;
+ case VIRTIO_VBUS_FUNC_SET_STATUS:
+ ret = _virtio_connection_setstatus(_vconn, data, len);
+ break;
+ case VIRTIO_VBUS_FUNC_RESET:
+ _virtio_connection_reset(_vconn);
+ break;
+ case VIRTIO_VBUS_FUNC_QUERY_QUEUE:
+ ret = _virtio_connection_queryqueue(_vconn, data, len);
+ break;
+ case VIRTIO_VBUS_FUNC_DEL_QUEUE:
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static void _virtio_config_isr(struct shm_signal_notifier *notifier)
+{
+ struct _virtio_connection *_vconn;
+ struct virtio_connection *vconn;
+
+ _vconn = container_of(notifier, struct _virtio_connection,
+ config.notifier);
+
+ vconn = _vconn->vconn;
+
+ if (vconn->ops->config_changed)
+ vconn->ops->config_changed(vconn);
+}
+
+static int
+_virtio_connection_open(struct _virtio_connection *_vconn)
+
+{
+ struct virtio_device_interface *vintf = _vconn->_vintf->vintf;
+ struct virtio_connection *vconn;
+ struct virtio_queue_def *def = vintf->queues;
+ int ret;
+
+ ret = vintf->ops->open(vintf, _vconn->ctx, &vconn);
+ if (ret < 0)
+ return ret;
+
+ while (def && def->index != -1) {
+ struct _virtio_queue *vq;
+
+ vq = kzalloc(sizeof(*vq), GFP_KERNEL);
+ if (!vq)
+ return -ENOMEM;
+
+ vq->index = def->index;
+ vq->num = def->entries;
+ vq->_vconn = _vconn;
+
+ list_add_tail(&vq->node, &_vconn->queues);
+
+ def++;
+ }
+
+ _vconn->vconn = vconn;
+ vconn->parent = &_vconn->conn;
+
+ return 0;
+}
+
+static int
+_virtio_connection_initconfig(struct _virtio_connection *_vconn,
+ struct vbus_shm *shm,
+ struct shm_signal *signal)
+{
+ int ret;
+
+ if (_vconn->running)
+ return -EINVAL;
+
+ _vconn->config.signal = signal;
+ _vconn->config.shm = shm;
+ _vconn->config.notifier.signal = &_virtio_config_isr;
+ signal->notifier = &_vconn->config.notifier;
+
+ shm_signal_enable(signal, 0);
+
+ ret = _virtio_connection_open(_vconn);
+ if (ret < 0)
+ return ret;
+
+ _vconn->running = 1;
+
+ return 0;
+}
+
+static void _vq_isr(struct shm_signal_notifier *notifier)
+{
+ struct _virtio_queue *vq;
+
+ vq = container_of(notifier, struct _virtio_queue, notifier);
+
+ vring_interrupt(0, vq->vq);
+}
+
+static void _vq_notify(struct virtqueue *vq)
+{
+ struct _virtio_queue *_vq = vq->priv;
+
+ shm_signal_inject(_vq->signal, 0);
+}
+
+static void _vq_callback(struct virtqueue *vq)
+{
+ struct _virtio_queue *_vq = vq->priv;
+ struct virtio_connection *vconn = _vq->_vconn->vconn;
+
+ vconn->ops->notify_vq(vconn, _vq->index);
+}
+
+static int
+_virtio_connection_shm(struct vbus_connection *conn,
+ unsigned long id,
+ struct vbus_shm *shm,
+ struct shm_signal *signal,
+ unsigned long flags)
+{
+ struct _virtio_connection *_vconn = to_vconn(conn);
+ struct virtio_connection *vconn = _vconn->vconn;
+ struct _virtio_queue *vq;
+ struct virtio_vbus_shm *_shm = shm->ptr;
+
+ /* All shm connections that we support require a signal */
+ if (!signal)
+ return -EINVAL;
+
+ if (!id)
+ return _virtio_connection_initconfig(_vconn, shm, signal);
+
+ vq = _virtio_find_queue(_vconn, id - VIRTIO_VBUS_RING_OFFSET);
+ if (!vq)
+ return -EINVAL;
+
+ if (vq->shm)
+ return -EEXIST;
+
+ vq->shm = shm;
+ vq->signal = signal;
+
+ vq->notifier.signal = &_vq_isr;
+ signal->notifier = &vq->notifier;
+
+ shm_signal_enable(signal, 0);
+
+ vq->vq = vring_new_virtqueue(vq->num, PAGE_SIZE, NULL,
+ &_shm->data[0],
+ _vq_notify, _vq_callback);
+
+ vq->vq->priv = vq;
+
+ vconn->ops->add_vq(vconn, vq->index, vq->vq);
+
+ return 0;
+}
+
+static void
+_virtio_connection_release(struct vbus_connection *conn)
+{
+ struct _virtio_connection *_vconn = to_vconn(conn);
+ struct virtio_connection *vconn = _vconn->vconn;
+ struct _virtio_queue *vq, *tmp;
+
+ vconn->ops->release(vconn);
+
+ list_for_each_entry_safe(vq, tmp, &_vconn->queues, node) {
+ if (vq->vq)
+ vring_del_virtqueue(vq->vq);
+
+ if (vq->shm)
+ vbus_shm_put(vq->shm);
+
+ if (vq->signal)
+ shm_signal_put(vq->signal);
+
+ list_del(&vq->node);
+ kfree(vq);
+ }
+
+ if (_vconn->config.signal)
+ shm_signal_put(_vconn->config.signal);
+
+ if (_vconn->config.shm)
+ vbus_shm_put(_vconn->config.shm);
+
+ kobject_put(&_vconn->_vintf->intf.kobj);
+ vbus_memctx_put(_vconn->ctx);
+
+ kfree(_vconn);
+}
+
+static struct vbus_connection_ops _virtio_connection_ops = {
+ .call = _virtio_connection_call,
+ .shm = _virtio_connection_shm,
+ .release = _virtio_connection_release,
+};
+
+static int
+_virtio_intf_open(struct vbus_device_interface *intf,
+ struct vbus_memctx *ctx,
+ int version,
+ struct vbus_connection **conn)
+{
+ struct _virtio_device_interface *_vintf = to_vintf(intf);
+ struct _virtio_connection *_vconn;
+
+ if (version != VIRTIO_VBUS_ABI_VERSION)
+ return -EINVAL;
+
+ _vconn = kzalloc(sizeof(*_vconn), GFP_KERNEL);
+ if (!_vconn)
+ return -ENOMEM;
+
+ vbus_connection_init(&_vconn->conn, &_virtio_connection_ops);
+ _vconn->_vintf = _vintf;
+ _vconn->ctx = ctx;
+ INIT_LIST_HEAD(&_vconn->queues);
+
+ vbus_memctx_get(ctx);
+ kobject_get(&intf->kobj);
+
+ *conn = &_vconn->conn;
+
+ return 0;
+}
+
+static void
+_virtio_intf_release(struct vbus_device_interface *intf)
+{
+ struct _virtio_device_interface *_vintf = to_vintf(intf);
+ struct virtio_device_interface *vintf = _vintf->vintf;
+
+ if (vintf && vintf->ops->release)
+ vintf->ops->release(vintf);
+ kfree(_vintf);
+}
+
+static struct vbus_device_interface_ops _virtio_device_interface_ops = {
+ .open = _virtio_intf_open,
+ .release = _virtio_intf_release,
+};
+
+int
+virtio_device_interface_register(struct vbus_device *dev,
+ struct vbus *vbus,
+ struct virtio_device_interface *vintf)
+{
+ struct _virtio_device_interface *_vintf;
+ struct vbus_device_interface *intf;
+
+ _vintf = kzalloc(sizeof(*_vintf), GFP_KERNEL);
+ if (!_vintf)
+ return -ENOMEM;
+
+ _vintf->vintf = vintf;
+
+ intf = &_vintf->intf;
+
+ intf->name = "0"; /* FIXME */
+ intf->type = "virtio";
+ intf->ops = &_virtio_device_interface_ops;
+
+ return vbus_device_interface_register(dev, vbus, intf);
+}
+EXPORT_SYMBOL_GPL(virtio_device_interface_register);
+
+int
+virtio_device_interface_unregister(struct virtio_device_interface *intf)
+{
+ return vbus_device_interface_unregister(intf->parent);
+}
+EXPORT_SYMBOL_GPL(virtio_device_interface_unregister);
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/