[RFC 1/2] virtio: Add AMBA bus driver for virtio device

From: Pawel Moll
Date: Fri Sep 02 2011 - 08:25:22 EST


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.moll@xxxxxxx>
---
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.moll@xxxxxxx>");
+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

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