Index: uspace/drv/audio/hdaudio/codec.c
===================================================================
--- uspace/drv/audio/hdaudio/codec.c	(revision a931b7b2faca447e0708fd5fae0f85116e4c788d)
+++ uspace/drv/audio/hdaudio/codec.c	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -437,4 +437,9 @@
 	}
 
+	if ((pcaps & BIT_V(uint32_t, pwc_input)) != 0) {
+		ddf_msg(LVL_NOTE, "PIN %d will enable input", aw);
+	    	pctl = pctl | BIT_V(uint8_t, pctl_in_enable);
+	}
+
 	if ((pcaps & BIT_V(uint32_t, pwc_hpd)) != 0) {
 		ddf_msg(LVL_NOTE, "PIN %d will enable headphone drive", aw);
@@ -503,4 +508,5 @@
 	codec->hda = hda;
 	codec->address = address;
+	codec->in_aw = -1;
 
 	rc = hda_get_subnc(codec, 0, &sfg, &nfg);
@@ -587,4 +593,24 @@
 				ddf_msg(LVL_NOTE, "Output widget %d: rates=0x%x formats=0x%x",
 				    aw, rates, formats);
+			} else if (awtype == awt_audio_input) {
+				if (codec->in_aw < 0) {
+					ddf_msg(LVL_NOTE, "Selected input "
+					    "widget %d\n", aw);
+					codec->in_aw = aw;
+				} else {
+					ddf_msg(LVL_NOTE, "Ignoring input "
+					    "widget %d\n", aw);
+				}
+
+				rc = hda_get_supp_rates(codec, aw, &rates);
+				if (rc != EOK)
+					goto error;
+
+				rc = hda_get_supp_formats(codec, aw, &formats);
+				if (rc != EOK)
+					goto error;
+
+				ddf_msg(LVL_NOTE, "Input widget %d: rates=0x%x formats=0x%x",
+				    aw, rates, formats);
 			}
 
@@ -623,14 +649,35 @@
 		/* Configure converter */
 
-		ddf_msg(LVL_NOTE, "Configure converter format");
+		ddf_msg(LVL_NOTE, "Configure output converter format");
 		rc = hda_set_converter_fmt(codec, out_aw, stream->fmt);
 		if (rc != EOK)
 			goto error;
 
-		ddf_msg(LVL_NOTE, "Configure converter stream, channel");
+		ddf_msg(LVL_NOTE, "Configure output converter stream, channel");
 		rc = hda_set_converter_ctl(codec, out_aw, stream->sid, 0);
 		if (rc != EOK)
 			goto error;
 	}
+
+	return EOK;
+error:
+	return rc;
+}
+
+int hda_in_converter_setup(hda_codec_t *codec, hda_stream_t *stream)
+{
+	int rc;
+
+	/* Configure converter */
+
+	ddf_msg(LVL_NOTE, "Configure input converter format");
+	rc = hda_set_converter_fmt(codec, codec->in_aw, stream->fmt);
+	if (rc != EOK)
+		goto error;
+
+	ddf_msg(LVL_NOTE, "Configure input converter stream, channel");
+	rc = hda_set_converter_ctl(codec, codec->in_aw, stream->sid, 0);
+	if (rc != EOK)
+		goto error;
 
 	return EOK;
Index: uspace/drv/audio/hdaudio/codec.h
===================================================================
--- uspace/drv/audio/hdaudio/codec.h	(revision a931b7b2faca447e0708fd5fae0f85116e4c788d)
+++ uspace/drv/audio/hdaudio/codec.h	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -47,4 +47,5 @@
 	int out_aw_num;
 	int out_aw_sel;
+	int in_aw;
 } hda_codec_t;
 
@@ -52,4 +53,5 @@
 extern void hda_codec_fini(hda_codec_t *);
 extern int hda_out_converter_setup(hda_codec_t *, hda_stream_t *);
+extern int hda_in_converter_setup(hda_codec_t *, hda_stream_t *);
 
 #endif
Index: uspace/drv/audio/hdaudio/hdactl.c
===================================================================
--- uspace/drv/audio/hdaudio/hdactl.c	(revision a931b7b2faca447e0708fd5fae0f85116e4c788d)
+++ uspace/drv/audio/hdaudio/hdactl.c	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -562,5 +562,6 @@
 	ctl->bss = BIT_RANGE_EXTRACT(uint16_t, gcap_bss_h, gcap_bss_l, gcap);
 	ddf_msg(LVL_NOTE, "GCAP: 0x%x (64OK=%d)", gcap, ctl->ok64bit);
-
+	ddf_msg(LVL_NOTE, "iss: %d, oss: %d, bss: %d\n",
+	    ctl->iss, ctl->oss, ctl->bss);
 	/* Give codecs enough time to enumerate themselves */
 	async_usleep(codec_enum_wait_us);
Index: uspace/drv/audio/hdaudio/hdaudio.c
===================================================================
--- uspace/drv/audio/hdaudio/hdaudio.c	(revision a931b7b2faca447e0708fd5fae0f85116e4c788d)
+++ uspace/drv/audio/hdaudio/hdaudio.c	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -385,5 +385,11 @@
 			hda_pcm_event(hda, PCM_EVENT_FRAMES_PLAYED);
 			hda_pcm_event(hda, PCM_EVENT_FRAMES_PLAYED);
+		} else if (hda->capturing) {
+			hda_pcm_event(hda, PCM_EVENT_FRAMES_CAPTURED);
+			hda_pcm_event(hda, PCM_EVENT_FRAMES_CAPTURED);
+			hda_pcm_event(hda, PCM_EVENT_FRAMES_CAPTURED);
+			hda_pcm_event(hda, PCM_EVENT_FRAMES_CAPTURED);
 		}
+
 		hda_unlock(hda);
 	}
Index: uspace/drv/audio/hdaudio/hdaudio.h
===================================================================
--- uspace/drv/audio/hdaudio/hdaudio.h	(revision a931b7b2faca447e0708fd5fae0f85116e4c788d)
+++ uspace/drv/audio/hdaudio/hdaudio.h	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -55,5 +55,7 @@
 	struct hda_ctl *ctl;
 	struct hda_stream *pcm_stream;
+	struct hda_stream_buffers *pcm_buffers;
 	bool playing;
+	bool capturing;
 } hda_t;
 
