This patch, based on virtio PCI driver, adds support for virtio
AMBA device. This should allow environments like qemu to use
virtio-based block & network devices.

For example, one can define and register an AMBA device like
this (emulation environment must of course provide a correctly
mapped "Virtio Block Device Prime Cell"):

struct amba_device virtio_block = {
        .dev.init_name  = "virtio-block",
        .res            = {
                .start  = 0x1c0d0000,
                .end    = 0x1c0d0fff,
                .flags  = IORESOURCE_MEM,
        },
        .irq            = { 73, },
};

This should be soon replaced with Device Tree entry
supplied by the simulation environment.

Signed-off-by: Pawel Moll <pawel.m...@arm.com>
---
 drivers/virtio/Kconfig       |   11 +
 drivers/virtio/Makefile      |    1 +
 drivers/virtio/virtio_amba.c |  443 ++++++++++++++++++++++++++++++++++++++++++
 include/linux/virtio_amba.h  |   62 ++++++
 4 files changed, 517 insertions(+), 0 deletions(-)
 create mode 100644 drivers/virtio/virtio_amba.c
 create mode 100644 include/linux/virtio_amba.h

diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
index 57e493b..a8530ab 100644
--- a/drivers/virtio/Kconfig
+++ b/drivers/virtio/Kconfig
@@ -35,4 +35,15 @@ config VIRTIO_BALLOON
 
         If unsure, say M.
 
+ config VIRTIO_AMBA
+       tristate "AMBA bus driver for virtio devices (EXPERIMENTAL)"
+       depends on ARM_AMBA && EXPERIMENTAL
+       select VIRTIO
+       select VIRTIO_RING
+       ---help---
+        This drivers provides support for virtio based paravirtual device
+        drivers over an AMBA bus.
+
+        If unsure, say N.
+
 endmenu
diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile
index 6738c44..49147cb 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_AMBA) += virtio_amba.o
 obj-$(CONFIG_VIRTIO_PCI) += virtio_pci.o
 obj-$(CONFIG_VIRTIO_BALLOON) += virtio_balloon.o
