Index: uspace/drv/nic/virtio-net/virtio-net.c
===================================================================
--- uspace/drv/nic/virtio-net/virtio-net.c	(revision 1c53d930aae19cd390d8ff0418fbfe7b5bcabc53)
+++ uspace/drv/nic/virtio-net/virtio-net.c	(revision cbcb34c71731e8f7f40ddb81bb612f2296305afc)
@@ -43,4 +43,14 @@
 #define NAME	"virtio-net"
 
+#define VIRTIO_NET_NUM_QUEUES	3
+
+#define RECVQ1		0
+#define TRANSQ1		1
+#define CTRLQ1		2
+
+#define RECVQ_SIZE	8
+#define TRANSQ_SIZE	8
+#define CTRLQ_SIZE	4
+
 static errno_t virtio_net_initialize(ddf_dev_t *dev)
 {
@@ -61,4 +71,5 @@
 		return rc;
 
+	virtio_dev_t *vdev = &virtio_net->virtio_dev;
 	virtio_pci_common_cfg_t *cfg = virtio_net->virtio_dev.common_cfg;
 	virtio_net_cfg_t *netcfg = virtio_net->virtio_dev.device_cfg;
@@ -86,5 +97,5 @@
 
 	ddf_msg(LVL_NOTE, "offered features %x", features);
-	features &= (1U << VIRTIO_NET_F_MAC);
+	features &= (1U << VIRTIO_NET_F_MAC) | (1U << VIRTIO_NET_F_CTRL_VQ);
 
 	if (!features) {
@@ -96,4 +107,6 @@
 	pio_write_32(&cfg->driver_feature_select, VIRTIO_NET_F_SELECT_PAGE_0);
 	pio_write_32(&cfg->driver_feature, features);
+
+	ddf_msg(LVL_NOTE, "accepted features %x", features);
 
 	/* 5. Set FEATURES_OK */
@@ -109,7 +122,38 @@
 
 	/* 7. Perform device-specific setup */
+
+	/*
+	 * Discover and configure the virtqueues
+	 */
+	uint16_t num_queues = pio_read_16(&cfg->num_queues);
+	if (num_queues != VIRTIO_NET_NUM_QUEUES) {
+		ddf_msg(LVL_NOTE, "Unsupported number of virtqueues: %u",
+		    num_queues);
+		goto fail;
+	}
+
+	vdev->queues = calloc(sizeof(virtq_t), num_queues);
+	if (!vdev->queues) {
+		rc = ENOMEM;
+		goto fail;
+	}
+
+	rc = virtio_virtq_setup(vdev, RECVQ1, RECVQ_SIZE, 1500,
+	    VIRTQ_DESC_F_WRITE);
+	if (rc != EOK)
+		goto fail;
+	rc = virtio_virtq_setup(vdev, TRANSQ1, TRANSQ_SIZE, 1500, 0);
+	if (rc != EOK)
+		goto fail;
+	rc = virtio_virtq_setup(vdev, CTRLQ1, CTRLQ_SIZE, 512, 0);
+	if (rc != EOK)
+		goto fail;
+
+	/*
+	 * Read the MAC address
+	 */
 	nic_address_t nic_addr;
 	for (unsigned i = 0; i < 6; i++)
-		nic_addr.address[i] = netcfg->mac[i];
+		nic_addr.address[i] = pio_read_8(&netcfg->mac[i]);
 	rc = nic_report_address(nic_data, &nic_addr);
 	if (rc != EOK)
Index: uspace/drv/nic/virtio-net/virtio-net.h
===================================================================
--- uspace/drv/nic/virtio-net/virtio-net.h	(revision 1c53d930aae19cd390d8ff0418fbfe7b5bcabc53)
+++ uspace/drv/nic/virtio-net/virtio-net.h	(revision cbcb34c71731e8f7f40ddb81bb612f2296305afc)
@@ -40,4 +40,6 @@
 /** Device has given MAC address. */
 #define VIRTIO_NET_F_MAC		5
+/** Control channel is available */
+#define VIRTIO_NET_F_CTRL_VQ		17
 
 typedef struct {
Index: uspace/lib/virtio/virtio-pci.c
===================================================================
--- uspace/lib/virtio/virtio-pci.c	(revision 1c53d930aae19cd390d8ff0418fbfe7b5bcabc53)
+++ uspace/lib/virtio/virtio-pci.c	(revision cbcb34c71731e8f7f40ddb81bb612f2296305afc)
@@ -32,4 +32,8 @@
 #include "virtio-pci.h"
 
+#include <as.h>
+#include <align.h>
+#include <macros.h>
+
 #include <ddf/driver.h>
 #include <ddf/log.h>
@@ -173,4 +177,97 @@
 
 	return EOK;
+}
+
+errno_t virtio_virtq_setup(virtio_dev_t *vdev, uint16_t num, uint16_t size,
+    size_t buf_size, uint16_t buf_flags)
+{
+	virtq_t *q = &vdev->queues[num];
+	virtio_pci_common_cfg_t *cfg = vdev->common_cfg;
+
+	/* Program the queue of our interest */
+	pio_write_16(&cfg->queue_select, num);
+
+	/* Trim the size of the queue as needed */
+	size = min(pio_read_16(&cfg->queue_size), size);
+	pio_write_16(&cfg->queue_size, size);
+	ddf_msg(LVL_NOTE, "Virtq %u: %u buffers", num, (unsigned) size);
+
+	/* Allocate array to hold virtual addresses of DMA buffers */
+	void **buffers = calloc(sizeof(void *), size);
+	if (!buffers)
+		return ENOMEM;
+
+	size_t avail_offset = 0;
+	size_t used_offset = 0;
+	size_t buffers_offset = 0;
+
+	/*
+	 * Compute the size of the needed DMA memory and also the offsets of
+	 * the individual components
+	 */
+	size_t mem_size = sizeof(virtq_desc_t[size]);
+	mem_size = ALIGN_UP(mem_size, _Alignof(virtq_avail_t));
+	avail_offset = mem_size;
+	mem_size += sizeof(virtq_avail_t) + sizeof(ioport16_t[size]) +
+	    sizeof(ioport16_t);
+	mem_size = ALIGN_UP(mem_size, _Alignof(virtq_used_t));
+	used_offset = mem_size;
+	mem_size += sizeof(virtq_used_t) + sizeof(virtq_used_elem_t[size]) +
+	    sizeof(ioport16_t);
+	buffers_offset = mem_size;
+	mem_size += size * buf_size;
+
+	/*
+	 * Allocate DMA memory for the virtqueues and the buffers
+	 */
+	q->virt = AS_AREA_ANY;
+	errno_t rc = dmamem_map_anonymous(mem_size, DMAMEM_4GiB,
+	    AS_AREA_READ | AS_AREA_WRITE, 0, &q->phys, &q->virt);
+	if (rc != EOK) {
+		free(buffers);
+		q->virt = NULL;
+		return rc;
+	}
+
+	q->size = mem_size;
+	q->queue_size = size;
+	q->desc = q->virt;
+	q->avail = q->virt + avail_offset;
+	q->used = q->virt + used_offset;
+	q->buffers = buffers;
+
+	memset(q->virt, 0, q->size);
+
+	/*
+	 * Initialize the descriptor table and the buffers array
+	 */
+	for (unsigned i = 0; i < size; i++) {
+		q->desc[i].addr = q->phys + buffers_offset + i * buf_size;
+		q->desc[i].len = buf_size;
+		q->desc[i].flags = buf_flags;
+
+		q->buffers[i] = q->virt + buffers_offset + i * buf_size;
+	}
+
+	/*
+	 * Write the configured addresses to device's common config
+	 */
+	pio_write_64(&cfg->queue_desc, q->phys);
+	pio_write_64(&cfg->queue_avail, q->phys + avail_offset);
+	pio_write_64(&cfg->queue_used, q->phys + used_offset);
+
+	ddf_msg(LVL_NOTE, "DMA memory for virtq %d: virt=%p, phys=%p, size=%zu",
+	    num, q->virt, (void *) q->phys, q->size);
+
+	return rc;
+}
+
+void virtio_virtq_teardown(virtio_dev_t *vdev, uint16_t num)
+{
+	virtq_t *q = &vdev->queues[num];
+	if (q->size)
+		dmamem_unmap_anonymous(q->virt);
+	if (q->buffers)
+		free(q->buffers);
 }
 
@@ -279,4 +376,10 @@
 errno_t virtio_pci_dev_cleanup(virtio_dev_t *vdev)
 {
+	if (vdev->queues) {
+		for (unsigned i = 0;
+		    i < pio_read_16(&vdev->common_cfg->num_queues); i++)
+			virtio_virtq_teardown(vdev, i);
+		free(vdev->queues);
+	}
 	return disable_resources(vdev);
 }
Index: uspace/lib/virtio/virtio-pci.h
===================================================================
--- uspace/lib/virtio/virtio-pci.h	(revision 1c53d930aae19cd390d8ff0418fbfe7b5bcabc53)
+++ uspace/lib/virtio/virtio-pci.h	(revision cbcb34c71731e8f7f40ddb81bb612f2296305afc)
@@ -124,4 +124,29 @@
 } virtq_used_t;
 
+typedef struct {
+	void *virt;
+	uintptr_t phys;
+	size_t size;
+
+	/**
+	 * Size of the queue which determines the number of descriptors and
+	 * DMA buffers.
+	 */
+	size_t queue_size;
+
+	/** Virtual address of queue size virtq descriptors */
+	virtq_desc_t *desc;
+	/** Virtual address of the available ring */
+	virtq_avail_t *avail;
+	/** Virtual address of the used ring */
+	virtq_used_t *used;
+
+	/**
+	 * Queue-size-sized array of virtual addresses of the atcual DMA
+	 * buffers.
+	 */
+	void **buffers;
+} virtq_t;
+
 /** VIRTIO-device specific data associated with the NIC framework nic_t */
 typedef struct {
@@ -145,5 +170,12 @@
 	/** Device-specific configuration */
 	void *device_cfg;
+
+	/** Virtqueues */
+	virtq_t *queues;
 } virtio_dev_t;
+
+extern errno_t virtio_virtq_setup(virtio_dev_t *, uint16_t, uint16_t, size_t,
+    uint16_t);
+extern void virtio_virtq_teardown(virtio_dev_t *, uint16_t);
 
 extern errno_t virtio_pci_dev_initialize(ddf_dev_t *, virtio_dev_t *);
