lkml.org 
[lkml]   [2011]   [Sep]   [12]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
From
Subject[RFC v2] arm: Add platform bus driver for virtio device
Date
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...

Cc: Rusty Russell <rusty@rustcorp.com.au>
Cc: Anthony Liguori <aliguori@us.ibm.com>
Cc: Michael S.Tsirkin <mst@redhat.com>
Cc: Magnus Damm <magnus.damm@gmail.com>
Signed-off-by: Pawel Moll <pawel.moll@arm.com>
---
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@arm.com>");
+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
--
1.6.3.3


\
 
 \ /
  Last update: 2011-09-12 18:59    [W:2.081 / U:0.016 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site