Re: [RFC v2] arm: Add platform bus driver for virtio device

From: Anthony Liguori
Date: Mon Sep 12 2011 - 13:27:19 EST


On 09/12/2011 11:51 AM, Pawel Moll wrote:
This patch, based on virtio PCI driver, adds support for memory
mapped (platform) virtio device. This should allow environments
like qemu to use virtio-based block& network devices.

One can define and register a platform device which resources
will describe memory mapped control registers and "mailbox"
interrupt. Such device can be also instantiated using the Device
Tree node with compatible property equal "virtio,platform".

Note: Work in progress...

Are you planning on sending patches to QEMU for this? I think it makes sense to start in QEMU with this effort and make a proper spec from which you can write the driver against.

benh has also written a platform virtio transports for use with Power. Ben, could you take a look and see if it's worth merging the two efforts?

Regards,

Anthony Liguori


Cc: Rusty Russell<rusty@xxxxxxxxxxxxxxx>
Cc: Anthony Liguori<aliguori@xxxxxxxxxx>
Cc: Michael S.Tsirkin<mst@xxxxxxxxxx>
Cc: Magnus Damm<magnus.damm@xxxxxxxxx>
Signed-off-by: Pawel Moll<pawel.moll@xxxxxxx>
---
drivers/virtio/Kconfig | 11 +
drivers/virtio/Makefile | 1 +
drivers/virtio/virtio_platform.c | 424 ++++++++++++++++++++++++++++++++++++++
include/linux/virtio_platform.h | 62 ++++++
4 files changed, 498 insertions(+), 0 deletions(-)
create mode 100644 drivers/virtio/virtio_platform.c
create mode 100644 include/linux/virtio_platform.h

diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
index 57e493b..63edf72 100644
--- a/drivers/virtio/Kconfig
+++ b/drivers/virtio/Kconfig
@@ -35,4 +35,15 @@ config VIRTIO_BALLOON

If unsure, say M.

