[PATCH 02/02] virtio: Add virtio platform driver

From: Magnus Damm
Date: Thu Mar 10 2011 - 02:00:36 EST


From: Magnus Damm <damm@xxxxxxxxxxxxx>

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 <damm@xxxxxxxxxxxxx>
---

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 */
--
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/