Subject: [RFC v2] arm: Add platform bus driver for virtio device

From: [email-masked] (Anthony Liguori)
Date: Mon, 12 Sep 2011 12:27:08 -0500

On 09/12/2011 11:51 AM, Pawel Moll wrote:
> 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...

Are you planning on sending patches to QEMU for this?  I think it makes 
sense to start in QEMU with this effort and make a proper spec from 
which you can write the driver against.

benh has also written a platform virtio transports for use with Power. 
Ben, could you take a look and see if it's worth merging the two efforts?

Regards,

Anthony Liguori

>
> Cc: Rusty Russell<[email-masked]>
> Cc: Anthony Liguori<[email-masked]>
> Cc: Michael S.Tsirkin<[email-masked]>
> Cc: Magnus Damm<[email-masked]>
> Signed-off-by: Pawel Moll<[email-masked]>
> ---
>   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<[email-masked]>");
> +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