Hi All,
These two patches are a proposal of memory mapped virtio device.
The main goal was to provide an equivalent of the virtio PCI device, happily used by KVM an qemu on x86, which could be used by PCI-less platforms (common in ARM world). The interface exposed by my device is very similar to the original virtio_pci one, maybe just a little bit simplified (no sophisticated IRQ infrastructure here). Of course all the control registers live in memory space, instead of PCI IO space (writel vs iowrite32).
This raises an obvious question whether these two implementation could share some common code. Probably some constant values could be shared (like VIRTIO_*_HOST_FEATURES or VIRTIO_*_VRING_ALIGN), maybe struct virtio_*_vq_info as well. On the other hand the vring interface itself is an abstraction layer already... I really don't know, all comments and ideas appreciated.
The second thing is AMBAness of the driver... I've decided to make it an AMBA device rather than simple platform device to add at least a touch of "dicoverability". The idea was simple - one can add "permanent" virtio AMBA device in the kernel placing it somewhere in reserved area of real hardware, so the kernel running on hardware will not get correct amba_id value and ignore the device. With the eve of Ultimate DeviceTreenes this argument is slightly out of date, as qemu could add the virtio device even in runtime. So maybe I should convert it into platform_device then?
This brings additional problem though. Magnus Damm already proposed a platform virtio device here:
http://thread.gmane.org/gmane.linux.ports.sh.devel/11554
however it's something completely different, as his use case is a heterogeneous system and the virtio is used mainly as a transport between nodes. It also uses lguest-based interface, which is much less applicable to the qemu reality.
Let me just add that Peter Maydell kindly agreed to work on the qemu side of the problem, once there is some consensus about the kernel part. He also suggested to send this to linaro-dev first, before throwing it into LKML fire. So here I am :-)
All comments welcome!
Paweł
Pawel Moll (2): arm: Add VIRTUALIZATION configuration menu and virtio options arm: Add AMBA bus driver for virtio device
arch/arm/Kconfig | 16 ++ drivers/virtio/Kconfig | 11 + drivers/virtio/Makefile | 1 + drivers/virtio/virtio_amba.c | 443 ++++++++++++++++++++++++++++++++++++++++++ include/linux/virtio_amba.h | 62 ++++++ 5 files changed, 533 insertions(+), 0 deletions(-) create mode 100644 drivers/virtio/virtio_amba.c create mode 100644 include/linux/virtio_amba.h
This patch adds menuconfig VIRTUALIZATION to arm Kconfig and includes virtio Kconfig in it.
Signed-off-by: Pawel Moll pawel.moll@arm.com --- arch/arm/Kconfig | 16 ++++++++++++++++ 1 files changed, 16 insertions(+), 0 deletions(-)
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 5ebc5d9..fca5987 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -2089,4 +2089,20 @@ source "security/Kconfig"
source "crypto/Kconfig"
+menuconfig VIRTUALIZATION + bool "Virtualization" + default n + ---help--- + Say Y here to get to see options for using your Linux host to run other + operating systems inside virtual machines (guests). + This option alone does not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if VIRTUALIZATION + +source drivers/virtio/Kconfig + +endif # VIRTUALIZATION + source "lib/Kconfig"
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