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


    \
     
     \ /
      Last update: 2011-09-02 14:27    [W:0.050 / U:69.400 seconds]
    ©2003-2017 Jasper Spaans. hosted at Digital OceanAdvertise on this site