Currently only supports sending (outbufs), doesn't have any
bells or whistles. Code adapted from the Linux Kernel.

Signed-off-by: Andrew Jones <drjo...@redhat.com>

---
v7:
 - {alloc,alloc_aligned} -> {calloc,memalign}
 - changes now split between virtio.* and virtio-mmio.* files
---
 lib/virtio-mmio.c |  64 +++++++++++++++++++++++++++++
 lib/virtio-mmio.h |  18 +++++++++
 lib/virtio.c      | 117 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 lib/virtio.h      |  73 ++++++++++++++++++++++++++++++++++
 4 files changed, 272 insertions(+)

diff --git a/lib/virtio-mmio.c b/lib/virtio-mmio.c
index 7331abf128cc5..3840838defa1c 100644
--- a/lib/virtio-mmio.c
+++ b/lib/virtio-mmio.c
@@ -1,4 +1,6 @@
 /*
+ * virtqueue support adapted from the Linux kernel.
+ *
  * Copyright (C) 2014, Red Hat Inc, Andrew Jones <drjo...@redhat.com>
  *
  * This work is licensed under the terms of the GNU LGPL, version 2.
@@ -6,6 +8,7 @@
 #include "libcflat.h"
 #include "devicetree.h"
 #include "alloc.h"
+#include "asm/page.h"
 #include "asm/io.h"
 #include "virtio.h"
 #include "virtio-mmio.h"
@@ -32,9 +35,68 @@ static void vm_set(struct virtio_device *vdev, unsigned 
offset,
                writeb(p[i], vm_dev->base + VIRTIO_MMIO_CONFIG + offset + i);
 }
 
+static bool vm_notify(struct virtqueue *vq)
+{
+       struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vq->vdev);
+       writel(vq->index, vm_dev->base + VIRTIO_MMIO_QUEUE_NOTIFY);
+       return true;
+}
+
+static struct virtqueue *vm_setup_vq(struct virtio_device *vdev,
+                                    unsigned index,
+                                    void (*callback)(struct virtqueue *vq),
+                                    const char *name)
+{
+       struct virtio_mmio_device *vm_dev = to_virtio_mmio_device(vdev);
+       struct vring_virtqueue *vq;
+       void *queue;
+       unsigned num = VIRTIO_MMIO_QUEUE_NUM_MIN;
+
+       vq = calloc(1, sizeof(*vq));
+       queue = memalign(PAGE_SIZE, VIRTIO_MMIO_QUEUE_SIZE_MIN);
+       if (!vq || !queue)
+               return NULL;
+
+       writel(index, vm_dev->base + VIRTIO_MMIO_QUEUE_SEL);
+
+       assert(readl(vm_dev->base + VIRTIO_MMIO_QUEUE_NUM_MAX) >= num);
+
+       if (readl(vm_dev->base + VIRTIO_MMIO_QUEUE_PFN) != 0) {
+               printf("%s: virtqueue %d already setup! base=%p\n",
+                               __func__, index, vm_dev->base);
+               return NULL;
+       }
+
+       writel(num, vm_dev->base + VIRTIO_MMIO_QUEUE_NUM);
+       writel(VIRTIO_MMIO_VRING_ALIGN,
+                       vm_dev->base + VIRTIO_MMIO_QUEUE_ALIGN);
+       writel(virt_to_pfn(queue), vm_dev->base + VIRTIO_MMIO_QUEUE_PFN);
+
+       vring_init_virtqueue(vq, index, num, VIRTIO_MMIO_VRING_ALIGN,
+                            vdev, queue, vm_notify, callback, name);
+
+       return &vq->vq;
+}
+
+static int vm_find_vqs(struct virtio_device *vdev, unsigned nvqs,
+                      struct virtqueue *vqs[], vq_callback_t *callbacks[],
+                      const char *names[])
+{
+       unsigned i;
+
+       for (i = 0; i < nvqs; ++i) {
+               vqs[i] = vm_setup_vq(vdev, i, callbacks[i], names[i]);
+               if (vqs[i] == NULL)
+                       return -1;
+       }
+
+       return 0;
+}
+
 static const struct virtio_config_ops vm_config_ops = {
        .get = vm_get,
        .set = vm_set,
+       .find_vqs = vm_find_vqs,
 };
 
 static void vm_device_init(struct virtio_mmio_device *vm_dev)
@@ -42,6 +104,8 @@ static void vm_device_init(struct virtio_mmio_device *vm_dev)
        vm_dev->vdev.id.device = readl(vm_dev->base + VIRTIO_MMIO_DEVICE_ID);
        vm_dev->vdev.id.vendor = readl(vm_dev->base + VIRTIO_MMIO_VENDOR_ID);
        vm_dev->vdev.config = &vm_config_ops;
+
+       writel(PAGE_SIZE, vm_dev->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
 }
 
 /******************************************************
diff --git a/lib/virtio-mmio.h b/lib/virtio-mmio.h
index 7cd610428b486..8046a4747959a 100644
--- a/lib/virtio-mmio.h
+++ b/lib/virtio-mmio.h
@@ -8,6 +8,7 @@
  * This work is licensed under the terms of the GNU LGPL, version 2.
  */
 #include "libcflat.h"