diff --git a/drivers/virtio/virtio_amba.c b/drivers/virtio/virtio_amba.c
new file mode 100644
index 0000000..0cfbaec
--- /dev/null
+++ b/drivers/virtio/virtio_amba.c
@@ -0,0 +1,443 @@
+/*
+ * Virtio AMBA driver
+ *
+ * Copyright 2011, ARM Ltd.
+ *
+ * This module allows virtio devices to be used over a virtual AMBA device.
+ *
+ * Registers layout:
+ *
+ * offset width name          description
+ * ------ ----- ------------- -----------------
+ *
+ *  0x000   32  HostFeatures  Features supported by the host
+ *  0x004   32  GuestFeatures Features activated by the guest
+ *  0x008   32  QueuePFN      PFN for the currently selected queue
+ *  0x00c   32  QueueNum      Queue size for the currently selected queue
+ *  0x010   32  QueueSel      Queue selector
+ *  0x014   32  QueueNotify   Queue notifier
+ *  0x018   8   InterruptACK  Interrupt acknowledge register
+ *  0x01c   8   Status        Device status register
+ *
+ *  0x020
+ *   ...                      Device-specific configuration space
+ *  0xfdf
+ *
+ *  0xfe0   8   PeriphID0     0x30
+ *  0xfe4   8   PeriphID1     0x17
+ *  0xfe8   8   PeriphID2     0x04
+ *  0xfec   8   PeriphID3     VIRTIO_ID_* (see <linux/virtio_ids.h>)
+ *  0xff0   8   PCellID0      0x0d
+ *  0xff4   8   PCellID1      0xf0
+ *  0xff8   8   PCellID2      0x05
+ *  0xffc   8   PCellID3      0xb1
+ *
+ * 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/module.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/amba/bus.h>
+#include <linux/interrupt.h>
+#include <linux/virtio.h>
+#include <linux/virtio_amba.h>
+#include <linux/virtio_config.h>
+#include <linux/virtio_ring.h>
+#include <linux/highmem.h>
+#include <linux/spinlock.h>
+
+
+
+#define to_virtio_amba_device(_amba_dev) \
+       container_of(_amba_dev, struct virtio_amba_device, vdev)
+
+struct virtio_amba_device {
+       struct virtio_device vdev;
+       struct amba_device *amba_dev;
+
+       void __iomem *base;
+
+       /* a list of queues so we can dispatch IRQs */
+       spinlock_t lock;
+       struct list_head virtqueues;
+};
+
+struct virtio_amba_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_amba_device *va_dev = to_virtio_amba_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(va_dev->base + VIRTIO_AMBA_HOST_FEATURES);
+}
+
+static void va_finalize_features(struct virtio_device *vdev)
+{
+       struct virtio_amba_device *va_dev = to_virtio_amba_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], va_dev->base + VIRTIO_AMBA_GUEST_FEATURES);
+}
+
+static void va_get(struct virtio_device *vdev, unsigned offset,
+                  void *buf, unsigned len)
+{
+       struct virtio_amba_device *va_dev = to_virtio_amba_device(vdev);
+       u8 *ptr = buf;
+       int i;
+
+       for (i = 0; i < len; i++)
+               ptr[i] = readb(va_dev->base + VIRTIO_AMBA_CONFIG + offset + i);
+}
+
+static void va_set(struct virtio_device *vdev, unsigned offset,
+                  const void *buf, unsigned len)
+{
+       struct virtio_amba_device *va_dev = to_virtio_amba_device(vdev);
+       const u8 *ptr = buf;
+       int i;
+
+       for (i = 0; i < len; i++)
+               writeb(ptr[i], va_dev->base + VIRTIO_AMBA_CONFIG + offset + i);
+}
+
+static u8 va_get_status(struct virtio_device *vdev)
+{
+       struct virtio_amba_device *va_dev = to_virtio_amba_device(vdev);
+
+       return readb(va_dev->base + VIRTIO_AMBA_STATUS) & 0xff;
+}
+
+static void va_set_status(struct virtio_device *vdev, u8 status)
+{
+       struct virtio_amba_device *va_dev = to_virtio_amba_device(vdev);
+
+       /* We should never be setting status to 0. */
+       BUG_ON(status == 0);
+
+       writeb(status, va_dev->base + VIRTIO_AMBA_STATUS);
+}
+
+static void va_reset(struct virtio_device *vdev)
+{
+       struct virtio_amba_device *va_dev = to_virtio_amba_device(vdev);
+
+       /* 0 status means a reset. */
+       writeb(0, va_dev->base + VIRTIO_AMBA_STATUS);
+}
+
+
+
+/* Transport interface */
+
+/* the notify function used when creating a virt queue */
+static void va_notify(struct virtqueue *vq)
+{
+       struct virtio_amba_device *va_dev = to_virtio_amba_device(vq->vdev);
+       struct virtio_amba_vq_info *info = vq->priv;
+
+       /* We write the queue's selector into the notification register to
+        * signal the other end */
+       writel(info->queue_index, va_dev->base + VIRTIO_AMBA_QUEUE_NOTIFY);
+}
+
+/* Notify all virtqueues on an interrupt. */
+static irqreturn_t va_interrupt(int irq, void *opaque)
+{
+       struct virtio_amba_device *va_dev = opaque;
+       struct virtio_amba_vq_info *info;
+       irqreturn_t ret = IRQ_NONE;
+       unsigned long flags;
+
+       writeb(1, va_dev->base + VIRTIO_AMBA_INTERRUPT_ACK);
+
+       spin_lock_irqsave(&va_dev->lock, flags);
+       list_for_each_entry(info, &va_dev->virtqueues, node) {
+               if (vring_interrupt(irq, info->vq) == IRQ_HANDLED)
+                       ret = IRQ_HANDLED;
+       }
+       spin_unlock_irqrestore(&va_dev->lock, flags);
+
+       return ret;
+}
+
+
+
+static void va_del_vq(struct virtqueue *vq)
+{
+       struct virtio_amba_device *va_dev = to_virtio_amba_device(vq->vdev);
+       struct virtio_amba_vq_info *info = vq->priv;
+       unsigned long flags, size;
+
+       spin_lock_irqsave(&va_dev->lock, flags);
+       list_del(&info->node);
+       spin_unlock_irqrestore(&va_dev->lock, flags);
+
+       writel(info->queue_index, va_dev->base + VIRTIO_AMBA_QUEUE_SEL);
+
+       vring_del_virtqueue(vq);
+
+       /* Select and deactivate the queue */
+       writel(0, va_dev->base + VIRTIO_AMBA_QUEUE_PFN);
+
+       size = PAGE_ALIGN(vring_size(info->num, VIRTIO_AMBA_VRING_ALIGN));
+       free_pages_exact(info->queue, size);
+       kfree(info);
+}
+
+static void va_del_vqs(struct virtio_device *vdev)
+{
+       struct virtio_amba_device *va_dev = to_virtio_amba_device(vdev);
+       struct virtqueue *vq, *n;
+
+       list_for_each_entry_safe(vq, n, &vdev->vqs, list)
+               va_del_vq(vq);
+
+       free_irq(va_dev->amba_dev->irq[0], va_dev);
+}
+
+
+
+static struct virtqueue *va_setup_vq(struct virtio_device *vdev, unsigned 
index,
+                                 void (*callback)(struct virtqueue *vq),
+                                 const char *name)
+{
+       struct virtio_amba_device *va_dev = to_virtio_amba_device(vdev);
+       struct virtio_amba_vq_info *info;
+       struct virtqueue *vq;
+       unsigned long flags, size;
+       u16 num;
+       int err;
+
+       /* Select the queue we're interested in */
+       writel(index, va_dev->base + VIRTIO_AMBA_QUEUE_SEL);
+
+       /* Check if queue is either not available or already active. */
+       num = readl(va_dev->base + VIRTIO_AMBA_QUEUE_NUM);
+       if (!num || readl(va_dev->base + VIRTIO_AMBA_QUEUE_PFN)) {
+               err = -ENOENT;
+               goto error_available;
+       }
+
+       /* Allocate and fill out our structure the represents an active
+        * queue */
+       info = kmalloc(sizeof(struct virtio_amba_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_AMBA_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_AMBA_QUEUE_ADDR_SHIFT,
+                 va_dev->base + VIRTIO_AMBA_QUEUE_PFN);
+
+       /* Create the vring */
+       vq = vring_new_virtqueue(info->num, VIRTIO_AMBA_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(&va_dev->lock, flags);
+       list_add(&info->node, &va_dev->virtqueues);
+       spin_unlock_irqrestore(&va_dev->lock, flags);
+
+       return vq;
+
+error_new_virtqueue:
+       writel(0, va_dev->base + VIRTIO_AMBA_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_amba_device *va_dev = to_virtio_amba_device(vdev);
+       int i, err;
+
+       err = request_irq(va_dev->amba_dev->irq[0], va_interrupt,
+                         IRQF_SHARED, dev_name(&vdev->dev), va_dev);
+       if (err)
+               goto error_request_irq;
+
+       for (i = 0; i < nvqs; ++i) {
+               vqs[i] = va_setup_vq(vdev, i, callbacks[i], names[i]);
+               if (IS_ERR(vqs[i])) {
+                       err = PTR_ERR(vqs[i]);
+                       goto error_va_setup_vq;
+               }
+       }
+
+       return 0;
+
+error_va_setup_vq:
+       va_del_vqs(vdev);
+error_request_irq:
+       return err;
+}
+
+
+
+static struct virtio_config_ops virtio_amba_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,
+};
+
+
+
+/* AMBA device */
+
+static int __devinit virtio_amba_probe(struct amba_device *amba_dev,
+               const struct amba_id *id)
+{
+       struct virtio_amba_device *va_dev;
+       int err;
+
+       va_dev = kzalloc(sizeof(struct virtio_amba_device), GFP_KERNEL);
+       if (va_dev == NULL) {
+               err = -ENOMEM;
+               goto error_kzalloc;
+       }
+
+       va_dev->vdev.dev.parent = &amba_dev->dev;
+       va_dev->vdev.config = &virtio_amba_config_ops;
+       va_dev->amba_dev = amba_dev;
+       INIT_LIST_HEAD(&va_dev->virtqueues);
+       spin_lock_init(&va_dev->lock);
+
+       err = amba_request_regions(amba_dev, "virtio-amba");
+       if (err)
+               goto error_request_regions;
+
+       va_dev->base = ioremap(amba_dev->res.start,
+                       resource_size(&amba_dev->res));
+       if (va_dev->base == NULL)
+               goto error_ioremap;
+
+       /* We use the "Configuration" field in Prime Cell ID
+        * as the virtio device id. */
+       va_dev->vdev.id.device = amba_config(amba_dev);
+
+       amba_set_drvdata(amba_dev, va_dev);
+
+       err = register_virtio_device(&va_dev->vdev);
+       if (err)
+               goto error_register_virtio_device;
+
+       return 0;
+
+error_register_virtio_device:
+       iounmap(va_dev->base);
+error_ioremap:
+       amba_release_regions(amba_dev);
+error_request_regions:
+       kfree(va_dev);
+error_kzalloc:
+       return err;
+}
+
+static int __devexit virtio_amba_remove(struct amba_device *amba_dev)
+{
+       struct virtio_amba_device *va_dev = amba_get_drvdata(amba_dev);
+
+       unregister_virtio_device(&va_dev->vdev);
+
+       iounmap(va_dev->base);
+       amba_release_regions(amba_dev);
+       kfree(va_dev);
+
+       return 0;
+}
+
+
+
+/* AMBA driver */
+
+static struct amba_id virtio_amba_ids[] = {
+       {
+               .id     = 0x00041730,
+               .mask   = 0x000fffff,
+       },
+       { 0, 0 }
+};
+
+static struct amba_driver virtio_amba_driver = {
+       .drv.name       = "virtio-amba",
+       .probe          = virtio_amba_probe,
+       .remove         = __devexit_p(virtio_amba_remove),
+       .id_table       = virtio_amba_ids,
+};
+
+static int __init virtio_amba_init(void)
+{
+       return amba_driver_register(&virtio_amba_driver);
+}
+
+static void __exit virtio_amba_exit(void)
+{
+       amba_driver_unregister(&virtio_amba_driver);
+}
+
+module_init(virtio_amba_init);
+module_exit(virtio_amba_exit);
+
+MODULE_AUTHOR("Pawel Moll <pawel.m...@arm.com>");
+MODULE_DESCRIPTION("AMBA bus driver for virtio devices");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/virtio_amba.h b/include/linux/virtio_amba.h
new file mode 100644
index 0000000..c754b87
--- /dev/null
+++ b/include/linux/virtio_amba.h
@@ -0,0 +1,62 @@
+/*
+ * Virtio AMBA 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_AMBA_H
+#define _LINUX_VIRTIO_AMBA_H
+
+/* Bitmask of the features supported by the host (32-bit register) */
+#define VIRTIO_AMBA_HOST_FEATURES      0x000
+
+/* Bitmask of features activated by the guest (32-bit register) */
+#define VIRTIO_AMBA_GUEST_FEATURES     0x004
+
+/* PFN for the currently selected queue (32-bit register) */
+#define VIRTIO_AMBA_QUEUE_PFN          0x008
+
+/* Queue size for the currently selected queue (32-bit register) */
+#define VIRTIO_AMBA_QUEUE_NUM          0x00c
+
+/* Queue selector (32-bit register) */
+#define VIRTIO_AMBA_QUEUE_SEL          0x010
+
+/* Queue notifier (32-bit register) */
+#define VIRTIO_AMBA_QUEUE_NOTIFY       0x014
+
+/* Interrupt acknowledge (8-bit register) */
+#define VIRTIO_AMBA_INTERRUPT_ACK      0x018
+
+/* Device status register (8-bit register) */
+#define VIRTIO_AMBA_STATUS             0x01c
+
+/* The config space is defined by each driver as
+ * the per-driver configuration space */
+#define VIRTIO_AMBA_CONFIG             0x020
+
+/* PrimeCell identification registers (8-bit registers) */
+#define VIRTIO_AMBA_PERIPH_ID0         0xfe0
+#define VIRTIO_AMBA_PERIPH_ID1         0xfe4
+#define VIRTIO_AMBA_PERIPH_ID2         0xfe8
+#define VIRTIO_AMBA_PERIPH_ID3         0xfec
+#define VIRTIO_AMBA_PCELL_ID0          0xff0
+#define VIRTIO_AMBA_PCELL_ID1          0xff4
+#define VIRTIO_AMBA_PCELL_ID2          0xff8
+#define VIRTIO_AMBA_PCELL_ID3          0xffc
+
+
+/* How many bits to shift physical queue address written to QUEUE_PFN.
+ * 12 is historical, and due to 4kb page size. */
+#define VIRTIO_AMBA_QUEUE_ADDR_SHIFT   12
+
+/* The alignment to use between consumer and producer parts of vring.
+ * Page size again. */
+#define VIRTIO_AMBA_VRING_ALIGN                4096
+
+#endif
-- 
1.6.3.3



_______________________________________________
linaro-dev mailing list
linaro-dev@lists.linaro.org
http://lists.linaro.org/mailman/listinfo/linaro-dev

Reply via email to