From: Magnus Damm <[email protected]>

This patch adds a virtio platform driver. The code is based on
the lguest implementation available in drivers/lguest/lguest_device.c

The iomem resource is used to point out the lguest device descriptor,
and the platform data contains two separate callbacks for notification.

Signed-off-by: Magnus Damm <[email protected]>
---

 drivers/virtio/Kconfig           |   11 +
 drivers/virtio/Makefile          |    1 
 drivers/virtio/virtio_platform.c |  282 ++++++++++++++++++++++++++++++++++++++
 include/linux/virtio_platform.h  |   12 +
 4 files changed, 306 insertions(+)

--- 0002/drivers/virtio/Kconfig
+++ work/drivers/virtio/Kconfig 2011-03-03 15:56:53.000000000 +0900
@@ -35,3 +35,14 @@ config VIRTIO_BALLOON
 
 config VIRTIO_LGUEST
        tristate
+
+config VIRTIO_PLATFORM
+       tristate "Platform driver for virtio devices (EXPERIMENTAL)"
+       select VIRTIO
+       select VIRTIO_RING
+       select VIRTIO_LGUEST
+       ---help---
+        This drivers provides support for virtio based paravirtual device
+        drivers over a platform bus.
+
+        If unsure, say N.
--- 0002/drivers/virtio/Makefile
+++ work/drivers/virtio/Makefile        2011-03-03 15:56:53.000000000 +0900
@@ -3,3 +3,4 @@ obj-$(CONFIG_VIRTIO_RING) += virtio_ring
 obj-$(CONFIG_VIRTIO_PCI) += virtio_pci.o
 obj-$(CONFIG_VIRTIO_BALLOON) += virtio_balloon.o
 obj-$(CONFIG_VIRTIO_LGUEST) += virtio_lguest.o