Index: uspace/drv/audio/hdaudio/pcm_iface.c
===================================================================
--- uspace/drv/audio/hdaudio/pcm_iface.c	(revision a931b7b2faca447e0708fd5fae0f85116e4c788d)
+++ uspace/drv/audio/hdaudio/pcm_iface.c	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -100,11 +100,16 @@
 static unsigned hda_query_cap(ddf_fun_t *fun, audio_cap_t cap)
 {
+	hda_t *hda = fun_to_hda(fun);
+
 	ddf_msg(LVL_NOTE, "hda_query_cap(%d)", cap);
 	switch (cap) {
 	case AUDIO_CAP_PLAYBACK:
 	case AUDIO_CAP_INTERRUPT:
+		/* XXX Only if we have an output converter */
 		return 1;
+	case AUDIO_CAP_CAPTURE:
+		/* Yes if we have an input converter */
+		return hda->ctl->codec->in_aw >= 0;
 	case AUDIO_CAP_BUFFER_POS:
-	case AUDIO_CAP_CAPTURE:
 		return 0;
 	case AUDIO_CAP_MAX_BUFFER:
@@ -148,8 +153,94 @@
 {
 	hda_t *hda = fun_to_hda(fun);
+	int rc;
 
 	hda_lock(hda);
 
 	ddf_msg(LVL_NOTE, "hda_get_buffer(): hda=%p", hda);
+	if (hda->pcm_buffers != NULL) {
+		hda_unlock(hda);
+		return EBUSY;
+	}
+
+	ddf_msg(LVL_NOTE, "hda_get_buffer() - allocate stream buffers");
+	rc = hda_stream_buffers_alloc(hda, &hda->pcm_buffers);
+	if (rc != EOK) {
+		assert(rc == ENOMEM);
+		hda_unlock(hda);
+		return ENOMEM;
+	}
+
+	ddf_msg(LVL_NOTE, "hda_get_buffer() - fill info");
+	/* XXX This is only one buffer */
+	*buffer = hda->pcm_buffers->buf[0];
+	*size = hda->pcm_buffers->bufsize * hda->pcm_buffers->nbuffers;
+
+	ddf_msg(LVL_NOTE, "hda_get_buffer() returing EOK, buffer=%p, size=%zu",
+	    *buffer, *size);
+
+	hda_unlock(hda);
+	return EOK;
+}
+
+static int hda_get_buffer_position(ddf_fun_t *fun, size_t *pos)
+{
+	ddf_msg(LVL_NOTE, "hda_get_buffer_position()");
+	return ENOTSUP;
+}
+
+static int hda_set_event_session(ddf_fun_t *fun, async_sess_t *sess)
+{
+	hda_t *hda = fun_to_hda(fun);
+
+	ddf_msg(LVL_NOTE, "hda_set_event_session()");
+	hda_lock(hda);
+	hda->ev_sess = sess;
+	hda_unlock(hda);
+
+	return EOK;
+}
+
+static async_sess_t *hda_get_event_session(ddf_fun_t *fun)
+{
+	hda_t *hda = fun_to_hda(fun);
+	async_sess_t *sess;
+
+	ddf_msg(LVL_NOTE, "hda_get_event_session()");
+
+	hda_lock(hda);
+	sess = hda->ev_sess;
+	hda_unlock(hda);
+
+	return sess;
+}
+
+static int hda_release_buffer(ddf_fun_t *fun)
+{
+	hda_t *hda = fun_to_hda(fun);
+
+	hda_lock(hda);
+
+	ddf_msg(LVL_NOTE, "hda_release_buffer()");
+	if (hda->pcm_buffers == NULL) {
+		hda_unlock(hda);
+		return EINVAL;
+	}
+
+	hda_stream_buffers_free(hda->pcm_buffers);
+	hda->pcm_buffers = NULL;
+
+	hda_unlock(hda);
+	return EOK;
+}
+
+static int hda_start_playback(ddf_fun_t *fun, unsigned frames,
+    unsigned channels, unsigned rate, pcm_sample_format_t format)
+{
+	hda_t *hda = fun_to_hda(fun);
+	int rc;
+
+	ddf_msg(LVL_NOTE, "hda_start_playback()");
+	hda_lock(hda);
+
 	if (hda->pcm_stream != NULL) {
 		hda_unlock(hda);
@@ -162,91 +253,22 @@
 	fmt = (fmt_base_44khz << fmt_base) | (fmt_bits_16 << fmt_bits_l) | 1;
 
-	ddf_msg(LVL_NOTE, "hda_get_buffer() - create stream");
-	hda->pcm_stream = hda_stream_create(hda, sdir_output, fmt);
+	ddf_msg(LVL_NOTE, "hda_start_playback() - create output stream");
+	hda->pcm_stream = hda_stream_create(hda, sdir_output, hda->pcm_buffers,
+	    fmt);
 	if (hda->pcm_stream == NULL) {
 		hda_unlock(hda);
 		return EIO;
 	}
-
-	ddf_msg(LVL_NOTE, "hda_get_buffer() - fill info");
-	/* XXX This is only one buffer */
-	*buffer = hda->pcm_stream->buf[0];
-	*size = hda->pcm_stream->bufsize * hda->pcm_stream->nbuffers;
-
-	ddf_msg(LVL_NOTE, "hda_get_buffer() retturing EOK, buffer=%p, size=%zu",
-	    *buffer, *size);
-
-	hda_unlock(hda);
-	return EOK;
-}
-
-static int hda_get_buffer_position(ddf_fun_t *fun, size_t *pos)
-{
-	ddf_msg(LVL_NOTE, "hda_get_buffer_position()");
-	return ENOTSUP;
-}
-
-static int hda_set_event_session(ddf_fun_t *fun, async_sess_t *sess)
-{
-	hda_t *hda = fun_to_hda(fun);
-
-	ddf_msg(LVL_NOTE, "hda_set_event_session()");
-	hda_lock(hda);
-	hda->ev_sess = sess;
-	hda_unlock(hda);
-
-	return EOK;
-}
-
-static async_sess_t *hda_get_event_session(ddf_fun_t *fun)
-{
-	hda_t *hda = fun_to_hda(fun);
-	async_sess_t *sess;
-
-	ddf_msg(LVL_NOTE, "hda_get_event_session()");
-
-	hda_lock(hda);
-	sess = hda->ev_sess;
-	hda_unlock(hda);
-
-	return sess;
-}
-
-static int hda_release_buffer(ddf_fun_t *fun)
-{
-	hda_t *hda = fun_to_hda(fun);
-
-	hda_lock(hda);
-
-	ddf_msg(LVL_NOTE, "hda_release_buffer()");
-	if (hda->pcm_stream == NULL) {
-		hda_unlock(hda);
-		return EINVAL;
-	}
-
-	hda_stream_destroy(hda->pcm_stream);
-	hda->pcm_stream = NULL;
-
-	hda_unlock(hda);
-	return EOK;
-}
-
-static int hda_start_playback(ddf_fun_t *fun, unsigned frames,
-    unsigned channels, unsigned rate, pcm_sample_format_t format)
-{
-	hda_t *hda = fun_to_hda(fun);
-	int rc;
-
-	ddf_msg(LVL_NOTE, "hda_start_playback()");
-	hda_lock(hda);
 
 	rc = hda_out_converter_setup(hda->ctl->codec, hda->pcm_stream);
 	if (rc != EOK) {
+		hda_stream_destroy(hda->pcm_stream);
+		hda->pcm_stream = NULL;
 		hda_unlock(hda);
 		return rc;
 	}
 
+	hda->playing = true;
 	hda_stream_start(hda->pcm_stream);
-	hda->playing = true;
 	hda_unlock(hda);
 	return EOK;
@@ -262,4 +284,7 @@
 	hda_stream_reset(hda->pcm_stream);
 	hda->playing = false;
+	hda_stream_destroy(hda->pcm_stream);
+	hda->pcm_stream = NULL;
+
 	hda_unlock(hda);
 
@@ -271,12 +296,57 @@
     unsigned rate, pcm_sample_format_t format)
 {
+	hda_t *hda = fun_to_hda(fun);
+	int rc;
+
 	ddf_msg(LVL_NOTE, "hda_start_capture()");
-	return ENOTSUP;
+	hda_lock(hda);
+
+	if (hda->pcm_stream != NULL) {
+		hda_unlock(hda);
+		return EBUSY;
+	}
+
+	/* XXX Choose appropriate parameters */
+	uint32_t fmt;
+	/* 48 kHz, 16-bits, 1 channel */
+	fmt = (fmt_base_44khz << fmt_base) | (fmt_bits_16 << fmt_bits_l) | 1;
+
+	ddf_msg(LVL_NOTE, "hda_start_capture() - create input stream");
+	hda->pcm_stream = hda_stream_create(hda, sdir_input, hda->pcm_buffers,
+	    fmt);
+	if (hda->pcm_stream == NULL) {
+		hda_unlock(hda);
+		return EIO;
+	}
+
+	rc = hda_in_converter_setup(hda->ctl->codec, hda->pcm_stream);
+	if (rc != EOK) {
+		hda_stream_destroy(hda->pcm_stream);
+		hda->pcm_stream = NULL;
+		hda_unlock(hda);
+		return rc;
+	}
+
+	hda->capturing = true;
+	hda_stream_start(hda->pcm_stream);
+	hda_unlock(hda);
+	return EOK;
 }
 
 static int hda_stop_capture(ddf_fun_t *fun, bool immediate)
 {
+	hda_t *hda = fun_to_hda(fun);
+
 	ddf_msg(LVL_NOTE, "hda_stop_capture()");
-	return ENOTSUP;
+	hda_lock(hda);
+	hda_stream_stop(hda->pcm_stream);
+	hda_stream_reset(hda->pcm_stream);
+	hda->capturing = false;
+	hda_stream_destroy(hda->pcm_stream);
+	hda->pcm_stream = NULL;
+	hda_unlock(hda);
+
+	hda_pcm_event(hda, PCM_EVENT_CAPTURE_TERMINATED);
+	return EOK;
 }
 
Index: uspace/drv/audio/hdaudio/stream.c
===================================================================
--- uspace/drv/audio/hdaudio/stream.c	(revision a931b7b2faca447e0708fd5fae0f85116e4c788d)
+++ uspace/drv/audio/hdaudio/stream.c	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -48,15 +48,22 @@
 #include "stream.h"
 
-static int hda_stream_buffers_alloc(hda_stream_t *stream)
+int hda_stream_buffers_alloc(hda_t *hda, hda_stream_buffers_t **rbufs)
 {
 	void *bdl;
 	void *buffer;
 	uintptr_t buffer_phys;
+	hda_stream_buffers_t *bufs = NULL;
 	size_t i;
 //	size_t j, k;
 	int rc;
 
-	stream->nbuffers = 4;
-	stream->bufsize = 16384;
+	bufs = calloc(1, sizeof(hda_stream_buffers_t));
+	if (bufs == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	bufs->nbuffers = 4;
+	bufs->bufsize = 16384;
 
 	/*
@@ -65,28 +72,28 @@
 	 */
 	bdl = AS_AREA_ANY;
-	rc = dmamem_map_anonymous(stream->nbuffers * sizeof(hda_buffer_desc_t),
-	    stream->hda->ctl->ok64bit ? 0 : DMAMEM_4GiB, AS_AREA_READ | AS_AREA_WRITE,
-	    0, &stream->bdl_phys, &bdl);
+	rc = dmamem_map_anonymous(bufs->nbuffers * sizeof(hda_buffer_desc_t),
+	    hda->ctl->ok64bit ? 0 : DMAMEM_4GiB, AS_AREA_READ | AS_AREA_WRITE,
+	    0, &bufs->bdl_phys, &bdl);
 	if (rc != EOK)
 		goto error;
 
-	stream->bdl = bdl;
+	bufs->bdl = bdl;
 
 	/* Allocate arrays of buffer pointers */
 
-	stream->buf = calloc(stream->nbuffers, sizeof(void *));
-	if (stream->buf == NULL)
-		goto error;
-
-	stream->buf_phys = calloc(stream->nbuffers, sizeof(uintptr_t));
-	if (stream->buf_phys == NULL)
+	bufs->buf = calloc(bufs->nbuffers, sizeof(void *));
+	if (bufs->buf == NULL)
+		goto error;
+
+	bufs->buf_phys = calloc(bufs->nbuffers, sizeof(uintptr_t));
+	if (bufs->buf_phys == NULL)
 		goto error;
 
 	/* Allocate buffers */
 /*
-	for (i = 0; i < stream->nbuffers; i++) {
+	for (i = 0; i < bufs->nbuffers; i++) {
 		buffer = AS_AREA_ANY;
-		rc = dmamem_map_anonymous(stream->bufsize,
-		    stream->hda->ctl->ok64bit ? 0 : DMAMEM_4GiB, AS_AREA_READ | AS_AREA_WRITE,
+		rc = dmamem_map_anonymous(bufs->bufsize,
+		    bufs->hda->ctl->ok64bit ? 0 : DMAMEM_4GiB, AS_AREA_READ | AS_AREA_WRITE,
 		    0, &buffer_phys, &buffer);
 		if (rc != EOK)
@@ -96,10 +103,10 @@
 		    (unsigned long long)buffer_phys, buffer);
 
-		stream->buf[i] = buffer;
-		stream->buf_phys[i] = buffer_phys;
+		bufs->buf[i] = buffer;
+		bufs->buf_phys[i] = buffer_phys;
 
 		k = 0;
-		for (j = 0; j < stream->bufsize / 2; j++) {
-			int16_t *bp = stream->buf[i];
+		for (j = 0; j < bufs->bufsize / 2; j++) {
+			int16_t *bp = bufs->buf[i];
 			bp[j] = (k > 128) ? -100 : 100;
 			++k;
@@ -111,6 +118,6 @@
 	/* audio_pcm_iface requires a single contiguous buffer */
 	buffer = AS_AREA_ANY;
-	rc = dmamem_map_anonymous(stream->bufsize * stream->nbuffers,
-	    stream->hda->ctl->ok64bit ? 0 : DMAMEM_4GiB, AS_AREA_READ | AS_AREA_WRITE,
+	rc = dmamem_map_anonymous(bufs->bufsize * bufs->nbuffers,
+	    hda->ctl->ok64bit ? 0 : DMAMEM_4GiB, AS_AREA_READ | AS_AREA_WRITE,
 	    0, &buffer_phys, &buffer);
 	if (rc != EOK) {
@@ -119,14 +126,14 @@
 	}
 
-	for (i = 0; i < stream->nbuffers; i++) {
-		stream->buf[i] = buffer + i * stream->bufsize;
-		stream->buf_phys[i] = buffer_phys + i * stream->bufsize;
+	for (i = 0; i < bufs->nbuffers; i++) {
+		bufs->buf[i] = buffer + i * bufs->bufsize;
+		bufs->buf_phys[i] = buffer_phys + i * bufs->bufsize;
 
 		ddf_msg(LVL_NOTE, "Stream buf phys=0x%llx virt=%p",
-		    (long long unsigned)(uintptr_t)stream->buf[i],
-		    (void *)stream->buf_phys[i]);
+		    (long long unsigned)(uintptr_t)bufs->buf[i],
+		    (void *)bufs->buf_phys[i]);
 /*		k = 0;
-		for (j = 0; j < stream->bufsize / 2; j++) {
-			int16_t *bp = stream->buf[i];
+		for (j = 0; j < bufs->bufsize / 2; j++) {
+			int16_t *bp = bufs->buf[i];
 			bp[j] = (k > 128) ? -10000 : 10000;
 			++k;
@@ -138,18 +145,30 @@
 
 	/* Fill in BDL */
-	for (i = 0; i < stream->nbuffers; i++) {
-		stream->bdl[i].address = host2uint64_t_le(stream->buf_phys[i]);
-		stream->bdl[i].length = host2uint32_t_le(stream->bufsize);
-		stream->bdl[i].flags = BIT_V(uint32_t, bdf_ioc);
-	}
-
+	for (i = 0; i < bufs->nbuffers; i++) {
+		bufs->bdl[i].address = host2uint64_t_le(bufs->buf_phys[i]);
+		bufs->bdl[i].length = host2uint32_t_le(bufs->bufsize);
+		bufs->bdl[i].flags = BIT_V(uint32_t, bdf_ioc);
+	}
+
+	*rbufs = bufs;
 	return EOK;
 error:
+	hda_stream_buffers_free(bufs);
 	return ENOMEM;
 }
 
+void hda_stream_buffers_free(hda_stream_buffers_t *bufs)
+{
+	if (bufs == NULL)
+		return;
+
+	/* XXX */
+	free(bufs);
+}
+
 static void hda_stream_desc_configure(hda_stream_t *stream)
 {
 	hda_sdesc_regs_t *sdregs;
+	hda_stream_buffers_t *bufs = stream->buffers;
 	uint8_t ctl1;
 	uint8_t ctl3;
@@ -161,9 +180,9 @@
 	hda_reg8_write(&sdregs->ctl3, ctl3);
 	hda_reg8_write(&sdregs->ctl1, ctl1);
-	hda_reg32_write(&sdregs->cbl, stream->nbuffers * stream->bufsize);
-	hda_reg16_write(&sdregs->lvi, stream->nbuffers - 1);
+	hda_reg32_write(&sdregs->cbl, bufs->nbuffers * bufs->bufsize);
+	hda_reg16_write(&sdregs->lvi, bufs->nbuffers - 1);
 	hda_reg16_write(&sdregs->fmt, stream->fmt);
-	hda_reg32_write(&sdregs->bdpl, LOWER32(stream->bdl_phys));
-	hda_reg32_write(&sdregs->bdpu, UPPER32(stream->bdl_phys));
+	hda_reg32_write(&sdregs->bdpl, LOWER32(bufs->bdl_phys));
+	hda_reg32_write(&sdregs->bdpu, UPPER32(bufs->bdl_phys));
 }
 
@@ -205,8 +224,8 @@
 
 hda_stream_t *hda_stream_create(hda_t *hda, hda_stream_dir_t dir,
-    uint32_t fmt)
+    hda_stream_buffers_t *bufs, uint32_t fmt)
 {
 	hda_stream_t *stream;
-	int rc;
+	uint8_t sdid;
 
 	stream = calloc(1, sizeof(hda_stream_t));
@@ -214,22 +233,30 @@
 		return NULL;
 
+	sdid = 0;
+
+	switch (dir) {
+	case sdir_input:
+		sdid = 0; /* XXX Allocate - first input SDESC */
+		break;
+	case sdir_output:
+		sdid = hda->ctl->iss; /* XXX Allocate - First output SDESC */
+		break;
+	case sdir_bidi:
+		sdid = hda->ctl->iss + hda->ctl->oss; /* XXX Allocate - First bidi SDESC */
+		break;
+	}
+
 	stream->hda = hda;
 	stream->dir = dir;
 	stream->sid = 1; /* XXX Allocate this */
-	stream->sdid = hda->ctl->iss; /* XXX Allocate - First output SDESC */
+	stream->sdid = sdid;
 	stream->fmt = fmt;
+	stream->buffers = bufs;
 
 	ddf_msg(LVL_NOTE, "snum=%d sdidx=%d", stream->sid, stream->sdid);
-
-	ddf_msg(LVL_NOTE, "Allocate buffers");
-	rc = hda_stream_buffers_alloc(stream);
-	if (rc != EOK)
-		goto error;
 
 	ddf_msg(LVL_NOTE, "Configure stream descriptor");
 	hda_stream_desc_configure(stream);
 	return stream;
-error:
-	return NULL;
 }
 
Index: uspace/drv/audio/hdaudio/stream.h
===================================================================
--- uspace/drv/audio/hdaudio/stream.h	(revision a931b7b2faca447e0708fd5fae0f85116e4c788d)
+++ uspace/drv/audio/hdaudio/stream.h	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -48,12 +48,5 @@
 } hda_stream_dir_t;
 
-typedef struct hda_stream {
-	hda_t *hda;
-	/** Stream ID */
-	uint8_t sid;
-	/** Stream descriptor index */
-	uint8_t sdid;
-	/** Direction */
-	hda_stream_dir_t dir;
+typedef struct hda_stream_buffers {
 	/** Number of buffers */
 	size_t nbuffers;
@@ -68,9 +61,24 @@
 	/** Physical addresses of buffers */
 	uintptr_t *buf_phys;
+} hda_stream_buffers_t;
+
+typedef struct hda_stream {
+	hda_t *hda;
+	/** Stream ID */
+	uint8_t sid;
+	/** Stream descriptor index */
+	uint8_t sdid;
+	/** Direction */
+	hda_stream_dir_t dir;
+	/** Buffers */
+	hda_stream_buffers_t *buffers;
 	/** Stream format */
 	uint32_t fmt;
 } hda_stream_t;
 
-extern hda_stream_t *hda_stream_create(hda_t *, hda_stream_dir_t, uint32_t);
+extern int hda_stream_buffers_alloc(hda_t *, hda_stream_buffers_t **);
+extern void hda_stream_buffers_free(hda_stream_buffers_t *);
+extern hda_stream_t *hda_stream_create(hda_t *, hda_stream_dir_t,
+    hda_stream_buffers_t *, uint32_t);
 extern void hda_stream_destroy(hda_stream_t *);
 extern void hda_stream_start(hda_stream_t *);
Index: uspace/drv/block/ata_bd/main.c
===================================================================
--- uspace/drv/block/ata_bd/main.c	(revision a931b7b2faca447e0708fd5fae0f85116e4c788d)
+++ uspace/drv/block/ata_bd/main.c	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -81,5 +81,4 @@
 		rc = EINVAL;
 		goto error;
-		return EINVAL;
 	}
 
Index: uspace/drv/block/ddisk/Makefile
===================================================================
--- uspace/drv/block/ddisk/Makefile	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
+++ uspace/drv/block/ddisk/Makefile	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2013 Jiri Svoboda
+# 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 = $(LIBDRV_PREFIX)/libdrv.a $(LIBSCSI_PREFIX)/libscsi.a
+EXTRA_CFLAGS += -I$(LIBDRV_PREFIX)/include -I$(LIBSCSI_PREFIX)/include
+BINARY = ddisk
+
+SOURCES = \
+	ddisk.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/drv/block/ddisk/ddisk.c
===================================================================
--- uspace/drv/block/ddisk/ddisk.c	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
+++ uspace/drv/block/ddisk/ddisk.c	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -0,0 +1,599 @@
+/*
+ * Copyright (c) 2015 Jakub Jermar
+ * Copyright (c) 2013 Jiri Svoboda
+ * 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.
+ */
+
+/** @file
+ * MSIM ddisk block device driver
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <str_error.h>
+#include <ddf/driver.h>
+#include <ddf/interrupt.h>
+#include <ddf/log.h>
+#include <device/hw_res_parsed.h>
+#include <ddi.h>
+#include <bd_srv.h>
+#include <fibril_synch.h>
+#include <as.h>
+
+#define NAME	"ddisk"
+
+#define DDISK_FUN_NAME	"a"
+
+#define DDISK_BLOCK_SIZE	512
+
+#define DDISK_STAT_IRQ_PENDING	0x4
+#define DDISK_CMD_READ		0x1
+#define DDISK_CMD_WRITE		0x2
+#define DDISK_CMD_IRQ_DEASSERT	0x4
+
+static int ddisk_dev_add(ddf_dev_t *);
+static int ddisk_dev_remove(ddf_dev_t *);
+static int ddisk_dev_gone(ddf_dev_t *);
+static int ddisk_fun_online(ddf_fun_t *);
+static int ddisk_fun_offline(ddf_fun_t *);
+
+static void ddisk_bd_connection(ipc_callid_t, ipc_call_t *, void *);
+
+static void ddisk_irq_handler(ipc_callid_t, ipc_call_t *, ddf_dev_t *);
+
+static driver_ops_t driver_ops = {
+	.dev_add = ddisk_dev_add,
+	.dev_remove = ddisk_dev_remove,
+	.dev_gone = ddisk_dev_gone,
+	.fun_online = ddisk_fun_online,
+	.fun_offline = ddisk_fun_offline
+};
+
+static driver_t ddisk_driver = {
+	.name = NAME,
+	.driver_ops = &driver_ops
+};
+
+typedef struct {
+	int irq;
+	uintptr_t base;
+} ddisk_res_t;
+
+typedef struct {
+	ioport32_t dma_buffer;
+	ioport32_t block;
+	union {
+		ioport32_t status;
+		ioport32_t command;
+	};
+	ioport32_t size;
+} ddisk_regs_t;
+
+typedef struct {
+	fibril_mutex_t lock;
+
+	fibril_condvar_t io_cv;
+	bool io_busy;
+
+	ssize_t size;
+	size_t blocks;
+
+	uintptr_t dma_buffer_phys;
+	void *dma_buffer;
+
+	ddf_dev_t *dev;
+	ddf_fun_t *fun;
+
+	ddisk_res_t ddisk_res;
+	ddisk_regs_t *ddisk_regs;
+
+	bd_srvs_t bds;
+} ddisk_t;
+
+static int ddisk_bd_open(bd_srvs_t *, bd_srv_t *);
+static int ddisk_bd_close(bd_srv_t *);
+static int ddisk_bd_read_blocks(bd_srv_t *, aoff64_t, size_t, void *, size_t);
+static int ddisk_bd_write_blocks(bd_srv_t *, aoff64_t, size_t, const void *,
+    size_t);
+static int ddisk_bd_get_block_size(bd_srv_t *, size_t *);
+static int ddisk_bd_get_num_blocks(bd_srv_t *, aoff64_t *);
+
+bd_ops_t ddisk_bd_ops = {
+	.open = ddisk_bd_open,
+	.close = ddisk_bd_close,
+	.read_blocks = ddisk_bd_read_blocks,
+	.write_blocks = ddisk_bd_write_blocks,
+	.get_block_size = ddisk_bd_get_block_size,
+	.get_num_blocks = ddisk_bd_get_num_blocks,
+};
+
+irq_pio_range_t ddisk_irq_pio_ranges[] = {
+	{
+		.base = 0,
+		.size = sizeof(ddisk_regs_t)
+	}
+};
+
+irq_cmd_t ddisk_irq_commands[] = {
+	{
+		.cmd = CMD_PIO_READ_32,
+		.addr = NULL,
+		.dstarg = 1
+	},
+	{
+		.cmd = CMD_AND,
+		.srcarg = 1,
+		.value = DDISK_STAT_IRQ_PENDING,
+		.dstarg = 2
+	},
+	{
+		.cmd = CMD_PREDICATE,
+		.srcarg = 2,
+		.value = 2 
+	},
+	{
+		/* Deassert the DMA interrupt. */
+		.cmd = CMD_PIO_WRITE_32,
+		.value = DDISK_CMD_IRQ_DEASSERT,
+		.addr = NULL 
+	},
+	{
+		.cmd = CMD_ACCEPT
+	}
+};
+
+irq_code_t ddisk_irq_code = {
+	.rangecount = 1,
+	.ranges = ddisk_irq_pio_ranges,
+	.cmdcount = sizeof(ddisk_irq_commands) / sizeof(irq_cmd_t),
+	.cmds = ddisk_irq_commands,
+};
+
+void ddisk_irq_handler(ipc_callid_t iid, ipc_call_t *icall, ddf_dev_t *dev)
+{
+	ddf_msg(LVL_DEBUG, "ddisk_irq_handler(), status=%" PRIx32,
+	    (uint32_t) IPC_GET_ARG1(*icall));
+
+	ddisk_t *ddisk = (ddisk_t *) ddf_dev_data_get(dev);
+	
+	fibril_mutex_lock(&ddisk->lock);
+	fibril_condvar_broadcast(&ddisk->io_cv);
+	fibril_mutex_unlock(&ddisk->lock);
+}
+
+int ddisk_bd_open(bd_srvs_t *bds, bd_srv_t *bd)
+{
+	return EOK;
+}
+
+int ddisk_bd_close(bd_srv_t *bd)
+{
+	return EOK;
+}
+
+static
+int ddisk_rw_block(ddisk_t *ddisk, bool read, aoff64_t ba, void *buf)
+{
+	fibril_mutex_lock(&ddisk->lock);
+
+	ddf_msg(LVL_DEBUG, "ddisk_rw_block(): read=%d, ba=%" PRId64 ", buf=%p",
+	    read, ba, buf);
+
+	if (ba >= ddisk->blocks)
+		return ELIMIT;
+
+	while (ddisk->io_busy)
+		fibril_condvar_wait(&ddisk->io_cv, &ddisk->lock);
+
+	ddisk->io_busy = true;
+
+	if (!read)
+		memcpy(ddisk->dma_buffer, buf, DDISK_BLOCK_SIZE);
+	
+	pio_write_32(&ddisk->ddisk_regs->dma_buffer,
+	    ddisk->dma_buffer_phys);
+	pio_write_32(&ddisk->ddisk_regs->block, (uint32_t) ba);
+	pio_write_32(&ddisk->ddisk_regs->command,
+	    read ? DDISK_CMD_READ : DDISK_CMD_WRITE);
+
+	fibril_condvar_wait(&ddisk->io_cv, &ddisk->lock);
+
+	if (read)
+		memcpy(buf, ddisk->dma_buffer, DDISK_BLOCK_SIZE);
+
+	ddisk->io_busy = false;
+	fibril_condvar_signal(&ddisk->io_cv);
+	fibril_mutex_unlock(&ddisk->lock);
+
+	return EOK;
+}
+
+static
+int ddisk_bd_rw_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt, void *buf,
+    size_t size, bool is_read)
+{
+	ddisk_t *ddisk = (ddisk_t *) bd->srvs->sarg;
+	aoff64_t i;
+	int rc;
+
+	if (size < cnt * DDISK_BLOCK_SIZE)
+		return EINVAL;		
+
+	for (i = 0; i < cnt; i++) {
+		rc = ddisk_rw_block(ddisk, is_read, ba + i,
+		    buf + i * DDISK_BLOCK_SIZE);
+		if (rc != EOK)
+			return rc;
+	}
+
+	return EOK;
+}
+
+int ddisk_bd_read_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt, void *buf,
+    size_t size)
+{
+	return ddisk_bd_rw_blocks(bd, ba, cnt, buf, size, true);
+}
+
+int ddisk_bd_write_blocks(bd_srv_t *bd, aoff64_t ba, size_t cnt,
+    const void *buf, size_t size)
+{
+	return ddisk_bd_rw_blocks(bd, ba, cnt, (void *) buf, size, false);
+}
+
+int ddisk_bd_get_block_size(bd_srv_t *bd, size_t *rsize)
+{
+	*rsize = DDISK_BLOCK_SIZE; 
+	return EOK;
+}
+
+int ddisk_bd_get_num_blocks(bd_srv_t *bd, aoff64_t *rnb)
+{
+	ddisk_t *ddisk = (ddisk_t *) bd->srvs->sarg;
+
+	*rnb = ddisk->blocks;
+	return EOK;	
+}
+
+static int ddisk_get_res(ddf_dev_t *dev, ddisk_res_t *ddisk_res)
+{
+	async_sess_t *parent_sess;
+	hw_res_list_parsed_t hw_res;
+	int rc;
+
+	parent_sess = ddf_dev_parent_sess_create(dev, EXCHANGE_SERIALIZE);
+	if (parent_sess == NULL)
+		return ENOMEM;
+
+	hw_res_list_parsed_init(&hw_res);
+	rc = hw_res_get_list_parsed(parent_sess, &hw_res, 0);
+	if (rc != EOK)
+		return rc;
+
+	if ((hw_res.mem_ranges.count != 1) || (hw_res.irqs.count != 1)) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	addr_range_t *regs = &hw_res.mem_ranges.ranges[0];
+	ddisk_res->base = RNGABS(*regs);
+	ddisk_res->irq = hw_res.irqs.irqs[0]; 
+
+	if (RNGSZ(*regs) < sizeof(ddisk_regs_t)) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	rc = EOK;
+error:
+	hw_res_list_parsed_clean(&hw_res);
+	return rc;
+}
+
+static int ddisk_fun_create(ddisk_t *ddisk)
+{
+	int rc;
+	ddf_fun_t *fun = NULL;
+
+	fun = ddf_fun_create(ddisk->dev, fun_exposed, DDISK_FUN_NAME);
+	if (fun == NULL) {
+		ddf_msg(LVL_ERROR, "Failed creating DDF function.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	/* Set up a connection handler. */
+	ddf_fun_set_conn_handler(fun, ddisk_bd_connection);
+
+	rc = ddf_fun_bind(fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed binding DDF function %s: %s",
+		    DDISK_FUN_NAME, str_error(rc));
+		goto error;
+	}
+
+	ddf_fun_add_to_category(fun, "bd");
+	ddisk->fun = fun;
+
+	return EOK;
+error:
+	if (fun != NULL)
+		ddf_fun_destroy(fun);
+
+	return rc;
+}
+
+static int ddisk_fun_remove(ddisk_t *ddisk)
+{
+	int rc;
+
+	if (ddisk->fun == NULL)
+		return EOK;
+
+	ddf_msg(LVL_DEBUG, "ddisk_fun_remove(%p, '%s')", ddisk,
+	    DDISK_FUN_NAME);
+	rc = ddf_fun_offline(ddisk->fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Error offlining function '%s'.",
+		    DDISK_FUN_NAME);
+		goto error;
+	}
+
+	rc = ddf_fun_unbind(ddisk->fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.",
+		    DDISK_FUN_NAME);
+		goto error;
+	}
+
+	ddf_fun_destroy(ddisk->fun);
+	ddisk->fun = NULL;
+	rc = EOK;
+
+error:
+	return rc;
+}
+
+static int ddisk_fun_unbind(ddisk_t *ddisk)
+{
+	int rc;
+
+	if (ddisk->fun == NULL)
+		return EOK;
+
+	ddf_msg(LVL_DEBUG, "ddisk_fun_unbind(%p, '%s')", ddisk,
+	    DDISK_FUN_NAME);
+	rc = ddf_fun_unbind(ddisk->fun);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed unbinding function '%s'.",
+		    DDISK_FUN_NAME);
+		goto error;
+	}
+
+	ddf_fun_destroy(ddisk->fun);
+	ddisk->fun = NULL;
+	rc = EOK;
+
+error:
+	return rc;
+}
+
+/** Add new device
+ *
+ * @param  dev New device
+ * @return     EOK on success or negative error code.
+ */
+static int ddisk_dev_add(ddf_dev_t *dev)
+{
+	ddisk_t *ddisk;
+	ddisk_res_t res;
+	int rc;
+
+	/*
+	 * Get our resources.
+	 */
+	rc = ddisk_get_res(dev, &res);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Invalid HW resource configuration.");
+		return EINVAL;
+	}
+
+	/*
+	 * Allocate soft state.
+	 */
+	ddisk = ddf_dev_data_alloc(dev, sizeof(ddisk_t));
+	if (!ddisk) {
+		ddf_msg(LVL_ERROR, "Failed allocating soft state.");
+		rc = ENOMEM;
+		goto error;
+	}
+
+	/*
+	 * Initialize soft state.
+	 */
+	fibril_mutex_initialize(&ddisk->lock);
+	ddisk->dev = dev;
+	ddisk->ddisk_res = res;
+
+	fibril_condvar_initialize(&ddisk->io_cv);
+	ddisk->io_busy = false;
+
+	bd_srvs_init(&ddisk->bds);
+	ddisk->bds.ops = &ddisk_bd_ops;
+	ddisk->bds.sarg = ddisk;
+
+	/*
+	 * Enable access to ddisk's PIO registers.
+	 */
+	void *vaddr;
+	rc = pio_enable((void *) res.base, sizeof(ddisk_regs_t), &vaddr);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Cannot initialize device I/O space.");
+		goto error;
+	}
+	ddisk->ddisk_regs = vaddr;
+
+	ddisk->size = (int32_t) pio_read_32(&ddisk->ddisk_regs->size);
+	ddisk->blocks = ddisk->size / DDISK_BLOCK_SIZE;
+
+	if (ddisk->size <= 0) {
+		ddf_msg(LVL_WARN, "No disk detected.");
+		rc = EIO;
+		goto error;
+	}
+
+	/*
+	 * Allocate DMA buffer.
+	 */
+	ddisk->dma_buffer = AS_AREA_ANY;
+	rc = dmamem_map_anonymous(DDISK_BLOCK_SIZE, DMAMEM_4GiB,
+	    AS_AREA_READ | AS_AREA_WRITE, 0, &ddisk->dma_buffer_phys,
+	    &ddisk->dma_buffer);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Cannot allocate DMA memory.");
+		goto error;
+	}
+
+	ddf_msg(LVL_NOTE, "Allocated DMA buffer at %p virtual and %p physical.",
+	    ddisk->dma_buffer, (void *) ddisk->dma_buffer_phys);
+
+	/*
+	 * Create an exposed function.
+	 */
+	rc = ddisk_fun_create(ddisk);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed initializing ddisk controller.");
+		rc = EIO;
+		goto error;
+	}
+
+	/*
+ 	 * Register IRQ handler.
+ 	 */
+	ddisk_regs_t *res_phys = (ddisk_regs_t *) res.base;
+	ddisk_irq_pio_ranges[0].base = res.base;
+	ddisk_irq_commands[0].addr = (void *) &res_phys->status;
+	ddisk_irq_commands[3].addr = (void *) &res_phys->command;
+	rc = register_interrupt_handler(dev, ddisk->ddisk_res.irq,
+	    ddisk_irq_handler, &ddisk_irq_code);
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Failed to register interrupt handler.");
+		goto error;
+	}
+
+	/*
+	 * Success, report what we have found.
+	 */
+	ddf_msg(LVL_NOTE,
+	    "Device at %p with %zd blocks (%zuB) using interrupt %d",
+	    (void *) ddisk->ddisk_res.base, ddisk->blocks,
+	    ddisk->size, ddisk->ddisk_res.irq);
+
+	return EOK;
+
+error:
+	if (ddisk->ddisk_regs)
+		pio_disable(ddisk->ddisk_regs, sizeof(ddisk_regs_t));
+	if (ddisk->dma_buffer)
+		dmamem_unmap_anonymous(ddisk->dma_buffer);
+
+	return rc;
+}
+
+
+static int ddisk_dev_remove_common(ddisk_t *ddisk, bool surprise)
+{
+	int rc;
+
+	if (!surprise)
+		rc = ddisk_fun_remove(ddisk);
+	else
+		rc = ddisk_fun_unbind(ddisk);
+
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Unable to cleanup function '%s'.",
+		    DDISK_FUN_NAME);
+		return rc;
+	}
+
+	unregister_interrupt_handler(ddisk->dev, ddisk->ddisk_res.irq);
+	
+	rc = pio_disable(ddisk->ddisk_regs, sizeof(ddisk_regs_t));
+	if (rc != EOK) {
+		ddf_msg(LVL_ERROR, "Unable to disable PIO.");
+		return rc;	
+	}
+
+	dmamem_unmap_anonymous(ddisk->dma_buffer);
+
+	return EOK;
+}
+
+static int ddisk_dev_remove(ddf_dev_t *dev)
+{
+	ddisk_t *ddisk = (ddisk_t *) ddf_dev_data_get(dev);
+
+	ddf_msg(LVL_DEBUG, "ddisk_dev_remove(%p)", dev);
+	return ddisk_dev_remove_common(ddisk, false);
+}
+
+static int ddisk_dev_gone(ddf_dev_t *dev)
+{
+	ddisk_t *ddisk = (ddisk_t *) ddf_dev_data_get(dev);
+
+	ddf_msg(LVL_DEBUG, "ddisk_dev_gone(%p)", dev);
+	return ddisk_dev_remove_common(ddisk, true);
+}
+
+static int ddisk_fun_online(ddf_fun_t *fun)
+{
+	ddf_msg(LVL_DEBUG, "ddisk_fun_online()");
+	return ddf_fun_online(fun);
+}
+
+static int ddisk_fun_offline(ddf_fun_t *fun)
+{
+	ddf_msg(LVL_DEBUG, "ddisk_fun_offline()");
+	return ddf_fun_offline(fun);
+}
+
+/** Block device connection handler */
+static void ddisk_bd_connection(ipc_callid_t iid, ipc_call_t *icall, void *arg)
+{
+	ddisk_t *ddisk;
+	ddf_fun_t *fun = (ddf_fun_t *) arg;
+
+	ddisk = (ddisk_t *) ddf_dev_data_get(ddf_fun_get_dev(fun));
+	bd_conn(iid, icall, &ddisk->bds);
+}
+
+int main(int argc, char *argv[])
+{
+	printf(NAME ": HelenOS MSIM ddisk device driver\n");
+	ddf_log_init(NAME);
+	return ddf_driver_main(&ddisk_driver);
+}
Index: uspace/drv/block/ddisk/ddisk.ma
===================================================================
--- uspace/drv/block/ddisk/ddisk.ma	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
+++ uspace/drv/block/ddisk/ddisk.ma	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -0,0 +1,1 @@
+10 msim/ddisk
Index: uspace/drv/bus/isa/isa.c
===================================================================
--- uspace/drv/bus/isa/isa.c	(revision a931b7b2faca447e0708fd5fae0f85116e4c788d)
+++ uspace/drv/bus/isa/isa.c	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -245,5 +245,6 @@
 	bool opened = false;
 	int fd;