+#include "asm/page.h"
 #include "virtio.h"
 
 #define VIRTIO_MMIO_MAGIC_VALUE                0x000
@@ -33,6 +34,23 @@
 #define VIRTIO_MMIO_INT_VRING          (1 << 0)
 #define VIRTIO_MMIO_INT_CONFIG         (1 << 1)
 
+#define VIRTIO_MMIO_VRING_ALIGN                PAGE_SIZE
+
+/*
+ * The minimum queue size is 2*VIRTIO_MMIO_VRING_ALIGN, which
+ * means the largest queue num for the minimum queue size is 128, i.e.
+ * 2*VIRTIO_MMIO_VRING_ALIGN = vring_size(128, VIRTIO_MMIO_VRING_ALIGN),
+ * where vring_size is
+ *
+ * unsigned vring_size(unsigned num, unsigned long align)
+ * {
+ *     return ((sizeof(struct vring_desc) * num + sizeof(u16) * (3 + num)
+ *              + align - 1) & ~(align - 1))
+ *             + sizeof(u16) * 3 + sizeof(struct vring_used_elem) * num;
+ * }
+ */
+#define VIRTIO_MMIO_QUEUE_SIZE_MIN     (2*VIRTIO_MMIO_VRING_ALIGN)
+#define VIRTIO_MMIO_QUEUE_NUM_MIN      128
 
 #define to_virtio_mmio_device(vdev_ptr) \
        container_of(vdev_ptr, struct virtio_mmio_device, vdev)
diff --git a/lib/virtio.c b/lib/virtio.c
index b9c403cc71e05..cb496ff2eabd5 100644
--- a/lib/virtio.c
+++ b/lib/virtio.c
@@ -1,12 +1,129 @@
 /*
+ * virtqueue support adapted from the Linux kernel.
+ *
  * Copyright (C) 2014, Red Hat Inc, Andrew Jones <drjo...@redhat.com>
  *
  * This work is licensed under the terms of the GNU LGPL, version 2.
  */
 #include "libcflat.h"
+#include "asm/io.h"
 #include "virtio.h"
 #include "virtio-mmio.h"
 
