Index: boot/Makefile.common
===================================================================
--- boot/Makefile.common	(revision a1f173d31707145708020a90d53ad89b627cd402)
+++ boot/Makefile.common	(revision 13eecc4035156c4453e34e965d30a472702312c3)
@@ -145,5 +145,6 @@
 	nic/ar9271 \
 	nic/virtio-net \
-	block/ahci
+	block/ahci \
+	block/virtio-blk
 
 RD_DRV_CFG =
Index: tools/ew.py
===================================================================
--- tools/ew.py	(revision a1f173d31707145708020a90d53ad89b627cd402)
+++ tools/ew.py	(revision 13eecc4035156c4453e34e965d30a472702312c3)
@@ -135,5 +135,12 @@
 	hdisk_mk()
 
-	return ' -drive file=hdisk.img,index=0,media=disk,format=raw'
+	hdd_options = ''
+	if 'hdd' in overrides.keys():
+		if 'ata' in overrides['hdd'].keys():
+			hdd_options += ''
+		elif 'virtio-blk' in overrides['hdd'].keys():
+			hdd_options += ',if=virtio'
+
+	return ' -drive file=hdisk.img,index=0,media=disk,format=raw' + hdd_options
 
 def qemu_nic_ne2k_options():
@@ -336,5 +343,5 @@
 def usage():
 	print("%s - emulator wrapper for running HelenOS\n" % os.path.basename(sys.argv[0]))