-	size_t len = 0;
+	size_t len;
+	ssize_t r;
 
 	fd = open(conf_path, O_RDONLY);
@@ -269,5 +270,6 @@
 	}
 
-	if (0 >= read(fd, buf, len)) {
+	r = read_all(fd, buf, len);
+	if (r < 0) {
 		ddf_msg(LVL_ERROR, "Unable to read file '%s'.", conf_path);
 		goto cleanup;
Index: uspace/drv/platform/msim/Makefile
===================================================================
--- uspace/drv/platform/msim/Makefile	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
+++ uspace/drv/platform/msim/Makefile	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2010 Lenka Trochtova
+# 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 = $(LIBDRV_PREFIX)/libdrv.a
+EXTRA_CFLAGS += -I$(LIBDRV_PREFIX)/include
+BINARY = msim
+
+SOURCES = \
+	msim.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/drv/platform/msim/msim.c
===================================================================
--- uspace/drv/platform/msim/msim.c	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
+++ uspace/drv/platform/msim/msim.c	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2010 Lenka Trochtova
+ * Copyright (c) 2015 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.
+ */
+
+/**
+ * @defgroup msim MSIM platform driver.
+ * @brief HelenOS MSIM platform driver.
+ * @{
+ */
+
+/** @file
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <macros.h>
+
+#include <ddi.h>
+#include <ddf/driver.h>
+#include <ddf/log.h>
+#include <ipc/dev_iface.h>
+#include <ops/hw_res.h>
+#include <ops/pio_window.h>
+
+#define NAME "msim"
+
+#define MSIM_DISK_BASE	UINT32_C(0x10000200)
+#define MSIM_DISK_SIZE	UINT32_C(0x00000010)
+
+typedef struct msim_fun {
+	hw_resource_list_t hw_resources;
+	pio_window_t pio_window;
+} msim_fun_t;
+
+static int msim_dev_add(ddf_dev_t *dev);
+static void msim_init(void);
+
+/** The root device driver's standard operations. */
+static driver_ops_t msim_ops = {
+	.dev_add = &msim_dev_add
+};
+
+/** The root device driver structure. */
+static driver_t msim_driver = {
+	.name = NAME,
+	.driver_ops = &msim_ops
+};
+
+static hw_resource_t disk_regs[] = {
+	{
+		.type = MEM_RANGE,
+		.res.mem_range = {
+			.address = 0,
+			.size = 16,
+			.relative = true,
+			.endianness = LITTLE_ENDIAN
+		}
+	},
+	{
+		.type = INTERRUPT,
+		.res.interrupt = {
+			.irq = 6
+		}
+	}
+};
+
+static msim_fun_t disk_data = {
+	.hw_resources = {
+		sizeof(disk_regs) / sizeof(disk_regs[0]),
+		disk_regs
+	},
+	.pio_window = {
+		.mem = {
+			.base = MSIM_DISK_BASE,
+			.size = MSIM_DISK_SIZE
+		}
+	}
+};
+
+/** Obtain function soft-state from DDF function node */
+static msim_fun_t *msim_fun(ddf_fun_t *fnode)
+{
+	return ddf_fun_data_get(fnode);
+}
+
+static hw_resource_list_t *msim_get_resources(ddf_fun_t *fnode)
+{
+	msim_fun_t *fun = msim_fun(fnode);
+	
+	assert(fun != NULL);
+	return &fun->hw_resources;
+}
+
+static bool msim_enable_interrupt(ddf_fun_t *fun)
+{
+	/* Nothing to do. */
+
+	return true;
+}
+
+static pio_window_t *msim_get_pio_window(ddf_fun_t *fnode)
+{
+	msim_fun_t *fun = msim_fun(fnode);
+
+	assert(fun != NULL);
+	return &fun->pio_window;
+}
+
+static hw_res_ops_t fun_hw_res_ops = {
+	.get_resource_list = &msim_get_resources,
+	.enable_interrupt = &msim_enable_interrupt,
+};
+
+static pio_window_ops_t fun_pio_window_ops = {
+	.get_pio_window = &msim_get_pio_window
+};
+
+/* Initialized in msim_init() function. */
+static ddf_dev_ops_t msim_fun_ops;
+
+static bool
+msim_add_fun(ddf_dev_t *dev, const char *name, const char *str_match_id,
+    msim_fun_t *fun_proto)
+{
+	ddf_msg(LVL_DEBUG, "Adding new function '%s'.", name);
+	
+	ddf_fun_t *fnode = NULL;
+	int rc;
+	
+	/* Create new device. */
+	fnode = ddf_fun_create(dev, fun_inner, name);
+	if (fnode == NULL)
+		goto failure;
+	
+	msim_fun_t *fun = ddf_fun_data_alloc(fnode, sizeof(msim_fun_t));
+	*fun = *fun_proto;
+	
+	/* Add match ID */
+	rc = ddf_fun_add_match_id(fnode, str_match_id, 100);
+	if (rc != EOK)
+		goto failure;
+	
+	/* Set provided operations to the device. */
+	ddf_fun_set_ops(fnode, &msim_fun_ops);
+	
+	/* Register function. */
+	if (ddf_fun_bind(fnode) != EOK) {
+		ddf_msg(LVL_ERROR, "Failed binding function %s.", name);
+		goto failure;
+	}
+	
+	return true;
+	
+failure:
+	if (fnode != NULL)
+		ddf_fun_destroy(fnode);
+	
+	ddf_msg(LVL_ERROR, "Failed adding function '%s'.", name);
+	
+	return false;
+}
+
+static bool msim_add_functions(ddf_dev_t *dev)
+{
+	return msim_add_fun(dev, "disk0", "msim/ddisk", &disk_data);
+}
+
+/** Get the root device.
+ *
+ * @param dev		The device which is root of the whole device tree (both
+ *			of HW and pseudo devices).
+ * @return		Zero on success, negative error number otherwise.
+ */
+static int msim_dev_add(ddf_dev_t *dev)
+{
+	ddf_msg(LVL_DEBUG, "msim_dev_add, device handle = %d",
+	    (int) ddf_dev_get_handle(dev));
+
+	/* Register functions. */
+	if (!msim_add_functions(dev))
+		ddf_msg(LVL_ERROR, "Failed to add functions for the MSIM platform.");
+	
+	return EOK;
+}
+
+static void msim_init(void)
+{
+	ddf_log_init(NAME);
+	msim_fun_ops.interfaces[HW_RES_DEV_IFACE] = &fun_hw_res_ops;
+	msim_fun_ops.interfaces[PIO_WINDOW_DEV_IFACE] = &fun_pio_window_ops;
+}
+
+int main(int argc, char *argv[])
+{
+	printf(NAME ": HelenOS MSIM platform driver\n");
+	msim_init();
+	return ddf_driver_main(&msim_driver);
+}
+
+/**
+ * @}
+ */
Index: uspace/drv/platform/msim/msim.ma
===================================================================
--- uspace/drv/platform/msim/msim.ma	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
+++ uspace/drv/platform/msim/msim.ma	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -0,0 +1,1 @@
+10 platform/msim
Index: uspace/drv/time/cmos-rtc/cmos-rtc.c
===================================================================
--- uspace/drv/time/cmos-rtc/cmos-rtc.c	(revision a931b7b2faca447e0708fd5fae0f85116e4c788d)
+++ uspace/drv/time/cmos-rtc/cmos-rtc.c	(revision cc575ef99163b3a869a65477f280acca17c5bdd5)
@@ -76,5 +76,5 @@
 	int clients_connected;
 	/** time at which the system booted */
-	time_t boottime;
+	struct timeval boot_time;
 } rtc_t;
 
@@ -97,5 +97,4 @@
 static int rtc_dev_remove(ddf_dev_t *dev);
 static void rtc_register_write(rtc_t *rtc, int reg, int data);
-static time_t uptime_get(void);
 static bool is_battery_ok(rtc_t *rtc);
 static int  rtc_fun_online(ddf_fun_t *fun);
@@ -205,5 +204,6 @@
 	ddf_msg(LVL_DEBUG, "rtc_dev_initialize %s", ddf_dev_get_name(rtc->dev));
 
-	rtc->boottime = 0;
+	rtc->boot_time.tv_sec = 0;
+	rtc->boot_time.tv_usec = 0;
 	rtc->clients_connected = 0;
 
@@ -326,14 +326,16 @@
 	fibril_mutex_lock(&rtc->mutex);
 
-	if (rtc->boottime != 0) {
+	if (rtc->boot_time.tv_sec) {
 		/* There is no need to read the current time from the
 		 * device because it has already been cached.
 		 */
 
-		time_t cur_time = rtc->boottime + uptime_get();
-
+		struct timeval curtime;
+		
+		getuptime(&curtime);
+		tv_add(&curtime, &rtc->boot_time);
 		fibril_mutex_unlock(&rtc->mutex);
 
-		return time_local2tm(cur_time, t);
+		return time_tv2tm(&curtime, t);
 	}
 
@@ -344,21 +346,25 @@
 	}
 
+	/* Microseconds are below RTC's resolution, assume 0. */
+	t->tm_usec = 0;
+
 	/* now read the registers */
 	do {
 		/* Suspend until the update process has finished */
-		while (rtc_update_in_progress(rtc));
-
-		t->tm_sec  = rtc_register_read(rtc, RTC_SEC);
-		t->tm_min  = rtc_register_read(rtc, RTC_MIN);
+		while (rtc_update_in_progress(rtc))
+			;
+
+		t->tm_sec = rtc_register_read(rtc, RTC_SEC);
+		t->tm_min = rtc_register_read(rtc, RTC_MIN);
 		t->tm_hour = rtc_register_read(rtc, RTC_HOUR);
 		t->tm_mday = rtc_register_read(rtc, RTC_DAY);
-		t->tm_mon  = rtc_register_read(rtc, RTC_MON);
+		t->tm_mon = rtc_register_read(rtc, RTC_MON);
 		t->tm_year = rtc_register_read(rtc, RTC_YEAR);
 
 		/* Now check if it is stable */
-	} while(t->tm_sec  != rtc_register_read(rtc, RTC_SEC) ||
-	    t->tm_min  != rtc_register_read(rtc, RTC_MIN) ||
+	} while(t->tm_sec != rtc_register_read(rtc, RTC_SEC) ||
+	    t->tm_min != rtc_register_read(rtc, RTC_MIN) ||
 	    t->tm_mday != rtc_register_read(rtc, RTC_DAY) ||
-	    t->tm_mon  != rtc_register_read(rtc, RTC_MON) ||
+	    t->tm_mon != rtc_register_read(rtc, RTC_MON) ||
 	    t->tm_year != rtc_register_read(rtc, RTC_YEAR));
 
@@ -366,5 +372,4 @@
 	bool _12h_mode = !(rtc_register_read(rtc, RTC_STATUS_B) &
 	    RTC_B_24H);
-
 	if (_12h_mode) {
 		/* The RTC is working in 12h mode, check if it is AM or PM */
@@ -378,11 +383,10 @@
 	/* Check if the RTC is working in BCD mode */
 	bcd_mode = !(rtc_register_read(rtc, RTC_STATUS_B) & RTC_B_BCD);
-
 	if (bcd_mode) {
-		t->tm_sec  = bcd2bin(t->tm_sec);
-		t->tm_min  = bcd2bin(t->tm_min);
+		t->tm_sec = bcd2bin(t->tm_sec);
+		t->tm_min = bcd2bin(t->tm_min);
 		t->tm_hour = bcd2bin(t->tm_hour);
 		t->tm_mday = bcd2bin(t->tm_mday);
-		t->tm_mon  = bcd2bin(t->tm_mon);
+		t->tm_mon = bcd2bin(t->tm_mon);
 		t->tm_year = bcd2bin(t->tm_year);
 	}
@@ -414,5 +418,10 @@
 		result = EINVAL;
 	else {
-		rtc->boottime = r - uptime_get();
+		struct timeval uptime;
+
+		getuptime(&uptime);
+		rtc->boot_time.tv_sec = r;
+		rtc->boot_time.tv_usec = t->tm_usec;	/* normalized */
+		tv_sub(&rtc->boot_time, &uptime);
 		result = EOK;
 	}
@@ -435,5 +444,6 @@
 	bool bcd_mode;
 	time_t norm_time;
-	time_t uptime;
+	struct timeval uptime;
+	struct timeval ntv;
 	int  reg_b;
 	int  reg_a;
@@ -445,6 +455,9 @@
 		return EINVAL;
 
-	uptime = uptime_get();
-	if (norm_time <= uptime) {
+	ntv.tv_sec = norm_time;
+	ntv.tv_usec = t->tm_usec;
+	getuptime(&uptime);
+
+	if (tv_gteq(&uptime, &ntv)) {
 		/* This is not acceptable */
 		return EINVAL;
@@ -458,6 +471,7 @@
 	}
 
-	/* boottime must be recomputed */
-	rtc->boottime = 0;
+	/* boot_time must be recomputed */
+	rtc->boot_time.tv_sec = 0;
+	rtc->boot_time.tv_usec = 0;
 
 	/* Detect the RTC epoch */
@@ -731,14 +745,4 @@
 }
 
-static time_t
-uptime_get(void)
-{
-	struct timeval tv;
-
-	getuptime(&tv);
-
-	return tv.tv_sec;
-}
-
 static int
 rtc_fun_online(ddf_fun_t *fun)