+obj-$(CONFIG_VIRTIO_PLATFORM) += virtio_platform.o
--- /dev/null
+++ work/drivers/virtio/virtio_platform.c       2011-03-03 16:07:31.000000000 
+0900
@@ -0,0 +1,282 @@
+#include <linux/init.h>
+#include <linux/virtio.h>
+#include <linux/virtio_config.h>
+#include <linux/virtio_ring.h>
+#include <linux/lguest_device.h>
+#include <linux/lguest_launcher.h>
+#include <linux/virtio_platform.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+
+static void __iomem *virtio_map(unsigned long phys_addr, unsigned long pages)
+{
+       return ioremap_nocache(phys_addr, PAGE_SIZE*pages);
+}
+
+static void virtio_unmap(void __iomem *addr)
+{
+       iounmap(addr);
+}
+
+static void set_status(struct virtio_device *vdev, u8 status)
+{
+       struct platform_device *pdev = to_platform_device(vdev->dev.parent);
+       struct virtio_platform_data *pdata = pdev->dev.platform_data;
+
+       to_lgdev(vdev)->desc->status = status;
+       pdata->notify_virtio(pdev);
+}
+
+static void lg_set_status(struct virtio_device *vdev, u8 status)
+{
+       BUG_ON(!status);
+       set_status(vdev, status);
+}
+
+static void lg_reset(struct virtio_device *vdev)
+{
+       set_status(vdev, 0);
+}
+
+/*
+ * Virtqueues
+ *
+ * The other piece of infrastructure virtio needs is a "virtqueue": a way of
+ * the Guest device registering buffers for the other side to read from or
+ * write into (ie. send and receive buffers).  Each device can have multiple
+ * virtqueues: for example the console driver uses one queue for sending and
+ * another for receiving.
+ *
+ * Fortunately for us, a very fast shared-memory-plus-descriptors virtqueue
+ * already exists in virtio_ring.c.  We just need to connect it up.
+ *
+ * We start with the information we need to keep about each virtqueue.
+ */
+
+/*D:140 This is the information we remember about each virtqueue. */
+struct lguest_vq_info {
+       /* A copy of the information contained in the device config. */
+       struct lguest_vqconfig config;
+
+       /* The address where we mapped the virtio ring, so we can unmap it. */
+       void __iomem *pages;
+};
+
+/*
+ * When the virtio_ring code wants to prod the Host, it calls us here and we
+ * call the board specific callback.  We hand a pointer to the configuration
+ * so the board code knows which virtqueue we're talking about.
+ */
+static void lg_notify(struct virtqueue *vq)
+{
+       struct platform_device *pdev = to_platform_device(vq->vdev->dev.parent);
+       struct virtio_platform_data *pdata = pdev->dev.platform_data;
+       struct lguest_vq_info *lvq = vq->priv;
+
+       pdata->notify_virtqueue(pdev, &lvq->config);
+}
+
+/*
+ * This routine finds the Nth virtqueue described in the configuration of
+ * this device and sets it up.
+ *
+ * This is kind of an ugly duckling.  It'd be nicer to have a standard
+ * representation of a virtqueue in the configuration space, but it seems that
+ * everyone wants to do it differently.  The KVM coders want the Guest to
+ * allocate its own pages and tell the Host where they are, but for lguest it's
+ * simpler for the Host to simply tell us where the pages are.
+ */
+static struct virtqueue *lg_find_vq(struct virtio_device *vdev,
+                                   unsigned index,
+                                   void (*callback)(struct virtqueue *vq),
+                                   const char *name)
+{
+       struct lguest_device *ldev = to_lgdev(vdev);
+       struct lguest_vq_info *lvq;
+       struct virtqueue *vq;
+       int err;
+
+       /* We must have this many virtqueues. */
+       if (index >= ldev->desc->num_vq)
+               return ERR_PTR(-ENOENT);
+
+       lvq = kmalloc(sizeof(*lvq), GFP_KERNEL);
+       if (!lvq)
+               return ERR_PTR(-ENOMEM);
+
+       /*
+        * Make a copy of the "struct lguest_vqconfig" entry, which sits after
+        * the descriptor.  We need a copy because the config space might not
+        * be aligned correctly.
+        */
+       memcpy(&lvq->config, lg_vq(ldev->desc)+index, sizeof(lvq->config));
+
+       dev_info(&vdev->dev, "Mapping virtqueue %i addr %lx\n", index,
+                (unsigned long)lvq->config.pfn << PAGE_SHIFT);
+       /* Figure out how many pages the ring will take, and map that memory */
+       lvq->pages = virtio_map((unsigned long)lvq->config.pfn << PAGE_SHIFT,
+                               DIV_ROUND_UP(vring_size(lvq->config.num,
+                                                       LGUEST_VRING_ALIGN),
+                                            PAGE_SIZE));
+       if (!lvq->pages) {
+               err = -ENOMEM;
+               goto free_lvq;
+       }
+
+       /*
+        * OK, tell virtio_ring.c to set up a virtqueue now we know its size
+        * and we've got a pointer to its pages.
+        */
+       vq = vring_new_virtqueue(lvq->config.num, LGUEST_VRING_ALIGN,
+                                vdev, lvq->pages, lg_notify, callback, name);
+       if (!vq) {
+               err = -ENOMEM;
+               goto unmap;
+       }
+
+       /*
+        * Tell the interrupt for this virtqueue to go to the virtio_ring
+        * interrupt handler.
+        *
+        * FIXME: We used to have a flag for the Host to tell us we could use
+        * the interrupt as a source of randomness: it'd be nice to have that
+        * back.
+        */
+       err = request_irq(lvq->config.irq, vring_interrupt, IRQF_SHARED,
+                         dev_name(&vdev->dev), vq);
+       if (err)
+               goto destroy_vring;
+
+       /*
+        * Last of all we hook up our 'struct lguest_vq_info" to the
+        * virtqueue's priv pointer.
+        */
+       vq->priv = lvq;
+       return vq;
+
+destroy_vring:
+       vring_del_virtqueue(vq);
+unmap:
+       virtio_unmap(lvq->pages);
+free_lvq:
+       kfree(lvq);
+       return ERR_PTR(err);
+}
+/*:*/
+
+/* Cleaning up a virtqueue is easy */
+static void lg_del_vq(struct virtqueue *vq)
+{
+       struct lguest_vq_info *lvq = vq->priv;
+
+       /* Release the interrupt */
+       free_irq(lvq->config.irq, vq);
+       /* Tell virtio_ring.c to free the virtqueue. */
+       vring_del_virtqueue(vq);
+       /* Unmap the pages containing the ring. */
+       virtio_unmap(lvq->pages);
+       /* Free our own queue information. */
+       kfree(lvq);
+}
+
+static void lg_del_vqs(struct virtio_device *vdev)
+{
+       struct virtqueue *vq, *n;
+
+       list_for_each_entry_safe(vq, n, &vdev->vqs, list)
+               lg_del_vq(vq);
+}
+
+static int lg_find_vqs(struct virtio_device *vdev, unsigned nvqs,
+                      struct virtqueue *vqs[],
+                      vq_callback_t *callbacks[],
+                      const char *names[])
+{
+       struct lguest_device *ldev = to_lgdev(vdev);
+       int i;
+
+       /* We must have this many virtqueues. */
+       if (nvqs > ldev->desc->num_vq)
+               return -ENOENT;
+
+       for (i = 0; i < nvqs; ++i) {
+               vqs[i] = lg_find_vq(vdev, i, callbacks[i], names[i]);
+               if (IS_ERR(vqs[i]))
+                       goto error;
+       }
+       return 0;
+
+error:
+       lg_del_vqs(vdev);
+       return PTR_ERR(vqs[i]);
+}
+
+/* The ops structure which hooks everything together. */
+static struct virtio_config_ops lguest_config_ops = {
+       .get_features = lg_get_features,
+       .finalize_features = lg_finalize_features,
+       .get = lg_get,
+       .set = lg_set,
+       .get_status = lg_get_status,
+       .set_status = lg_set_status,
+       .reset = lg_reset,
+       .find_vqs = lg_find_vqs,
+       .del_vqs = lg_del_vqs,
+};
+
+static int virtio_platform_probe(struct platform_device *pdev)
+{
+       struct virtio_platform_data *pdata = pdev->dev.platform_data;
+       struct resource *res;
+       void *devices;
+
+       if (!pdata) {
+               dev_err(&pdev->dev, "no platform data defined\n");
+               return -EINVAL;
+       }
+
+       res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (res == NULL) {
+               dev_err(&pdev->dev, "cannot find IO resource\n");
+               return -ENOENT;
+       }
+
+       /* Devices are pointed out using struct resource */
+       devices = virtio_map(res->start, resource_size(res) >> PAGE_SHIFT);
+       lg_scan_devices(devices, &pdev->dev, &lguest_config_ops);
+       return 0;
+}
+
+static int virtio_platform_remove(struct platform_device *pdev)
+{
+       return -ENOTSUPP;
+}
+
+static struct platform_driver virtio_platform_driver = {
+       .driver         = {
+               .name           = "virtio-platform",
+               .owner          = THIS_MODULE,
+       },
+       .probe          = virtio_platform_probe,
+       .remove         = virtio_platform_remove,
+};
+
+static int __init virtio_platform_init(void)
+{
+       return platform_driver_register(&virtio_platform_driver);
+}
+
+static void __exit virtio_platform_exit(void)
+{
+       platform_driver_unregister(&virtio_platform_driver);
+}
+
+module_init(virtio_platform_init);
+module_exit(virtio_platform_exit);
+
+MODULE_DESCRIPTION("VirtIO Platform Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:virtio-platform");
--- /dev/null
+++ work/include/linux/virtio_platform.h        2011-03-03 15:56:54.000000000 
+0900
@@ -0,0 +1,12 @@
+#ifndef _LINUX_VIRTIO_PLATFORM_H
+#define _LINUX_VIRTIO_PLATFORM_H
+#include <linux/platform_device.h>
+#include <linux/lguest_launcher.h>
+
+struct virtio_platform_data {
+       void (*notify_virtio)(struct platform_device *pdev);
+       void (*notify_virtqueue)(struct platform_device *pdev,
+                                struct lguest_vqconfig *config);
+};
+
+#endif /* _LINUX_VIRTIO_PLATFORM_H */
_______________________________________________
Virtualization mailing list
[email protected]
https://lists.linux-foundation.org/mailman/listinfo/virtualization

Reply via email to