-	print("%s [-d] [-h] [-net e1k|rtl8139|ne2k|virtio-net] [-nohdd] [-nokvm] [-nonet] [-nosnd] [-nousb] [-noxhci] [-notablet]\n" %
+	print("%s [-d] [-h] [-net e1k|rtl8139|ne2k|virtio-net] [-hdd ata|virtio-blk] [-nohdd] [-nokvm] [-nonet] [-nosnd] [-nousb] [-noxhci] [-notablet]\n" %
 	    os.path.basename(sys.argv[0]))
 	print("-d\tDry run: do not run the emulation, just print the command line.")
@@ -357,4 +364,5 @@
 def run():
 	expect_nic = False
+	expect_hdd = False
 	expect_qemu = False
 
@@ -378,4 +386,17 @@
 			continue
 
+		if expect_hdd:
+			expect_hdd = False
+			if not 'hdd' in overrides.keys():
+				overrides['hdd'] = {}
+			if sys.argv[i] == 'ata':
+				overrides['hdd']['ata'] = True
+			elif sys.argv[i] == 'virtio-blk':
+				overrides['hdd']['virtio-blk'] = True
+			else:
+				usage()
+				exit()
+			continue
+
 		if expect_qemu:
 			expect_qemu = False
@@ -389,4 +410,6 @@
 		elif sys.argv[i] == '-net' and i < len(sys.argv) - 1:
 			expect_nic = True
+		elif sys.argv[i] == '-hdd' and i < len(sys.argv) - 1:
+			expect_hdd = True
 		elif sys.argv[i] == '-nohdd':
 			overrides['nohdd'] = True
Index: uspace/Makefile
===================================================================
--- uspace/Makefile	(revision a1f173d31707145708020a90d53ad89b627cd402)
+++ uspace/Makefile	(revision 13eecc4035156c4453e34e965d30a472702312c3)
@@ -152,4 +152,5 @@
 	drv/block/ddisk \
 	drv/block/usbmast \
+	drv/block/virtio-blk \
 	drv/bus/adb/cuda_adb \
 	drv/bus/isa \
Index: uspace/drv/block/virtio-blk/Makefile
===================================================================
--- uspace/drv/block/virtio-blk/Makefile	(revision 13eecc4035156c4453e34e965d30a472702312c3)
+++ uspace/drv/block/virtio-blk/Makefile	(revision 13eecc4035156c4453e34e965d30a472702312c3)
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2019 Jakub Jermar
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# - Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# - The name of the author may not be used to endorse or promote products
+#   derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+USPACE_PREFIX = ../../..
+LIBS = drv scsi virtio
+BINARY = virtio-blk
+
+SOURCES = \
+	virtio-blk.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/drv/block/virtio-blk/virtio-blk.c
===================================================================
--- uspace/drv/block/virtio-blk/virtio-blk.c	(revision 13eecc4035156c4453e34e965d30a472702312c3)
+++ uspace/drv/block/virtio-blk/virtio-blk.c	(revision 13eecc4035156c4453e34e965d30a472702312c3)
@@ -0,0 +1,495 @@
+/*
+ * Copyright (c) 2019 Jakub Jermar
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "virtio-blk.h"
+
+#include <stdio.h>
+#include <stdint.h>
+
+#include <as.h>
+#include <ddf/driver.h>
+#include <ddf/interrupt.h>
+#include <ddf/log.h>
+#include <pci_dev_iface.h>
+#include <fibril_synch.h>
+
+#include <bd_srv.h>
+
+#include <virtio-pci.h>
+
+#define NAME	"virtio-blk"
+
+#define VIRTIO_BLK_NUM_QUEUES	1
+
+#define RQ_QUEUE	0
+
+/*
+ * VIRTIO_BLK requests need at least two descriptors so that device-read-only
+ * buffers are separated from device-writable buffers. For convenience, we
+ * always use three descriptors for the request header, buffer and footer.
+ * We therefore organize the virtque so that first RQ_BUFFERS descriptors are
+ * used for request headers, the following RQ_BUFFERS descriptors are used
+ * for in/out buffers and the last RQ_BUFFERS descriptors are used for request
+ * footers.
+ */
+#define REQ_HEADER_DESC(descno)	(0 * RQ_BUFFERS + (descno))
+#define REQ_BUFFER_DESC(descno)	(1 * RQ_BUFFERS + (descno))
+#define REQ_FOOTER_DESC(descno)	(2 * RQ_BUFFERS + (descno))
+
+static errno_t virtio_blk_dev_add(ddf_dev_t *dev);
+
+static driver_ops_t virtio_blk_driver_ops = {
+	.dev_add = virtio_blk_dev_add
+};
+
+static driver_t virtio_blk_driver = {
+	.name = NAME,
+	.driver_ops = &virtio_blk_driver_ops
+};
+
+static void virtio_blk_irq_handler(ipc_call_t *icall, ddf_dev_t *dev)
+{
+	virtio_blk_t *virtio_blk = (virtio_blk_t *) ddf_dev_data_get(dev);
+	virtio_dev_t *vdev = &virtio_blk->virtio_dev;
+
+	uint16_t descno;
+	uint32_t len;
+
+	while (virtio_virtq_consume_used(vdev, RQ_QUEUE, &descno, &len)) {
+		assert(descno < RQ_BUFFERS);
+		fibril_mutex_lock(&virtio_blk->completion_lock[descno]);
+		fibril_condvar_signal(&virtio_blk->completion_cv[descno]);
+		fibril_mutex_unlock(&virtio_blk->completion_lock[descno]);
+	}
+}
+
+static errno_t virtio_blk_register_interrupt(ddf_dev_t *dev)
+{
+	virtio_blk_t *virtio_blk = (virtio_blk_t *) ddf_dev_data_get(dev);
+	virtio_dev_t *vdev = &virtio_blk->virtio_dev;
+
+	async_sess_t *parent_sess = ddf_dev_parent_sess_get(dev);
+	if (parent_sess == NULL)
+		return ENOMEM;
+
+	hw_res_list_parsed_t res;
+	hw_res_list_parsed_init(&res);
+
+	hw_res_list_parsed_init(&res);
+	errno_t rc = hw_res_get_list_parsed(parent_sess, &res, 0);
+	if (rc != EOK)
+		return rc;
+
+	if (res.irqs.count < 1) {
+		hw_res_list_parsed_clean(&res);
+		return EINVAL;
+	}
+
+	virtio_blk->irq = res.irqs.irqs[0];
+	hw_res_list_parsed_clean(&res);
+
+	irq_pio_range_t pio_ranges[] = {
+		{
+			.base = vdev->isr_phys,
+			.size = sizeof(vdev->isr_phys),
+		}
+	};
+
+	irq_cmd_t irq_commands[] = {
+		{
+			.cmd = CMD_PIO_READ_8,
+			.addr = (void *) vdev->isr_phys,
+			.dstarg = 2
+		},
+		{
+			.cmd = CMD_PREDICATE,
+			.value = 1,
+			.srcarg = 2
+		},
+		{
+			.cmd = CMD_ACCEPT
+		}
+	};
+
+	irq_code_t irq_code = {
+		.rangecount = sizeof(pio_ranges) / sizeof(irq_pio_range_t),
+		.ranges = pio_ranges,
+		.cmdcount = sizeof(irq_commands) / sizeof(irq_cmd_t),
+		.cmds = irq_commands
+	};
+
+	return register_interrupt_handler(dev, virtio_blk->irq,
+	    virtio_blk_irq_handler, &irq_code, &virtio_blk->irq_handle);
+}
+
+static errno_t virtio_blk_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
+{
+	return EOK;
+}
+
+static errno_t virtio_blk_bd_close(bd_srv_t *bd)
+{
+	return EOK;
+}
+
+static errno_t virtio_blk_rw_block(virtio_blk_t *virtio_blk, bool read,
+    aoff64_t ba, void *buf)
+{
+	virtio_dev_t *vdev = &virtio_blk->virtio_dev;
+
+	/*
+	 * Allocate a descriptor.
+	 *
+	 * The allocated descno will determine the header descriptor
+	 * (REQ_HEADER_DESC), the buffer descriptor (REQ_BUFFER_DESC) and the
+	 * footer (REQ_FOOTER_DESC) descriptor.
+	 */
+	fibril_mutex_lock(&virtio_blk->free_lock);
+	uint16_t descno = virtio_alloc_desc(vdev, RQ_QUEUE,
+	    &virtio_blk->rq_free_head);
+	while (descno == (uint16_t) -1U) {
+		fibril_condvar_wait(&virtio_blk->free_cv,
+		    &virtio_blk->free_lock);
+		descno = virtio_alloc_desc(vdev, RQ_QUEUE,
+		    &virtio_blk->rq_free_head);
+	}
+	fibril_mutex_unlock(&virtio_blk->free_lock);
+
+	assert(descno < RQ_BUFFERS);
+
+	/* Setup the request header */
+	virtio_blk_req_header_t *req_header =
+	    (virtio_blk_req_header_t *) virtio_blk->rq_header[descno];
+	memset(req_header, 0, sizeof(virtio_blk_req_header_t));
+	pio_write_le32(&req_header->type,
+	    read ? VIRTIO_BLK_T_IN : VIRTIO_BLK_T_OUT);
+	pio_write_le64(&req_header->sector, ba);
+
+	/* Copy write data to the request. */
+	if (!read)
+		memcpy(virtio_blk->rq_buf[descno], buf, VIRTIO_BLK_BLOCK_SIZE);
+
+	fibril_mutex_lock(&virtio_blk->completion_lock[descno]);
+
+	/*
+	 * Set the descriptors, chain them in the virtqueue and notify the
+	 * device.
+	 */
+	virtio_virtq_desc_set(vdev, RQ_QUEUE, REQ_HEADER_DESC(descno),
+	    virtio_blk->rq_header_p[descno], sizeof(virtio_blk_req_header_t),
+	    VIRTQ_DESC_F_NEXT, REQ_BUFFER_DESC(descno));
+	virtio_virtq_desc_set(vdev, RQ_QUEUE, REQ_BUFFER_DESC(descno),
+	    virtio_blk->rq_buf_p[descno], VIRTIO_BLK_BLOCK_SIZE,
+	    VIRTQ_DESC_F_NEXT | (read ? VIRTQ_DESC_F_WRITE : 0),
+	    REQ_FOOTER_DESC(descno));
+	virtio_virtq_desc_set(vdev, RQ_QUEUE, REQ_FOOTER_DESC(descno),
+	    virtio_blk->rq_footer_p[descno], sizeof(virtio_blk_req_footer_t),
+	    VIRTQ_DESC_F_WRITE, 0);
+	virtio_virtq_produce_available(vdev, RQ_QUEUE, descno);
+
+	/*
+	 * Wait for the completion of the request.
+	 */
+	fibril_condvar_wait(&virtio_blk->completion_cv[descno],
+	    &virtio_blk->completion_lock[descno]);
+	fibril_mutex_unlock(&virtio_blk->completion_lock[descno]);
+
+	errno_t rc;
+	virtio_blk_req_footer_t *footer =
+	    (virtio_blk_req_footer_t *) virtio_blk->rq_footer[descno];
+	switch (footer->status) {
+	case VIRTIO_BLK_S_OK:
+		rc = EOK;
+		break;
+	case VIRTIO_BLK_S_IOERR:
+		rc = EIO;
+		break;
+	case VIRTIO_BLK_S_UNSUPP:
+		rc = ENOTSUP;
+		break;
+	default:
+		ddf_msg(LVL_DEBUG, "device returned unknown status=%d\n",
+		    (int) footer->status);
+		rc = EIO;
+		break;
+	}
+
+	/* Copy read data from the request */
+	if (rc == EOK && read)
+		memcpy(buf, virtio_blk->rq_buf[descno], VIRTIO_BLK_BLOCK_SIZE);
+
+	/* Free the descriptor and buffer */
+	fibril_mutex_lock(&virtio_blk->free_lock);
+	virtio_free_desc(vdev, RQ_QUEUE, &virtio_blk->rq_free_head, descno);
+	fibril_condvar_signal(&virtio_blk->free_cv);
+	fibril_mutex_unlock(&virtio_blk->free_lock);
+
+	return rc;
+}
+
+static errno_t virtio_blk_bd_rw_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt,
+    void *buf, size_t size, bool read)
+{
+	virtio_blk_t *virtio_blk = (virtio_blk_t *) bd->srvs->sarg;
+	aoff64_t i;
+	errno_t rc;
+
+	if (size != cnt * VIRTIO_BLK_BLOCK_SIZE)
+		return EINVAL;
+
+	for (i = 0; i < cnt; i++) {
+		rc = virtio_blk_rw_block(virtio_blk, read, ba + i,
+		    buf + i * VIRTIO_BLK_BLOCK_SIZE);
+		if (rc != EOK)
+			return rc;
+	}
+
+	return EOK;
+}
+
+static errno_t virtio_blk_bd_read_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt,
+    void *buf, size_t size)
+{
+	return virtio_blk_bd_rw_blocks(bd, ba, cnt, buf, size, true);
+}
+
+
+static errno_t virtio_blk_bd_write_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt,
+    const void *buf, size_t size)
+{
+	return virtio_blk_bd_rw_blocks(bd, ba, cnt, (void *) buf, size, false);
+}
+
+static errno_t virtio_blk_bd_get_block_size(bd_srv_t *bd, size_t *size)
+{
+	*size = VIRTIO_BLK_BLOCK_SIZE;
+	return EOK;
+}
+
+static errno_t virtio_blk_bd_get_num_blocks(bd_srv_t *bd, aoff64_t *nb)
+{
+	virtio_blk_t *virtio_blk = (virtio_blk_t *) bd->srvs->sarg;
+	virtio_blk_cfg_t *blkcfg = virtio_blk->virtio_dev.device_cfg;
+	*nb = pio_read_le64(&blkcfg->capacity);
+	return EOK;
+}
+
+bd_ops_t virtio_blk_bd_ops = {
+	.open = virtio_blk_bd_open,
+	.close = virtio_blk_bd_close,
+	.read_blocks = virtio_blk_bd_read_blocks,
+	.write_blocks = virtio_blk_bd_write_blocks,
+	.get_block_size = virtio_blk_bd_get_block_size,
+	.get_num_blocks = virtio_blk_bd_get_num_blocks,
+};
+
+static errno_t virtio_blk_initialize(ddf_dev_t *dev)
+{
+	virtio_blk_t *virtio_blk = ddf_dev_data_alloc(dev,
+	    sizeof(virtio_blk_t));
+	if (!virtio_blk)
+		return ENOMEM;
+
+	fibril_mutex_initialize(&virtio_blk->free_lock);
+	fibril_condvar_initialize(&virtio_blk->free_cv);
+
+	for (unsigned i = 0; i < RQ_BUFFERS; i++) {
+		fibril_mutex_initialize(&virtio_blk->completion_lock[i]);
+		fibril_condvar_initialize(&virtio_blk->completion_cv[i]);
+	}
+
+	bd_srvs_init(&virtio_blk->bds);
+	virtio_blk->bds.ops = &virtio_blk_bd_ops;
+	virtio_blk->bds.sarg = virtio_blk;
+
+	errno_t rc = virtio_pci_dev_initialize(dev, &virtio_blk->virtio_dev);
+	if (rc != EOK)
+		return rc;
+
+	virtio_dev_t *vdev = &virtio_blk->virtio_dev;
+	virtio_pci_common_cfg_t *cfg = virtio_blk->virtio_dev.common_cfg;
+
+	/*
+	 * Register IRQ
+	 */
+	rc = virtio_blk_register_interrupt(dev);
+	if (rc != EOK)
+		goto fail;
+
+	/* Reset the device and negotiate the feature bits */
+	rc = virtio_device_setup_start(vdev, 0);
+	if (rc != EOK)
+		goto fail;
+
+	/* Perform device-specific setup */
+
+	/*
+	 * Discover and configure the virtqueue
+	 */
+	uint16_t num_queues = pio_read_le16(&cfg->num_queues);
+	if (num_queues != VIRTIO_BLK_NUM_QUEUES) {
+		ddf_msg(LVL_NOTE, "Unsupported number of virtqueues: %u",
+		    num_queues);
+		rc = ELIMIT;
+		goto fail;
+	}
+
+	vdev->queues = calloc(sizeof(virtq_t), num_queues);
+	if (!vdev->queues) {
+		rc = ENOMEM;
+		goto fail;
+	}
+
+	/* For each in/out request we need 3 descriptors */
+	rc = virtio_virtq_setup(vdev, RQ_QUEUE, 3 * RQ_BUFFERS);
+	if (rc != EOK)
+		goto fail;
+
+	/*
+	 * Setup DMA buffers
+	 */
+	rc = virtio_setup_dma_bufs(RQ_BUFFERS, sizeof(virtio_blk_req_header_t),
+	    true, virtio_blk->rq_header, virtio_blk->rq_header_p);
+	if (rc != EOK)
+		goto fail;
+	rc = virtio_setup_dma_bufs(RQ_BUFFERS, VIRTIO_BLK_BLOCK_SIZE,
+	    true, virtio_blk->rq_buf, virtio_blk->rq_buf_p);
+	if (rc != EOK)
+		goto fail;
+	rc = virtio_setup_dma_bufs(RQ_BUFFERS, sizeof(virtio_blk_req_footer_t),
+	    false, virtio_blk->rq_footer, virtio_blk->rq_footer_p);
+	if (rc != EOK)
+		goto fail;
+
+	/*
+	 * Put all request descriptors on a free list. Because of the
+	 * correspondence between the request, buffer and footer descriptors,
+	 * we only need to manage allocations for one set: the request header
+	 * descriptors.
+	 */
+	virtio_create_desc_free_list(vdev, RQ_QUEUE, RQ_BUFFERS,
+	    &virtio_blk->rq_free_head);
+
+	/*
+	 * Enable IRQ
+	 */
+	rc = hw_res_enable_interrupt(ddf_dev_parent_sess_get(dev),
+	    virtio_blk->irq);
+	if (rc != EOK) {
+		ddf_msg(LVL_NOTE, "Failed to enable interrupt");
+		goto fail;
+	}
+
+	ddf_msg(LVL_NOTE, "Registered IRQ %d", virtio_blk->irq);
+
+	/* Go live */
+	virtio_device_setup_finalize(vdev);
+
+	return EOK;
+
+fail:
+	virtio_teardown_dma_bufs(virtio_blk->rq_header);
+	virtio_teardown_dma_bufs(virtio_blk->rq_buf);
+	virtio_teardown_dma_bufs(virtio_blk->rq_footer);
+
+	virtio_device_setup_fail(vdev);
+	virtio_pci_dev_cleanup(vdev);
+	return rc;
+}
+
+static void virtio_blk_uninitialize(ddf_dev_t *dev)
+{
+	virtio_blk_t *virtio_blk = (virtio_blk_t *) ddf_dev_data_get(dev);
+
+	virtio_teardown_dma_bufs(virtio_blk->rq_header);
+	virtio_teardown_dma_bufs(virtio_blk->rq_buf);
+	virtio_teardown_dma_bufs(virtio_blk->rq_footer);
+
+	virtio_device_setup_fail(&virtio_blk->virtio_dev);
+	virtio_pci_dev_cleanup(&virtio_blk->virtio_dev);
+}
+
+static void virtio_blk_bd_connection(ipc_call_t *icall, void *arg)
+{
+	virtio_blk_t *virtio_blk;
+	ddf_fun_t *fun = (ddf_fun_t *) arg;
+
+	virtio_blk = (virtio_blk_t *) ddf_dev_data_get(ddf_fun_get_dev(fun));
+	bd_conn(icall, &virtio_blk->bds);
+}
+
+static errno_t virtio_blk_dev_add(ddf_dev_t *dev)
+{
+	ddf_msg(LVL_NOTE, "%s %s (handle = %zu)", __func__,
+	    ddf_dev_get_name(dev), ddf_dev_get_handle(dev));
+
+	errno_t rc = virtio_blk_initialize(dev);
+	if (rc != EOK)
+		return rc;
+
+	ddf_fun_t *fun = ddf_fun_create(dev, fun_exposed, "port0");
+	if (fun == NULL) {
+		rc = ENOMEM;
+		goto uninitialize;
+	}
+
+	ddf_fun_set_conn_handler(fun, virtio_blk_bd_connection);
+
+	rc = ddf_fun_bind(fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed binding device function");
+		goto destroy;
+	}
+
+	rc = ddf_fun_add_to_category(fun, "disk");
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed adding function to category");
+		goto unbind;
+	}
+
+	ddf_msg(LVL_NOTE, "The %s device has been successfully initialized.",
+	    ddf_dev_get_name(dev));
+
+	return EOK;
+
+unbind:
+	ddf_fun_unbind(fun);
+destroy:
+	ddf_fun_destroy(fun);
+uninitialize:
+	virtio_blk_uninitialize(dev);
+	return rc;
+}
+
+int main(void)
+{
+	printf("%s: HelenOS virtio-blk driver\n", NAME);
+
+	(void) ddf_log_init(NAME);
+	return ddf_driver_main(&virtio_blk_driver);
+}
Index: uspace/drv/block/virtio-blk/virtio-blk.h
===================================================================
--- uspace/drv/block/virtio-blk/virtio-blk.h	(revision 13eecc4035156c4453e34e965d30a472702312c3)
+++ uspace/drv/block/virtio-blk/virtio-blk.h	(revision 13eecc4035156c4453e34e965d30a472702312c3)
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2019 Jakub Jermar
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _VIRTIO_BLK_H_
+#define _VIRTIO_BLK_H_
+
+#include <virtio-pci.h>
+#include <bd_srv.h>
+#include <abi/cap.h>
+
+#include <fibril_synch.h>
+
+#define VIRTIO_BLK_BLOCK_SIZE	512
+
+/* Operation types. */
+#define VIRTIO_BLK_T_IN		0
+#define VIRTIO_BLK_T_OUT	1
+
+/* Status codes returned by the device. */
+#define VIRTIO_BLK_S_OK		0
+#define VIRTIO_BLK_S_IOERR	1
+#define VIRTIO_BLK_S_UNSUPP	2
+
+#define RQ_BUFFERS	32
+
+/** Device is read-only. */
+#define VIRTIO_BLK_F_RO		(1U << 5)
+
+typedef struct {
+	uint32_t type;
+	uint32_t reserved;
+	uint64_t sector;
+} virtio_blk_req_header_t;
+
+typedef struct {
+	uint8_t status;
+} virtio_blk_req_footer_t;
+
+typedef struct {
+	uint64_t capacity;
+} virtio_blk_cfg_t;
+
+typedef struct {
+	virtio_dev_t virtio_dev;
+
+	void *rq_header[RQ_BUFFERS];
+	uintptr_t rq_header_p[RQ_BUFFERS];
+
+	void *rq_buf[RQ_BUFFERS];
+	uintptr_t rq_buf_p[RQ_BUFFERS];
+
+	void *rq_footer[RQ_BUFFERS];
+	uintptr_t rq_footer_p[RQ_BUFFERS];
+
+	uint16_t rq_free_head;
+
+	int irq;
+	cap_irq_handle_t irq_handle;
+
+	bd_srvs_t bds;
+
+	fibril_mutex_t free_lock;
+	fibril_condvar_t free_cv;
+
+	fibril_mutex_t completion_lock[RQ_BUFFERS];
+	fibril_condvar_t completion_cv[RQ_BUFFERS];
+} virtio_blk_t;
+
+#endif
Index: uspace/drv/block/virtio-blk/virtio-blk.ma
===================================================================
--- uspace/drv/block/virtio-blk/virtio-blk.ma	(revision 13eecc4035156c4453e34e965d30a472702312c3)
+++ uspace/drv/block/virtio-blk/virtio-blk.ma	(revision 13eecc4035156c4453e34e965d30a472702312c3)
@@ -0,0 +1,2 @@
+10 pci/ven=1af4&dev=1001
+10 pci/ven=1af4&dev=1042
Index: uspace/lib/virtio/virtio.c
===================================================================
--- uspace/lib/virtio/virtio.c	(revision a1f173d31707145708020a90d53ad89b627cd402)
+++ uspace/lib/virtio/virtio.c	(revision 13eecc4035156c4453e34e965d30a472702312c3)
@@ -63,5 +63,6 @@
 	uintptr_t phys;
 	errno_t rc = dmamem_map_anonymous(buffers * size, 0,
-	    write ? AS_AREA_WRITE : AS_AREA_READ, 0, &phys, &virt);
+	    write ? AS_AREA_WRITE | AS_AREA_READ : AS_AREA_READ, 0, &phys,
+	    &virt);
 	if (rc != EOK)
 		return rc;