+void vring_init(struct vring *vr, unsigned int num, void *p,
+                      unsigned long align)
+{
+       vr->num = num;
+       vr->desc = p;
+       vr->avail = p + num*sizeof(struct vring_desc);
+       vr->used = (void *)(((unsigned long)&vr->avail->ring[num] + sizeof(u16)
+               + align-1) & ~(align - 1));
+}
+
+void vring_init_virtqueue(struct vring_virtqueue *vq, unsigned index,
+                         unsigned num, unsigned vring_align,
+                         struct virtio_device *vdev, void *pages,
+                         bool (*notify)(struct virtqueue *),
+                         void (*callback)(struct virtqueue *),
+                         const char *name)
+{
+       unsigned i;
+
+       vring_init(&vq->vring, num, pages, vring_align);
+       vq->vq.callback = callback;
+       vq->vq.vdev = vdev;
+       vq->vq.name = name;
+       vq->vq.num_free = num;
+       vq->vq.index = index;
+       vq->notify = notify;
+       vq->last_used_idx = 0;
+       vq->num_added = 0;
+       vq->free_head = 0;
+
+       for (i = 0; i < num-1; i++) {
+               vq->vring.desc[i].next = i+1;
+               vq->data[i] = NULL;
+       }
+       vq->data[i] = NULL;
+}
+
+int virtqueue_add_outbuf(struct virtqueue *_vq, char *buf, size_t len)
+{
+       struct vring_virtqueue *vq = to_vvq(_vq);
+       unsigned avail;
+       int head;
+
+       assert(buf != NULL);
+       assert(len != 0);
+
+       if (!vq->vq.num_free)
+               return -1;
+
+       --vq->vq.num_free;
+
+       head = vq->free_head;
+
+       vq->vring.desc[head].flags = 0;
+       vq->vring.desc[head].addr = virt_to_phys(buf);
+       vq->vring.desc[head].len = len;
+
+       vq->free_head = vq->vring.desc[head].next;
+
+       vq->data[head] = buf;
+
+       avail = (vq->vring.avail->idx & (vq->vring.num-1));
+       vq->vring.avail->ring[avail] = head;
+       wmb();
+       vq->vring.avail->idx++;
+       vq->num_added++;
+
+       return 0;
+}
+
+bool virtqueue_kick(struct virtqueue *_vq)
+{
+       struct vring_virtqueue *vq = to_vvq(_vq);
+       mb();
+       return vq->notify(_vq);
+}
+
+void detach_buf(struct vring_virtqueue *vq, unsigned head)
+{
+       unsigned i = head;
+
+       vq->data[head] = NULL;
+
+       while (vq->vring.desc[i].flags & VRING_DESC_F_NEXT) {
+               i = vq->vring.desc[i].next;
+               vq->vq.num_free++;
+       }
+
+       vq->vring.desc[i].next = vq->free_head;
+       vq->free_head = head;
+       vq->vq.num_free++;
+}
+
+void *virtqueue_get_buf(struct virtqueue *_vq, unsigned int *len)
+{
+       struct vring_virtqueue *vq = to_vvq(_vq);
+       u16 last_used;
+       unsigned i;
+       void *ret;
+
+       rmb();
+
+       last_used = (vq->last_used_idx & (vq->vring.num-1));
+       i = vq->vring.used->ring[last_used].id;
+       *len = vq->vring.used->ring[last_used].len;
+
+       ret = vq->data[i];
+       detach_buf(vq, i);
+
+       vq->last_used_idx++;
+
+       return ret;
+}
+
 struct virtio_device *virtio_bind(u32 devid)
 {
        return virtio_mmio_bind(devid);
diff --git a/lib/virtio.h b/lib/virtio.h
index 5941fa40a8655..37ce028b2c2bb 100644
--- a/lib/virtio.h
+++ b/lib/virtio.h
@@ -20,11 +20,25 @@ struct virtio_device {
        const struct virtio_config_ops *config;
 };
 
+struct virtqueue {
+       void (*callback)(struct virtqueue *vq);
+       const char *name;
+       struct virtio_device *vdev;
+       unsigned int index;
+       unsigned int num_free;
+       void *priv;
+};
+
+typedef void vq_callback_t(struct virtqueue *);
 struct virtio_config_ops {
        void (*get)(struct virtio_device *vdev, unsigned offset,
                    void *buf, unsigned len);
        void (*set)(struct virtio_device *vdev, unsigned offset,
                    const void *buf, unsigned len);
+       int (*find_vqs)(struct virtio_device *vdev, unsigned nvqs,
+                       struct virtqueue *vqs[],
+                       vq_callback_t *callbacks[],
+                       const char *names[]);
 };
 
 static inline u8
@@ -69,6 +83,65 @@ virtio_config_writel(struct virtio_device *vdev, unsigned 
offset, u32 val)
        vdev->config->set(vdev, offset, &val, 4);
 }
 
+#define VRING_DESC_F_NEXT      1
+#define VRING_DESC_F_WRITE     2
+
+struct vring_desc {
+       u64 addr;
+       u32 len;
+       u16 flags;
+       u16 next;
+};
+
+struct vring_avail {
+       u16 flags;
+       u16 idx;
+       u16 ring[];
+};
+
+struct vring_used_elem {
+       u32 id;
+       u32 len;
+};
+
+struct vring_used {
+       u16 flags;
+       u16 idx;
+       struct vring_used_elem ring[];
+};
+
+struct vring {
+       unsigned int num;
+       struct vring_desc *desc;
+       struct vring_avail *avail;
+       struct vring_used *used;
+};
+
+struct vring_virtqueue {
+       struct virtqueue vq;
+       struct vring vring;
+       unsigned int free_head;
+       unsigned int num_added;
+       u16 last_used_idx;
+       bool (*notify)(struct virtqueue *vq);
+       void *data[];
+};
+
+#define to_vvq(_vq) container_of(_vq, struct vring_virtqueue, vq)
+
+extern void vring_init(struct vring *vr, unsigned int num, void *p,
+                      unsigned long align);
+extern void vring_init_virtqueue(struct vring_virtqueue *vq, unsigned index,
+                                unsigned num, unsigned vring_align,
+                                struct virtio_device *vdev, void *pages,
+                                bool (*notify)(struct virtqueue *),
+                                void (*callback)(struct virtqueue *),
+                                const char *name);
+extern int virtqueue_add_outbuf(struct virtqueue *vq, char *buf, size_t len);
+extern bool virtqueue_kick(struct virtqueue *vq);
+extern void detach_buf(struct vring_virtqueue *vq, unsigned head);
+extern void *virtqueue_get_buf(struct virtqueue *_vq, unsigned int *len);
+
 extern struct virtio_device *virtio_bind(u32 devid);
 
 #endif /* _VIRTIO_H_ */
-- 
1.9.3

--
To unsubscribe from this list: send the line "unsubscribe kvm" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to