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 <drjones@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 <drjones@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_ */