+ config VIRTIO_PLATFORM
+ tristate "Platform bus driver for virtio devices (EXPERIMENTAL)"
+ depends on EXPERIMENTAL
+ select VIRTIO
+ select VIRTIO_RING
+ ---help---
+ This drivers provides support for memory mapped (platform) virtio
+ based paravirtual device driver.
+
+ If unsure, say N.
+
endmenu
diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile
index 6738c44..4d175c0 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_PLATFORM) += virtio_platform.o
obj-$(CONFIG_VIRTIO_PCI) += virtio_pci.o
obj-$(CONFIG_VIRTIO_BALLOON) += virtio_balloon.o
diff --git a/drivers/virtio/virtio_platform.c b/drivers/virtio/virtio_platform.c
new file mode 100644
index 0000000..b16027b
--- /dev/null
+++ b/drivers/virtio/virtio_platform.c
@@ -0,0 +1,424 @@
+/*
+ * Virtio platform device driver
+ *
+ * Copyright 2011, ARM Ltd.
+ *
+ * This module allows virtio devices to be used over a virtual platform device.
+ *
+ * Registers layout:
+ *
+ * offset width name description
+ * ------ ----- ------------- -----------------
+ *
+ * 0x000 32 MagicValue Magic value "virt" (0x74726976 LE)
+ * 0x004 32 DeviceID Virtio device ID
+ * 0x008 32 VendorID Virtio vendor ID
+ *
+ * 0x010 32 HostFeatures Features supported by the host
+ * 0x020 32 GuestFeatures Features activated by the guest
+ *
+ * 0x030 32 QueuePFN PFN for the currently selected queue
+ * 0x034 32 QueueNum Queue size for the currently selected queue
+ * 0x038 32 QueueSel Queue selector
+ * 0x03c 32 QueueNotify Queue notifier
+ *
+ * 0x040 32 InterruptACK Interrupt acknowledge register
+ * 0x050 8 Status Device status register
+ *
+ * 0x100
+ * ... Device-specific configuration space
+ * 0xfff
+ *
+ * Based on Virtio PCI driver by Anthony Liguori, copyright IBM Corp. 2007
+ *
+ * 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/highmem.h>
+#include<linux/interrupt.h>
+#include<linux/io.h>
+#include<linux/list.h>
+#include<linux/module.h>
+#include<linux/platform_device.h>
+#include<linux/slab.h>
+#include<linux/spinlock.h>
+#include<linux/virtio.h>
+#include<linux/virtio_config.h>
+#include<linux/virtio_platform.h>
+#include<linux/virtio_ring.h>
+
+
+
+#define to_virtio_plat_device(_plat_dev) \
+ container_of(_plat_dev, struct virtio_plat_device, vdev)
+
+struct virtio_plat_device {
+ struct virtio_device vdev;
+ struct platform_device *pdev;
+
+ void __iomem *base;
+
+ /* a list of queues so we can dispatch IRQs */
+ spinlock_t lock;
+ struct list_head virtqueues;
+};
+
+struct virtio_plat_vq_info {
+ /* the actual virtqueue */
+ struct virtqueue *vq;
+
+ /* the number of entries in the queue */
+ int num;
+
+ /* the index of the queue */
+ int queue_index;
+
+ /* the virtual address of the ring queue */
+ void *queue;
+
+ /* the list node for the virtqueues list */
+ struct list_head node;
+};
+
+
+
+/* Configuration interface */
+
+static u32 va_get_features(struct virtio_device *vdev)
+{
+ struct virtio_plat_device *vpdev = to_virtio_plat_device(vdev);
+
+ /* When someone needs more than 32 feature bits, we'll need to
+ * steal a bit to indicate that the rest are somewhere else. */
+ return readl(vpdev->base + VIRTIO_PLAT_HOST_FEATURES);
+}
+
+static void va_finalize_features(struct virtio_device *vdev)
+{
+ struct virtio_plat_device *vpdev = to_virtio_plat_device(vdev);
+
+ /* Give virtio_ring a chance to accept features. */
+ vring_transport_features(vdev);
+
+ /* We only support 32 feature bits. */
+ BUILD_BUG_ON(ARRAY_SIZE(vdev->features) != 1);
+ writel(vdev->features[0], vpdev->base + VIRTIO_PLAT_GUEST_FEATURES);
+}
+
+static void va_get(struct virtio_device *vdev, unsigned offset,
+ void *buf, unsigned len)
+{
+ struct virtio_plat_device *vpdev = to_virtio_plat_device(vdev);
+ u8 *ptr = buf;
+ int i;
+
+ for (i = 0; i< len; i++)
+ ptr[i] = readb(vpdev->base + VIRTIO_PLAT_CONFIG + offset + i);
+}
+
+static void va_set(struct virtio_device *vdev, unsigned offset,
+ const void *buf, unsigned len)
+{
+ struct virtio_plat_device *vpdev = to_virtio_plat_device(vdev);
+ const u8 *ptr = buf;
+ int i;
+
+ for (i = 0; i< len; i++)
+ writeb(ptr[i], vpdev->base + VIRTIO_PLAT_CONFIG + offset + i);
+}
+
+static u8 va_get_status(struct virtio_device *vdev)
+{
+ struct virtio_plat_device *vpdev = to_virtio_plat_device(vdev);
+
+ return readb(vpdev->base + VIRTIO_PLAT_STATUS)& 0xff;
+}
+
+static void va_set_status(struct virtio_device *vdev, u8 status)
+{
+ struct virtio_plat_device *vpdev = to_virtio_plat_device(vdev);
+
+ /* We should never be setting status to 0. */
+ BUG_ON(status == 0);
+
+ writeb(status, vpdev->base + VIRTIO_PLAT_STATUS);
+}
+
+static void va_reset(struct virtio_device *vdev)
+{
+ struct virtio_plat_device *vpdev = to_virtio_plat_device(vdev);
+
+ /* 0 status means a reset. */
+ writeb(0, vpdev->base + VIRTIO_PLAT_STATUS);
+}
+
+
+
+/* Transport interface */
+
+/* the notify function used when creating a virt queue */
+static void va_notify(struct virtqueue *vq)
+{
+ struct virtio_plat_device *vpdev = to_virtio_plat_device(vq->vdev);
+ struct virtio_plat_vq_info *info = vq->priv;
+
+ /* We write the queue's selector into the notification register to
+ * signal the other end */
+ writel(info->queue_index, vpdev->base + VIRTIO_PLAT_QUEUE_NOTIFY);
+}
+
+/* Notify all virtqueues on an interrupt. */
+static irqreturn_t va_interrupt(int irq, void *opaque)
+{
+ struct virtio_plat_device *vpdev = opaque;
+ struct virtio_plat_vq_info *info;
+ irqreturn_t ret = IRQ_NONE;
+ unsigned long flags;
+
+ writel(1, vpdev->base + VIRTIO_PLAT_INTERRUPT_ACK);
+
+ spin_lock_irqsave(&vpdev->lock, flags);
+ list_for_each_entry(info,&vpdev->virtqueues, node) {
+ if (vring_interrupt(irq, info->vq) == IRQ_HANDLED)
+ ret = IRQ_HANDLED;
+ }
+ spin_unlock_irqrestore(&vpdev->lock, flags);
+
+ return ret;
+}
+
+
+
+static void va_del_vq(struct virtqueue *vq)
+{
+ struct virtio_plat_device *vpdev = to_virtio_plat_device(vq->vdev);
+ struct virtio_plat_vq_info *info = vq->priv;
+ unsigned long flags, size;
+
+ spin_lock_irqsave(&vpdev->lock, flags);
+ list_del(&info->node);
+ spin_unlock_irqrestore(&vpdev->lock, flags);
+
+ writel(info->queue_index, vpdev->base + VIRTIO_PLAT_QUEUE_SEL);
+
+ vring_del_virtqueue(vq);
+
+ /* Select and deactivate the queue */
+ writel(0, vpdev->base + VIRTIO_PLAT_QUEUE_PFN);
+
+ size = PAGE_ALIGN(vring_size(info->num, VIRTIO_PLAT_VRING_ALIGN));
+ free_pages_exact(info->queue, size);
+ kfree(info);
+}
+
+static void va_del_vqs(struct virtio_device *vdev)
+{
+ struct virtio_plat_device *vpdev = to_virtio_plat_device(vdev);
+ struct virtqueue *vq, *n;
+
+ list_for_each_entry_safe(vq, n,&vdev->vqs, list)
+ va_del_vq(vq);
+
+ free_irq(platform_get_irq(vpdev->pdev, 0), vpdev);
+}
+
+
+
+static struct virtqueue *va_setup_vq(struct virtio_device *vdev, unsigned index,
+ void (*callback)(struct virtqueue *vq),
+ const char *name)
+{
+ struct virtio_plat_device *vpdev = to_virtio_plat_device(vdev);
+ struct virtio_plat_vq_info *info;
+ struct virtqueue *vq;
+ unsigned long flags, size;
+ u16 num;
+ int err;
+
+ /* Select the queue we're interested in */
+ writel(index, vpdev->base + VIRTIO_PLAT_QUEUE_SEL);
+
+ /* Check if queue is either not available or already active. */
+ num = readl(vpdev->base + VIRTIO_PLAT_QUEUE_NUM);
+ if (!num || readl(vpdev->base + VIRTIO_PLAT_QUEUE_PFN)) {
+ err = -ENOENT;
+ goto error_available;
+ }
+
+ /* Allocate and fill out our structure the represents an active
+ * queue */
+ info = kmalloc(sizeof(struct virtio_plat_vq_info), GFP_KERNEL);
+ if (!info) {
+ err = -ENOMEM;
+ goto error_kmalloc;
+ }
+
+ info->queue_index = index;
+ info->num = num;
+
+ size = PAGE_ALIGN(vring_size(num, VIRTIO_PLAT_VRING_ALIGN));
+ info->queue = alloc_pages_exact(size, GFP_KERNEL | __GFP_ZERO);
+ if (info->queue == NULL) {
+ err = -ENOMEM;
+ goto error_alloc_pages;
+ }
+
+ /* Activate the queue */
+ writel(virt_to_phys(info->queue)>> VIRTIO_PLAT_QUEUE_ADDR_SHIFT,
+ vpdev->base + VIRTIO_PLAT_QUEUE_PFN);
+
+ /* Create the vring */
+ vq = vring_new_virtqueue(info->num, VIRTIO_PLAT_VRING_ALIGN,
+ vdev, info->queue, va_notify, callback, name);
+ if (!vq) {
+ err = -ENOMEM;
+ goto error_new_virtqueue;
+ }
+
+ vq->priv = info;
+ info->vq = vq;
+
+ spin_lock_irqsave(&vpdev->lock, flags);
+ list_add(&info->node,&vpdev->virtqueues);
+ spin_unlock_irqrestore(&vpdev->lock, flags);
+
+ return vq;
+
+error_new_virtqueue:
+ writel(0, vpdev->base + VIRTIO_PLAT_QUEUE_PFN);
+ free_pages_exact(info->queue, size);
+error_alloc_pages:
+ kfree(info);
+error_kmalloc:
+error_available:
+ return ERR_PTR(err);
+}
+
+static int va_find_vqs(struct virtio_device *vdev, unsigned nvqs,
+ struct virtqueue *vqs[],
+ vq_callback_t *callbacks[],
+ const char *names[])
+{
+ struct virtio_plat_device *vpdev = to_virtio_plat_device(vdev);
+ unsigned int irq = platform_get_irq(vpdev->pdev, 0);
+ int i, err;
+
+ err = request_irq(irq, va_interrupt, IRQF_SHARED,
+ dev_name(&vdev->dev), vpdev);
+ if (err)
+ return err;
+
+ for (i = 0; i< nvqs; ++i) {
+ vqs[i] = va_setup_vq(vdev, i, callbacks[i], names[i]);
+ if (IS_ERR(vqs[i])) {
+ va_del_vqs(vdev);
+ free_irq(irq, vpdev);
+ return PTR_ERR(vqs[i]);
+ }
+ }
+
+ return 0;
+}
+
+
+
+static struct virtio_config_ops virtio_plat_config_ops = {
+ .get = va_get,
+ .set = va_set,
+ .get_status = va_get_status,
+ .set_status = va_set_status,
+ .reset = va_reset,
+ .find_vqs = va_find_vqs,
+ .del_vqs = va_del_vqs,
+ .get_features = va_get_features,
+ .finalize_features = va_finalize_features,
+};
+
+
+
+/* Platform device */
+
+static int __devinit virtio_plat_probe(struct platform_device *pdev)
+{
+ struct virtio_plat_device *vpdev;
+ struct resource *mem;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem)
+ return -EINVAL;
+
+ if (!devm_request_mem_region(&pdev->dev, mem->start,
+ resource_size(mem), pdev->name))
+ return -EBUSY;
+
+ vpdev = devm_kzalloc(&pdev->dev, sizeof(struct virtio_plat_device),
+ GFP_KERNEL);
+ if (!vpdev)
+ return -ENOMEM;
+
+ vpdev->vdev.dev.parent =&pdev->dev;
+ vpdev->vdev.config =&virtio_plat_config_ops;
+ vpdev->pdev = pdev;
+ INIT_LIST_HEAD(&vpdev->virtqueues);
+ spin_lock_init(&vpdev->lock);
+
+ vpdev->base = devm_ioremap(&pdev->dev, mem->start, resource_size(mem));
+ if (vpdev->base == NULL)
+ return -EFAULT;
+
+ /* TODO: check magic value (VIRTIO_PLAT_MAGIC_VALUE) */
+
+ vpdev->vdev.id.device = readl(vpdev->base + VIRTIO_PLAT_DEVICE_ID);
+ vpdev->vdev.id.vendor = readl(vpdev->base + VIRTIO_PLAT_VENDOR_ID);
+
+ platform_set_drvdata(pdev, vpdev);
+
+ return register_virtio_device(&vpdev->vdev);
+}
+
+static int __devexit virtio_plat_remove(struct platform_device *pdev)
+{
+ struct virtio_plat_device *vpdev = platform_get_drvdata(pdev);
+
+ unregister_virtio_device(&vpdev->vdev);
+
+ return 0;
+}
+
+
+
+/* Platform driver */
+
+static struct of_device_id virtio_plat_match[] = {
+ { .compatible = "virtio,platform", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, virtio_plat_match);
+
+static struct platform_driver virtio_plat_driver = {
+ .probe = virtio_plat_probe,
+ .remove = __devexit_p(virtio_plat_remove),
+ .driver = {
+ .name = "virtio-platform",
+ .owner = THIS_MODULE,
+ .of_match_table = virtio_plat_match,
+ },
+};
+
+static int __init virtio_plat_init(void)
+{
+ return platform_driver_register(&virtio_plat_driver);
+}
+
+static void __exit virtio_plat_exit(void)
+{
+ platform_driver_unregister(&virtio_plat_driver);
+}
+
+module_init(virtio_plat_init);
+module_exit(virtio_plat_exit);
+
+MODULE_AUTHOR("Pawel Moll<pawel.moll@xxxxxxx>");
+MODULE_DESCRIPTION("Platform bus driver for virtio devices");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/virtio_platform.h b/include/linux/virtio_platform.h
new file mode 100644
index 0000000..d4b26f1
--- /dev/null
+++ b/include/linux/virtio_platform.h
@@ -0,0 +1,62 @@
+/*
+ * Virtio platform device driver
+ *
+ * Copyright 2011, ARM Ltd.
+ *
+ * Based on Virtio PCI driver by Anthony Liguori, copyright IBM Corp. 2007
+ *
+ * This header is BSD licensed so anyone can use the definitions to implement
+ * compatible drivers/servers.
+ */
+
+#ifndef _LINUX_VIRTIO_PLATFORM_H
+#define _LINUX_VIRTIO_PLATFORM_H
+
+/* Magic value ("virt" string == 0x74726976 Little Endian word */
+#define VIRTIO_PLAT_MAGIC_VALUE 0x000
+
+/* Virtio device ID */
+#define VIRTIO_PLAT_DEVICE_ID 0x004
+
+/* Virtio vendor ID */
+#define VIRTIO_PLAT_VENDOR_ID 0x008
+
+/* Bitmask of the features supported by the host (32-bit register) */
+#define VIRTIO_PLAT_HOST_FEATURES 0x010
+
+/* Bitmask of features activated by the guest (32-bit register) */
+#define VIRTIO_PLAT_GUEST_FEATURES 0x020
+
+/* PFN for the currently selected queue (32-bit register) */
+#define VIRTIO_PLAT_QUEUE_PFN 0x030
+
+/* Queue size for the currently selected queue (32-bit register) */
+#define VIRTIO_PLAT_QUEUE_NUM 0x034
+
+/* Queue selector (32-bit register) */
+#define VIRTIO_PLAT_QUEUE_SEL 0x038
+
+/* Queue notifier (32-bit register) */
+#define VIRTIO_PLAT_QUEUE_NOTIFY 0x03c
+
+/* Interrupt acknowledge (32-bit register) */
+#define VIRTIO_PLAT_INTERRUPT_ACK 0x040
+
+/* Device status register (8-bit register) */
+#define VIRTIO_PLAT_STATUS 0x050
+
+/* The config space is defined by each driver as
+ * the per-driver configuration space */
+#define VIRTIO_PLAT_CONFIG 0x100
+
+
+
+/* How many bits to shift physical queue address written to QUEUE_PFN.
+ * 12 is historical, and due to 4kb page size. */
+#define VIRTIO_PLAT_QUEUE_ADDR_SHIFT 12
+
+/* The alignment to use between consumer and producer parts of vring.
+ * Page size again. */
+#define VIRTIO_PLAT_VRING_ALIGN 4096
+
+#endif

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