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 <[email protected]>
---

 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 <[email protected]>
+ *
+ * Derived from virtio-pci, written by
+ *  Anthony Liguori  <[email protected]>
+ *
+ * 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 <[email protected]>");
+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 <[email protected]>
+ *
+ * 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 <[email protected]>
+ *
+ * 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 kvm" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to