Index: uspace/Makefile
===================================================================
--- uspace/Makefile	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/Makefile	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -50,4 +50,5 @@
 	app/loc \
 	app/logset \
+	app/mixerctl \
 	app/mkfat \
 	app/mkexfat \
@@ -76,8 +77,10 @@
 	app/mkbd \
 	app/date \
-	app/websrv \
 	app/vdemo \
 	app/vlaunch \
 	app/vterm \
+	app/wavplay \
+	app/websrv \
+	srv/audio/hound \
 	srv/clipboard \
 	srv/locsrv \
@@ -116,4 +119,5 @@
 	srv/hid/remcons \
 	srv/hw/char/s3c24xx_uart \
+	drv/audio/sb16 \
 	drv/infrastructure/root \
 	drv/infrastructure/rootvirt \
@@ -217,4 +221,5 @@
 	lib/graph \
 	lib/gui \
+	lib/hound \
 	lib/softrend \
 	lib/draw \
@@ -227,4 +232,5 @@
 	lib/usbhid \
 	lib/usbvirt \
+	lib/pcm \
 	lib/bithenge \
 	lib/posix
Index: uspace/Makefile.common
===================================================================
--- uspace/Makefile.common	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/Makefile.common	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -131,4 +131,6 @@
 
 LIBDRV_PREFIX = $(LIB_PREFIX)/drv
+LIBHOUND_PREFIX = $(LIB_PREFIX)/hound
+LIBPCM_PREFIX = $(LIB_PREFIX)/pcm
 LIBNET_PREFIX = $(LIB_PREFIX)/net
 LIBNIC_PREFIX = $(LIB_PREFIX)/nic
Index: uspace/app/mixerctl/Makefile
===================================================================
--- uspace/app/mixerctl/Makefile	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/app/mixerctl/Makefile	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,41 @@
+#
+# Copyright (c) 2011 Jan Vesely
+# 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 = ../..
+BINARY = mixerctl
+
+LIBS = \
+	$(LIBDRV_PREFIX)/libdrv.a
+
+EXTRA_CFLAGS = \
+	-I$(LIBDRV_PREFIX)/include
+
+SOURCES = \
+	mixerctl.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/app/mixerctl/mixerctl.c
===================================================================
--- uspace/app/mixerctl/mixerctl.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/app/mixerctl/mixerctl.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup mixerctl
+ * @{
+ */
+/** @file Mixer control for audio devices
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <str_error.h>
+#include <str.h>
+#include <devman.h>
+#include <audio_mixer_iface.h>
+#include <stdio.h>
+
+#define DEFAULT_DEVICE "/hw/pci0/00:01.0/sb16/control"
+
+/**
+ * Print volume levels on all channels on all control items.
+ * @param exch IPC exchange
+ */
+static void print_levels(async_exch_t *exch)
+{
+	const char* name = NULL;
+	unsigned count = 0;
+	int ret = audio_mixer_get_info(exch, &name, &count);
+	if (ret != EOK) {
+		printf("Failed to get mixer info: %s.\n", str_error(ret));
+		return;
+	}
+	printf("MIXER %s:\n\n", name);
+
+	for (unsigned i = 0; i < count; ++i) {
+		const char *name = NULL;
+		unsigned levels = 0, current = 0;
+		int ret =
+		    audio_mixer_get_item_info(exch, i, &name, &levels);
+		if (ret != EOK) {
+			printf("Failed to get item %u info: %s.\n",
+			    i, str_error(ret));
+			continue;
+		}
+		ret = audio_mixer_get_item_level(exch, i, &current);
+		if (ret != EOK) {
+			printf("Failed to get item %u info: %s.\n",
+			    i, str_error(ret));
+			continue;
+		}
+
+		printf("Control item %u `%s' : %u/%u.\n",
+		    i, name, current, levels - 1);
+		free(name);
+
+	}
+}
+
+static unsigned get_number(const char* str)
+{
+	uint16_t num;
+	str_uint16_t(str, NULL, 10, false, &num);
+	return num;
+}
+
+static void set_level(async_exch_t *exch, int argc, char *argv[])
+{
+	assert(exch);
+	if (argc != 4 && argc != 5) {
+		printf("%s [device] setlevel item value\n", argv[0]);
+		return;
+	}
+	unsigned params = argc == 5 ? 3 : 2;
+	const unsigned item = get_number(argv[params++]);
+	const unsigned value = get_number(argv[params]);
+	int ret = audio_mixer_set_item_level(exch, item, value);
+	if (ret != EOK) {
+		printf("Failed to set item level: %s.\n", str_error(ret));
+		return;
+	}
+	printf("Control item %u new level is %u.\n", item, value);
+}
+
+static void get_level(async_exch_t *exch, int argc, char *argv[])
+{
+	assert(exch);
+	if (argc != 3 && argc != 4) {
+		printf("%s [device] getlevel item \n", argv[0]);
+		return;
+	}
+	unsigned params = argc == 4 ? 3 : 2;
+	const unsigned item = get_number(argv[params++]);
+	unsigned value = 0;
+
+	int ret = audio_mixer_get_item_level(exch, item, &value);
+	if (ret != EOK) {
+		printf("Failed to get item level: %s.\n", str_error(ret));
+		return;
+	}
+	printf("Control item %u level: %u.\n", item, value);
+}
+
+int main(int argc, char *argv[])
+{
+	const char *device = DEFAULT_DEVICE;
+	void (*command)(async_exch_t *, int, char*[]) = NULL;
+
+	if (argc >= 2 && str_cmp(argv[1], "setlevel") == 0) {
+		command = set_level;
+		if (argc == 5)
+			device = argv[1];
+	}
+
+	if (argc >= 2 && str_cmp(argv[1], "getlevel") == 0) {
+		command = get_level;
+		if (argc == 4)
+			device = argv[1];
+	}
+
+	if ((argc == 2 && command == NULL))
+		device = argv[1];
+
+
+	devman_handle_t mixer_handle;
+	int ret = devman_fun_get_handle(device, &mixer_handle, 0);
+	if (ret != EOK) {
+		printf("Failed to get device(%s) handle: %s.\n",
+		    device, str_error(ret));
+		return 1;
+	}
+
+	async_sess_t *session = devman_device_connect(
+	    EXCHANGE_ATOMIC, mixer_handle, IPC_FLAG_BLOCKING);
+	if (!session) {
+		printf("Failed to connect to device.\n");
+		return 1;
+	}
+
+	async_exch_t *exch = async_exchange_begin(session);
+	if (!exch) {
+		printf("Failed to start session exchange.\n");
+		async_hangup(session);
+		return 1;
+	}
+
+	if (command) {
+		command(exch, argc, argv);
+	} else {
+		print_levels(exch);
+		printf("\n%s:\n", argv[0]);
+		printf("Use '%s getlevel idx' command to read individual "
+		    "settings\n", argv[0]);
+		printf("Use '%s setlevel idx' command to change "
+		    "settings\n", argv[0]);
+	}
+
+	async_exchange_end(exch);
+	async_hangup(session);
+	return 0;
+}
+
+/** @}
+ */
Index: uspace/app/wavplay/Makefile
===================================================================
--- uspace/app/wavplay/Makefile	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/app/wavplay/Makefile	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,49 @@
+#
+# Copyright (c) 2012 Jan Vesely
+# 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 = ../..
+BINARY = wavplay
+
+LIBS = \
+	$(LIBHOUND_PREFIX)/libhound.a \
+	$(LIBDRV_PREFIX)/libdrv.a \
+	$(LIBPCM_PREFIX)/libpcm.a
+
+EXTRA_CFLAGS = \
+	-I$(LIBDRV_PREFIX)/include \
+	-I$(LIBHOUND_PREFIX)/include \
+	-I$(LIBPCM_PREFIX)/include
+
+SOURCES = \
+	dplay.c \
+	drec.c \
+	main.c \
+	wave.c
+
+include $(USPACE_PREFIX)/Makefile.common
+
Index: uspace/app/wavplay/dplay.c
===================================================================
--- uspace/app/wavplay/dplay.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/app/wavplay/dplay.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,403 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup dplay
+ * @{
+ */
+/**
+ * @file PCM playback audio devices
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <str_error.h>
+#include <str.h>
+#include <audio_pcm_iface.h>
+#include <fibril_synch.h>
+#include <pcm/format.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <inttypes.h>
+
+#include <stdio.h>
+#include <macros.h>
+
+#include "wave.h"
+#include "dplay.h"
+
+#define DEFAULT_FRAGMENTS 2
+
+/** Playback helper structure */
+typedef struct {
+	struct {
+		void *base;
+		size_t size;
+		void* write_ptr;
+	} buffer;
+	pcm_format_t f;
+	FILE* source;
+	volatile bool playing;
+	fibril_mutex_t mutex;
+	fibril_condvar_t cv;
+	audio_pcm_sess_t *device;
+} playback_t;
+
+/**
+ * Initialize playback helper structure.
+ * @param pb Pointer to helper structure to initialize
+ * @param sess Pointer to audio device IPC session
+ * @return
+ */
+static void playback_initialize(playback_t *pb, audio_pcm_sess_t *sess)
+{
+	assert(sess);
+	assert(pb);
+	pb->buffer.base = NULL;
+	pb->buffer.size = 0;
+	pb->buffer.write_ptr = NULL;
+	pb->playing = false;
+	pb->source = NULL;
+	pb->device = sess;
+	fibril_mutex_initialize(&pb->mutex);
+	fibril_condvar_initialize(&pb->cv);
+}
+
+/**
+ * Fragment playback callback function.
+ * @param iid IPC call id.
+ * @param icall Pointer to the call structure
+ * @param arg Argument, pointer to the playback helper function
+ */
+static void device_event_callback(ipc_callid_t iid, ipc_call_t *icall, void* arg)
+{
+	async_answer_0(iid, EOK);
+	playback_t *pb = arg;
+	const size_t fragment_size = pb->buffer.size / DEFAULT_FRAGMENTS;
+	while (1) {
+		ipc_call_t call;
+		ipc_callid_t callid = async_get_call(&call);
+		switch(IPC_GET_IMETHOD(call)) {
+		case PCM_EVENT_PLAYBACK_STARTED:
+		case PCM_EVENT_FRAMES_PLAYED:
+			printf("%" PRIun " frames: ", IPC_GET_ARG1(call));
+			async_answer_0(callid, EOK);
+			break;
+		case PCM_EVENT_PLAYBACK_TERMINATED:
+			printf("Playback terminated\n");
+			fibril_mutex_lock(&pb->mutex);
+			pb->playing = false;
+			fibril_condvar_signal(&pb->cv);
+			async_answer_0(callid, EOK);
+			fibril_mutex_unlock(&pb->mutex);
+			return;
+		default:
+			printf("Unknown event %" PRIun ".\n", IPC_GET_IMETHOD(call));
+			async_answer_0(callid, ENOTSUP);
+			continue;
+
+		}
+		const size_t bytes = fread(pb->buffer.write_ptr,
+		   sizeof(uint8_t), fragment_size, pb->source);
+		printf("Copied from position %p size %zu/%zu\n",
+		    pb->buffer.write_ptr, bytes, fragment_size);
+		if (bytes == 0) {
+			audio_pcm_last_playback_fragment(pb->device);
+		}
+		/* any constant is silence */
+		memset(pb->buffer.write_ptr + bytes, 0, fragment_size - bytes);
+		pb->buffer.write_ptr += fragment_size;
+
+		if (pb->buffer.write_ptr >= (pb->buffer.base + pb->buffer.size))
+			pb->buffer.write_ptr -= pb->buffer.size;
+	}
+}
+
+/**
+ * Start event based playback.
+ * @param pb Playback helper structure.
+ */
+static void play_fragment(playback_t *pb)
+{
+	assert(pb);
+	assert(pb->device);
+	const size_t fragment_size = pb->buffer.size / DEFAULT_FRAGMENTS;
+	printf("Registering event callback\n");
+	int ret = audio_pcm_register_event_callback(pb->device,
+	    device_event_callback, pb);
+	if (ret != EOK) {
+		printf("Failed to register event callback.\n");
+		return;
+	}
+	printf("Playing: %dHz, %s, %d channel(s).\n", pb->f.sampling_rate,
+	    pcm_sample_format_str(pb->f.sample_format), pb->f.channels);
+	const size_t bytes = fread(pb->buffer.base, sizeof(uint8_t),
+	    fragment_size, pb->source);
+	if (bytes != fragment_size)
+		memset(pb->buffer.base + bytes, 0, fragment_size - bytes);
+	printf("Initial: Copied from position %p size %zu/%zu\n",
+	    pb->buffer.base, bytes, fragment_size);
+	pb->buffer.write_ptr = pb->buffer.base + fragment_size;
+	fibril_mutex_lock(&pb->mutex);
+	const unsigned frames =
+	    pcm_format_size_to_frames(fragment_size, &pb->f);
+	ret = audio_pcm_start_playback_fragment(pb->device, frames,
+	    pb->f.channels, pb->f.sampling_rate, pb->f.sample_format);
+	if (ret != EOK) {
+		fibril_mutex_unlock(&pb->mutex);
+		printf("Failed to start playback: %s.\n", str_error(ret));
+		audio_pcm_unregister_event_callback(pb->device);
+		return;
+	}
+
+	for (pb->playing = true; pb->playing;
+	    fibril_condvar_wait(&pb->cv, &pb->mutex));
+
+	fibril_mutex_unlock(&pb->mutex);
+	printf("\n");
+	audio_pcm_unregister_event_callback(pb->device);
+}
+
+/**
+ * Count occupied space in a cyclic buffer.
+ * @param pb Playback helper structure.
+ * @param pos read pointer position.
+ * @return Occupied space size.
+ */
+static size_t buffer_occupied(const playback_t *pb, size_t pos)
+{
+	assert(pb);
+	void *read_ptr = pb->buffer.base + pos;
+	if (read_ptr > pb->buffer.write_ptr)
+		return pb->buffer.write_ptr + pb->buffer.size - read_ptr;
+	return pb->buffer.write_ptr - read_ptr;
+
+}
+
+/**
+ * Count available space in a cyclic buffer.
+ * @param pb Playback helper structure.
+ * @param pos read pointer position.
+ * @return Free space size.
+ */
+static size_t buffer_avail(const playback_t *pb, size_t pos)
+{
+	assert(pb);
+	void *read_ptr = pb->buffer.base + pos;
+	if (read_ptr <= pb->buffer.write_ptr)
+		return read_ptr + pb->buffer.size - pb->buffer.write_ptr - 1;
+	return (read_ptr - pb->buffer.write_ptr) - 1;
+}
+
+/**
+ * Size of the space between write pointer and the end of a cyclic buffer
+ * @param pb Playback helper structure.
+ */
+static size_t buffer_remain(const playback_t *pb)
+{
+	assert(pb);
+	return (pb->buffer.base + pb->buffer.size) - pb->buffer.write_ptr;
+}
+
+/**
+ * Move write pointer forward. Wrap around the end.
+ * @param pb Playback helper structure.
+ * @param bytes NUmber of bytes to advance.
+ */
+static void buffer_advance(playback_t *pb, size_t bytes)
+{
+	assert(pb);
+	pb->buffer.write_ptr += bytes;
+	while (pb->buffer.write_ptr >= (pb->buffer.base + pb->buffer.size))
+		pb->buffer.write_ptr -= pb->buffer.size;
+}
+
+#define DPRINTF(f, ...) \
+	printf("%.2lu:%.6lu   "f, time.tv_sec % 100, time.tv_usec, __VA_ARGS__)
+
+/**
+ * Start playback using buffer position api.
+ * @param pb Playback helper function.
+ */
+static void play(playback_t *pb)
+{
+	assert(pb);
+	assert(pb->device);
+	pb->buffer.write_ptr = pb->buffer.base;
+	printf("Playing: %dHz, %s, %d channel(s).\n", pb->f.sampling_rate,
+	    pcm_sample_format_str(pb->f.sample_format), pb->f.channels);
+	useconds_t work_time = 50000; /* 50 ms */
+	bool started = false;
+	size_t pos = 0;
+	struct timeval time = { 0 };
+	getuptime(&time);
+	do {
+		size_t available = buffer_avail(pb, pos);
+		/* Writing might need wrap around the end,
+		 * read directly to device buffer */
+		size_t bytes = fread(pb->buffer.write_ptr, sizeof(uint8_t),
+		    min(available, buffer_remain(pb)), pb->source);
+		buffer_advance(pb, bytes);
+		DPRINTF("POS %zu: %zu bytes free in buffer, read %zu, wp %zu\n",
+		    pos, available, bytes,
+		    pb->buffer.write_ptr - pb->buffer.base);
+		available -= bytes;
+
+		/* continue if we wrapped around the end */
+		if (available) {
+			bytes = fread(pb->buffer.write_ptr,
+			    sizeof(uint8_t), min(available, buffer_remain(pb)),
+			    pb->source);
+			buffer_advance(pb, bytes);
+			DPRINTF("POS %zu: %zu bytes still free in buffer, "
+			    "read %zu, wp %zu\n", pos, available, bytes,
+			    pb->buffer.write_ptr - pb->buffer.base);
+			available -= bytes;
+		}
+
+		if (!started) {
+			int ret = audio_pcm_start_playback(pb->device,
+			    pb->f.channels, pb->f.sampling_rate,
+			    pb->f.sample_format);
+			if (ret != EOK) {
+				printf("Failed to start playback\n");
+				return;
+			}
+			started = true;
+			ret = audio_pcm_get_buffer_pos(pb->device, &pos);
+			if (ret != EOK) {
+				printf("Failed to update position indicator\n");
+			}
+		}
+		const size_t to_play = buffer_occupied(pb, pos);
+		const useconds_t usecs =
+		    pcm_format_size_to_usec(to_play, &pb->f);
+
+		/* Compute delay time */
+		const useconds_t real_delay = (usecs > work_time)
+		    ? usecs - work_time : 0;
+		DPRINTF("POS %zu: %u usecs (%u) to play %zu bytes.\n",
+		    pos, usecs, real_delay, to_play);
+		if (real_delay)
+			async_usleep(real_delay);
+		/* update buffer position */
+		const int ret = audio_pcm_get_buffer_pos(pb->device, &pos);
+		if (ret != EOK) {
+			printf("Failed to update position indicator\n");
+		}
+		getuptime(&time);
+
+		/* we did not use all the space we had,
+		 * that is the end */
+		if (available)
+			break;
+
+	} while (1);
+	audio_pcm_stop_playback_immediate(pb->device);
+}
+
+/**
+ * Play audio file usign direct device access.
+ * @param device The device.
+ * @param file The file.
+ * @return Error code.
+ */
+int dplay(const char *device, const char *file)
+{
+	int ret = EOK;
+	audio_pcm_sess_t *session = NULL;
+	if (str_cmp(device, "default") == 0) {
+		session = audio_pcm_open_default();
+	} else {
+		session = audio_pcm_open(device);
+	}
+	if (!session) {
+		printf("Failed to connect to device %s.\n", device);
+		return 1;
+	}
+	printf("Playing on device: %s.\n", device);
+	if (audio_pcm_query_cap(session, AUDIO_CAP_PLAYBACK) <= 0) {
+		printf("Device %s does not support playback\n", device);
+		ret = ENOTSUP;
+		goto close_session;
+	}
+
+	const char* info = NULL;
+	ret = audio_pcm_get_info_str(session, &info);
+	if (ret != EOK) {
+		printf("Failed to get PCM info.\n");
+		goto close_session;
+	}
+	printf("Playing on %s.\n", info);
+	free(info);
+
+	playback_t pb;
+	playback_initialize(&pb, session);
+
+	ret = audio_pcm_get_buffer(pb.device, &pb.buffer.base, &pb.buffer.size);
+	if (ret != EOK) {
+		printf("Failed to get PCM buffer: %s.\n", str_error(ret));
+		goto close_session;
+	}
+	printf("Buffer: %p %zu.\n", pb.buffer.base, pb.buffer.size);
+
+	pb.source = fopen(file, "rb");
+	if (pb.source == NULL) {
+		ret = ENOENT;
+		printf("Failed to open file: %s.\n", file);
+		goto cleanup;
+	}
+
+	wave_header_t header;
+	fread(&header, sizeof(header), 1, pb.source);
+	const char *error;
+	ret = wav_parse_header(&header, NULL, NULL,
+	    &pb.f.channels, &pb.f.sampling_rate, &pb.f.sample_format, &error);
+	if (ret != EOK) {
+		printf("Error parsing wav header: %s.\n", error);
+		goto cleanup;
+	}
+	if (audio_pcm_query_cap(pb.device, AUDIO_CAP_BUFFER_POS) > 0) {
+		play(&pb);
+	} else {
+		if (audio_pcm_query_cap(pb.device, AUDIO_CAP_INTERRUPT) > 0)
+			play_fragment(&pb);
+		else
+			printf("Neither playing method is supported");
+	}
+
+cleanup:
+	fclose(pb.source);
+	munmap(pb.buffer.base, pb.buffer.size);
+	audio_pcm_release_buffer(pb.device);
+close_session:
+	audio_pcm_close(session);
+	return ret == EOK ? 0 : 1;
+}
+/**
+ * @}
+ */
Index: uspace/app/wavplay/dplay.h
===================================================================
--- uspace/app/wavplay/dplay.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/app/wavplay/dplay.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+/** @addtogroup wavplay
+ * @{
+ */
+/** @file
+ * @brief .wav file format.
+ */
+#ifndef DPLAY_H
+#define DPLAY_H
+
+int dplay(const char *device, const char *file);
+
+#endif
+/**
+ * @}
+ */
+
Index: uspace/app/wavplay/drec.c
===================================================================
--- uspace/app/wavplay/drec.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/app/wavplay/drec.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2013 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup wavplay
+ * @{
+ */
+/**
+ * @file PCM playback audio devices
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <str_error.h>
+#include <audio_pcm_iface.h>
+#include <pcm/format.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <inttypes.h>
+
+#include "wave.h"
+#include "drec.h"
+
+
+#define BUFFER_PARTS   2
+
+/** Recording format */
+static const pcm_format_t format = {
+	.sampling_rate = 44100,
+	.channels = 2,
+	.sample_format = PCM_SAMPLE_SINT16_LE,
+};
+
+/** Recording helper structure */
+typedef struct {
+	struct {
+		void *base;
+		size_t size;
+		unsigned id;
+		void* position;
+	} buffer;
+	FILE* file;
+	audio_pcm_sess_t *device;
+} record_t;
+
+/**
+ * Initialize recording helper structure.
+ * @param rec Recording structure.
+ * @param sess Session to IPC device.
+ */
+static void record_initialize(record_t *rec, audio_pcm_sess_t *sess)
+{
+	assert(sess);
+	assert(rec);
+	rec->buffer.base = NULL;
+	rec->buffer.size = 0;
+	rec->buffer.position = NULL;
+	rec->file = NULL;
+	rec->device = sess;
+}
+
+/**
+ * Recording callback. Writes recorded data.
+ * @param iid IPC call id.
+ * @param icall Poitner to IPC call structure.
+ * @param arg Argument. Poitner to recording helper structure.
+ */
+static void device_event_callback(ipc_callid_t iid, ipc_call_t *icall, void* arg)
+{
+	async_answer_0(iid, EOK);
+	record_t *rec = arg;
+	const size_t buffer_part = rec->buffer.size / BUFFER_PARTS;
+	bool record = true;
+	while (record) {
+		ipc_call_t call;
+		ipc_callid_t callid = async_get_call(&call);
+		switch(IPC_GET_IMETHOD(call)) {
+		case PCM_EVENT_CAPTURE_TERMINATED:
+			printf("Recording terminated\n");
+			record = false;
+		case PCM_EVENT_FRAMES_CAPTURED:
+			printf("%" PRIun " frames\n", IPC_GET_ARG1(call));
+			async_answer_0(callid, EOK);
+			break;
+		default:
+			printf("Unknown event %" PRIun ".\n", IPC_GET_IMETHOD(call));
+			async_answer_0(callid, ENOTSUP);
+			continue;
+
+		}
+
+		/* Write directly from device buffer to file */
+		const size_t bytes = fwrite(rec->buffer.position,
+		   sizeof(uint8_t), buffer_part, rec->file);
+		printf("%zu ", bytes);
+		rec->buffer.position += buffer_part;
+
+		if (rec->buffer.position >= (rec->buffer.base + rec->buffer.size))
+			rec->buffer.position = rec->buffer.base;
+		async_answer_0(callid, EOK);
+	}
+}
+
+/**
+ * Start fragment based recording.
+ * @param rec Recording helper structure.
+ * @param f PCM format
+ */
+static void record_fragment(record_t *rec, pcm_format_t f)
+{
+	assert(rec);
+	assert(rec->device);
+	int ret = audio_pcm_register_event_callback(rec->device,
+	    device_event_callback, rec);
+	if (ret != EOK) {
+		printf("Failed to register for events: %s.\n", str_error(ret));
+		return;
+	}
+	rec->buffer.position = rec->buffer.base;
+	printf("Recording: %dHz, %s, %d channel(s).\n", f.sampling_rate,
+	    pcm_sample_format_str(f.sample_format), f.channels);
+	const unsigned frames =
+		pcm_format_size_to_frames(rec->buffer.size / BUFFER_PARTS, &f);
+	ret = audio_pcm_start_capture_fragment(rec->device,
+	    frames, f.channels, f.sampling_rate, f.sample_format);
+	if (ret != EOK) {
+		printf("Failed to start recording: %s.\n", str_error(ret));
+		return;
+	}
+
+	getchar();
+	printf("\n");
+	audio_pcm_stop_capture(rec->device);
+}
+
+/**
+ * Record directly from a device to a file.
+ * @param device The device.
+ * @param file The file.
+ * @return Error code.
+ */
+int drecord(const char *device, const char *file)
+{
+	int ret = EOK;
+	audio_pcm_sess_t *session = NULL;
+	if (str_cmp(device, "default") == 0) {
+		session = audio_pcm_open_default();
+	} else {
+		session = audio_pcm_open(device);
+	}
+	if (!session) {
+		printf("Failed to connect to device %s.\n", device);
+		return 1;
+	}
+	printf("Recording on device: %s.\n", device);
+	if (audio_pcm_query_cap(session, AUDIO_CAP_CAPTURE) <= 0) {
+		printf("Device %s does not support recording\n", device);
+		ret = ENOTSUP;
+		goto close_session;
+	}
+
+	const char* info = NULL;
+	ret = audio_pcm_get_info_str(session, &info);
+	if (ret != EOK) {
+		printf("Failed to get PCM info.\n");
+		goto close_session;
+	}
+	printf("Capturing on %s.\n", info);
+	free(info);
+
+	record_t rec;
+	record_initialize(&rec, session);
+
+	ret = audio_pcm_get_buffer(rec.device, &rec.buffer.base,
+	    &rec.buffer.size);
+	if (ret != EOK) {
+		printf("Failed to get PCM buffer: %s.\n", str_error(ret));
+		goto close_session;
+	}
+	printf("Buffer: %p %zu.\n", rec.buffer.base, rec.buffer.size);
+
+	rec.file = fopen(file, "w");
+	if (rec.file == NULL) {
+		ret = ENOENT;
+		printf("Failed to open file: %s.\n", file);
+		goto cleanup;
+	}
+
+	wave_header_t header;
+	fseek(rec.file, sizeof(header), SEEK_SET);
+	const char *error;
+	if (ret != EOK) {
+		printf("Error parsing wav header: %s.\n", error);
+		goto cleanup;
+	}
+	if (audio_pcm_query_cap(rec.device, AUDIO_CAP_INTERRUPT) > 0)
+		record_fragment(&rec, format);
+	else
+		printf("Recording method is not supported");
+	//TODO consider buffer position interface
+
+	wav_init_header(&header, format, ftell(rec.file) - sizeof(header));
+	fseek(rec.file, 0, SEEK_SET);
+	fwrite(&header, sizeof(header), 1, rec.file);
+
+cleanup:
+	fclose(rec.file);
+	munmap(rec.buffer.base, rec.buffer.size);
+	audio_pcm_release_buffer(rec.device);
+close_session:
+	audio_pcm_close(session);
+	return ret == EOK ? 0 : 1;
+}
+/**
+ * @}
+ */
Index: uspace/app/wavplay/drec.h
===================================================================
--- uspace/app/wavplay/drec.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/app/wavplay/drec.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2013 Jan Vesely
+ * 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.
+ */
+/** @addtogroup wavplay
+ * @{
+ */
+/** @file
+ * @brief .wav file format.
+ */
+#ifndef DREC_H
+#define DREC_H
+
+int drecord(const char *device, const char *file);
+
+#endif
+/**
+ * @}
+ */
+
Index: uspace/app/wavplay/main.c
===================================================================
--- uspace/app/wavplay/main.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/app/wavplay/main.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup wavplay
+ * @{
+ */
+/**
+ * @file PCM playback audio devices
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <fibril_synch.h>
+#include <malloc.h>
+#include <str_error.h>
+#include <stdio.h>
+#include <hound/client.h>
+#include <pcm/sample_format.h>
+#include <getopt.h>
+
+#include "dplay.h"
+#include "drec.h"
+#include "wave.h"
+
+#define READ_SIZE   (32 * 1024)
+#define STREAM_BUFFER_SIZE   (64 * 1024)
+
+/**
+ * Play audio file using a new stream on provided context.
+ * @param ctx Provided context.
+ * @param filename File to play.
+ * @return Error code.
+ */
+static int hplay_ctx(hound_context_t *ctx, const char *filename)
+{
+	printf("Hound context playback: %s\n", filename);
+	FILE *source = fopen(filename, "rb");
+	if (!source) {
+		printf("Failed to open file %s\n", filename);
+		return EINVAL;
+	}
+
+	/* Read and parse WAV header */
+	wave_header_t header;
+	size_t read = fread(&header, sizeof(header), 1, source);
+	if (read != 1) {
+		printf("Failed to read WAV header: %zu\n", read);
+		fclose(source);
+		return EIO;
+	}
+	pcm_format_t format;
+	const char *error;
+	int ret = wav_parse_header(&header, NULL, NULL, &format.channels,
+	    &format.sampling_rate, &format.sample_format, &error);
+	if (ret != EOK) {
+		printf("Error parsing wav header: %s.\n", error);
+		fclose(source);
+		return EINVAL;
+	}
+
+	/* Allocate buffer and create new context */
+	char * buffer = malloc(READ_SIZE);
+	if (!buffer) {
+		fclose(source);
+		return ENOMEM;
+	}
+	hound_stream_t *stream = hound_stream_create(ctx,
+	    HOUND_STREAM_DRAIN_ON_EXIT, format, STREAM_BUFFER_SIZE);
+
+	/* Read and play */
+	while ((read = fread(buffer, sizeof(char), READ_SIZE, source)) > 0) {
+		ret = hound_stream_write(stream, buffer, read);
+		if (ret != EOK) {
+			printf("Failed to write to hound stream: %s\n",
+			    str_error(ret));
+			break;
+		}
+	}
+
+	/* Cleanup */
+	free(buffer);
+	fclose(source);
+	return ret;
+}
+
+/**
+ * Play audio file via hound server.
+ * @param filename File to play.
+ * @return Error code
+ */
+static int hplay(const char *filename)
+{
+	printf("Hound playback: %s\n", filename);
+	FILE *source = fopen(filename, "rb");
+	if (!source) {
+		printf("Failed to open file %s\n", filename);
+		return EINVAL;
+	}
+
+	/* Read and parse WAV header */
+	wave_header_t header;
+	size_t read = fread(&header, sizeof(header), 1, source);
+	if (read != 1) {
+		printf("Failed to read WAV header: %zu\n", read);
+		fclose(source);
+		return EIO;
+	}
+	pcm_format_t format;
+	const char *error;
+	int ret = wav_parse_header(&header, NULL, NULL, &format.channels,
+	    &format.sampling_rate, &format.sample_format, &error);
+	if (ret != EOK) {
+		printf("Error parsing wav header: %s.\n", error);
+		fclose(source);
+		return EINVAL;
+	}
+
+	/* Connect new playback context */
+	hound_context_t *hound = hound_context_create_playback(filename,
+	    format, STREAM_BUFFER_SIZE);
+	if (!hound) {
+		printf("Failed to create HOUND context\n");
+		fclose(source);
+		return ENOMEM;
+	}
+
+	ret = hound_context_connect_target(hound, HOUND_DEFAULT_TARGET);
+	if (ret != EOK) {
+		printf("Failed to connect to default target: %s\n",
+		    str_error(ret));
+		hound_context_destroy(hound);
+		fclose(source);
+		return ret;
+	}
+
+	/* Read and play */
+	static char buffer[READ_SIZE];
+	while ((read = fread(buffer, sizeof(char), READ_SIZE, source)) > 0) {
+		ret = hound_write_main_stream(hound, buffer, read);
+		if (ret != EOK) {
+			printf("Failed to write to main context stream: %s\n",
+			    str_error(ret));
+			break;
+		}
+	}
+
+	/* Cleanup */
+	hound_context_destroy(hound);
+	fclose(source);
+	return ret;
+}
+
+/**
+ * Helper structure for playback in separate fibrils
+ */
+typedef struct {
+	hound_context_t *ctx;
+	atomic_t *count;
+	const char *file;
+} fib_play_t;
+
+/**
+ * Fibril playback wrapper.
+ * @param arg Argument, pointer to playback helper structure.
+ * @return Error code.
+ */
+static int play_wrapper(void *arg)
+{
+	assert(arg);
+	fib_play_t *p = arg;
+	const int ret = hplay_ctx(p->ctx, p->file);
+	atomic_dec(p->count);
+	free(arg);
+	return ret;
+}
+
+/**
+ * Array of supported commandline options
+ */
+static const struct option opts[] = {
+	{"device", required_argument, 0, 'd'},
+	{"parallel", no_argument, 0, 'p'},
+	{"record", no_argument, 0, 'r'},
+	{"help", no_argument, 0, 'h'},
+	{0, 0, 0, 0}
+};
+
+/**
+ * Print usage help.
+ * @param name Name of the program.
+ */
+static void print_help(const char* name)
+{
+	printf("Usage: %s [options] file [files...]\n", name);
+	printf("supported options:\n");
+	printf("\t -h, --help\t Print this help.\n");
+	printf("\t -r, --record\t Start recording instead of playback. "
+	    "(Not implemented)\n");
+	printf("\t -d, --device\t Use specified device instead of the sound "
+	    "service. Use location path or a special device `default'\n");
+	printf("\t -p, --parallel\t Play given files in parallel instead of "
+	    "sequentially (does not work with -d).\n");
+}
+
+int main(int argc, char *argv[])
+{
+	const char *device = "default";
+	int idx = 0;
+	bool direct = false, record = false, parallel = false;
+	optind = 0;
+	int ret = 0;
+
+	/* Parse command line options */
+	while (ret != -1) {
+		ret = getopt_long(argc, argv, "d:prh", opts, &idx);
+		switch (ret) {
+		case 'd':
+			direct = true;
+			device = optarg;
+			break;
+		case 'r':
+			record = true;
+			break;
+		case 'p':
+			parallel = true;
+			break;
+		case 'h':
+			print_help(*argv);
+			return 0;
+		};
+	}
+
+	if (parallel && direct) {
+		printf("Parallel playback is available only if using sound "
+		    "server (no -d)\n");
+		print_help(*argv);
+		return 1;
+	}
+
+	if (optind == argc) {
+		printf("Not enough arguments.\n");
+		print_help(*argv);
+		return 1;
+	}
+
+	/* Init parallel playback variables */
+	hound_context_t *hound_ctx = NULL;
+	atomic_t playcount;
+	atomic_set(&playcount, 0);
+
+	/* Init parallel playback context if necessary */
+	if (parallel) {
+		hound_ctx = hound_context_create_playback("wavplay",
+		    AUDIO_FORMAT_DEFAULT, STREAM_BUFFER_SIZE);
+		if (!hound_ctx) {
+			printf("Failed to create global hound context\n");
+			return 1;
+		}
+		const int ret = hound_context_connect_target(hound_ctx,
+		    HOUND_DEFAULT_TARGET);
+		if (ret != EOK) {
+			printf("Failed to connect hound context to default "
+			   "target.\n");
+			hound_context_destroy(hound_ctx);
+			return 1;
+		}
+	}
+
+	/* play or record all files */
+	for (int i = optind; i < argc; ++i) {
+		const char *file = argv[i];
+
+		printf("%s (%d/%d) %s\n", record ? "Recording" : "Playing",
+		    i - optind + 1, argc - optind, file);
+		if (record) {
+			if (direct) {
+				drecord(device, file);
+			} else {
+				printf("Indirect recording is not supported "
+				    "yet.\n");
+				break;
+			}
+		}
+
+		if (direct) {
+			dplay(device, file);
+		} else {
+			if (parallel) {
+				/* Start new fibril for parallel playback */
+				fib_play_t *data = malloc(sizeof(fib_play_t));
+				if (!data) {
+					printf("Playback of %s failed.\n",
+						file);
+					continue;
+				}
+				data->file = file;
+				data->count = &playcount;
+				data->ctx = hound_ctx;
+				fid_t fid = fibril_create(play_wrapper, data);
+				atomic_inc(&playcount);
+				fibril_add_ready(fid);
+			} else {
+				hplay(file);
+			}
+		}
+	}
+
+	/* Wait for all fibrils to finish */
+	while (atomic_get(&playcount) > 0)
+		async_usleep(1000000);
+
+	/* Destroy parallel playback context, if initialized */
+	if (hound_ctx)
+		hound_context_destroy(hound_ctx);
+	return 0;
+}
+/**
+ * @}
+ */
Index: uspace/app/wavplay/wave.c
===================================================================
--- uspace/app/wavplay/wave.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/app/wavplay/wave.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+/** @addtogroup dplay
+ * @{
+ */
+/**
+ * @file PCM playback audio devices
+ */
+
+#include <byteorder.h>
+#include <str.h>
+#include <errno.h>
+
+#include "wave.h"
+
+/**
+ * Parse wav header data.
+ * @param[in] hdata Header data to parse.
+ * @param[out] data Pointer to audio data.
+ * @param[out] data_size Size of the data after the header.
+ * @param[out] channels Number of channels in stored audio format.
+ * @param[out] sampling_rate Sampling rate of the store data.
+ * @param[out] format Sample format.
+ * @param[out] error String representatoin of error, if any.
+ * @return Error code.
+ *
+ * Does sanity checks and endian conversion.
+ */
+int wav_parse_header(const void *hdata, const void **data, size_t *data_size,
+    unsigned *channels, unsigned *sampling_rate, pcm_sample_format_t *format,
+    const char **error)
+{
+	if (!hdata) {
+		if (error)
+			*error = "no header";
+		return EINVAL;
+	}
+
+	const wave_header_t *header = hdata;
+	if (str_lcmp(header->chunk_id, CHUNK_ID, 4) != 0) {
+		if (error)
+			*error = "invalid chunk id";
+		return EINVAL;
+	}
+
+	if (str_lcmp(header->format, FORMAT_STR, 4) != 0) {
+		if (error)
+			*error = "invalid format string";
+		return EINVAL;
+	}
+
+	if (str_lcmp(header->subchunk1_id, SUBCHUNK1_ID, 4) != 0) {
+		if (error)
+			*error = "invalid subchunk1 id";
+		return EINVAL;
+	}
+
+	if (uint16_t_le2host(header->subchunk1_size) != PCM_SUBCHUNK1_SIZE) {
+		if (error)
+			*error = "invalid subchunk1 size";
+		return EINVAL;
+	}
+
+	if (uint16_t_le2host(header->audio_format) != FORMAT_LINEAR_PCM) {
+		if (error)
+			*error = "unknown format";
+		return ENOTSUP;
+	}
+
+	if (str_lcmp(header->subchunk2_id, SUBCHUNK2_ID, 4) != 0) {
+		if (error)
+			*error = "invalid subchunk2 id";
+		return EINVAL;
+	}
+
+
+	if (data)
+		*data = header->data;
+	if (data_size)
+		*data_size = uint32_t_le2host(header->subchunk2_size);
+
+	if (sampling_rate)
+		*sampling_rate = uint32_t_le2host(header->sampling_rate);
+	if (channels)
+		*channels = uint16_t_le2host(header->channels);
+	if (format) {
+		const unsigned size = uint32_t_le2host(header->sample_size);
+		switch (size) {
+		case 8: *format = PCM_SAMPLE_UINT8; break;
+		case 16: *format = PCM_SAMPLE_SINT16_LE; break;
+		case 24: *format = PCM_SAMPLE_SINT24_LE; break;
+		case 32: *format = PCM_SAMPLE_SINT32_LE; break;
+		default:
+			*error = "Unknown format";
+			return ENOTSUP;
+		}
+	}
+	if (error)
+		*error = "no error";
+
+	return EOK;
+}
+
+/**
+ * Initialize wave fromat ehader structure.
+ * @param header Structure to initialize.
+ * @param format Desired PCM format
+ * @param size Size of the stored data.
+ *
+ * Initializes format specific elements and covnerts endian
+ */
+void wav_init_header(wave_header_t *header, pcm_format_t format, size_t size)
+{
+	assert(header);
+#define COPY_STR(dst, src)   memcpy(dst, src, str_size(src))
+
+	COPY_STR(&header->chunk_id, CHUNK_ID);
+	COPY_STR(&header->format, FORMAT_STR);
+	COPY_STR(&header->subchunk1_id, SUBCHUNK1_ID);
+	header->subchunk1_size = host2uint16_t_le(PCM_SUBCHUNK1_SIZE);
+	header->audio_format = host2uint16_t_le(FORMAT_LINEAR_PCM);
+
+	COPY_STR(&header->subchunk2_id, SUBCHUNK2_ID);
+	header->subchunk2_size = host2uint32_t_le(size);
+	header->sampling_rate = host2uint32_t_le(format.sampling_rate);
+	header->channels = host2uint32_t_le(format.channels);
+	header->sample_size =
+	    host2uint32_t_le(pcm_sample_format_size(format.sample_format));
+}
+/**
+ * @}
+ */
Index: uspace/app/wavplay/wave.h
===================================================================
--- uspace/app/wavplay/wave.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/app/wavplay/wave.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+/** @addtogroup dplay
+ * @{
+ */
+/** @file
+ * @brief .wav file format.
+ */
+#ifndef WAVE_H
+#define WAVE_H
+
+#include <stdint.h>
+#include <pcm/format.h>
+#include <pcm/sample_format.h>
+
+/** Wave file header format.
+ *
+ * https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
+ * @note: 8-bit samples are stored as unsigned bytes,
+ * 16-bit samples are stored as signed integers.
+ * @note: The default byte ordering assumed for WAVE data files is
+ * little-endian. Files written using the big-endian byte ordering scheme have
+ * the identifier RIFX instead of RIFF.
+ */
+typedef struct wave_header {
+	/** Should be 'R', 'I', 'F', 'F'. */
+	char chunk_id[4];
+#define CHUNK_ID "RIFF"
+
+	/** Total size minus the first 8 bytes */
+	uint32_t chunk_size;
+	/** Should be 'W', 'A', 'V', 'E'. */
+	char format[4];
+#define FORMAT_STR "WAVE"
+
+	/** Should be 'f', 'm', 't', ' '. */
+	char subchunk1_id[4];
+#define SUBCHUNK1_ID "fmt "
+
+	/** Size of the ret of this subchunk. 16 for PCM file. */
+	uint32_t subchunk1_size;
+#define PCM_SUBCHUNK1_SIZE 16
+	/** Format. 1 for Linear PCM */
+	uint16_t audio_format;
+#define FORMAT_LINEAR_PCM 1
+	/** Number of channels. */
+	uint16_t channels;
+	/** Sampling rate. */
+	uint32_t sampling_rate;
+	/** Byte rate. */
+	uint32_t byte_rate;
+	/** Block align. Bytes in one block (samples for all channels). */
+	uint16_t block_align;
+	/** Bits per sample (one channel). */
+	uint16_t sample_size;
+
+	/** Should be 'd', 'a', 't', 'a'. */
+	char subchunk2_id[4];
+#define SUBCHUNK2_ID "data"
+	/** Audio data size. */
+	uint32_t subchunk2_size;
+	/** Audio data. */
+	uint8_t data[];
+
+} wave_header_t;
+
+int wav_parse_header(const void *, const void**, size_t *, unsigned *,
+    unsigned *, pcm_sample_format_t *, const char **);
+
+void wav_init_header(wave_header_t *, pcm_format_t , size_t);
+#endif
+/**
+ * @}
+ */
Index: uspace/drv/audio/sb16/Makefile
===================================================================
--- uspace/drv/audio/sb16/Makefile	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/drv/audio/sb16/Makefile	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,47 @@
+#
+# Copyright (c) 2011 Jan Vesely
+# 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 -I$(LIBPCM_PREFIX)/include
+
+BINARY = sb16
+
+SOURCES = \
+	dsp.c \
+	main.c \
+	mixer.c \
+	mixer_iface.c \
+	pcm_iface.c \
+	sb16.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/drv/audio/sb16/ddf_log.h
===================================================================
--- uspace/drv/audio/sb16/ddf_log.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/drv/audio/sb16/ddf_log.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup drvaudiosb16
+ * @{
+ */
+/** @file
+ * @brief DDF log helper macros
+ */
+#ifndef DRV_AUDIO_SB16_DDF_LOG_H
+#define DRV_AUDIO_SB16_DDF_LOG_H
+
+#include <ddf/log.h>
+
+#define ddf_log_fatal(msg...) ddf_msg(LVL_FATAL, msg)
+#define ddf_log_error(msg...) ddf_msg(LVL_ERROR, msg)
+#define ddf_log_warning(msg...) ddf_msg(LVL_WARN, msg)
+#define ddf_log_note(msg...) ddf_msg(LVL_NOTE, msg)
+#define ddf_log_debug(msg...) ddf_msg(LVL_DEBUG, msg)
+#define ddf_log_verbose(msg...) ddf_msg(LVL_DEBUG2, msg)
+
+#endif
+/**
+ * @}
+ */
Index: uspace/drv/audio/sb16/dsp.c
===================================================================
--- uspace/drv/audio/sb16/dsp.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/drv/audio/sb16/dsp.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,554 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+/** @addtogroup drvaudiosb16
+ * @{
+ */
+/** @file
+ * @brief DSP helper functions implementation
+ */
+
+#include <as.h>
+#include <stdbool.h>
+#include <ddi.h>
+#include <devman.h>
+#include <device/hw_res.h>
+#include <libarch/ddi.h>
+#include <libarch/barrier.h>
+#include <macros.h>
+#include <str_error.h>
+#include <audio_pcm_iface.h>
+
+#include "ddf_log.h"
+#include "dsp_commands.h"
+#include "dsp.h"
+
+/* Maximum allowed transfer size for ISA DMA transfers is 64kB */
+#define MAX_BUFFER_SIZE (64 * 1024)
+
+#ifndef DSP_RETRY_COUNT
+#define DSP_RETRY_COUNT 100
+#endif
+
+#define DSP_RESET_RESPONSE 0xaa
+
+/* These are only for SB16 (DSP4.00+) */
+#define DSP_RATE_UPPER_LIMIT 44100
+#define DSP_RATE_LOWER_LIMIT 5000
+
+#define AUTO_DMA_MODE
+
+static inline const char * dsp_state_to_str(dsp_state_t state)
+{
+	static const char* state_names[] = {
+		[DSP_PLAYBACK_ACTIVE_EVENTS] = "PLAYBACK w/ EVENTS",
+		[DSP_CAPTURE_ACTIVE_EVENTS] = "CAPTURE w/ EVENTS",
+		[DSP_PLAYBACK_NOEVENTS] = "PLAYBACK w/o EVENTS",
+		[DSP_CAPTURE_NOEVENTS] = "CAPTURE w/o EVENTS",
+		[DSP_PLAYBACK_TERMINATE] = "PLAYBACK TERMINATE",
+		[DSP_CAPTURE_TERMINATE] = "CAPTURE TERMINATE",
+		[DSP_READY] = "READY",
+		[DSP_NO_BUFFER] = "NO BUFFER",
+	};
+	if (state < ARRAY_SIZE(state_names))
+		return state_names[state];
+	return "UNKNOWN";
+}
+
+
+static inline void dsp_change_state(sb_dsp_t *dsp, dsp_state_t state)
+{
+	assert(dsp);
+	ddf_log_verbose("Changing state from %s to %s",
+	    dsp_state_to_str(dsp->state), dsp_state_to_str(state));
+	dsp->state = state;
+}
+
+static inline int dsp_read(sb_dsp_t *dsp, uint8_t *data)
+{
+	assert(data);
+	assert(dsp);
+	uint8_t status;
+	size_t attempts = DSP_RETRY_COUNT;
+	do {
+		status = pio_read_8(&dsp->regs->dsp_read_status);
+	} while (--attempts && ((status & DSP_READ_READY) == 0));
+
+	if ((status & DSP_READ_READY) == 0)
+		return EIO;
+
+	*data = pio_read_8(&dsp->regs->dsp_data_read);
+	return EOK;
+}
+
+static inline int dsp_write(sb_dsp_t *dsp, uint8_t data)
+{
+	assert(dsp);
+	uint8_t status;
+	size_t attempts = DSP_RETRY_COUNT;
+	do {
+		status = pio_read_8(&dsp->regs->dsp_write);
+	} while (--attempts && ((status & DSP_WRITE_BUSY) != 0));
+
+	if ((status & DSP_WRITE_BUSY))
+		return EIO;
+
+	pio_write_8(&dsp->regs->dsp_write, data);
+	return EOK;
+}
+
+static inline void dsp_reset(sb_dsp_t *dsp)
+{
+	assert(dsp);
+	/* Reset DSP, see Chapter 2 of Sound Blaster HW programming guide */
+	pio_write_8(&dsp->regs->dsp_reset, 1);
+	udelay(3); /* Keep reset for 3 us */
+	pio_write_8(&dsp->regs->dsp_reset, 0);
+	/* "DSP takes about 100 microseconds to initialize itself" */
+	udelay(100);
+}
+
+static inline void dsp_start_current_active(sb_dsp_t *dsp, uint8_t command)
+{
+	dsp_write(dsp, command);
+	dsp_write(dsp, dsp->active.mode);
+	dsp_write(dsp, (dsp->active.samples - 1) & 0xff);
+	dsp_write(dsp, (dsp->active.samples - 1) >> 8);
+}
+
+static inline void dsp_set_sampling_rate(sb_dsp_t *dsp, unsigned rate)
+{
+	dsp_write(dsp, SET_SAMPLING_RATE_OUTPUT);
+	dsp_write(dsp, rate >> 8);
+	dsp_write(dsp, rate & 0xff);
+	ddf_log_verbose("Sampling rate: %hhx:%hhx.", rate >> 8, rate & 0xff);
+}
+
+static inline void dsp_report_event(sb_dsp_t *dsp, pcm_event_t event)
+{
+	assert(dsp);
+	if (!dsp->event_exchange)
+		ddf_log_warning("No one listening for event %u", event);
+	async_msg_1(dsp->event_exchange, event, dsp->active.frame_count);
+}
+
+static inline int setup_dma(sb_dsp_t *dsp, uintptr_t pa, size_t size)
+{
+	async_sess_t *sess = devman_parent_device_connect(EXCHANGE_ATOMIC,
+	    ddf_dev_get_handle(dsp->sb_dev), IPC_FLAG_BLOCKING);
+
+	const int ret = hw_res_dma_channel_setup(sess,
+	    dsp->dma16_channel, pa, size,
+	    DMA_MODE_READ | DMA_MODE_AUTO | DMA_MODE_ON_DEMAND);
+
+	async_hangup(sess);
+	return ret;
+}
+
+static inline int setup_buffer(sb_dsp_t *dsp, size_t size)
+{
+	assert(dsp);
+	if (size > MAX_BUFFER_SIZE || size == 0 || (size % 2) == 1)
+		size = MAX_BUFFER_SIZE;
+	void *buffer = NULL, *pa = NULL;
+	int ret = dmamem_map_anonymous(size, AS_AREA_WRITE | AS_AREA_READ,
+	    0, &pa, &buffer);
+	if (ret != EOK) {
+		ddf_log_error("Failed to allocate DMA buffer.");
+		return ENOMEM;
+	}
+
+	ddf_log_verbose("Setup dma buffer at %p(%p) %zu.", buffer, pa, size);
+	assert((uintptr_t)pa < (1 << 25));
+
+	/* Setup 16 bit channel */
+	ret = setup_dma(dsp, (uintptr_t)pa, size);
+	if (ret == EOK) {
+		dsp->buffer.data = buffer;
+		dsp->buffer.size = size;
+	} else {
+		ddf_log_error("Failed to setup DMA16 channel: %s.",
+		    str_error(ret));
+		dmamem_unmap_anonymous(buffer);
+	}
+	return ret;
+}
+
+static inline size_t sample_count(pcm_sample_format_t format, size_t byte_count)
+{
+	return byte_count / pcm_sample_format_size(format);
+}
+
+int sb_dsp_init(sb_dsp_t *dsp, sb16_regs_t *regs, ddf_dev_t *dev,
+    int dma8, int dma16)
+{
+	assert(dsp);
+	dsp->regs = regs;
+	dsp->dma8_channel = dma8;
+	dsp->dma16_channel = dma16;
+	dsp->event_session = NULL;
+	dsp->event_exchange = NULL;
+	dsp->sb_dev = dev;
+	dsp->state = DSP_NO_BUFFER;
+	dsp_reset(dsp);
+	uint8_t response;
+	const int ret = dsp_read(dsp, &response);
+	if (ret != EOK) {
+		ddf_log_error("Failed to read DSP reset response value.");
+		return ret;
+	}
+	if (response != DSP_RESET_RESPONSE) {
+		ddf_log_error("Invalid DSP reset response: %x.", response);
+		return EIO;
+	}
+
+	/* Get DSP version number */
+	dsp_write(dsp, DSP_VERSION);
+	dsp_read(dsp, &dsp->version.major);
+	dsp_read(dsp, &dsp->version.minor);
+
+	return ret;
+}
+
+void sb_dsp_interrupt(sb_dsp_t *dsp)
+{
+	assert(dsp);
+
+	dsp->active.frame_count +=
+	    dsp->active.samples / ((dsp->active.mode & DSP_MODE_STEREO) ? 2 : 1);
+
+	switch (dsp->state)
+	{
+	case DSP_PLAYBACK_ACTIVE_EVENTS:
+		dsp_report_event(dsp, PCM_EVENT_FRAMES_PLAYED);
+	case DSP_PLAYBACK_NOEVENTS:
+#ifndef AUTO_DMA_MODE
+		dsp_start_current_active(dsp, SINGLE_DMA_16B_DA);
+#endif
+		break;
+	case DSP_CAPTURE_ACTIVE_EVENTS:
+		dsp_report_event(dsp, PCM_EVENT_FRAMES_CAPTURED);
+	case DSP_CAPTURE_NOEVENTS:
+#ifndef AUTO_DMA_MODE
+		dsp_start_current_active(dsp, SINGLE_DMA_16B_DA);
+#endif
+		break;
+	case DSP_CAPTURE_TERMINATE:
+		dsp_change_state(dsp, DSP_READY);
+		dsp_report_event(dsp, PCM_EVENT_CAPTURE_TERMINATED);
+		async_exchange_end(dsp->event_exchange);
+		dsp->event_exchange = NULL;
+		break;
+	case DSP_PLAYBACK_TERMINATE:
+		dsp_change_state(dsp, DSP_READY);
+		dsp_report_event(dsp, PCM_EVENT_PLAYBACK_TERMINATED);
+		async_exchange_end(dsp->event_exchange);
+		dsp->event_exchange = NULL;
+		break;
+	default:
+		ddf_log_warning("Interrupt while DSP not active (%s)",
+		    dsp_state_to_str(dsp->state));
+	}
+}
+
+unsigned sb_dsp_query_cap(sb_dsp_t *dsp, audio_cap_t cap)
+{
+	ddf_log_verbose("Querying cap %s", audio_pcm_cap_str(cap));
+	switch(cap) {
+	case AUDIO_CAP_CAPTURE:
+	case AUDIO_CAP_PLAYBACK:
+	case AUDIO_CAP_INTERRUPT:
+	case AUDIO_CAP_BUFFER_POS:
+		return 1;
+	case AUDIO_CAP_MAX_BUFFER:
+		return MAX_BUFFER_SIZE;
+	case AUDIO_CAP_INTERRUPT_MIN_FRAMES:
+		return 1;
+	case AUDIO_CAP_INTERRUPT_MAX_FRAMES:
+		return 16535;
+	default:
+		return ENOTSUP;
+	}
+}
+
+int sb_dsp_get_buffer_position(sb_dsp_t *dsp, size_t *pos)
+{
+	if (dsp->state == DSP_NO_BUFFER)
+		return ENOENT;
+
+	assert(dsp->buffer.data);
+	async_sess_t *sess = devman_parent_device_connect(EXCHANGE_ATOMIC,
+	    ddf_dev_get_handle(dsp->sb_dev), IPC_FLAG_BLOCKING);
+
+	// TODO: Assumes DMA 16
+	const int remain = hw_res_dma_channel_remain(sess, dsp->dma16_channel);
+	async_hangup(sess);
+	if (remain >= 0) {
+		*pos = dsp->buffer.size - remain;
+		return EOK;
+	}
+	return remain;
+}
+
+int sb_dsp_test_format(sb_dsp_t *dsp, unsigned *channels, unsigned *rate,
+  pcm_sample_format_t *format)
+{
+	int ret = EOK;
+	if (*channels == 0 || *channels > 2) {
+		*channels = 2;
+		ret = ELIMIT;
+	}
+	//TODO 8bit DMA supports 8bit formats
+	if (*format != PCM_SAMPLE_SINT16_LE && *format != PCM_SAMPLE_UINT16_LE) {
+		*format = pcm_sample_format_is_signed(*format) ?
+		    PCM_SAMPLE_SINT16_LE : PCM_SAMPLE_UINT16_LE;
+		ret = ELIMIT;
+	}
+	if (*rate > DSP_RATE_UPPER_LIMIT) {
+		*rate = DSP_RATE_UPPER_LIMIT;
+		ret = ELIMIT;
+	}
+	if (*rate < DSP_RATE_LOWER_LIMIT) {
+		*rate = DSP_RATE_LOWER_LIMIT;
+		ret = ELIMIT;
+	}
+	return ret;
+}
+
+int sb_dsp_set_event_session(sb_dsp_t *dsp, async_sess_t *session)
+{
+	assert(dsp);
+	if (dsp->event_session && session)
+		return EBUSY;
+	dsp->event_session = session;
+	ddf_log_debug("Set event session to %p.", session);
+	return EOK;
+}
+
+async_sess_t * sb_dsp_get_event_session(sb_dsp_t *dsp)
+{
+	assert(dsp);
+	ddf_log_debug("Get event session: %p.", dsp->event_session);
+	return dsp->event_session;
+}
+
+int sb_dsp_get_buffer(sb_dsp_t *dsp, void **buffer, size_t *size)
+{
+	assert(dsp);
+	assert(size);
+
+	/* buffer is already setup by for someone, refuse to work until
+	 * it's released */
+	if (dsp->state != DSP_NO_BUFFER)
+		return EBUSY;
+	assert(dsp->buffer.data == NULL);
+
+	const int ret = setup_buffer(dsp, *size);
+	if (ret == EOK) {
+		ddf_log_debug("Providing buffer: %p, %zu B.",
+		    dsp->buffer.data, dsp->buffer.size);
+
+		if (buffer)
+			*buffer = dsp->buffer.data;
+		if (size)
+			*size = dsp->buffer.size;
+		dsp_change_state(dsp, DSP_READY);
+	}
+	return ret;
+}
+
+int sb_dsp_release_buffer(sb_dsp_t *dsp)
+{
+	assert(dsp);
+	if (dsp->state != DSP_READY)
+		return EINVAL;
+	assert(dsp->buffer.data);
+	dmamem_unmap_anonymous(dsp->buffer.data);
+	dsp->buffer.data = NULL;
+	dsp->buffer.size = 0;
+	ddf_log_debug("DSP buffer released.");
+	dsp_change_state(dsp, DSP_NO_BUFFER);
+	return EOK;
+}
+
+int sb_dsp_start_playback(sb_dsp_t *dsp, unsigned frames,
+    unsigned channels, unsigned sampling_rate, pcm_sample_format_t format)
+{
+	assert(dsp);
+
+	if (!dsp->buffer.data || dsp->state != DSP_READY)
+		return EINVAL;
+
+	/* Check supported parameters */
+	ddf_log_debug("Requested playback: %u frames, %uHz, %s, %u channel(s).",
+	    frames, sampling_rate, pcm_sample_format_str(format), channels);
+	if (sb_dsp_test_format(dsp, &channels, &sampling_rate, &format) != EOK)
+		return ENOTSUP;
+
+	/* Client requested regular events */
+	if (frames && !dsp->event_session)
+		return EINVAL;
+
+	if (dsp->event_session) {
+		dsp->event_exchange = async_exchange_begin(dsp->event_session);
+		if (!dsp->event_exchange)
+			return ENOMEM;
+	}
+
+	dsp->active.mode = 0
+	    | (pcm_sample_format_is_signed(format) ? DSP_MODE_SIGNED : 0)
+	    | (channels == 2 ? DSP_MODE_STEREO : 0);
+	dsp->active.samples = frames * channels;
+	dsp->active.frame_count = 0;
+
+	dsp_set_sampling_rate(dsp, sampling_rate);
+
+#ifdef AUTO_DMA_MODE
+	dsp_start_current_active(dsp, AUTO_DMA_16B_DA_FIFO);
+#else
+	dsp_start_current_active(dsp, SINGLE_DMA_16B_DA);
+#endif
+
+	ddf_log_verbose("Playback started, event every %u samples",
+	    dsp->active.samples);
+
+	dsp_change_state(dsp,
+	    frames ? DSP_PLAYBACK_ACTIVE_EVENTS : DSP_PLAYBACK_NOEVENTS);
+	if (dsp->state == DSP_PLAYBACK_ACTIVE_EVENTS)
+		dsp_report_event(dsp, PCM_EVENT_PLAYBACK_STARTED);
+
+	return EOK;
+}
+
+int sb_dsp_stop_playback(sb_dsp_t *dsp, bool immediate)
+{
+	assert(dsp);
+	if ((dsp->state == DSP_PLAYBACK_NOEVENTS ||
+	    dsp->state == DSP_PLAYBACK_ACTIVE_EVENTS) &&
+	    immediate)
+	{
+		dsp_write(dsp, DMA_16B_PAUSE);
+		dsp_reset(dsp);
+		ddf_log_debug("Stopped playback");
+		dsp_change_state(dsp, DSP_READY);
+		if (dsp->event_exchange) {
+			dsp_report_event(dsp, PCM_EVENT_PLAYBACK_TERMINATED);
+			async_exchange_end(dsp->event_exchange);
+			dsp->event_exchange = NULL;
+		}
+		return EOK;
+	}
+	if (dsp->state == DSP_PLAYBACK_ACTIVE_EVENTS)
+	{
+		/* Stop after current fragment */
+		assert(!immediate);
+		dsp_write(dsp, DMA_16B_EXIT);
+		ddf_log_debug("Last playback fragment");
+		dsp_change_state(dsp, DSP_PLAYBACK_TERMINATE);
+		return EOK;
+	}
+	return EINVAL;
+}
+
+int sb_dsp_start_capture(sb_dsp_t *dsp, unsigned frames,
+    unsigned channels, unsigned sampling_rate, pcm_sample_format_t format)
+{
+	assert(dsp);
+	if (!dsp->buffer.data || dsp->state != DSP_READY)
+		return EINVAL;
+
+	/* Check supported parameters */
+	ddf_log_debug("Requested capture: %u frames, %uHz, %s, %u channel(s).",
+	    frames, sampling_rate, pcm_sample_format_str(format), channels);
+	if (sb_dsp_test_format(dsp, &channels, &sampling_rate, &format) != EOK)
+		return ENOTSUP;
+
+	/* Client requested regular events */
+	if (frames && !dsp->event_session)
+		return EINVAL;
+
+	if (dsp->event_session) {
+		dsp->event_exchange = async_exchange_begin(dsp->event_session);
+		if (!dsp->event_exchange)
+			return ENOMEM;
+	}
+
+	dsp->active.mode = 0
+	    | (pcm_sample_format_is_signed(format) ? DSP_MODE_SIGNED : 0)
+	    | (channels == 2 ? DSP_MODE_STEREO : 0);
+	dsp->active.samples = frames * channels;
+	dsp->active.frame_count = 0;
+
+	dsp_set_sampling_rate(dsp, sampling_rate);
+
+#ifdef AUTO_DMA_MODE
+	dsp_start_current_active(dsp, AUTO_DMA_16B_AD_FIFO);
+#else
+	dsp_start_current_active(dsp, SINGLE_DMA_16B_AD);
+#endif
+
+	ddf_log_verbose("Capture started started, event every %u samples",
+	    dsp->active.samples);
+	dsp_change_state(dsp,
+	    frames ? DSP_CAPTURE_ACTIVE_EVENTS : DSP_CAPTURE_NOEVENTS);
+	if (dsp->state == DSP_CAPTURE_ACTIVE_EVENTS)
+		dsp_report_event(dsp, PCM_EVENT_CAPTURE_STARTED);
+	return EOK;
+}
+
+int sb_dsp_stop_capture(sb_dsp_t *dsp, bool immediate)
+{
+	assert(dsp);
+	if ((dsp->state == DSP_CAPTURE_NOEVENTS ||
+	    dsp->state == DSP_CAPTURE_ACTIVE_EVENTS) &&
+	    immediate)
+	{
+		dsp_write(dsp, DMA_16B_PAUSE);
+		dsp_reset(dsp);
+		ddf_log_debug("Stopped capture fragment");
+		dsp_change_state(dsp, DSP_READY);
+		if (dsp->event_exchange) {
+			dsp_report_event(dsp, PCM_EVENT_CAPTURE_TERMINATED);
+			async_exchange_end(dsp->event_exchange);
+			dsp->event_exchange = NULL;
+		}
+		return EOK;
+	}
+	if (dsp->state == DSP_CAPTURE_ACTIVE_EVENTS)
+	{
+		/* Stop after current fragment */
+		assert(!immediate);
+		dsp_write(dsp, DMA_16B_EXIT);
+		ddf_log_debug("Last capture fragment");
+		dsp_change_state(dsp, DSP_CAPTURE_TERMINATE);
+		return EOK;
+	}
+	return EINVAL;
+}
+/**
+ * @}
+ */
Index: uspace/drv/audio/sb16/dsp.h
===================================================================
--- uspace/drv/audio/sb16/dsp.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/drv/audio/sb16/dsp.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+/** @addtogroup drvaudiosb16
+ * @{
+ */
+/** @file
+ * @brief Sound Blaster Digital Sound Processor (DSP) helper functions.
+ */
+#ifndef DRV_AUDIO_SB16_DSP_H
+#define DRV_AUDIO_SB16_DSP_H
+
+#include <ddf/driver.h>
+#include <libarch/ddi.h>
+#include <errno.h>
+#include <pcm/sample_format.h>
+#include <audio_pcm_iface.h>
+
+#include "registers.h"
+typedef enum {
+	DSP_PLAYBACK_ACTIVE_EVENTS,
+	DSP_CAPTURE_ACTIVE_EVENTS,
+	DSP_PLAYBACK_NOEVENTS,
+	DSP_CAPTURE_NOEVENTS,
+	DSP_PLAYBACK_TERMINATE,
+	DSP_CAPTURE_TERMINATE,
+	DSP_READY,
+	DSP_NO_BUFFER,
+} dsp_state_t;
+
+typedef struct sb_dsp {
+	sb16_regs_t *regs;
+	int dma8_channel;
+	int dma16_channel;
+	struct {
+		uint8_t major;
+		uint8_t minor;
+	} version;
+	struct {
+		uint8_t *data;
+		size_t size;
+	} buffer;
+	struct {
+		uint8_t mode;
+		uint16_t samples;
+		unsigned frame_count;
+	} active;
+	dsp_state_t state;
+	async_sess_t *event_session;
+	async_exch_t *event_exchange;
+	ddf_dev_t *sb_dev;
+} sb_dsp_t;
+
+int sb_dsp_init(sb_dsp_t *dsp, sb16_regs_t *regs, ddf_dev_t *dev,
+    int dma8, int dma16);
+void sb_dsp_interrupt(sb_dsp_t *dsp);
+unsigned sb_dsp_query_cap(sb_dsp_t *dsp, audio_cap_t cap);
+int sb_dsp_get_buffer_position(sb_dsp_t *dsp, size_t *size);
+int sb_dsp_test_format(sb_dsp_t *dsp, unsigned *channels, unsigned *rate,
+  pcm_sample_format_t *format);
+int sb_dsp_get_buffer(sb_dsp_t *dsp, void **buffer, size_t *size);
+int sb_dsp_set_event_session(sb_dsp_t *dsp, async_sess_t *session);
+async_sess_t * sb_dsp_get_event_session(sb_dsp_t *dsp);
+int sb_dsp_release_buffer(sb_dsp_t *dsp);
+int sb_dsp_start_playback(sb_dsp_t *dsp, unsigned frames,
+    unsigned channels, unsigned sample_rate, pcm_sample_format_t format);
+int sb_dsp_stop_playback(sb_dsp_t *dsp, bool immediate);
+int sb_dsp_start_capture(sb_dsp_t *dsp, unsigned frames,
+    unsigned channels, unsigned sample_rate, pcm_sample_format_t format);
+int sb_dsp_stop_capture(sb_dsp_t *dsp, bool immediate);
+
+#endif
+/**
+ * @}
+ */
Index: uspace/drv/audio/sb16/dsp_commands.h
===================================================================
--- uspace/drv/audio/sb16/dsp_commands.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/drv/audio/sb16/dsp_commands.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,184 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup drvaudiosb16
+ * @{
+ */
+/** @file
+ * @brief SB16 DSP Command constants
+ */
+#ifndef DRV_AUDIO_SB16_DSP_COMMANDS_H
+#define DRV_AUDIO_SB16_DSP_COMMANDS_H
+
+/** See Sound Blaster Series HW programming Guide Chapter 6. */
+typedef enum dsp_command {
+	DIRECT_8B_OUTPUT = 0x10, /* Followed by unsigned byte of digital data,
+	                          * software controls sampling rate */
+	DIRECT_8B_INPUT = 0x20,  /* Same as DIRECT_8B_OUTPUT but for input */
+
+	TRANSFER_TIME_CONSTANT = 0x40, /* Followed by time constant.
+	                                * TC = 65536 - (256 000 000 /
+					*   (channels * sampling rate))
+					* Send only high byte */
+
+	SINGLE_DMA_8B_OUTPUT = 0x14, /* Followed by length.high and length.low
+	                              * starts single-cycle DMA, length is -1 */
+	SINGLE_DMA_8B_INPUT = 0x24,  /* Same as SINGLE_DMA_8B_OUTPUT, but for
+	                              * input */
+	SINGLE_DMA_8B_ADPCM_2B_OUT = 0x16, /* Starts single-cycle DMA using
+	                                    * Creative ADPSM 8->2 bit compressed
+	                                    * data, Followed by length.low
+					    * and length.high. Length is -1 */
+	SINGLE_DMA_8B_ADPCM_2B_OUT_REF = 0x17, /* Starts single-cycle DMA using
+	                                        * DPSM 8->2 bit compressed data
+	                                        * with reference byte.
+						* Followed by length.low and
+						* length.high. Length is -1 */
+	SINGLE_DMA_8B_ADPCM_4B_OUT = 0x74, /* Same as
+	                                    * SINGLE_DMA_8B_ADPCM_2B_OUT */
+	SINGLE_DMA_8B_ADPCM_4B_OUT_REF = 0x75, /* Same as
+	                                        * SINGLE_DMA_8B_ADPCM_2B_OUT_REF
+						*/
+	SINGLE_DMA_8B_ADPCM_3B_OUT = 0x76, /* Same as
+	                                    * SINGLE_DMA_8B_ADPCM_2B_OUT */
+	SINGLE_DMA_8B_ADPCM_3B_OUT_REF = 0x77, /* Same as
+	                                        * SINGLE_DMA_8B_ADPCM_2B_OUT_REF
+						*/
+
+	DMA_8B_PAUSE = 0xd0, /* Stop sending DMA request,
+	                      * works for SINGLE and AUTO */
+	DMA_8B_CONTINUE = 0xd4, /* Resume transfers paused by DMA_8B_PAUSE */
+
+	SPEAKER_ON = 0xd1,  /* Connect speaker via internal amplifier,
+	                     * has no effect on 4.xx */
+	SPEAKER_OFF = 0xd3, /* Disconnect output from the amplifier,
+	                     * has no effect on 4.xx */
+
+	MIDI_POLLING = 0x30, /* Read DSP for MIDI data */
+	MIDI_INTERRUPT = 0x31, /* Start interrupt mode, interrupt will be
+	                        * generated when there is in-bound data.
+				* To exit send again */
+	MIDI_OUTPUT = 0x38, /* Followed by midi_data */
+
+	PAUSE = 0x80, /* Followed by duration.low, duration.high. Duration is -1
+	               * In the units of sampling period. Generates interrupt
+		       * at the end of period */
+	DSP_VERSION = 0xe1, /* Read 2 bytes, major and minor number */
+
+	AUTO_DMA_8B_OUTPUT = 0x1c, /* Starts auto-init DMA mode using 8-bit
+	                            * Interrupt after every block.
+				    * To terminate, switch to single or use
+				    * EXIT command*/
+	AUTO_DMA_8B_INPUT = 0x2c, /* Same as AUTO_DMA_8B_OUTPUT, but for input*/
+	AUTO_DMA_8B_ADPCM_2B_REF = 0x1f, /* Same as AUTO_DMA_8B_OUTPUT, but use
+	                                  * 8->2bit ADPCM audio format */
+	AUTO_DMA_8B_ADPCM_4B_REF = 0x7d, /* Same as AUTO_DMA_8B_ADPCM_2B_REF */
+	AUTO_DMA_8B_ADPCM_3B_REF = 0x7f, /* Same as AUTO_DMA_8B_ADPCM_2B_REF */
+
+	DMA_8B_EXIT = 0xda, /* Ends DMA transfer and terminates I/O process */
+
+	BLOCK_TRANSFER_SIZE = 0x48, /* Followed by size.low, size.high
+	                             * Used with HIGH_SPEED AUTO_DMA */
+
+	UART_MIDI_POLLING = 0x34, /* Start UART MIDI polling mode, read and
+	                           * write from/to DSP is interpreted as
+				   * read/write from/to MIDI.
+				   * To exit use reset signal. Note that reset
+				   * will restore previous state and won't do
+				   * complete reset */
+	UART_MIDI_INTERRUPT = 0x35, /* Same as UART_MIDI_POLLING, but use
+	                             * interrupts instead of polling. */
+	UART_MIDI_POLLING_TS = 0x36, /* Add time stamp to inbound data, the
+	                              * order is time.low time.mid time.high
+				      * data */
+	UART_MIDI_INTERRUPT_TS = 0x37, /* Same as UART_MIDI_POLLING_TS, but use
+	                                * interrupts instead of polling */
+
+	SPEAKER_STATUS = 0xd8, /* 0xff means amp is on, 0x00 means it's off */
+
+	AUTO_DMA_8B_HIGH_OUTPUT = 0x90, /* DSP will generate interrupt after
+	                                 * every block. No other commands are
+					 * accepted in this mode. To exit
+					 * the mode send RESET command.
+					 * Note that reset will restore
+					 * previous state. */
+	AUTO_DMA_8B_HIGH_INPUT = 0x98, /* Same as AUTO_DMA_8B_HIGH_OUTPUT */
+	SINGLE_DMA_8B_HIGH_OUTPUT = 0x91, /* Transfer one block and exit,
+	                                   * generates interrupt */
+	SINGLE_DMA_8B_HIGH_INPUT = 0x99, /* Same as SINGLE_DMA_8B_HIGH_OUTPUT */
+
+	SET_MONO_INPUT = 0xa0, /* Mono mode is the default, only on 3.xx */
+	SET_STEREO_INPUT = 0xa8, /* Switch to stereo recording, only on 3.xx */
+
+	SET_SAMPLING_RATE_OUTPUT = 0x41, /* Followed by sapling rate
+	                                  * 5000 to 45000 Hz, inclusive */
+	SET_SAMPLING_RATE_INPUT = 0x42, /* Same as SET_SAMPLING_RATE_OUTPUT */
+
+	SINGLE_DMA_16B_DA = 0xb0,     /* Followed by mode, size.low, size.high*/
+	SINGLE_DMA_16B_DA_FIFO = 0xb2,/* mode format is:                      */
+	AUTO_DMA_16B_DA = 0xb4,       /*    0x00 - unsigned mono              */
+	AUTO_DMA_16B_DA_FIFO = 0xb6,  /*    0x10 - signed mono                */
+	SINGLE_DMA_16B_AD = 0xb8,     /*    0x20 - unsigned stereo            */
+	SINGLE_DMA_16B_AD_FIFO = 0xba,/*    0x30 - signed stereo              */
+	AUTO_DMA_16B_AD = 0xbc,       /* Size is -1. Terminate by EXIT        */
+	AUTO_DMA_16B_AD_FIFO = 0xbe,  /* or switch to SINGLE_DMA              */
+
+	SINGLE_DMA_8B_DA = 0xc0,     /* Followed by mode, size.low, size.high */
+	SINGLE_DMA_8B_DA_FIFO = 0xc2,/* mode format is:                       */
+	AUTO_DMA_8B_DA = 0xc4,       /*    0x00 - unsigned mono               */
+	AUTO_DMA_8B_DA_FIFO = 0xc6,  /*    0x10 - signed mono                 */
+	SINGLE_DMA_8B_AD = 0xc8,     /*    0x20 - unsigned stereo             */
+	SINGLE_DMA_8B_AD_FIFO = 0xca,/*    0x30 - signed stereo               */
+	AUTO_DMA_8B_AD = 0xcc,       /* Size is -1. Terminate by EXIT         */
+	AUTO_DMA_8B_AD_FIFO = 0xce,  /* or switch to SINGLE_DMA               */
+
+	DMA_16B_PAUSE = 0xd5,/* Stop sending DMA request, both SINGLE and AUTO*/
+	DMA_16B_CONTINUE = 0xd6, /* Resume requests paused by DMA_16B_PAUSE */
+	DMA_16B_EXIT = 0xd9, /* Ends DMA transfer and terminates I/O process */
+} dsp_command_t;
+
+#define DSP_MODE_SIGNED 0x10
+#define DSP_MODE_STEREO 0x20
+
+static inline const char * mode_to_str(uint8_t mode)
+{
+	if (mode & 0xcf)
+		return "unknown";
+	static const char * names[] = {
+		"unsigned mono (8bit)",
+		"signed mono (16bit)",
+		"unsigned stereo (8bit)",
+		"signed stereo (16bit)",
+	};
+	return names[mode >> 4];
+}
+
+#endif
+/**
+ * @}
+ */
Index: uspace/drv/audio/sb16/main.c
===================================================================
--- uspace/drv/audio/sb16/main.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/drv/audio/sb16/main.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,265 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * Copyright (c) 2011 Vojtech Horky
+ * 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.
+ */
+/** @addtogroup drvaudiosb16
+ * @{
+ */
+/** @file
+ * Main routines of Creative Labs SoundBlaster 16 driver
+ */
+#include <ddf/driver.h>
+#include <ddf/interrupt.h>
+#include <ddf/log.h>
+#include <device/hw_res_parsed.h>
+#include <devman.h>
+#include <assert.h>
+#include <stdio.h>
+#include <errno.h>
+#include <str_error.h>
+
+#include "ddf_log.h"
+#include "sb16.h"
+
+#define NAME "sb16"
+
+static int sb_add_device(ddf_dev_t *device);
+static int sb_get_res(ddf_dev_t *device, uintptr_t *sb_regs,
+    size_t *sb_regs_size, uintptr_t *mpu_regs, size_t *mpu_regs_size,
+    int *irq, int *dma8, int *dma16);
+static int sb_enable_interrupts(ddf_dev_t *device);
+
+static driver_ops_t sb_driver_ops = {
+	.dev_add = sb_add_device,
+};
+
+static driver_t sb_driver = {
+	.name = NAME,
+	.driver_ops = &sb_driver_ops
+};
+
+/** Initialize global driver structures (NONE).
+ *
+ * @param[in] argc Nmber of arguments in argv vector (ignored).
+ * @param[in] argv Cmdline argument vector (ignored).
+ * @return Error code.
+ *
+ * Driver debug level is set here.
+ */
+int main(int argc, char *argv[])
+{
+	printf(NAME": HelenOS SB16 audio driver.\n");
+	ddf_log_init(NAME);
+	return ddf_driver_main(&sb_driver);
+}
+
+static void irq_handler(ddf_dev_t *dev, ipc_callid_t iid, ipc_call_t *call)
+{
+	sb16_t *sb16_dev = ddf_dev_data_get(dev);
+	sb16_interrupt(sb16_dev);
+}
+
+/** Initialize new SB16 driver instance.
+ *
+ * @param[in] device DDF instance of the device to initialize.
+ * @return Error code.
+ */
+static int sb_add_device(ddf_dev_t *device)
+{
+	bool handler_regd = false;
+	const size_t irq_cmd_count = sb16_irq_code_size();
+	irq_cmd_t irq_cmds[irq_cmd_count];
+	irq_pio_range_t irq_ranges[1];
+
+	sb16_t *soft_state = ddf_dev_data_alloc(device, sizeof(sb16_t));
+	int rc = soft_state ? EOK : ENOMEM;
+	if (rc != EOK) {
+		ddf_log_error("Failed to allocate sb16 structure.");
+		goto error;
+	}
+
+	uintptr_t sb_regs = 0, mpu_regs = 0;
+	size_t sb_regs_size = 0, mpu_regs_size = 0;
+	int irq = 0, dma8 = 0, dma16 = 0;
+
+	rc = sb_get_res(device, &sb_regs, &sb_regs_size, &mpu_regs,
+	    &mpu_regs_size, &irq, &dma8, &dma16);
+	if (rc != EOK) {
+		ddf_log_error("Failed to get resources: %s.", str_error(rc));
+		goto error;
+	}
+
+	sb16_irq_code((void*)sb_regs, dma8, dma16, irq_cmds, irq_ranges);
+
+	irq_code_t irq_code = {
+		.cmdcount = irq_cmd_count,
+		.cmds = irq_cmds,
+		.rangecount = 1,
+		.ranges = irq_ranges
+	};
+
+	rc = register_interrupt_handler(device, irq, irq_handler, &irq_code);
+	if (rc != EOK) {
+		ddf_log_error("Failed to register irq handler: %s.",
+		    str_error(rc));
+		goto error;
+	}
+
+	handler_regd = true;
+
+	rc = sb_enable_interrupts(device);
+	if (rc != EOK) {
+		ddf_log_error("Failed to enable interrupts: %s.",
+		    str_error(rc));
+		goto error;
+	}
+
+	rc = sb16_init_sb16(soft_state, (void*)sb_regs, sb_regs_size, device,
+	    dma8, dma16);
+	if (rc != EOK) {
+		ddf_log_error("Failed to init sb16 driver: %s.",
+		    str_error(rc));
+		goto error;
+	}
+
+	rc = sb16_init_mpu(soft_state, (void*)mpu_regs, mpu_regs_size);
+	if (rc == EOK) {
+		ddf_fun_t *mpu_fun =
+		    ddf_fun_create(device, fun_exposed, "midi");
+		if (mpu_fun) {
+			rc = ddf_fun_bind(mpu_fun);
+			if (rc != EOK)
+				ddf_log_error(
+				    "Failed to bind midi function: %s.",
+				    str_error(rc));
+		} else {
+			ddf_log_error("Failed to create midi function.");
+		}
+	} else {
+		ddf_log_warning("Failed to init mpu driver: %s.",
+		    str_error(rc));
+	}
+
+	/* MPU state does not matter */
+	return EOK;
+error:
+	if (handler_regd)
+		unregister_interrupt_handler(device, irq);
+	return rc;
+}
+
+static int sb_get_res(ddf_dev_t *device, uintptr_t *sb_regs,
+    size_t *sb_regs_size, uintptr_t *mpu_regs, size_t *mpu_regs_size,
+    int *irq, int *dma8, int *dma16)
+{
+	assert(device);
+
+	async_sess_t *parent_sess = devman_parent_device_connect(
+	    EXCHANGE_SERIALIZE, ddf_dev_get_handle(device), IPC_FLAG_BLOCKING);
+	if (!parent_sess)
+		return ENOMEM;
+
+	hw_res_list_parsed_t hw_res;
+	hw_res_list_parsed_init(&hw_res);
+	const int ret = hw_res_get_list_parsed(parent_sess, &hw_res, 0);
+	async_hangup(parent_sess);
+	if (ret != EOK) {
+		return ret;
+	}
+
+	/* 1x IRQ, 1-2x DMA(8,16), 1-2x IO (MPU is separate). */
+	if (hw_res.irqs.count != 1 ||
+	   (hw_res.io_ranges.count != 1 && hw_res.io_ranges.count != 2) ||
+	   (hw_res.dma_channels.count != 1 && hw_res.dma_channels.count != 2)) {
+		hw_res_list_parsed_clean(&hw_res);
+		return EINVAL;
+	}
+
+	if (irq)
+		*irq = hw_res.irqs.irqs[0];
+
+	if (dma8) {
+		if (hw_res.dma_channels.channels[0] < 4) {
+			*dma8 = hw_res.dma_channels.channels[0];
+		} else {
+			if (hw_res.dma_channels.count == 2 &&
+			    hw_res.dma_channels.channels[1] < 4) {
+				*dma8 = hw_res.dma_channels.channels[1];
+			}
+		}
+	}
+
+	if (dma16) {
+		if (hw_res.dma_channels.channels[0] > 4) {
+			*dma16 = hw_res.dma_channels.channels[0];
+		} else {
+			if (hw_res.dma_channels.count == 2 &&
+			    hw_res.dma_channels.channels[1] > 4) {
+				*dma16 = hw_res.dma_channels.channels[1];
+			}
+		}
+	}
+
+
+	if (hw_res.io_ranges.count == 1) {
+		if (sb_regs)
+			*sb_regs = hw_res.io_ranges.ranges[0].address;
+		if (sb_regs_size)
+			*sb_regs_size = hw_res.io_ranges.ranges[0].size;
+	} else {
+		const int sb =
+		    (hw_res.io_ranges.ranges[0].size >= sizeof(sb16_regs_t))
+		        ? 1 : 0;
+		const int mpu = 1 - sb;
+		if (sb_regs)
+			*sb_regs = hw_res.io_ranges.ranges[sb].address;
+		if (sb_regs_size)
+			*sb_regs_size = hw_res.io_ranges.ranges[sb].size;
+		if (mpu_regs)
+			*sb_regs = hw_res.io_ranges.ranges[mpu].address;
+		if (mpu_regs_size)
+			*sb_regs_size = hw_res.io_ranges.ranges[mpu].size;
+	}
+
+	return EOK;
+}
+
+int sb_enable_interrupts(ddf_dev_t *device)
+{
+	async_sess_t *parent_sess = devman_parent_device_connect(
+	    EXCHANGE_SERIALIZE, ddf_dev_get_handle(device), IPC_FLAG_BLOCKING);
+	if (!parent_sess)
+		return ENOMEM;
+
+	bool enabled = hw_res_enable_interrupt(parent_sess);
+	async_hangup(parent_sess);
+
+	return enabled ? EOK : EIO;
+}
+/**
+ * @}
+ */
Index: uspace/drv/audio/sb16/mixer.c
===================================================================
--- uspace/drv/audio/sb16/mixer.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/drv/audio/sb16/mixer.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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 <assert.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <libarch/ddi.h>
+#include <macros.h>
+#include <unistd.h>
+
+#include "ddf_log.h"
+#include "mixer.h"
+
+typedef struct channel {
+	const char *name;
+	uint8_t address;
+	unsigned shift;
+	unsigned volume_levels;
+	bool preserve_bits;
+} channel_t;
+
+/* CT1335 channels */
+static const channel_t channels_table_ct1335[] = {
+	{ "Master", 0x02, 1, 8, false }, /* Master, Mono, 3bit volume level */
+	{ "Midi", 0x06, 1, 8, false }, /* Midi, Mono, 3bit volume level */
+	{ "CD", 0x08, 1, 8, false }, /* CD, Mono, 3bit volume level */
+	{ "Voice", 0x0a, 1, 4, false }, /* Voice, Mono, 2bit volume level */
+};
+
+/* CT1345 channels */
+static const channel_t channels_table_ct1345[] = {
+	{ "Master Left", 0x22, 5, 8, true }, /* Master, Left, 3bit volume level */
+	{ "Master Right", 0x22, 1, 8, true }, /* Master, Right, 3bit volume level */
+	{ "MIDI Left", 0x26, 5, 8, true }, /* Midi, Left, 3bit volume level */
+	{ "MIDI Right", 0x26, 1, 8, true }, /* Midi, Right, 3bit volume level */
+	{ "CD Left", 0x28, 5, 8, true }, /* CD, Left, 3bit volume level */
+	{ "CD Right", 0x28, 1, 8, true }, /* CD, Right, 3bit volume level */
+	{ "Line In Left", 0x2e, 5, 8, true }, /* Line, Left, 3bit volume level */
+	{ "Line In Right", 0x2e, 1, 8, true }, /* Line, Right, 3bit volume level */
+	{ "Voice Left", 0x04, 5, 8, true }, /* Voice, Left, 3bit volume level */
+	{ "Voice Right", 0x04, 1, 8, true }, /* Voice, Right, 3bit volume level */
+	{ "Mic", 0x0a, 1, 4, false }, /* Mic, Mono, 2bit volume level */
+};
+
+/* CT1745 channels */
+static const channel_t channels_table_ct1745[] = {
+	{ "Master Left", 0x30, 3, 32, false },  /* Master, Left, 5bit volume level */
+	{ "Master Right", 0x31, 3, 32, false }, /* Master, Right, 5bit volume level */
+	{ "Voice Left", 0x32, 3, 32, false },  /* Voice, Left, 5bit volume level */
+	{ "Voice Right", 0x33, 3, 32, false }, /* Voice, Right, 5bit volume level */
+	{ "MIDI Left", 0x34, 3, 32, false }, /* MIDI, Left, 5bit volume level */
+	{ "MIDI Right", 0x35, 3, 32, false }, /* MIDI, Right, 5bit volume level */
+	{ "CD Left", 0x36, 3, 32, false }, /* CD, Left, 5bit volume level */
+	{ "CD Right", 0x37, 3, 32, false }, /* CD, Right, 5bit volume level */
+	{ "Line In Left", 0x38, 3, 32, false }, /* Line, Left, 5bit volume level */
+	{ "Line In Right", 0x39, 3, 32, false }, /* Line, Right, 5bit volume level */
+	{ "Mic", 0x3a, 3, 32, false }, /* Mic, Mono, 5bit volume level */
+	{ "PC Speaker", 0x3b, 6, 4, false }, /* PC speaker, Mono, 2bit level */
+	{ "Input Gain Left", 0x3f, 6, 4, false }, /* Input Gain, Left, 2bit level */
+	{ "Input Gain Right", 0x40, 6, 4, false }, /* Input Gain, Right, 2bit level */
+	{ "Output Gain Left", 0x41, 6, 4, false }, /* Output Gain, Left, 2bit level */
+	{ "Output Gain Right", 0x42, 6, 4, false }, /* Output Gain, Right, 2bit level */
+	{ "Treble Left", 0x44, 4, 16, false }, /* Treble, Left, 4bit volume level */
+	{ "Treble Right", 0x45, 4, 16, false }, /* Treble, Right, 4bit volume level */
+	{ "Bass Left", 0x46, 4, 16, false }, /* Bass, Left, 4bit volume level */
+	{ "Bass Right", 0x47, 4, 16, false }, /* Bass, Right, 4bit volume level */
+};
+
+static const struct {
+	const channel_t *table;
+	size_t count;
+} volume_table[] = {
+	[SB_MIXER_NONE] = { NULL, 0 },
+	[SB_MIXER_UNKNOWN] = { NULL, 0 },
+	[SB_MIXER_CT1335] = {
+	    channels_table_ct1335,
+	    ARRAY_SIZE(channels_table_ct1335),
+	},
+	[SB_MIXER_CT1345] = {
+	    channels_table_ct1345,
+	    ARRAY_SIZE(channels_table_ct1345),
+	},
+	[SB_MIXER_CT1745] = {
+	    channels_table_ct1745,
+	    ARRAY_SIZE(channels_table_ct1745),
+	},
+};
+
+const char * sb_mixer_type_str(sb_mixer_type_t type)
+{
+	static const char *names[] = {
+		[SB_MIXER_NONE] = "No mixer",
+		[SB_MIXER_CT1335] = "CT 1335",
+		[SB_MIXER_CT1345] = "CT 1345",
+		[SB_MIXER_CT1745] = "CT 1745",
+		[SB_MIXER_UNKNOWN] = "Unknown mixer",
+	};
+	return names[type];
+}
+
+int sb_mixer_init(sb_mixer_t *mixer, sb16_regs_t *regs, sb_mixer_type_t type)
+{
+	assert(mixer);
+	mixer->regs = regs;
+	mixer->type = type;
+	if (type == SB_MIXER_UNKNOWN)
+		return ENOTSUP;
+
+	if (type != SB_MIXER_NONE) {
+		pio_write_8(&regs->mixer_address, MIXER_RESET_ADDRESS);
+		pio_write_8(&regs->mixer_data, 1);
+	}
+	pio_write_8(&regs->mixer_address, MIXER_PNP_IRQ_ADDRESS);
+	const uint8_t irq = pio_read_8(&regs->mixer_data);
+	pio_write_8(&regs->mixer_address, MIXER_PNP_DMA_ADDRESS);
+	const uint8_t dma = pio_read_8(&regs->mixer_data);
+	ddf_log_debug("SB16 setup with IRQ 0x%hhx and DMA 0x%hhx.", irq, dma);
+	return EOK;
+}
+
+int sb_mixer_get_control_item_count(const sb_mixer_t *mixer)
+{
+	assert(mixer);
+	return volume_table[mixer->type].count;
+}
+
+int sb_mixer_get_control_item_info(const sb_mixer_t *mixer, unsigned item,
+    const char** name, unsigned *levels)
+{
+	assert(mixer);
+	if (item > volume_table[mixer->type].count)
+		return ENOENT;
+
+	const channel_t *ch = &volume_table[mixer->type].table[item];
+	if (name)
+		*name = ch->name;
+	if (levels)
+		*levels = ch->volume_levels;
+	return EOK;
+}
+
+/**
+ * Write new volume level from mixer registers.
+ * @param mixer SB Mixer to use.
+ * @param index Control item(channel) index.
+ * @param value New volume level.
+ * @return Error code.
+ */
+int sb_mixer_get_control_item_value(const sb_mixer_t *mixer, unsigned item,
+    unsigned *value)
+{
+	assert(mixer);
+	if (!value)
+		return EBADMEM;
+	if (item > volume_table[mixer->type].count)
+		return ENOENT;
+
+	const channel_t *chan = &volume_table[mixer->type].table[item];
+	pio_write_8(&mixer->regs->mixer_address, chan->address);
+	*value = (pio_read_8(&mixer->regs->mixer_data) >> chan->shift)
+	    & (chan->volume_levels - 1);
+	return EOK;
+}
+
+/**
+ * Write new volume level to mixer registers.
+ * @param mixer SB Mixer to use.
+ * @param index Control item(channel) index.
+ * @param value New volume level.
+ * @return Error code.
+ */
+int sb_mixer_set_control_item_value(const sb_mixer_t *mixer, unsigned item,
+    unsigned value)
+{
+	assert(mixer);
+	if (item > volume_table[mixer->type].count)
+		return ENOENT;
+
+	const channel_t *chan = &volume_table[mixer->type].table[item];
+	if (value >= chan->volume_levels)
+		value = chan->volume_levels - 1;
+
+	pio_write_8(&mixer->regs->mixer_address, chan->address);
+
+	uint8_t regv = 0;
+	if (chan->preserve_bits) {
+		regv = pio_read_8(&mixer->regs->mixer_data);
+		regv &= ~(uint8_t)((chan->volume_levels - 1) << chan->shift);
+	}
+
+	regv |= value << chan->shift;
+	pio_write_8(&mixer->regs->mixer_data, regv);
+	ddf_log_note("Item %s new value is: %u.",
+	    volume_table[mixer->type].table[item].name, value);
+	return EOK;
+}
Index: uspace/drv/audio/sb16/mixer.h
===================================================================
--- uspace/drv/audio/sb16/mixer.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/drv/audio/sb16/mixer.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+/** @addtogroup drvaudiosb16
+ * @{
+ */
+/** @file
+ * @brief SB16 main structure combining all functionality
+ */
+#ifndef DRV_AUDIO_SB16_MIXER_H
+#define DRV_AUDIO_SB16_MIXER_H
+
+#include "registers.h"
+
+typedef enum mixer_type {
+	SB_MIXER_NONE,
+	SB_MIXER_CT1335,
+	SB_MIXER_CT1345,
+	SB_MIXER_CT1745,
+	SB_MIXER_UNKNOWN,
+} sb_mixer_type_t;
+
+typedef struct sb_mixer {
+	sb16_regs_t *regs;
+	sb_mixer_type_t type;
+} sb_mixer_t;
+
+const char * sb_mixer_type_str(sb_mixer_type_t type);
+int sb_mixer_init(sb_mixer_t *mixer, sb16_regs_t *regs, sb_mixer_type_t type);
+int sb_mixer_get_control_item_count(const sb_mixer_t *mixer);
+int sb_mixer_get_control_item_info(const sb_mixer_t *mixer, unsigned index,
+    const char **name, unsigned *levels);
+int sb_mixer_get_control_item_value(const sb_mixer_t *mixer, unsigned index,
+    unsigned *value);
+int sb_mixer_set_control_item_value(const sb_mixer_t *mixer, unsigned index,
+    unsigned value);
+#endif
+/**
+ * @}
+ */
Index: uspace/drv/audio/sb16/mixer_iface.c
===================================================================
--- uspace/drv/audio/sb16/mixer_iface.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/drv/audio/sb16/mixer_iface.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+/** @addtogroup drvaudiosb16
+ * @{
+ */
+/** @file
+ * Main routines of Creative Labs SoundBlaster 16 driver
+ */
+
+#include <errno.h>
+#include <audio_mixer_iface.h>
+
+#include "mixer.h"
+
+static int sb_get_info(ddf_fun_t *fun, const char** name, unsigned *items)
+{
+	assert(fun);
+	const sb_mixer_t *mixer = ddf_fun_data_get(fun);
+	assert(mixer);
+	if (name)
+		*name = sb_mixer_type_str(mixer->type);
+	if (items)
+		*items = sb_mixer_get_control_item_count(mixer);
+
+	return EOK;
+}
+
+static int sb_get_item_info(ddf_fun_t *fun, unsigned item, const char** name,
+    unsigned *max_level)
+{
+	assert(fun);
+	const sb_mixer_t *mixer = ddf_fun_data_get(fun);
+	assert(mixer);
+	return
+	    sb_mixer_get_control_item_info(mixer, item, name, max_level);
+}
+
+static int sb_set_item_level(ddf_fun_t *fun, unsigned item, unsigned value)
+{
+	assert(fun);
+	const sb_mixer_t *mixer = ddf_fun_data_get(fun);
+	assert(mixer);
+	return sb_mixer_set_control_item_value(mixer, item, value);
+}
+
+static int sb_get_item_level(ddf_fun_t *fun, unsigned item, unsigned *value)
+{
+	assert(fun);
+	const sb_mixer_t *mixer = ddf_fun_data_get(fun);
+	assert(mixer);
+	return sb_mixer_get_control_item_value(mixer, item, value);
+}
+
+audio_mixer_iface_t sb_mixer_iface = {
+	.get_info = sb_get_info,
+	.get_item_info = sb_get_item_info,
+
+	.get_item_level = sb_get_item_level,
+	.set_item_level = sb_set_item_level,
+};
+/**
+ * @}
+ */
Index: uspace/drv/audio/sb16/pcm_iface.c
===================================================================
--- uspace/drv/audio/sb16/pcm_iface.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/drv/audio/sb16/pcm_iface.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+/** @addtogroup drvaudiosb16
+ * @{
+ */
+/** @file
+ * Main routines of Creative Labs SoundBlaster 16 driver
+ */
+
+#include <async.h>
+#include <errno.h>
+#include <audio_pcm_iface.h>
+#include <pcm/sample_format.h>
+
+#include "dsp.h"
+
+static inline sb_dsp_t * fun_to_dsp(ddf_fun_t *fun)
+{
+	assert(fun);
+	sb_dsp_t *dsp = ddf_fun_data_get(fun);
+	assert(dsp);
+	return dsp;
+}
+
+static int sb_get_info_str(ddf_fun_t *fun, const char** name)
+{
+	if (name)
+		*name = "SB 16 DSP";
+	return EOK;
+}
+
+static unsigned sb_query_cap(ddf_fun_t *fun, audio_cap_t cap)
+{
+	return sb_dsp_query_cap(fun_to_dsp(fun), cap);
+}
+
+static int sb_test_format(ddf_fun_t *fun, unsigned *channels, unsigned *rate,
+    pcm_sample_format_t *format)
+{
+	return sb_dsp_test_format(fun_to_dsp(fun), channels, rate, format);
+}
+static int sb_get_buffer(ddf_fun_t *fun, void **buffer, size_t *size)
+{
+	return sb_dsp_get_buffer(fun_to_dsp(fun), buffer, size);
+}
+
+static int sb_get_buffer_position(ddf_fun_t *fun, size_t *size)
+{
+	return sb_dsp_get_buffer_position(fun_to_dsp(fun), size);
+}
+
+static int sb_set_event_session(ddf_fun_t *fun, async_sess_t *sess)
+{
+	return sb_dsp_set_event_session(fun_to_dsp(fun), sess);
+}
+
+static async_sess_t * sb_get_event_session(ddf_fun_t *fun)
+{
+	return sb_dsp_get_event_session(fun_to_dsp(fun));
+}
+
+static int sb_release_buffer(ddf_fun_t *fun)
+{
+	return sb_dsp_release_buffer(fun_to_dsp(fun));
+}
+
+static int sb_start_playback(ddf_fun_t *fun, unsigned frames,
+    unsigned channels, unsigned sample_rate, pcm_sample_format_t format)
+{
+	return sb_dsp_start_playback(
+	    fun_to_dsp(fun), frames, channels, sample_rate, format);
+}
+
+static int sb_stop_playback(ddf_fun_t *fun, bool immediate)
+{
+	return sb_dsp_stop_playback(fun_to_dsp(fun), immediate);
+}
+
+static int sb_start_capture(ddf_fun_t *fun, unsigned frames,
+    unsigned channels, unsigned sample_rate, pcm_sample_format_t format)
+{
+	return sb_dsp_start_capture(
+	    fun_to_dsp(fun), frames, channels, sample_rate, format);
+}
+
+static int sb_stop_capture(ddf_fun_t *fun, bool immediate)
+{
+	return sb_dsp_stop_capture(fun_to_dsp(fun), immediate);
+}
+
+audio_pcm_iface_t sb_pcm_iface = {
+	.get_info_str = sb_get_info_str,
+	.test_format = sb_test_format,
+	.query_cap = sb_query_cap,
+
+	.get_buffer = sb_get_buffer,
+	.release_buffer = sb_release_buffer,
+	.set_event_session = sb_set_event_session,
+	.get_event_session = sb_get_event_session,
+	.get_buffer_pos = sb_get_buffer_position,
+
+	.start_playback = sb_start_playback,
+	.stop_playback = sb_stop_playback,
+
+	.start_capture = sb_start_capture,
+	.stop_capture = sb_stop_capture,
+};
+/**
+ * @}
+ */
Index: uspace/drv/audio/sb16/registers.h
===================================================================
--- uspace/drv/audio/sb16/registers.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/drv/audio/sb16/registers.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup drvaudiosb16
+ * @{
+ */
+/** @file
+ * @brief SB16 main structure combining all functionality
+ */
+#ifndef DRV_AUDIO_SB16_REGISTERS_H
+#define DRV_AUDIO_SB16_REGISTERS_H
+
+#include <ddi.h>
+
+typedef struct sb16_regs {
+	ioport8_t fm_address_status;
+	ioport8_t fm_data;
+	ioport8_t afm_address_status;
+	ioport8_t afm_data;
+	ioport8_t mixer_address;
+#define MIXER_RESET_ADDRESS 0x00
+#define MIXER_PNP_IRQ_ADDRESS 0x80
+#define MIXER_PNP_DMA_ADDRESS 0x81
+#define MIXER_IRQ_STATUS_ADDRESS 0x82 /* The Interrupt Status register,
+                                       * addressed as register 82h on the
+                                       * Mixer register map p.27 */
+	ioport8_t mixer_data;
+	ioport8_t dsp_reset;
+	ioport8_t __reserved1; /* 0x7 */
+	ioport8_t fm_address_status2;
+	ioport8_t fm_data2;
+	ioport8_t dsp_data_read;
+	ioport8_t __reserved2; /*0xb*/
+	ioport8_t dsp_write; /* Both command and data, bit 7 is write status */
+#define DSP_WRITE_BUSY (1 << 7)
+	ioport8_t __reserved3; /*0xd*/
+	ioport8_t dsp_read_status; /* Bit 7 */
+#define DSP_READ_READY (1 << 7)
+	ioport8_t dma16_ack; /*0xf*/
+	ioport8_t cd_command_data;
+	ioport8_t cd_status;
+	ioport8_t cd_reset;
+	ioport8_t cd_enable;
+} sb16_regs_t;
+
+typedef struct mpu_regs {
+	ioport8_t data;
+#define MPU_CMD_ACK (0xfe)
+
+	ioport8_t status_command;
+#define MPU_STATUS_OUTPUT_BUSY (1 << 6)
+#define MPU_STATUS_INPUT_BUSY (1 << 7)
+
+#define MPU_CMD_RESET (0xff)
+#define MPU_CMD_ENTER_UART (0x3f)
+} mpu_regs_t;
+
+#endif
+/**
+ * @}
+ */
+
Index: uspace/drv/audio/sb16/sb16.c
===================================================================
--- uspace/drv/audio/sb16/sb16.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/drv/audio/sb16/sb16.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+
+#define _DDF_DATA_IMPLANT
+
+#include <errno.h>
+#include <str_error.h>
+#include <macros.h>
+#include <audio_mixer_iface.h>
+#include <audio_pcm_iface.h>
+
+#include "ddf_log.h"
+#include "dsp_commands.h"
+#include "dsp.h"
+#include "sb16.h"
+
+extern audio_mixer_iface_t sb_mixer_iface;
+extern audio_pcm_iface_t sb_pcm_iface;
+
+static ddf_dev_ops_t sb_mixer_ops = {
+	.interfaces[AUDIO_MIXER_IFACE] = &sb_mixer_iface,
+};
+
+static ddf_dev_ops_t sb_pcm_ops = {
+	.interfaces[AUDIO_PCM_BUFFER_IFACE] = &sb_pcm_iface,
+};
+
+/* ISA interrupts should be edge-triggered so there should be no need for
+ * irq code magic, but we still need to ack those interrupts ASAP. */
+static const irq_cmd_t irq_cmds[] = {
+	{ .cmd = CMD_PIO_READ_8, .dstarg = 1 }, /* Address patched at runtime */
+	{ .cmd = CMD_PIO_READ_8, .dstarg = 1 }, /* Address patched at runtime */
+	{ .cmd = CMD_ACCEPT },
+};
+
+static inline sb_mixer_type_t sb_mixer_type_by_dsp_version(
+    unsigned major, unsigned minor)
+{
+	switch (major)
+	{
+	case 1: return SB_MIXER_NONE; /* SB 1.5 and early 2.0 = no mixer chip */
+	case 2: return (minor == 0) ? SB_MIXER_NONE : SB_MIXER_CT1335;
+	case 3: return SB_MIXER_CT1345; /* SB Pro */
+	case 4: return SB_MIXER_CT1745; /* SB 16  */
+	default: return SB_MIXER_UNKNOWN;
+	}
+}
+
+size_t sb16_irq_code_size(void)
+{
+	return ARRAY_SIZE(irq_cmds);
+}
+
+void sb16_irq_code(void *regs, int dma8, int dma16, irq_cmd_t cmds[], irq_pio_range_t ranges[])
+{
+	assert(regs);
+	assert(dma8 > 0 && dma8 < 4);
+	sb16_regs_t *registers = regs;
+	memcpy(cmds, irq_cmds, sizeof(irq_cmds));
+	cmds[0].addr = (void*)&registers->dsp_read_status;
+	ranges[0].base = (uintptr_t)registers;
+	ranges[0].size = sizeof(*registers);
+	if (dma16 > 4 && dma16 < 8) {
+		/* Valid dma16 */
+		cmds[1].addr = (void*)&registers->dma16_ack;
+	} else {
+		cmds[1].cmd = CMD_ACCEPT;
+	}
+}
+
+int sb16_init_sb16(sb16_t *sb, void *regs, size_t size,
+    ddf_dev_t *dev, int dma8, int dma16)
+{
+	assert(sb);
+	/* Setup registers */
+	int ret = pio_enable(regs, size, (void**)&sb->regs);
+	if (ret != EOK)
+		return ret;
+	ddf_log_debug("PIO registers at %p accessible.", sb->regs);
+
+	/* Initialize DSP */
+	ddf_fun_t *dsp_fun = ddf_fun_create(dev, fun_exposed, "pcm");
+	if (!dsp_fun) {
+		ddf_log_error("Failed to create dsp function.");
+		return ENOMEM;
+	}
+
+	ret = sb_dsp_init(&sb->dsp, sb->regs, dev, dma8, dma16);
+	if (ret != EOK) {
+		ddf_log_error("Failed to initialize SB DSP: %s.",
+		    str_error(ret));
+		ddf_fun_destroy(dsp_fun);
+		return ret;
+	}
+	//TODO remove data implant
+	ddf_fun_data_implant(dsp_fun, &sb->dsp);
+	ddf_fun_set_ops(dsp_fun, &sb_pcm_ops);
+	ddf_log_note("Sound blaster DSP (%x.%x) initialized.",
+	    sb->dsp.version.major, sb->dsp.version.minor);
+
+	ret = ddf_fun_bind(dsp_fun);
+	if (ret != EOK) {
+		ddf_log_error(
+		    "Failed to bind PCM function: %s.", str_error(ret));
+		// TODO implanted data
+		ddf_fun_destroy(dsp_fun);
+		return ret;
+	}
+
+	ret = ddf_fun_add_to_category(dsp_fun, "audio-pcm");
+	if (ret != EOK) {
+		ddf_log_error("Failed register PCM function in category: %s.",
+		    str_error(ret));
+		ddf_fun_unbind(dsp_fun);
+		// TODO implanted data
+		ddf_fun_destroy(dsp_fun);
+		return ret;
+	}
+
+	/* Initialize mixer */
+	const sb_mixer_type_t mixer_type = sb_mixer_type_by_dsp_version(
+	    sb->dsp.version.major, sb->dsp.version.minor);
+
+	ddf_fun_t *mixer_fun = ddf_fun_create(dev, fun_exposed, "control");
+	if (!mixer_fun) {
+		ddf_log_error("Failed to create mixer function.");
+		ddf_fun_unbind(dsp_fun);
+		// TODO implanted data
+		ddf_fun_destroy(dsp_fun);
+		return ENOMEM;
+	}
+	ret = sb_mixer_init(&sb->mixer, sb->regs, mixer_type);
+	if (ret != EOK) {
+		ddf_log_error("Failed to initialize SB mixer: %s.",
+		    str_error(ret));
+		ddf_fun_unbind(dsp_fun);
+		// TODO implanted data
+		ddf_fun_destroy(dsp_fun);
+		ddf_fun_destroy(mixer_fun);
+		return ret;
+	}
+
+	ddf_log_note("Initialized mixer: %s.",
+	    sb_mixer_type_str(sb->mixer.type));
+	ddf_fun_data_implant(mixer_fun, &sb->mixer);
+	ddf_fun_set_ops(mixer_fun, &sb_mixer_ops);
+
+	ret = ddf_fun_bind(mixer_fun);
+	if (ret != EOK) {
+		ddf_log_error(
+		    "Failed to bind mixer function: %s.", str_error(ret));
+		// TODO implanted data
+		ddf_fun_destroy(mixer_fun);
+
+		ddf_fun_unbind(dsp_fun);
+		// TODO implanted data
+		ddf_fun_destroy(dsp_fun);
+		return ret;
+	}
+
+	return EOK;
+}
+
+int sb16_init_mpu(sb16_t *sb, void *regs, size_t size)
+{
+	sb->mpu_regs = NULL;
+	return ENOTSUP;
+}
+
+void sb16_interrupt(sb16_t *sb)
+{
+	assert(sb);
+	/* The acknowledgment of interrupts on DSP version 4.xx is different;
+	 * It can contain MPU-401 indicator and DMA16 transfers are acked
+	 * differently */
+	if (sb->dsp.version.major >= 4) {
+		pio_write_8(&sb->regs->mixer_address, MIXER_IRQ_STATUS_ADDRESS);
+		const uint8_t irq_mask = pio_read_8(&sb->regs->mixer_data);
+		/* Third bit is MPU-401 interrupt */
+		if (irq_mask & 0x4) {
+			return;
+		}
+	} else {
+		ddf_log_debug("SB16 interrupt.");
+	}
+	sb_dsp_interrupt(&sb->dsp);
+}
Index: uspace/drv/audio/sb16/sb16.h
===================================================================
--- uspace/drv/audio/sb16/sb16.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/drv/audio/sb16/sb16.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup drvaudiosb16
+ * @{
+ */
+/** @file
+ * @brief SB16 main structure combining all functionality
+ */
+#ifndef DRV_AUDIO_SB16_SB16_H
+#define DRV_AUDIO_SB16_SB16_H
+
+#include <ddf/driver.h>
+#include <ddi.h>
+
+#include "dsp.h"
+#include "mixer.h"
+#include "registers.h"
+
+typedef struct sb16 {
+	sb16_regs_t *regs;
+	mpu_regs_t *mpu_regs;
+	sb_dsp_t dsp;
+	sb_mixer_t mixer;
+} sb16_t;
+
+size_t sb16_irq_code_size(void);
+void sb16_irq_code(void *regs, int dma8, int dma16, irq_cmd_t cmds[], irq_pio_range_t ranges[]);
+int sb16_init_sb16(sb16_t *sb, void *regs, size_t size,
+    ddf_dev_t *dev, int dma8, int dma16);
+int sb16_init_mpu(sb16_t *sb, void *regs, size_t size);
+void sb16_interrupt(sb16_t *sb);
+
+#endif
+/**
+ * @}
+ */
Index: uspace/drv/audio/sb16/sb16.ma
===================================================================
--- uspace/drv/audio/sb16/sb16.ma	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/drv/audio/sb16/sb16.ma	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,1 @@
+100 isa/sb16
Index: uspace/drv/bus/isa/i8237.c
===================================================================
--- uspace/drv/bus/isa/i8237.c	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/drv/bus/isa/i8237.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -38,4 +38,6 @@
 #include <stdbool.h>
 #include <errno.h>
+#include <ddi.h>
+#include <ddf/log.h>
 #include <fibril_synch.h>
 #include <ddi.h>
@@ -198,69 +200,69 @@
 	.channels = {
 		/* The first chip 8-bit */
-		{
-			(uint8_t *) 0x00,
-			(uint8_t *) 0x01,
-			(uint8_t *) 0x87,
-			(uint8_t *) 0x0a,
-			(uint8_t *) 0x0b,
-			(uint8_t *) 0x0c,
-		},
-		{
-			(uint8_t *) 0x02,
-			(uint8_t *) 0x03,
-			(uint8_t *) 0x83,
-			(uint8_t *) 0x0a,
-			(uint8_t *) 0x0b,
-			(uint8_t *) 0x0c,
-		},
-		{
-			(uint8_t *) 0x04,
-			(uint8_t *) 0x05,
-			(uint8_t *) 0x81,
-			(uint8_t *) 0x0a,
-			(uint8_t *) 0x0b,
-			(uint8_t *) 0x0c,
-		},
-		{
-			(uint8_t *) 0x06,
-			(uint8_t *) 0x07,
-			(uint8_t *) 0x82,
-			(uint8_t *) 0x0a,
-			(uint8_t *) 0x0b,
-			(uint8_t *) 0x0c,
+		{ /* Channel 0 - Unusable*/
+			.offset_reg_address = (uint8_t *) 0x00,
+			.size_reg_address = (uint8_t *) 0x01,
+			.page_reg_address = (uint8_t *) 0x87,
+			.single_mask_address = (uint8_t *) 0x0a,
+			.mode_address = (uint8_t *) 0x0b,
+			.flip_flop_address = (uint8_t *) 0x0c,
+		},
+		{ /* Channel 1 */
+			.offset_reg_address = (uint8_t *) 0x02,
+			.size_reg_address = (uint8_t *) 0x03,
+			.page_reg_address = (uint8_t *) 0x83,
+			.single_mask_address = (uint8_t *) 0x0a,
+			.mode_address = (uint8_t *) 0x0b,
+			.flip_flop_address = (uint8_t *) 0x0c,
+		},
+		{ /* Channel 2 */
+			.offset_reg_address = (uint8_t *) 0x04,
+			.size_reg_address = (uint8_t *) 0x05,
+			.page_reg_address = (uint8_t *) 0x81,
+			.single_mask_address = (uint8_t *) 0x0a,
+			.mode_address = (uint8_t *) 0x0b,
+			.flip_flop_address = (uint8_t *) 0x0c,
+		},
+		{ /* Channel 3 */
+			.offset_reg_address = (uint8_t *) 0x06,
+			.size_reg_address = (uint8_t *) 0x07,
+			.page_reg_address = (uint8_t *) 0x82,
+			.single_mask_address = (uint8_t *) 0x0a,
+			.mode_address = (uint8_t *) 0x0b,
+			.flip_flop_address = (uint8_t *) 0x0c,
 		},
 		
 		/* The second chip 16-bit */
-		{
-			(uint8_t *) 0xc0,
-			(uint8_t *) 0xc2,
-			(uint8_t *) 0x8f,
-			(uint8_t *) 0xd4,
-			(uint8_t *) 0xd6,
-			(uint8_t *) 0xd8,
-		},
-		{
-			(uint8_t *) 0xc4,
-			(uint8_t *) 0xc6,
-			(uint8_t *) 0x8b,
-			(uint8_t *) 0xd4,
-			(uint8_t *) 0xd6,
-			(uint8_t *) 0xd8,
-		},
-		{
-			(uint8_t *) 0xc8,
-			(uint8_t *) 0xca,
-			(uint8_t *) 0x89,
-			(uint8_t *) 0xd4,
-			(uint8_t *) 0xd6,
-			(uint8_t *) 0xd8,
-		},
-		{
-			(uint8_t *) 0xcc,
-			(uint8_t *) 0xce,
-			(uint8_t *) 0x8a,
-			(uint8_t *) 0xd4,
-			(uint8_t *) 0xd6,
-			(uint8_t *) 0xd8,
+		{ /* Channel 4 - Unusable */
+			.offset_reg_address = (uint8_t *) 0xc0,
+			.size_reg_address = (uint8_t *) 0xc2,
+			.page_reg_address = (uint8_t *) 0x8f,
+			.single_mask_address = (uint8_t *) 0xd4,
+			.mode_address = (uint8_t *) 0xd6,
+			.flip_flop_address = (uint8_t *) 0xd8,
+		},
+		{ /* Channel 5 */
+			.offset_reg_address = (uint8_t *) 0xc4,
+			.size_reg_address = (uint8_t *) 0xc6,
+			.page_reg_address = (uint8_t *) 0x8b,
+			.single_mask_address = (uint8_t *) 0xd4,
+			.mode_address = (uint8_t *) 0xd6,
+			.flip_flop_address = (uint8_t *) 0xd8,
+		},
+		{ /* Channel 6 */
+			.offset_reg_address = (uint8_t *) 0xc8,
+			.size_reg_address = (uint8_t *) 0xca,
+			.page_reg_address = (uint8_t *) 0x89,
+			.single_mask_address = (uint8_t *) 0xd4,
+			.mode_address = (uint8_t *) 0xd6,
+			.flip_flop_address = (uint8_t *) 0xd8,
+		},
+		{ /* Channel 7 */
+			.offset_reg_address = (uint8_t *) 0xcc,
+			.size_reg_address = (uint8_t *) 0xce,
+			.page_reg_address = (uint8_t *) 0x8a,
+			.single_mask_address = (uint8_t *) 0xd4,
+			.mode_address = (uint8_t *) 0xd6,
+			.flip_flop_address = (uint8_t *) 0xd8,
 		},
 	},
@@ -272,10 +274,9 @@
 };
 
-/* Initialize I/O access to DMA controller I/O ports.
+/** Initialize I/O access to DMA controller I/O ports.
  *
  * @param controller DMA Controller structure to initialize.
  *
  * @return Error code.
- *
  */
 static inline int dma_controller_init(dma_controller_t *controller)
@@ -304,4 +305,22 @@
 	
 	return EOK;
+}
+
+/** Helper function. Channels 4,5,6, and 7 are 8 bit DMA.
+ * @pram channel DMA channel.
+ * @reutrn True, if channel is 4,5,6, or 7, false otherwise.
+ */
+static inline bool is_dma16(unsigned channel)
+{
+	return (channel >= 4) && (channel < 8);
+}
+
+/** Helper function. Channels 0,1,2, and 3 are 8 bit DMA.
+ * @pram channel DMA channel.
+ * @reutrn True, if channel is 0,1,2, or 3, false otherwise.
+ */
+static inline bool is_dma8(unsigned channel)
+{
+	return (channel < 4);
 }
 
@@ -320,14 +339,13 @@
  *
  * @return Error code.
- *
- */
-int dma_setup_channel(unsigned int channel, uint32_t pa, uint16_t size,
+ */
+int dma_channel_setup(unsigned int channel, uint32_t pa, uint32_t size,
     uint8_t mode)
 {
+	if (!is_dma8(channel) && !is_dma16(channel))
+		return ENOENT;
+
 	if ((channel == 0) || (channel == 4))
 		return ENOTSUP;
-	
-	if (channel > 7)
-		return ENOENT;
 	
 	/* DMA is limited to 24bit addresses. */
@@ -336,5 +354,9 @@
 	
 	/* 8 bit channels use only 4 bits from the page register. */
-	if ((channel > 0) && (channel < 4) && (pa >= (1 << 20)))
+	if (is_dma8(channel) && (pa >= (1 << 20)))
+		return EINVAL;
+
+	/* Buffers cannot cross 64K page boundaries */
+	if ((pa & 0xffff0000) != ((pa + size - 1) & 0xffff0000))
 		return EINVAL;
 	
@@ -352,5 +374,5 @@
 	ddf_msg(LVL_DEBUG, "Unspoiled address %#" PRIx32 " (size %" PRIu16 ")",
 	    pa, size);
-	if (channel > 4) {
+	if (is_dma16(channel)) {
 		/* Size must be aligned to 16 bits */
 		if ((size & 1) != 0) {
@@ -358,7 +380,6 @@
 			return EINVAL;
 		}
-		
+		/* Size is in 2byte words */
 		size >>= 1;
-		
 		/* Address is fun: lower 16 bits need to be shifted by 1 */
 		pa = ((pa & 0xffff) >> 1) | (pa & 0xff0000);
@@ -426,4 +447,51 @@
 }
 
+/** Query remaining buffer size.
+ *
+ * @param channel DMA Channel 1, 2, 3 for 8 bit transfers,
+ *                    5, 6, 7 for 16 bit.
+ * @param size    Place to store number of bytes pending in the assigned buffer.
+ *
+ * @return Error code.
+ */
+int dma_channel_remain(unsigned channel, size_t *size)
+{
+	assert(size);
+	if (!is_dma8(channel) && !is_dma16(channel))
+		return ENOENT;
+	
+	if ((channel == 0) || (channel == 4))
+		return ENOTSUP;
+	
+	fibril_mutex_lock(&guard);
+	if (!controller_8237.initialized) {
+		fibril_mutex_unlock(&guard);
+		return EIO;
+	}
+
+	const dma_channel_t dma_channel = controller_8237.channels[channel];
+	/* Get size - reset flip-flop */
+	pio_write_8(dma_channel.flip_flop_address, 0);
+	
+	/* Low byte */
+	const uint8_t value_low = pio_read_8(dma_channel.size_reg_address);
+	ddf_msg(LVL_DEBUG2, "Read size low byte: %p:%x.",
+	    dma_channel.size_reg_address, value_low);
+	
+	/* High byte */
+	const uint8_t value_high = pio_read_8(dma_channel.size_reg_address);
+	ddf_msg(LVL_DEBUG2, "Read size high byte: %p:%x.",
+	    dma_channel.size_reg_address, value_high);
+	fibril_mutex_unlock(&guard);
+
+	uint16_t remain = (value_high << 8 | value_low) ;
+	/* 16 bit DMA size is in words,
+	 * the upper bits are bogus for 16bit transfers so we need to get
+	 * rid of them. Using limited type works well.*/
+	if (is_dma16(channel))
+		remain <<= 1;
+	*size =  is_dma16(channel) ? remain + 2: remain + 1;
+	return EOK;
+}
 /**
  * @}
Index: uspace/drv/bus/isa/i8237.h
===================================================================
--- uspace/drv/bus/isa/i8237.h	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/drv/bus/isa/i8237.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -38,5 +38,6 @@
 #define DRV_BUS_ISA_I8237_H
 
-extern int dma_setup_channel(unsigned int, uint32_t, uint16_t, uint8_t);
+extern int dma_channel_setup(unsigned, uint32_t, uint32_t, uint8_t);
+extern int dma_channel_remain(unsigned, size_t *);
 
 #endif
Index: uspace/drv/bus/isa/isa.c
===================================================================
--- uspace/drv/bus/isa/isa.c	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/drv/bus/isa/isa.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -85,4 +85,5 @@
 	fibril_mutex_t mutex;
 	ddf_fun_t *fnode;
+	hw_resource_t resources[ISA_MAX_HW_RES];
 	hw_resource_list_t hw_resources;
 	link_t bus_link;
@@ -103,15 +104,16 @@
 static hw_resource_list_t *isa_get_fun_resources(ddf_fun_t *fnode)
 {
-	isa_fun_t *fun = isa_fun(fnode);
-	assert(fun != NULL);
-
-	return &fun->hw_resources;
-}
-
-static bool isa_enable_fun_interrupt(ddf_fun_t *fnode)
+	isa_fun_t *isa = isa_fun(fnode);
+	assert(isa);
+
+	return &isa->hw_resources;
+}
+
+static bool isa_fun_enable_interrupt(ddf_fun_t *fnode)
 {
 	/* This is an old ugly way, copied from pci driver */
 	assert(fnode);
-	isa_fun_t *fun = isa_fun(fnode);
+	isa_fun_t *isa = isa_fun(fnode);
+	assert(isa);
 
 	sysarg_t apic;
@@ -129,5 +131,5 @@
 		return false;
 
-	const hw_resource_list_t *res = &fun->hw_resources;
+	const hw_resource_list_t *res = &isa->hw_resources;
 	assert(res);
 	for (size_t i = 0; i < res->count; ++i) {
@@ -151,22 +153,46 @@
 }
 
-static int isa_dma_channel_fun_setup(ddf_fun_t *fnode,
-    unsigned int channel, uint32_t pa, uint16_t size, uint8_t mode)
+static int isa_fun_setup_dma(ddf_fun_t *fnode,
+    unsigned int channel, uint32_t pa, uint32_t size, uint8_t mode)
 {
 	assert(fnode);
-	isa_fun_t *fun = isa_fun(fnode);
-	const hw_resource_list_t *res = &fun->hw_resources;
+	isa_fun_t *isa = isa_fun(fnode);
+	assert(isa);
+	const hw_resource_list_t *res = &isa->hw_resources;
 	assert(res);
-	
-	const unsigned int ch = channel;
+
 	for (size_t i = 0; i < res->count; ++i) {
+		/* Check for assigned channel */
 		if (((res->resources[i].type == DMA_CHANNEL_16) &&
-		    (res->resources[i].res.dma_channel.dma16 == ch)) ||
+		    (res->resources[i].res.dma_channel.dma16 == channel)) ||
 		    ((res->resources[i].type == DMA_CHANNEL_8) &&
-		    (res->resources[i].res.dma_channel.dma8 == ch))) {
-			return dma_setup_channel(channel, pa, size, mode);
-		}
-	}
-	
+		    (res->resources[i].res.dma_channel.dma8 == channel))) {
+			return dma_channel_setup(channel, pa, size, mode);
+		}
+	}
+
+	return EINVAL;
+}
+
+static int isa_fun_remain_dma(ddf_fun_t *fnode,
+    unsigned channel, size_t *size)
+{
+	assert(size);
+	assert(fnode);
+	isa_fun_t *isa = isa_fun(fnode);
+	assert(isa);
+	const hw_resource_list_t *res = &isa->hw_resources;
+	assert(res);
+
+	for (size_t i = 0; i < res->count; ++i) {
+		/* Check for assigned channel */
+		if (((res->resources[i].type == DMA_CHANNEL_16) &&
+		    (res->resources[i].res.dma_channel.dma16 == channel)) ||
+		    ((res->resources[i].type == DMA_CHANNEL_8) &&
+		    (res->resources[i].res.dma_channel.dma8 == channel))) {
+			return dma_channel_remain(channel, size);
+		}
+	}
+
 	return EINVAL;
 }
@@ -174,9 +200,12 @@
 static hw_res_ops_t isa_fun_hw_res_ops = {
 	.get_resource_list = isa_get_fun_resources,
-	.enable_interrupt = isa_enable_fun_interrupt,
-	.dma_channel_setup = isa_dma_channel_fun_setup,
+	.enable_interrupt = isa_fun_enable_interrupt,
+	.dma_channel_setup = isa_fun_setup_dma,
+	.dma_channel_remain = isa_fun_remain_dma,
 };
 
-static ddf_dev_ops_t isa_fun_ops;
+static ddf_dev_ops_t isa_fun_ops= {
+	.interfaces[HW_RES_DEV_IFACE] = &isa_fun_hw_res_ops,
+};
 
 static int isa_dev_add(ddf_dev_t *dev);
@@ -212,4 +241,6 @@
 
 	fibril_mutex_initialize(&fun->mutex);
+	fun->hw_resources.resources = fun->resources;
+
 	fun->fnode = fnode;
 	return fun;
@@ -270,7 +301,7 @@
 {
 	char *line = str;
+	*next = NULL;
 
 	if (str == NULL) {
-		*next = NULL;
 		return NULL;
 	}
@@ -282,6 +313,4 @@
 	if (*str != '\0') {
 		*next = str + 1;
-	} else {
-		*next = NULL;
 	}
 
@@ -310,18 +339,8 @@
 	/* Get the name part of the rest of the line. */
 	strtok(line, ":");
-
-	/* Allocate output buffer. */
-	size_t size = str_size(line) + 1;
-	char *name = malloc(size);
-
-	if (name != NULL) {
-		/* Copy the result to the output buffer. */
-		str_cpy(name, size, line);
-	}
-
-	return name;
-}
-
-static inline char *skip_spaces(char *line)
+	return line;
+}
+
+static inline const char *skip_spaces(const char *line)
 {
 	/* Skip leading spaces. */
@@ -332,5 +351,5 @@
 }
 
-static void isa_fun_set_irq(isa_fun_t *fun, int irq)
+static void isa_fun_add_irq(isa_fun_t *fun, int irq)
 {
 	size_t count = fun->hw_resources.count;
@@ -348,5 +367,5 @@
 }
 
-static void isa_fun_set_dma(isa_fun_t *fun, int dma)
+static void isa_fun_add_dma(isa_fun_t *fun, int dma)
 {
 	size_t count = fun->hw_resources.count;
@@ -381,5 +400,5 @@
 }
 
-static void isa_fun_set_io_range(isa_fun_t *fun, size_t addr, size_t len)
+static void isa_fun_add_io_range(isa_fun_t *fun, size_t addr, size_t len)
 {
 	size_t count = fun->hw_resources.count;
@@ -400,5 +419,5 @@
 }
 
-static void fun_parse_irq(isa_fun_t *fun, char *val)
+static void fun_parse_irq(isa_fun_t *fun, const char *val)
 {
 	int irq = 0;
@@ -409,20 +428,19 @@
 
 	if (val != end)
-		isa_fun_set_irq(fun, irq);
-}
-
-static void fun_parse_dma(isa_fun_t *fun, char *val)
-{
-	unsigned int dma = 0;
+		isa_fun_add_irq(fun, irq);
+}
+
+static void fun_parse_dma(isa_fun_t *fun, const char *val)
+{
 	char *end = NULL;
 	
 	val = skip_spaces(val);
-	dma = (unsigned int) strtol(val, &end, 10);
+	const int dma = strtol(val, &end, 10);
 	
 	if (val != end)
-		isa_fun_set_dma(fun, dma);
-}
-
-static void fun_parse_io_range(isa_fun_t *fun, char *val)
+		isa_fun_add_dma(fun, dma);
+}
+
+static void fun_parse_io_range(isa_fun_t *fun, const char *val)
 {
 	size_t addr, len;
@@ -441,10 +459,10 @@
 		return;
 
-	isa_fun_set_io_range(fun, addr, len);
-}
-
-static void get_match_id(char **id, char *val)
-{
-	char *end = val;
+	isa_fun_add_io_range(fun, addr, len);
+}
+
+static void get_match_id(char **id, const char *val)
+{
+	const char *end = val;
 
 	while (!isspace(*end))
@@ -456,14 +474,12 @@
 }
 
-static void fun_parse_match_id(isa_fun_t *fun, char *val)
+static void fun_parse_match_id(isa_fun_t *fun, const char *val)
 {
 	char *id = NULL;
-	int score = 0;
 	char *end = NULL;
-	int rc;
 
 	val = skip_spaces(val);
 
-	score = (int)strtol(val, &end, 10);
+	int score = (int)strtol(val, &end, 10);
 	if (val == end) {
 		ddf_msg(LVL_ERROR, "Cannot read match score for function "
@@ -483,5 +499,5 @@
 	    "function %s", id, score, ddf_fun_get_name(fun->fnode));
 
-	rc = ddf_fun_add_match_id(fun->fnode, id, score);
+	int rc = ddf_fun_add_match_id(fun->fnode, id, score);
 	if (rc != EOK) {
 		ddf_msg(LVL_ERROR, "Failed adding match ID: %s",
@@ -492,6 +508,6 @@
 }
 
-static bool prop_parse(isa_fun_t *fun, char *line, const char *prop,
-    void (*read_fn)(isa_fun_t *, char *))
+static bool prop_parse(isa_fun_t *fun, const char *line, const char *prop,
+    void (*read_fn)(isa_fun_t *, const char *))
 {
 	size_t proplen = str_size(prop);
@@ -508,5 +524,5 @@
 }
 
-static void fun_prop_parse(isa_fun_t *fun, char *line)
+static void fun_prop_parse(isa_fun_t *fun, const char *line)
 {
 	/* Skip leading spaces. */
@@ -523,23 +539,10 @@
 }
 
-static void fun_hw_res_alloc(isa_fun_t *fun)
-{
-	fun->hw_resources.resources =
-	    (hw_resource_t *) malloc(sizeof(hw_resource_t) * ISA_MAX_HW_RES);
-}
-
-static void fun_hw_res_free(isa_fun_t *fun)
-{
-	free(fun->hw_resources.resources);
-	fun->hw_resources.resources = NULL;
-}
-
 static char *isa_fun_read_info(char *fun_conf, isa_bus_t *isa)
 {
 	char *line;
-	char *fun_name = NULL;
 
 	/* Skip empty lines. */
-	while (true) {
+	do {
 		line = str_get_line(fun_conf, &fun_conf);
 
@@ -549,21 +552,15 @@
 		}
 
-		if (!line_empty(line))
-			break;
-	}
+	} while (line_empty(line));
 
 	/* Get device name. */
-	fun_name = get_device_name(line);
+	const char *fun_name = get_device_name(line);
 	if (fun_name == NULL)
 		return NULL;
 
 	isa_fun_t *fun = isa_fun_create(isa, fun_name);
-	free(fun_name);
 	if (fun == NULL) {
 		return NULL;
 	}
-
-	/* Allocate buffer for the list of hardware resources of the device. */
-	fun_hw_res_alloc(fun);
 
 	/* Get properties of the device (match ids, irq and io range). */
@@ -596,30 +593,19 @@
 }
 
-static void fun_conf_parse(char *conf, isa_bus_t *isa)
-{
+static void isa_functions_add(isa_bus_t *isa)
+{
+	char *conf = fun_conf_read(CHILD_FUN_CONF_PATH);
 	while (conf != NULL && *conf != '\0') {
 		conf = isa_fun_read_info(conf, isa);
 	}
-}
-
-static void isa_functions_add(isa_bus_t *isa)
-{
-	char *fun_conf;
-
-	fun_conf = fun_conf_read(CHILD_FUN_CONF_PATH);
-	if (fun_conf != NULL) {
-		fun_conf_parse(fun_conf, isa);
-		free(fun_conf);
-	}
+	free(conf);
 }
 
 static int isa_dev_add(ddf_dev_t *dev)
 {
-	isa_bus_t *isa;
-
 	ddf_msg(LVL_DEBUG, "isa_dev_add, device handle = %d",
 	    (int) ddf_dev_get_handle(dev));
 
-	isa = ddf_dev_data_alloc(dev, sizeof(isa_bus_t));
+	isa_bus_t *isa = ddf_dev_data_alloc(dev, sizeof(isa_bus_t));
 	if (isa == NULL)
 		return ENOMEM;
@@ -658,5 +644,4 @@
 {
 	isa_bus_t *isa = isa_bus(dev);
-	int rc;
 
 	fibril_mutex_lock(&isa->mutex);
@@ -666,5 +651,5 @@
 		    isa_fun_t, bus_link);
 
-		rc = ddf_fun_offline(fun->fnode);
+		int rc = ddf_fun_offline(fun->fnode);
 		if (rc != EOK) {
 			fibril_mutex_unlock(&isa->mutex);
@@ -682,5 +667,4 @@
 		list_remove(&fun->bus_link);
 
-		fun_hw_res_free(fun);
 		ddf_fun_destroy(fun->fnode);
 	}
@@ -709,15 +693,8 @@
 }
 
-
-static void isa_init()
-{
+int main(int argc, char *argv[])
+{
+	printf(NAME ": HelenOS ISA bus driver\n");
 	ddf_log_init(NAME);
-	isa_fun_ops.interfaces[HW_RES_DEV_IFACE] = &isa_fun_hw_res_ops;
-}
-
-int main(int argc, char *argv[])
-{
-	printf(NAME ": HelenOS ISA bus driver\n");
-	isa_init();
 	return ddf_driver_main(&isa_driver);
 }
Index: uspace/drv/infrastructure/root/root.c
===================================================================
--- uspace/drv/infrastructure/root/root.c	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/drv/infrastructure/root/root.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -208,11 +208,13 @@
 	/*
 	 * Register virtual devices root.
-	 * We ignore error occurrence because virtual devices shall not be
+	 * We warn on error occurrence because virtual devices shall not be
 	 * vital for the system.
 	 */
-	(void) add_virtual_root_fun(dev);
+	int res = add_virtual_root_fun(dev);
+	if (res != EOK)
+		ddf_msg(LVL_WARN, "Failed to add virtual child.");
 
 	/* Register root device's children. */
-	int res = add_platform_fun(dev);
+	res = add_platform_fun(dev);
 	if (EOK != res)
 		ddf_msg(LVL_ERROR, "Failed adding child device for platform.");
Index: uspace/drv/infrastructure/rootamdm37x/rootamdm37x.c
===================================================================
--- uspace/drv/infrastructure/rootamdm37x/rootamdm37x.c	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/drv/infrastructure/rootamdm37x/rootamdm37x.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -48,12 +48,23 @@
 
 typedef struct {
+	const char *name;
+	match_id_t match_id;
 	hw_resource_list_t hw_resources;
 } rootamdm37x_fun_t;
 
-/* See amdm37x TRM page. 3316 for these values */
-#define OHCI_BASE_ADDRESS  0x48064400
-#define OHCI_SIZE  1024
-#define EHCI_BASE_ADDRESS  0x48064800
-#define EHCI_SIZE  1024
+/* See amdm37x TRM page 3316 for these values */
+#define OHCI_BASE_ADDRESS   0x48064400
+#define OHCI_SIZE   1024
+#define EHCI_BASE_ADDRESS   0x48064800
+#define EHCI_SIZE   1024
+
+/* See amdm37x TRM page 1813 for these values */
+#define DSS_BASE_ADDRESS   0x48050000
+#define DSS_SIZE   512
+#define DISPC_BASE_ADDRESS   0x48050400
+#define DISPC_SIZE   1024
+#define VIDEO_ENC_BASE_ADDRESS   0x48050C00
+#define VIDEO_ENC_SIZE   256
+
 
 static hw_resource_t ohci_res[] = {
@@ -88,17 +99,53 @@
 };
 
-static const rootamdm37x_fun_t ohci = {
-	.hw_resources = {
-	    .resources = ohci_res,
-	    .count = sizeof(ohci_res)/sizeof(ohci_res[0]),
-	}
-};
-
-static const rootamdm37x_fun_t ehci = {
-	.hw_resources = {
-	    .resources = ehci_res,
-	    .count = sizeof(ehci_res) / sizeof(ehci_res[0]),
-	}
-};
+static hw_resource_t disp_res[] = {
+	{
+		.type = MEM_RANGE,
+		.res.io_range = {
+			.address = DSS_BASE_ADDRESS,
+			.size = DSS_SIZE,
+			.endianness = LITTLE_ENDIAN
+		},
+	},
+	{
+		.type = MEM_RANGE,
+		.res.io_range = {
+			.address = DISPC_BASE_ADDRESS,
+			.size = DISPC_SIZE,
+			.endianness = LITTLE_ENDIAN
+		},
+	},
+	{
+		.type = MEM_RANGE,
+		.res.io_range = {
+			.address = VIDEO_ENC_BASE_ADDRESS,
+			.size = VIDEO_ENC_SIZE,
+			.endianness = LITTLE_ENDIAN
+		},
+	},
+	{
+		.type = INTERRUPT,
+		.res.interrupt = { .irq = 25 },
+	},
+};
+
+static const rootamdm37x_fun_t amdm37x_funcs[] = {
+{
+	.name = "ohci",
+	.match_id = { .id = "usb/host=ohci", .score = 90 },
+	.hw_resources = { .resources = ohci_res, .count = ARRAY_SIZE(ohci_res) }
+},
+{
+	.name = "ehci",
+	.match_id = { .id = "usb/host=ehci", .score = 90 },
+	.hw_resources = { .resources = ehci_res, .count = ARRAY_SIZE(ehci_res) }
+},
+{
+	.name = "fb",
+	.match_id = { .id = "amdm37x&dispc", .score = 90 },
+	.hw_resources = { .resources = disp_res, .count = ARRAY_SIZE(disp_res) }
+},
+};
+
 
 static hw_resource_list_t *rootamdm37x_get_resources(ddf_fun_t *fnode);
@@ -114,16 +161,19 @@
 };
 
-static int rootamdm37x_add_fun(ddf_dev_t *dev, const char *name,
-    const char *str_match_id, const rootamdm37x_fun_t *fun)
-{
-	ddf_msg(LVL_DEBUG, "Adding new function '%s'.", name);
-	
+static int rootamdm37x_add_fun(ddf_dev_t *dev, const rootamdm37x_fun_t *fun)
+{
+	assert(dev);
+	assert(fun);
+
+	ddf_msg(LVL_DEBUG, "Adding new function '%s'.", fun->name);
+
 	/* Create new device function. */
-	ddf_fun_t *fnode = ddf_fun_create(dev, fun_inner, name);
+	ddf_fun_t *fnode = ddf_fun_create(dev, fun_inner, fun->name);
 	if (fnode == NULL)
 		return ENOMEM;
 	
 	/* Add match id */
-	int ret = ddf_fun_add_match_id(fnode, str_match_id, 100);
+	int ret = ddf_fun_add_match_id(fnode,
+	    fun->match_id.id, fun->match_id.score);
 	if (ret != EOK) {
 		ddf_fun_destroy(fnode);
@@ -146,5 +196,5 @@
 	ret = ddf_fun_bind(fnode);
 	if (ret != EOK) {
-		ddf_msg(LVL_ERROR, "Failed binding function %s.", name);
+		ddf_msg(LVL_ERROR, "Failed binding function %s.", fun->name);
 		ddf_fun_destroy(fnode);
 		return ret;
@@ -189,14 +239,9 @@
 
 	/* Register functions */
-	if (rootamdm37x_add_fun(dev, "ohci", "usb/host=ohci", &ohci) != EOK)
-		ddf_msg(LVL_ERROR, "Failed to add OHCI function for "
-		    "BeagleBoard-xM platform.");
-	if (rootamdm37x_add_fun(dev, "ehci", "usb/host=ehci", &ehci) != EOK)
-		ddf_msg(LVL_ERROR, "Failed to add EHCI function for "
-		    "BeagleBoard-xM platform.");
-	if (rootamdm37x_add_fun(dev, "dispc", "amdm37x&dispc", &ehci) != EOK)
-		ddf_msg(LVL_ERROR, "Failed to add dispc function for "
-		    "BeagleBoard-xM platform.");
-
+	for (unsigned i = 0; i < ARRAY_SIZE(amdm37x_funcs); ++i) {
+		if (rootamdm37x_add_fun(dev, &amdm37x_funcs[i]) != EOK)
+			ddf_msg(LVL_ERROR, "Failed to add %s function for "
+			    "BeagleBoard-xM platform.", amdm37x_funcs[i].name);
+	}
 	return EOK;
 }
Index: uspace/drv/infrastructure/rootamdm37x/uhh.h
===================================================================
--- uspace/drv/infrastructure/rootamdm37x/uhh.h	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/drv/infrastructure/rootamdm37x/uhh.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -85,5 +85,5 @@
 #define UHH_DEBUG_CSR_EHCI_SIMULATION_MODE_FLAG  (1 << 6)
 #define UHH_DEBUG_CSR_OHCI_CNTSEL_FLAG  (1 << 7)
-#define UHH_DEBUG_CSR_OHCI_GLOBAL_sUSPEND_FLAG  (1 << 16)
+#define UHH_DEBUG_CSR_OHCI_GLOBAL_SUSPEND_FLAG  (1 << 16)
 #define UHH_DEBUG_CSR_OHCI_CCS1_FLAG  (1 << 17)
 #define UHH_DEBUG_CSR_OHCI_CCS2_FLAG  (1 << 18)
Index: uspace/drv/nic/e1k/e1k.c
===================================================================
--- uspace/drv/nic/e1k/e1k.c	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/drv/nic/e1k/e1k.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -59,7 +59,7 @@
 #define E1000_DEFAULT_INTERRUPT_INTERVAL_USEC  250
 
-/* Must be power of 8 */
-#define E1000_RX_FRAME_COUNT  128
-#define E1000_TX_FRAME_COUNT  128
+/* Must be power of 8 */ //TODO power or multiple? previous value was 128
+#define E1000_RX_FRAME_COUNT  8
+#define E1000_TX_FRAME_COUNT  8
 
 #define E1000_RECEIVE_ADDRESS  16
Index: uspace/lib/c/arch/sparc64/Makefile.common
===================================================================
--- uspace/lib/c/arch/sparc64/Makefile.common	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/c/arch/sparc64/Makefile.common	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -27,5 +27,12 @@
 #
 
-GCC_CFLAGS += -mcpu=ultrasparc -m64 -mcmodel=medlow
+ifeq ($(PROCESSOR),sun4v)
+GCC_CFLAGS += -mcpu=niagara -mno-vis
+else
+GCC_CFLAGS += -mcpu=ultrasparc
+endif
+
+GCC_CFLAGS += -m64 -mcmodel=medlow
+
 LFLAGS = -no-check-sections
 
Index: uspace/lib/c/generic/async.c
===================================================================
--- uspace/lib/c/generic/async.c	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/c/generic/async.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -2281,14 +2281,35 @@
 bool async_data_read_receive(ipc_callid_t *callid, size_t *size)
 {
+	ipc_call_t data;
+	return async_data_read_receive_call(callid, &data, size);
+}
+
+/** Wrapper for receiving the IPC_M_DATA_READ calls using the async framework.
+ *
+ * This wrapper only makes it more comfortable to receive IPC_M_DATA_READ
+ * calls so that the user doesn't have to remember the meaning of each IPC
+ * argument.
+ *
+ * So far, this wrapper is to be used from within a connection fibril.
+ *
+ * @param callid Storage for the hash of the IPC_M_DATA_READ.
+ * @param size   Storage for the maximum size. Can be NULL.
+ *
+ * @return True on success, false on failure.
+ *
+ */
+bool async_data_read_receive_call(ipc_callid_t *callid, ipc_call_t *data,
+    size_t *size)
+{
 	assert(callid);
-	
-	ipc_call_t data;
-	*callid = async_get_call(&data);
-	
-	if (IPC_GET_IMETHOD(data) != IPC_M_DATA_READ)
+	assert(data);
+	
+	*callid = async_get_call(data);
+	
+	if (IPC_GET_IMETHOD(*data) != IPC_M_DATA_READ)
 		return false;
 	
 	if (size)
-		*size = (size_t) IPC_GET_ARG2(data);
+		*size = (size_t) IPC_GET_ARG2(*data);
 	
 	return true;
@@ -2385,14 +2406,36 @@
 bool async_data_write_receive(ipc_callid_t *callid, size_t *size)
 {
+	ipc_call_t data;
+	return async_data_write_receive_call(callid, &data, size);
+}
+
+/** Wrapper for receiving the IPC_M_DATA_WRITE calls using the async framework.
+ *
+ * This wrapper only makes it more comfortable to receive IPC_M_DATA_WRITE
+ * calls so that the user doesn't have to remember the meaning of each IPC
+ * argument.
+ *
+ * So far, this wrapper is to be used from within a connection fibril.
+ *
+ * @param callid Storage for the hash of the IPC_M_DATA_WRITE.
+ * @param data   Storage for the ipc call data.
+ * @param size   Storage for the suggested size. May be NULL.
+ *
+ * @return True on success, false on failure.
+ *
+ */
+bool async_data_write_receive_call(ipc_callid_t *callid, ipc_call_t *data,
+    size_t *size)
+{
 	assert(callid);
-	
-	ipc_call_t data;
-	*callid = async_get_call(&data);
-	
-	if (IPC_GET_IMETHOD(data) != IPC_M_DATA_WRITE)
+	assert(data);
+	
+	*callid = async_get_call(data);
+	
+	if (IPC_GET_IMETHOD(*data) != IPC_M_DATA_WRITE)
 		return false;
 	
 	if (size)
-		*size = (size_t) IPC_GET_ARG2(data);
+		*size = (size_t) IPC_GET_ARG2(*data);
 	
 	return true;
Index: uspace/lib/c/generic/device/hw_res.c
===================================================================
--- uspace/lib/c/generic/device/hw_res.c	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/c/generic/device/hw_res.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -44,4 +44,6 @@
 	
 	async_exch_t *exch = async_exchange_begin(sess);
+	if (exch == NULL)
+		return ENOMEM;
 	int rc = async_req_1_1(exch, DEV_IFACE_ID(HW_RES_DEV_IFACE),
 	    HW_RES_GET_RESOURCE_LIST, &count);
@@ -77,4 +79,6 @@
 {
 	async_exch_t *exch = async_exchange_begin(sess);
+	if (exch == NULL)
+		return false;
 	int rc = async_req_1_0(exch, DEV_IFACE_ID(HW_RES_DEV_IFACE),
 	    HW_RES_ENABLE_INTERRUPT);
@@ -84,4 +88,50 @@
 }
 
+/**
+ * Setup DMA channel to specified place and mode.
+ * @param channel DMA Channel 1,2,3 for 8 bit transfers, 5,6,7 for 16 bit.
+ * @param pa Physical address of the buffer. Must be < 16MB for 16 bit and < 1MB
+ *           for 8 bit transfers.
+ * @param size DMA buffer size, limited to 64K.
+ * @param mode Mode of the DMA channel:
+ *              - Read or Write
+ *              - Allow automatic reset
+ *              - Use address decrement instead of increment
+ *              - Use SINGLE/BLOCK/ON DEMAND transfer mode
+ * @return Error code.
+ */
+int hw_res_dma_channel_setup(async_sess_t *sess,
+    unsigned channel, uint32_t pa, uint32_t size, uint8_t mode)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	if (exch == NULL)
+		return ENOMEM;
+	const uint32_t packed = (channel & 0xffff) | (mode << 16);
+	const int ret = async_req_4_0(exch, DEV_IFACE_ID(HW_RES_DEV_IFACE),
+	    HW_RES_DMA_CHANNEL_SETUP, packed, pa, size);
+	async_exchange_end(exch);
+
+	return ret;
+}
+
+/**
+ * Query remaining bytes in the buffer.
+ * @param channel DMA Channel 1,2,3 for 8 bit transfers, 5,6,7 for 16 bit.
+ * @return Number of bytes remaining in the buffer(>=0) or error code(<0).
+ */
+int hw_res_dma_channel_remain(async_sess_t *sess, unsigned channel)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	if (exch == NULL)
+		return ENOMEM;
+	sysarg_t remain;
+	const int ret = async_req_2_1(exch, DEV_IFACE_ID(HW_RES_DEV_IFACE),
+	    HW_RES_DMA_CHANNEL_REMAIN, channel, &remain);
+	async_exchange_end(exch);
+	if (ret == EOK)
+		return remain;
+	return ret;
+}
+
 /** @}
  */
Index: uspace/lib/c/generic/device/hw_res_parsed.c
===================================================================
--- uspace/lib/c/generic/device/hw_res_parsed.c	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/c/generic/device/hw_res_parsed.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -38,6 +38,28 @@
 #include <errno.h>
 
-static void hw_res_parse_add_irq(hw_res_list_parsed_t *out, hw_resource_t *res,
-    int flags)
+static void hw_res_parse_add_dma_channel(hw_res_list_parsed_t *out,
+    const hw_resource_t *res, int flags)
+{
+	assert(res);
+	assert((res->type == DMA_CHANNEL_8) || (res->type == DMA_CHANNEL_16));
+	
+	const unsigned channel = (res->type == DMA_CHANNEL_8) ?
+	    res->res.dma_channel.dma8 : res->res.dma_channel.dma16;
+	const size_t count = out->dma_channels.count;
+	const int keep_duplicit = flags & HW_RES_KEEP_DUPLICIT;
+	
+	if (!keep_duplicit) {
+		for (size_t i = 0; i < count; ++i) {
+			if (out->dma_channels.channels[i] == channel)
+				return;
+		}
+	}
+	
+	out->dma_channels.channels[count] = channel;
+	++out->dma_channels.count;
+}
+
+static void hw_res_parse_add_irq(hw_res_list_parsed_t *out,
+    const hw_resource_t *res, int flags)
 {
 	assert(res && (res->type == INTERRUPT));
@@ -59,5 +81,5 @@
 
 static void hw_res_parse_add_io_range(hw_res_list_parsed_t *out,
-    hw_resource_t *res, int flags)
+    const hw_resource_t *res, int flags)
 {
 	assert(res && (res->type == IO_RANGE));
@@ -90,5 +112,5 @@
 
 static void hw_res_parse_add_mem_range(hw_res_list_parsed_t *out,
-    hw_resource_t *res, int flags)
+    const hw_resource_t *res, int flags)
 {
 	assert(res && (res->type == MEM_RANGE));
@@ -132,5 +154,5 @@
  *
  */
-int hw_res_list_parse(hw_resource_list_t *hw_resources,
+int hw_res_list_parse(const hw_resource_list_t *hw_resources,
     hw_res_list_parsed_t *out, int flags)
 {
@@ -141,10 +163,16 @@
 	hw_res_list_parsed_clean(out);
 	
-	out->irqs.irqs = malloc(res_count * sizeof(int));
-	out->io_ranges.ranges = malloc(res_count * sizeof(io_range_t));
-	out->mem_ranges.ranges = malloc(res_count * sizeof(mem_range_t));
+	out->irqs.irqs = calloc(res_count, sizeof(int));
+	out->dma_channels.channels = calloc(res_count, sizeof(int));
+	out->io_ranges.ranges = calloc(res_count, sizeof(io_range_t));
+	out->mem_ranges.ranges = calloc(res_count, sizeof(mem_range_t));
+	if (!out->irqs.irqs || !out->dma_channels.channels ||
+	    !out->io_ranges.ranges || !out->mem_ranges.ranges) {
+		hw_res_list_parsed_clean(out);
+		return ENOMEM;
+	}
 	
 	for (size_t i = 0; i < res_count; ++i) {
-		hw_resource_t *resource = &(hw_resources->resources[i]);
+		const hw_resource_t *resource = &(hw_resources->resources[i]);
 		
 		switch (resource->type) {
@@ -158,5 +186,10 @@
 			hw_res_parse_add_mem_range(out, resource, flags);
 			break;
+		case DMA_CHANNEL_8:
+		case DMA_CHANNEL_16:
+			hw_res_parse_add_dma_channel(out, resource, flags);
+			break;
 		default:
+			hw_res_list_parsed_clean(out);
 			return EINVAL;
 		}
Index: uspace/lib/c/generic/futex.c
===================================================================
--- uspace/lib/c/generic/futex.c	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/c/generic/futex.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -35,4 +35,5 @@
 #include <futex.h>
 #include <atomic.h>
+#include <libarch/barrier.h>
 #include <libc.h>
 #include <sys/types.h>
@@ -59,5 +60,10 @@
 int futex_trydown(futex_t *futex)
 {
-	return cas(futex, 1, 0);
+	int rc;
+
+	rc = cas(futex, 1, 0);
+	CS_ENTER_BARRIER();
+
+	return rc;
 }
 
@@ -73,5 +79,9 @@
 int futex_down(futex_t *futex)
 {
-	if ((atomic_signed_t) atomic_predec(futex) < 0)
+	atomic_signed_t nv;
+
+	nv = (atomic_signed_t) atomic_predec(futex);
+	CS_ENTER_BARRIER();
+	if (nv < 0)
 		return __SYSCALL1(SYS_FUTEX_SLEEP, (sysarg_t) &futex->count);
 	
@@ -89,4 +99,6 @@
 int futex_up(futex_t *futex)
 {
+	CS_LEAVE_BARRIER();
+
 	if ((atomic_signed_t) atomic_postinc(futex) < 0)
 		return __SYSCALL1(SYS_FUTEX_WAKEUP, (sysarg_t) &futex->count);
Index: uspace/lib/c/include/adt/list.h
===================================================================
--- uspace/lib/c/include/adt/list.h	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/c/include/adt/list.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -1,5 +1,5 @@
 /*
  * Copyright (c) 2001-2004 Jakub Jermar
- * Copyright (c) 2011 Jiri Svoboda
+ * Copyright (c) 2013 Jiri Svoboda
  * All rights reserved.
  *
@@ -65,5 +65,5 @@
 
 #define list_get_instance(link, type, member) \
-	((type *) (((void *)(link)) - ((void *) &(((type *) NULL)->member))))
+	((type *) (((void *)(link)) - list_link_to_void(&(((type *) NULL)->member))))
 
 #define list_foreach(list, iterator) \
@@ -318,4 +318,13 @@
 }
 
+/** Verify that argument type is a pointer to link_t (at compile time).
+ *
+ * This can be used to check argument type in a macro.
+ */
+static inline const void *list_link_to_void(const link_t *link)
+{
+	return link;
+}
+
 extern int list_member(const link_t *, const list_t *);
 extern void list_concat(list_t *, list_t *);
Index: uspace/lib/c/include/async.h
===================================================================
--- uspace/lib/c/include/async.h	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/c/include/async.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -399,4 +399,5 @@
 extern int async_data_read_start(async_exch_t *, void *, size_t);
 extern bool async_data_read_receive(ipc_callid_t *, size_t *);
+extern bool async_data_read_receive_call(ipc_callid_t *, ipc_call_t *, size_t *);
 extern int async_data_read_finalize(ipc_callid_t, const void *, size_t);
 
@@ -437,4 +438,5 @@
 extern int async_data_write_start(async_exch_t *, const void *, size_t);
 extern bool async_data_write_receive(ipc_callid_t *, size_t *);
+extern bool async_data_write_receive_call(ipc_callid_t *, ipc_call_t *, size_t *);
 extern int async_data_write_finalize(ipc_callid_t, void *, size_t);
 
Index: uspace/lib/c/include/device/hw_res.h
===================================================================
--- uspace/lib/c/include/device/hw_res.h	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/c/include/device/hw_res.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -53,4 +53,5 @@
 	HW_RES_ENABLE_INTERRUPT,
 	HW_RES_DMA_CHANNEL_SETUP,
+	HW_RES_DMA_CHANNEL_REMAIN,
 } hw_res_method_t;
 
@@ -115,5 +116,6 @@
 
 extern int hw_res_dma_channel_setup(async_sess_t *, unsigned int, uint32_t,
-    uint16_t, uint8_t);
+    uint32_t, uint8_t);
+extern int hw_res_dma_channel_remain(async_sess_t *, unsigned);
 
 #endif
Index: uspace/lib/c/include/device/hw_res_parsed.h
===================================================================
--- uspace/lib/c/include/device/hw_res_parsed.h	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/c/include/device/hw_res_parsed.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -139,5 +139,6 @@
 }
 
-extern int hw_res_list_parse(hw_resource_list_t *, hw_res_list_parsed_t *, int);
+extern int hw_res_list_parse(const hw_resource_list_t *,
+    hw_res_list_parsed_t *, int);
 extern int hw_res_get_list_parsed(async_sess_t *, hw_res_list_parsed_t *, int);
 
Index: uspace/lib/c/include/ipc/dev_iface.h
===================================================================
--- uspace/lib/c/include/ipc/dev_iface.h	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/c/include/ipc/dev_iface.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -36,4 +36,5 @@
 typedef enum {
 	HW_RES_DEV_IFACE = 0,
+
 	/** Character device interface */
 	CHAR_DEV_IFACE,
@@ -41,4 +42,9 @@
 	/** Graphic device interface */
 	GRAPH_DEV_IFACE,
+
+	/** Audio device mixer interface */
+	AUDIO_MIXER_IFACE,
+	/** Audio device pcm buffer interface */
+	AUDIO_PCM_BUFFER_IFACE,
 	
 	/** Network interface controller interface */
@@ -54,8 +60,11 @@
 	/** Interface provided by USB HID devices. */
 	USBHID_DEV_IFACE,
+
 	/** Interface provided by Real Time Clock devices */
 	CLOCK_DEV_IFACE,
+
 	/** Interface provided by battery powered devices */
 	BATTERY_DEV_IFACE,
+
 	/** Interface provided by AHCI devices. */
 	AHCI_DEV_IFACE,
Index: uspace/lib/c/include/macros.h
===================================================================
--- uspace/lib/c/include/macros.h	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/c/include/macros.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -40,4 +40,5 @@
 #define abs(a)     ((a) >= 0 ? (a) : -(a))
 
+#define ARRAY_SIZE(array)   (sizeof(array) / sizeof(array[0]))
 
 #define KiB2SIZE(kb)  ((kb) << 10)
Index: uspace/lib/drv/Makefile
===================================================================
--- uspace/lib/drv/Makefile	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/drv/Makefile	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -29,5 +29,5 @@
 
 USPACE_PREFIX = ../..
-EXTRA_CFLAGS = -Iinclude -I$(LIBUSB_PREFIX)/include
+EXTRA_CFLAGS = -Iinclude -I$(LIBUSB_PREFIX)/include -I$(LIBPCM_PREFIX)/include
 LIBRARY = libdrv
 
@@ -38,4 +38,6 @@
 	generic/log.c \
 	generic/logbuf.c \
+	generic/remote_audio_mixer.c \
+	generic/remote_audio_pcm.c \
 	generic/remote_hw_res.c \
 	generic/remote_char_dev.c \
Index: uspace/lib/drv/generic/dev_iface.c
===================================================================
--- uspace/lib/drv/generic/dev_iface.c	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/drv/generic/dev_iface.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -49,19 +49,23 @@
 #include "remote_usbhid.h"
 #include "remote_pci.h"
+#include "remote_audio_mixer.h"
+#include "remote_audio_pcm.h"
 #include "remote_ahci.h"
 
-static iface_dipatch_table_t remote_ifaces = {
+static const iface_dipatch_table_t remote_ifaces = {
 	.ifaces = {
-		&remote_hw_res_iface,
-		&remote_char_dev_iface,
-		&remote_graph_dev_iface,
-		&remote_nic_iface,
-		&remote_pci_iface,
-		&remote_usb_iface,
-		&remote_usbhc_iface,
-		&remote_usbhid_iface,
-		&remote_clock_dev_iface,
-		&remote_battery_dev_iface,
-		&remote_ahci_iface
+		[AUDIO_MIXER_IFACE] = &remote_audio_mixer_iface,
+		[AUDIO_PCM_BUFFER_IFACE] = &remote_audio_pcm_iface,
+		[HW_RES_DEV_IFACE] = &remote_hw_res_iface,
+		[CHAR_DEV_IFACE] = &remote_char_dev_iface,
+		[GRAPH_DEV_IFACE] = &remote_graph_dev_iface,
+		[NIC_DEV_IFACE] = &remote_nic_iface,
+		[PCI_DEV_IFACE] = &remote_pci_iface,
+		[USB_DEV_IFACE] = &remote_usb_iface,
+		[USBHC_DEV_IFACE] = &remote_usbhc_iface,
+		[USBHID_DEV_IFACE] = &remote_usbhid_iface,
+		[CLOCK_DEV_IFACE] = &remote_clock_dev_iface,
+		[BATTERY_DEV_IFACE] = &remote_battery_dev_iface,
+		[AHCI_DEV_IFACE] = &remote_ahci_iface
 	}
 };
Index: uspace/lib/drv/generic/remote_audio_mixer.c
===================================================================
--- uspace/lib/drv/generic/remote_audio_mixer.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/drv/generic/remote_audio_mixer.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,315 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup libdrv
+ * @{
+ */
+/** @file
+ */
+
+#include <async.h>
+#include <errno.h>
+#include <assert.h>
+#include <str.h>
+
+#include "audio_mixer_iface.h"
+#include "ddf/driver.h"
+
+typedef enum {
+	/** Asks for basic mixer info: Mixer name and number of controllable
+	 * items.
+	 * Answer:
+	 * - ENOTSUP - call not supported
+	 * - EOK - call successful, info is valid
+	 * Answer arguments:
+	 * - Mixer name
+	 * - Number of controllable items
+	 */
+	IPC_M_AUDIO_MIXER_GET_INFO,
+
+	/** Asks for item mixer info: Item name and number of controllable
+	 * channels.
+	 * Answer:
+	 * - ENOTSUP - call not supported
+	 * - ENOENT - no such item
+	 * - EOK - call successful, info is valid
+	 * Answer arguments:
+	 * - Item name
+	 * - Number of controllable channels
+	 */
+	IPC_M_AUDIO_MIXER_GET_ITEM_INFO,
+
+	/** Set new control item setting
+	 * Answer:
+	 * - ENOTSUP - call not supported
+	 * - ENOENT - no such control item
+	 * - EOK - call successful, info is valid
+	 */
+	IPC_M_AUDIO_MIXER_SET_ITEM_LEVEL,
+
+	/** Get control item setting
+	 * Answer:
+	 * - ENOTSUP - call not supported
+	 * - ENOENT - no such control item
+	 * - EOK - call successful, info is valid
+	 */
+	IPC_M_AUDIO_MIXER_GET_ITEM_LEVEL,
+} audio_mixer_iface_funcs_t;
+
+/*
+ * CLIENT SIDE
+ */
+
+/**
+ * Query audio mixer for basic info (name and items count).
+ * @param[in] exch IPC exchange connected to the device
+ * @param[out] name Audio mixer string identifier
+ * @param[out] items Number of items controlled by the mixer.
+ * @return Error code.
+ */
+int audio_mixer_get_info(async_exch_t *exch, const char **name, unsigned *items)
+{
+	if (!exch)
+		return EINVAL;
+	sysarg_t name_size, itemc;
+	const int ret = async_req_1_2(exch, DEV_IFACE_ID(AUDIO_MIXER_IFACE),
+	    IPC_M_AUDIO_MIXER_GET_INFO, &name_size, &itemc);
+	if (ret == EOK && name) {
+		char *name_place = calloc(1, name_size);
+		if (!name_place) {
+			/* Make the other side fail
+			 * as it waits for read request */
+			async_data_read_start(exch, (void*)-1, 0);
+			return ENOMEM;
+		}
+		const int ret =
+		    async_data_read_start(exch, name_place, name_size);
+		if (ret != EOK) {
+			free(name_place);
+			return ret;
+		}
+		*name = name_place;
+	}
+	if (ret == EOK && items)
+		*items = itemc;
+	return ret;
+}
+
+/**
+ * Query audio mixer for item specific info (name and channels count).
+ * @param[in] exch IPC exchange connected to the device
+ * @param[in] item The control item
+ * @param[out] name Control item string identifier.
+ * @param[out] channles Number of channels associated with this control item.
+ * @return Error code.
+ */
+int audio_mixer_get_item_info(async_exch_t *exch, unsigned item,
+    const char **name, unsigned *levels)
+{
+	if (!exch)
+		return EINVAL;
+	sysarg_t name_size, lvls;
+	const int ret = async_req_2_2(exch, DEV_IFACE_ID(AUDIO_MIXER_IFACE),
+	    IPC_M_AUDIO_MIXER_GET_ITEM_INFO, item, &name_size, &lvls);
+	if (ret == EOK && name) {
+		char *name_place = calloc(1, name_size);
+		if (!name_place) {
+			/* Make the other side fail
+			 * as it waits for read request */
+			async_data_read_start(exch, (void*)-1, 0);
+			return ENOMEM;
+		}
+		const int ret =
+		    async_data_read_start(exch, name_place, name_size);
+		if (ret != EOK) {
+			free(name_place);
+			return ret;
+		}
+		*name = name_place;
+	}
+	if (ret == EOK && levels)
+		*levels = lvls;
+	return ret;
+}
+
+/**
+ * Set control item to a new level.
+ * @param[in] exch IPC exchange connected to the device.
+ * @param[in] item The control item controlling the channel.
+ * @param[in] level The new value.
+ * @return Error code.
+ */
+int audio_mixer_set_item_level(async_exch_t *exch, unsigned item,
+    unsigned level)
+{
+	if (!exch)
+		return EINVAL;
+	return async_req_3_0(exch, DEV_IFACE_ID(AUDIO_MIXER_IFACE),
+	    IPC_M_AUDIO_MIXER_SET_ITEM_LEVEL, item, level);
+}
+
+/**
+ * Get current level of a control item.
+ * @param[in] exch IPC exchange connected to the device.
+ * @param[in] item The control item controlling the channel.
+ * @param[in] channel The channel index.
+ * @param[out] level Currently set value.
+ * @return Error code.
+ */
+int audio_mixer_get_item_level(async_exch_t *exch, unsigned item,
+    unsigned *level)
+{
+	if (!exch)
+		return EINVAL;
+	sysarg_t current;
+	const int ret = async_req_2_1(exch, DEV_IFACE_ID(AUDIO_MIXER_IFACE),
+	    IPC_M_AUDIO_MIXER_GET_ITEM_LEVEL, item, &current);
+	if (ret == EOK && level)
+		*level = current;
+	return ret;
+}
+
+/*
+ * SERVER SIDE
+ */
+static void remote_audio_mixer_get_info(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_audio_mixer_get_item_info(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_audio_mixer_get_item_level(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_audio_mixer_set_item_level(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+
+/** Remote audio mixer interface operations. */
+static remote_iface_func_ptr_t remote_audio_mixer_iface_ops[] = {
+	[IPC_M_AUDIO_MIXER_GET_INFO] = remote_audio_mixer_get_info,
+	[IPC_M_AUDIO_MIXER_GET_ITEM_INFO] = remote_audio_mixer_get_item_info,
+	[IPC_M_AUDIO_MIXER_GET_ITEM_LEVEL] = remote_audio_mixer_get_item_level,
+	[IPC_M_AUDIO_MIXER_SET_ITEM_LEVEL] = remote_audio_mixer_set_item_level,
+};
+
+/** Remote audio mixer interface structure. */
+remote_iface_t remote_audio_mixer_iface = {
+	.method_count = sizeof(remote_audio_mixer_iface_ops) /
+	    sizeof(remote_audio_mixer_iface_ops[0]),
+	.methods = remote_audio_mixer_iface_ops
+};
+
+void remote_audio_mixer_get_info(
+    ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	audio_mixer_iface_t *mixer_iface = iface;
+
+	if (!mixer_iface->get_info) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+	const char *name = NULL;
+	unsigned items = 0;
+	const int ret = mixer_iface->get_info(fun, &name, &items);
+	const size_t name_size = name ? str_size(name) + 1 : 0;
+	async_answer_2(callid, ret, name_size, items);
+	/* Send the name. */
+	if (ret == EOK && name_size > 0) {
+		size_t size;
+		ipc_callid_t name_id;
+		if (!async_data_read_receive(&name_id, &size)) {
+			async_answer_0(name_id, EPARTY);
+			return;
+		}
+		if (size != name_size) {
+			async_answer_0(name_id, ELIMIT);
+			return;
+		}
+		async_data_read_finalize(name_id, name, name_size);
+	}
+}
+
+void remote_audio_mixer_get_item_info(
+    ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	audio_mixer_iface_t *mixer_iface = iface;
+
+	if (!mixer_iface->get_item_info) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	const unsigned item = DEV_IPC_GET_ARG1(*call);
+	const char *name = NULL;
+	unsigned values = 0;
+	const int ret = mixer_iface->get_item_info(fun, item, &name, &values);
+	const size_t name_size = name ? str_size(name) + 1 : 0;
+	async_answer_2(callid, ret, name_size, values);
+	/* Send the name. */
+	if (ret == EOK && name_size > 0) {
+		size_t size;
+		ipc_callid_t name_id;
+		if (!async_data_read_receive(&name_id, &size)) {
+			async_answer_0(name_id, EPARTY);
+			return;
+		}
+		if (size != name_size) {
+			async_answer_0(name_id, ELIMIT);
+			return;
+		}
+		async_data_read_finalize(name_id, name, name_size);
+	}
+}
+
+void remote_audio_mixer_set_item_level(
+    ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	audio_mixer_iface_t *mixer_iface = iface;
+
+	if (!mixer_iface->set_item_level) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+	const unsigned item = DEV_IPC_GET_ARG1(*call);
+	const unsigned value = DEV_IPC_GET_ARG2(*call);
+	const int ret = mixer_iface->set_item_level(fun, item, value);
+	async_answer_0(callid, ret);
+}
+
+void remote_audio_mixer_get_item_level(
+    ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	audio_mixer_iface_t *mixer_iface = iface;
+
+	if (!mixer_iface->get_item_level) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+	const unsigned item = DEV_IPC_GET_ARG1(*call);
+	unsigned current = 0;
+	const int ret =
+	    mixer_iface->get_item_level(fun, item, &current);
+	async_answer_1(callid, ret, current);
+}
+
+/**
+ * @}
+ */
Index: uspace/lib/drv/generic/remote_audio_pcm.c
===================================================================
--- uspace/lib/drv/generic/remote_audio_pcm.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/drv/generic/remote_audio_pcm.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,858 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+/** @addtogroup libdrv
+ * @{
+ */
+/** @file
+ */
+
+#include <async.h>
+#include <devman.h>
+#include <ddf/log.h>
+#include <errno.h>
+#include <macros.h>
+#include <str.h>
+#include <as.h>
+#include <sys/mman.h>
+
+#include "audio_pcm_iface.h"
+#include "ddf/driver.h"
+
+typedef enum {
+	IPC_M_AUDIO_PCM_GET_INFO_STR,
+	IPC_M_AUDIO_PCM_QUERY_CAPS,
+	IPC_M_AUDIO_PCM_REGISTER_EVENTS,
+	IPC_M_AUDIO_PCM_UNREGISTER_EVENTS,
+	IPC_M_AUDIO_PCM_TEST_FORMAT,
+	IPC_M_AUDIO_PCM_GET_BUFFER,
+	IPC_M_AUDIO_PCM_RELEASE_BUFFER,
+	IPC_M_AUDIO_PCM_GET_BUFFER_POS,
+	IPC_M_AUDIO_PCM_START_PLAYBACK,
+	IPC_M_AUDIO_PCM_STOP_PLAYBACK,
+	IPC_M_AUDIO_PCM_START_CAPTURE,
+	IPC_M_AUDIO_PCM_STOP_CAPTURE,
+} audio_pcm_iface_funcs_t;
+
+/**
+ * Get human readable capability name.
+ * @param cap audio capability.
+ * @return Valid string
+ */
+const char *audio_pcm_cap_str(audio_cap_t cap)
+{
+	static const char *caps[] = {
+		[AUDIO_CAP_CAPTURE] = "CAPTURE",
+		[AUDIO_CAP_PLAYBACK] = "PLAYBACK",
+		[AUDIO_CAP_MAX_BUFFER] = "MAXIMUM BUFFER SIZE",
+		[AUDIO_CAP_BUFFER_POS] = "KNOWS BUFFER POSITION",
+		[AUDIO_CAP_INTERRUPT] = "FRAGMENT INTERRUPTS",
+		[AUDIO_CAP_INTERRUPT_MIN_FRAMES] = "MINIMUM FRAGMENT SIZE",
+		[AUDIO_CAP_INTERRUPT_MAX_FRAMES] = "MAXIMUM FRAGMENT SIZE",
+	};
+	if (cap >= ARRAY_SIZE(caps))
+		return "UNKNOWN CAP";
+	return caps[cap];
+
+}
+
+/**
+ * Get human readable event name.
+ * @param event Audio device event
+ * @return Valid string
+ */
+const char *audio_pcm_event_str(pcm_event_t event)
+{
+	static const char *events[] = {
+		[PCM_EVENT_PLAYBACK_STARTED] = "PLAYBACK STARTED",
+		[PCM_EVENT_CAPTURE_STARTED] = "CAPTURE STARTED",
+		[PCM_EVENT_FRAMES_PLAYED] = "FRAGMENT PLAYED",
+		[PCM_EVENT_FRAMES_CAPTURED] = "FRAGMENT CAPTURED",
+		[PCM_EVENT_PLAYBACK_TERMINATED] = "PLAYBACK TERMINATED",
+		[PCM_EVENT_CAPTURE_TERMINATED] = "CAPTURE TERMINATED",
+	};
+	if (event >= ARRAY_SIZE(events))
+		return "UNKNOWN EVENT";
+	return events[event];
+}
+
+/*
+ * CLIENT SIDE
+ */
+
+/**
+ * Open audio session with the first registered device.
+ *
+ * @return Pointer to a new audio device session, NULL on failure.
+ */
+audio_pcm_sess_t *audio_pcm_open_default(void)
+{
+	static bool resolved = false;
+	static category_id_t pcm_id = 0;
+	if (!resolved) {
+		const int ret = loc_category_get_id("audio-pcm", &pcm_id,
+		    IPC_FLAG_BLOCKING);
+		if (ret != EOK)
+			return NULL;
+		resolved = true;
+	}
+
+	service_id_t *svcs = NULL;
+	size_t count = 0;
+	const int ret = loc_category_get_svcs(pcm_id, &svcs, &count);
+	if (ret != EOK)
+		return NULL;
+
+	audio_pcm_sess_t *session = NULL;
+	if (count)
+		session = audio_pcm_open_service(svcs[0]);
+	free(svcs);
+	return session;
+}
+
+/**
+ * Open audio session with device identified by location service string.
+ *
+ * @param name Location service string.
+ * @return Pointer to a new audio device session, NULL on failure.
+ */
+audio_pcm_sess_t *audio_pcm_open(const char *name)
+{
+	devman_handle_t device_handle = 0;
+	const int ret = devman_fun_get_handle(name, &device_handle, 0);
+	if (ret != EOK)
+		return NULL;
+	return devman_device_connect(EXCHANGE_SERIALIZE, device_handle,
+	    IPC_FLAG_BLOCKING);
+}
+
+/**
+ * Open audio session with device identified by location service id
+ *
+ * @param name Location service id.
+ * @return Pointer to a new audio device session, NULL on failure.
+ */
+audio_pcm_sess_t *audio_pcm_open_service(service_id_t id)
+{
+	return loc_service_connect(EXCHANGE_SERIALIZE, id, IPC_FLAG_BLOCKING);
+}
+
+/**
+ * Close open audio device session.
+ *
+ * @param name Open audio device session.
+ *
+ * @note Calling this function on already closed or invalid session results
+ * in undefined behavior.
+ */
+void audio_pcm_close(audio_pcm_sess_t *sess)
+{
+	if (sess)
+		async_hangup(sess);
+}
+
+/**
+ * Get a short description string.
+ *
+ * @param sess Audio device session.
+ * @param name Place to store newly allocated string.
+ *
+ * @return Error code.
+ *
+ * @note Caller is responsible for freeing newly allocated memory.
+ */
+int audio_pcm_get_info_str(audio_pcm_sess_t *sess, const char **name)
+{
+	if (!name)
+		return EINVAL;
+	async_exch_t *exch = async_exchange_begin(sess);
+	sysarg_t name_size;
+	const int ret = async_req_1_1(exch,
+	    DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE),
+	    IPC_M_AUDIO_PCM_GET_INFO_STR, &name_size);
+	if (ret == EOK) {
+		char *name_place = calloc(1, name_size);
+		if (!name_place) {
+			/* Make the other side fail
+			 * as it waits for read request */
+			async_data_read_start(exch, (void*)-1, 0);
+			async_exchange_end(exch);
+			return ENOMEM;
+		}
+		const int ret =
+		    async_data_read_start(exch, name_place, name_size);
+		if (ret != EOK) {
+			free(name_place);
+			async_exchange_end(exch);
+			return ret;
+		}
+		*name = name_place;
+	}
+	async_exchange_end(exch);
+	return ret;
+}
+
+
+/**
+ * Query value of specified capability.
+ *
+ * @param sess Audio device session.
+ * @param cap  Audio device capability.
+ * @param val  Place to store queried value.
+ *
+ * @return Error code.
+ */
+int audio_pcm_query_cap(audio_pcm_sess_t *sess, audio_cap_t cap)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	sysarg_t value = 0;
+	const int ret = async_req_2_1(exch,
+	    DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE), IPC_M_AUDIO_PCM_QUERY_CAPS,
+	    cap, &value);
+	async_exchange_end(exch);
+	if (ret == EOK)
+		return value;
+	return ret;
+}
+
+/**
+ * Query current position in device buffer.
+ *
+ * @param sess Audio device session.
+ * @param pos Place to store the result.
+ *
+ * @return Error code.
+ *
+ * Works for both playback and capture.
+ */
+int audio_pcm_get_buffer_pos(audio_pcm_sess_t *sess, size_t *pos)
+{
+	if (!pos)
+		return EINVAL;
+	async_exch_t *exch = async_exchange_begin(sess);
+	sysarg_t value = 0;
+	const int ret = async_req_1_1(exch,
+	    DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE),
+	    IPC_M_AUDIO_PCM_GET_BUFFER_POS, &value);
+	if (ret == EOK)
+		*pos = value;
+	async_exchange_end(exch);
+	return ret;
+}
+
+/**
+ * Test format parameters for device support.
+ *
+ * @param sess Audio device session.
+ * @param channels Number of channels
+ * @param rate Sampling rate.
+ * @format Sample format.
+ *
+ * @return Error code.
+ *
+ * Works for both playback and capture. This function modifies provided
+ * parameters to the nearest values supported by the device.
+ */
+int audio_pcm_test_format(audio_pcm_sess_t *sess, unsigned *channels,
+    unsigned *rate, pcm_sample_format_t *format)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	sysarg_t channels_arg = channels ? *channels : 0;
+	sysarg_t rate_arg = rate ? *rate : 0;
+	sysarg_t format_arg = format ? *format : 0;
+	const int ret = async_req_4_3(exch,
+	    DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE),
+	    IPC_M_AUDIO_PCM_TEST_FORMAT, channels_arg, rate_arg, format_arg,
+	    &channels_arg, &rate_arg, &format_arg);
+	async_exchange_end(exch);
+
+	/* All OK or something has changed. Verify that it was not one of the
+	 * params we care about */
+	if ((ret == EOK || ret == ELIMIT)
+	    && (!channels || *channels == channels_arg)
+	    && (!rate || *rate == rate_arg)
+	    && (!format || *format == format_arg))
+		return EOK;
+	if (channels)
+		*channels = channels_arg;
+	if (rate)
+		*rate = rate_arg;
+	if (format)
+		*format = format_arg;
+	return ret;
+}
+
+/**
+ * Register callback for device generated events.
+ *
+ * @param sess Audio device session.
+ * @param event_rec Event callback function.
+ * @param arg Event callback custom parameter.
+ *
+ * @return Error code.
+ */
+int audio_pcm_register_event_callback(audio_pcm_sess_t *sess,
+    async_client_conn_t event_callback, void *arg)
+{
+	if (!event_callback)
+		return EINVAL;
+
+	async_exch_t *exch = async_exchange_begin(sess);
+	int ret = async_req_1_0(exch, DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE),
+	    IPC_M_AUDIO_PCM_REGISTER_EVENTS);
+	if (ret == EOK) {
+		ret = async_connect_to_me(exch, 0, 0, 0, event_callback, arg);
+	}
+	async_exchange_end(exch);
+	return ret;
+}
+
+/**
+ * Unregister callback for device generated events.
+ *
+ * @param sess Audio device session.
+ *
+ * @return Error code.
+ */
+int audio_pcm_unregister_event_callback(audio_pcm_sess_t *sess)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	const int ret = async_req_1_0(exch,
+	    DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE),
+	    IPC_M_AUDIO_PCM_UNREGISTER_EVENTS);
+	async_exchange_end(exch);
+	return ret;
+}
+
+/**
+ * Get device accessible playback/capture buffer.
+ *
+ * @param sess Audio device session.
+ * @param buffer Place to store pointer to the buffer.
+ * @param size Place to store buffer size (bytes).
+ *
+ * @return Error code.
+ */
+int audio_pcm_get_buffer(audio_pcm_sess_t *sess, void **buffer, size_t *size)
+{
+	if (!buffer || !size)
+		return EINVAL;
+
+	async_exch_t *exch = async_exchange_begin(sess);
+
+	sysarg_t buffer_size = *size;
+	int ret = async_req_2_1(exch, DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE),
+	    IPC_M_AUDIO_PCM_GET_BUFFER, (sysarg_t)buffer_size, &buffer_size);
+	if (ret == EOK) {
+		void *dst = NULL;
+		ret = async_share_in_start_0_0(exch, buffer_size, &dst);
+		if (ret != EOK) {
+			async_exchange_end(exch);
+			return ret;
+		}
+		*buffer = dst;
+		*size = buffer_size;
+	}
+	async_exchange_end(exch);
+	return ret;
+}
+
+/**
+ * Release device accessible playback/capture buffer.
+ *
+ * @param sess Audio device session.
+ *
+ * @return Error code.
+ */
+int audio_pcm_release_buffer(audio_pcm_sess_t *sess)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	const int ret = async_req_1_0(exch,
+	    DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE),
+	    IPC_M_AUDIO_PCM_RELEASE_BUFFER);
+	async_exchange_end(exch);
+	return ret;
+}
+
+/**
+ * Start playback on buffer from position 0.
+ *
+ * @param sess Audio device session.
+ * @param frames Size of fragment (in frames).
+ * @param channels Number of channels.
+ * @param sample_rate Sampling rate (for one channel).
+ * @param format Sample format.
+ *
+ * @return Error code.
+ *
+ * Event will be generated after every fragment. Set fragment size to
+ * 0 to turn off event generation.
+ */
+int audio_pcm_start_playback_fragment(audio_pcm_sess_t *sess, unsigned frames,
+    unsigned channels, unsigned sample_rate, pcm_sample_format_t format)
+{
+	if (channels > UINT16_MAX)
+		return EINVAL;
+	assert((format & UINT16_MAX) == format);
+	const sysarg_t packed = (channels << 16) | (format & UINT16_MAX);
+	async_exch_t *exch = async_exchange_begin(sess);
+	const int ret = async_req_4_0(exch,
+	    DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE),
+	    IPC_M_AUDIO_PCM_START_PLAYBACK,
+	    frames, sample_rate, packed);
+	async_exchange_end(exch);
+	return ret;
+}
+/**
+ * Stops playback after current fragment.
+ *
+ * @param sess Audio device session.
+ *
+ * @return Error code.
+ */
+int audio_pcm_last_playback_fragment(audio_pcm_sess_t *sess)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	const int ret = async_req_2_0(exch,
+	    DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE),
+	    IPC_M_AUDIO_PCM_STOP_PLAYBACK, false);
+	async_exchange_end(exch);
+	return ret;
+}
+
+/**
+ * Start playback on buffer from the current position.
+ *
+ * @param sess Audio device session.
+ * @param channels Number of channels.
+ * @param sample_rate Sampling rate (for one channel).
+ * @param format Sample format.
+ *
+ * @return Error code.
+ */
+int audio_pcm_start_playback(audio_pcm_sess_t *sess,
+    unsigned channels, unsigned sample_rate, pcm_sample_format_t format)
+{
+	return audio_pcm_start_playback_fragment(
+	    sess, 0, channels, sample_rate, format);
+}
+
+/**
+ * Immediately stops current playback.
+ *
+ * @param sess Audio device session.
+ *
+ * @return Error code.
+ */
+int audio_pcm_stop_playback_immediate(audio_pcm_sess_t *sess)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	const int ret = async_req_2_0(exch,
+	    DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE),
+	    IPC_M_AUDIO_PCM_STOP_PLAYBACK, true);
+	async_exchange_end(exch);
+	return ret;
+}
+
+/**
+ * Stops playback at the end of the current fragment.
+ *
+ * @param sess Audio device session.
+ *
+ * @return Error code.
+ */
+int audio_pcm_stop_playback(audio_pcm_sess_t *sess)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	const int ret = async_req_2_0(exch,
+	    DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE),
+	    IPC_M_AUDIO_PCM_STOP_PLAYBACK, false);
+	async_exchange_end(exch);
+	return ret;
+}
+
+/**
+ * Start capture on buffer from the current position.
+ *
+ * @param sess Audio device session.
+ * @param frames Size of fragment (in frames).
+ * @param channels Number of channels.
+ * @param sample_rate Sampling rate (for one channel).
+ * @param format Sample format.
+ *
+ * @return Error code.
+ *
+ * Event will be generated after every fragment. Set fragment size to
+ * 0 to turn off event generation.
+ */
+int audio_pcm_start_capture_fragment(audio_pcm_sess_t *sess, unsigned frames,
+    unsigned channels, unsigned sample_rate, pcm_sample_format_t format)
+{
+	if (channels > UINT16_MAX)
+		return EINVAL;
+	assert((format & UINT16_MAX) == format);
+	const sysarg_t packed = (channels << 16) | (format & UINT16_MAX);
+	async_exch_t *exch = async_exchange_begin(sess);
+	const int ret = async_req_4_0(exch,
+	    DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE), IPC_M_AUDIO_PCM_START_CAPTURE,
+	    frames, sample_rate, packed);
+	async_exchange_end(exch);
+	return ret;
+}
+
+/**
+ * Start capture on buffer from the current position.
+ *
+ * @param sess Audio device session.
+ * @param channels Number of channels.
+ * @param sample_rate Sampling rate (for one channel).
+ * @param format Sample format.
+ *
+ * @return Error code.
+ */
+int audio_pcm_start_capture(audio_pcm_sess_t *sess,
+    unsigned channels, unsigned sample_rate, pcm_sample_format_t format)
+{
+	return audio_pcm_start_capture_fragment(
+	    sess, 0, channels, sample_rate, format);
+}
+
+/**
+ * Stops capture at the end of current fragment.
+ *
+ * Won't work if capture was started with fragment size 0.
+ * @param sess Audio device session.
+ *
+ * @return Error code.
+ */
+int audio_pcm_last_capture_fragment(audio_pcm_sess_t *sess)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	const int ret = async_req_2_0(exch,
+	    DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE),
+	    IPC_M_AUDIO_PCM_STOP_CAPTURE, false);
+	async_exchange_end(exch);
+	return ret;
+}
+
+/**
+ * Immediately stops current capture.
+ *
+ * @param sess Audio device session.
+ *
+ * @return Error code.
+ */
+int audio_pcm_stop_capture_immediate(audio_pcm_sess_t *sess)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	const int ret = async_req_2_0(exch,
+	    DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE),
+	    IPC_M_AUDIO_PCM_STOP_CAPTURE, true);
+	async_exchange_end(exch);
+	return ret;
+}
+
+/**
+ * Stops capture at the end of the current fragment.
+ *
+ * @param sess Audio device session.
+ *
+ * @return Error code.
+ */
+int audio_pcm_stop_capture(audio_pcm_sess_t *sess)
+{
+	async_exch_t *exch = async_exchange_begin(sess);
+	const int ret = async_req_2_0(exch,
+	    DEV_IFACE_ID(AUDIO_PCM_BUFFER_IFACE),
+	    IPC_M_AUDIO_PCM_STOP_CAPTURE, false);
+	async_exchange_end(exch);
+	return ret;
+}
+
+/*
+ * SERVER SIDE
+ */
+static void remote_audio_pcm_get_info_str(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_audio_pcm_query_caps(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_audio_pcm_events_register(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_audio_pcm_events_unregister(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_audio_pcm_get_buffer_pos(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_audio_pcm_test_format(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_audio_pcm_get_buffer(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_audio_pcm_release_buffer(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_audio_pcm_start_playback(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_audio_pcm_stop_playback(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_audio_pcm_start_capture(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+static void remote_audio_pcm_stop_capture(ddf_fun_t *, void *, ipc_callid_t, ipc_call_t *);
+
+/** Remote audio pcm buffer interface operations. */
+static remote_iface_func_ptr_t remote_audio_pcm_iface_ops[] = {
+	[IPC_M_AUDIO_PCM_GET_INFO_STR] = remote_audio_pcm_get_info_str,
+	[IPC_M_AUDIO_PCM_QUERY_CAPS] = remote_audio_pcm_query_caps,
+	[IPC_M_AUDIO_PCM_REGISTER_EVENTS] = remote_audio_pcm_events_register,
+	[IPC_M_AUDIO_PCM_UNREGISTER_EVENTS] = remote_audio_pcm_events_unregister,
+	[IPC_M_AUDIO_PCM_GET_BUFFER_POS] = remote_audio_pcm_get_buffer_pos,
+	[IPC_M_AUDIO_PCM_TEST_FORMAT] = remote_audio_pcm_test_format,
+	[IPC_M_AUDIO_PCM_GET_BUFFER] = remote_audio_pcm_get_buffer,
+	[IPC_M_AUDIO_PCM_RELEASE_BUFFER] = remote_audio_pcm_release_buffer,
+	[IPC_M_AUDIO_PCM_START_PLAYBACK] = remote_audio_pcm_start_playback,
+	[IPC_M_AUDIO_PCM_STOP_PLAYBACK] = remote_audio_pcm_stop_playback,
+	[IPC_M_AUDIO_PCM_START_CAPTURE] = remote_audio_pcm_start_capture,
+	[IPC_M_AUDIO_PCM_STOP_CAPTURE] = remote_audio_pcm_stop_capture,
+};
+
+/** Remote audio mixer interface structure. */
+remote_iface_t remote_audio_pcm_iface = {
+	.method_count = sizeof(remote_audio_pcm_iface_ops) /
+	    sizeof(remote_audio_pcm_iface_ops[0]),
+	.methods = remote_audio_pcm_iface_ops
+};
+
+void remote_audio_pcm_get_info_str(ddf_fun_t *fun, void *iface,
+    ipc_callid_t callid, ipc_call_t *call)
+{
+	const audio_pcm_iface_t *pcm_iface = iface;
+
+	if (!pcm_iface->get_info_str) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+	const char *name = NULL;
+	const int ret = pcm_iface->get_info_str(fun, &name);
+	const size_t name_size = name ? str_size(name) + 1 : 0;
+	async_answer_1(callid, ret, name_size);
+	/* Send the string. */
+	if (ret == EOK && name_size > 0) {
+		size_t size;
+		ipc_callid_t name_id;
+		if (!async_data_read_receive(&name_id, &size)) {
+			async_answer_0(name_id, EPARTY);
+			return;
+		}
+		if (size != name_size) {
+			async_answer_0(name_id, ELIMIT);
+			return;
+		}
+		async_data_read_finalize(name_id, name, name_size);
+	}
+}
+
+void remote_audio_pcm_query_caps(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const audio_pcm_iface_t *pcm_iface = iface;
+	const audio_cap_t cap = DEV_IPC_GET_ARG1(*call);
+	if (pcm_iface->query_cap) {
+		const unsigned value = pcm_iface->query_cap(fun, cap);
+		async_answer_1(callid, EOK, value);
+	} else {
+		async_answer_0(callid, ENOTSUP);
+	}
+}
+
+static void remote_audio_pcm_events_register(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const audio_pcm_iface_t *pcm_iface = iface;
+	if (!pcm_iface->get_event_session ||
+	    !pcm_iface->set_event_session) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+
+	async_answer_0(callid, EOK);
+
+	ipc_call_t callback_call;
+	ipc_callid_t callback_id = async_get_call(&callback_call);
+	async_sess_t *sess =
+	    async_callback_receive_start(EXCHANGE_ATOMIC, &callback_call);
+	if (sess == NULL) {
+		ddf_msg(LVL_DEBUG, "Failed to create event callback");
+		async_answer_0(callback_id, EAGAIN);
+		return;
+	}
+	const int ret = pcm_iface->set_event_session(fun, sess);
+	if (ret != EOK) {
+		ddf_msg(LVL_DEBUG, "Failed to set event callback.");
+		async_hangup(sess);
+		async_answer_0(callback_id, ret);
+		return;
+	}
+	async_answer_0(callback_id, EOK);
+}
+
+static void remote_audio_pcm_events_unregister(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const audio_pcm_iface_t *pcm_iface = iface;
+	if (!pcm_iface->get_event_session ||
+	    !pcm_iface->set_event_session) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+	async_sess_t *sess = pcm_iface->get_event_session(fun);
+	if (sess) {
+		async_hangup(sess);
+		pcm_iface->set_event_session(fun, NULL);
+	}
+	async_answer_0(callid, EOK);
+}
+
+void remote_audio_pcm_get_buffer_pos(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const audio_pcm_iface_t *pcm_iface = iface;
+	size_t pos = 0;
+	const int ret = pcm_iface->get_buffer_pos ?
+	    pcm_iface->get_buffer_pos(fun, &pos) : ENOTSUP;
+	async_answer_1(callid, ret, pos);
+}
+
+void remote_audio_pcm_test_format(ddf_fun_t *fun, void *iface, ipc_callid_t callid, ipc_call_t *call)
+{
+	const audio_pcm_iface_t *pcm_iface = iface;
+	unsigned channels = DEV_IPC_GET_ARG1(*call);
+	unsigned rate = DEV_IPC_GET_ARG2(*call);
+	pcm_sample_format_t format = DEV_IPC_GET_ARG3(*call);
+	const int ret = pcm_iface->test_format ?
+	    pcm_iface->test_format(fun, &channels, &rate, &format) : ENOTSUP;
+	async_answer_3(callid, ret, channels, rate, format);
+}
+
+void remote_audio_pcm_get_buffer(ddf_fun_t *fun, void *iface,
+    ipc_callid_t callid, ipc_call_t *call)
+{
+	const audio_pcm_iface_t *pcm_iface = iface;
+
+	if (!pcm_iface->get_buffer ||
+	    !pcm_iface->release_buffer) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+	void *buffer = NULL;
+	size_t size = DEV_IPC_GET_ARG1(*call);
+	int ret = pcm_iface->get_buffer(fun, &buffer, &size);
+	async_answer_1(callid, ret, size);
+	if (ret != EOK || size == 0)
+		return;
+
+	/* Share the buffer. */
+	size_t share_size = 0;
+	ipc_callid_t share_id = 0;
+
+	ddf_msg(LVL_DEBUG2, "Receiving share request.");
+	if (!async_share_in_receive(&share_id, &share_size)) {
+		ddf_msg(LVL_DEBUG, "Failed to share pcm buffer.");
+		pcm_iface->release_buffer(fun);
+		async_answer_0(share_id, EPARTY);
+		return;
+	}
+
+	ddf_msg(LVL_DEBUG2, "Checking requested share size.");
+	if (share_size != size) {
+		ddf_msg(LVL_DEBUG, "Incorrect pcm buffer size requested.");
+		pcm_iface->release_buffer(fun);
+		async_answer_0(share_id, ELIMIT);
+		return;
+	}
+
+	ddf_msg(LVL_DEBUG2, "Calling share finalize.");
+	ret = async_share_in_finalize(share_id, buffer, AS_AREA_WRITE
+	| AS_AREA_READ);
+	if (ret != EOK) {
+		ddf_msg(LVL_DEBUG, "Failed to share buffer.");
+		pcm_iface->release_buffer(fun);
+		return;
+	}
+
+	ddf_msg(LVL_DEBUG2, "Buffer shared with size %zu.", share_size);
+}
+
+void remote_audio_pcm_release_buffer(ddf_fun_t *fun, void *iface,
+    ipc_callid_t callid, ipc_call_t *call)
+{
+	const audio_pcm_iface_t *pcm_iface = iface;
+
+	const int ret = pcm_iface->release_buffer ?
+	    pcm_iface->release_buffer(fun) : ENOTSUP;
+	async_answer_0(callid, ret);
+}
+
+void remote_audio_pcm_start_playback(ddf_fun_t *fun, void *iface,
+    ipc_callid_t callid, ipc_call_t *call)
+{
+	const audio_pcm_iface_t *pcm_iface = iface;
+
+	const unsigned frames = DEV_IPC_GET_ARG1(*call);
+	const unsigned rate = DEV_IPC_GET_ARG2(*call);
+	const unsigned channels = (DEV_IPC_GET_ARG3(*call) >> 16) & UINT8_MAX;
+	const pcm_sample_format_t format = DEV_IPC_GET_ARG3(*call) & UINT16_MAX;
+
+	const int ret = pcm_iface->start_playback
+	    ? pcm_iface->start_playback(fun, frames, channels, rate, format)
+	    : ENOTSUP;
+	async_answer_0(callid, ret);
+}
+
+void remote_audio_pcm_stop_playback(ddf_fun_t *fun, void *iface,
+    ipc_callid_t callid, ipc_call_t *call)
+{
+	const audio_pcm_iface_t *pcm_iface = iface;
+	const bool immediate = DEV_IPC_GET_ARG1(*call);
+
+	const int ret = pcm_iface->stop_playback ?
+	    pcm_iface->stop_playback(fun, immediate) : ENOTSUP;
+	async_answer_0(callid, ret);
+}
+
+void remote_audio_pcm_start_capture(ddf_fun_t *fun, void *iface,
+    ipc_callid_t callid, ipc_call_t *call)
+{
+	const audio_pcm_iface_t *pcm_iface = iface;
+
+	const unsigned frames = DEV_IPC_GET_ARG1(*call);
+	const unsigned rate = DEV_IPC_GET_ARG2(*call);
+	const unsigned channels = (DEV_IPC_GET_ARG3(*call) >> 16) & UINT16_MAX;
+	const pcm_sample_format_t format = DEV_IPC_GET_ARG3(*call) & UINT16_MAX;
+
+	const int ret = pcm_iface->start_capture
+	    ? pcm_iface->start_capture(fun, frames, channels, rate, format)
+	    : ENOTSUP;
+	async_answer_0(callid, ret);
+}
+
+void remote_audio_pcm_stop_capture(ddf_fun_t *fun, void *iface,
+    ipc_callid_t callid, ipc_call_t *call)
+{
+	const audio_pcm_iface_t *pcm_iface = iface;
+	const bool immediate = DEV_IPC_GET_ARG1(*call);
+
+	const int ret = pcm_iface->stop_capture ?
+	    pcm_iface->stop_capture(fun, immediate) : ENOTSUP;
+	async_answer_0(callid, ret);
+}
+
+/**
+ * @}
+ */
+
Index: uspace/lib/drv/generic/remote_hw_res.c
===================================================================
--- uspace/lib/drv/generic/remote_hw_res.c	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/drv/generic/remote_hw_res.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -1,4 +1,5 @@
 /*
  * Copyright (c) 2010 Lenka Trochtova
+ * Copyright (c) 2011 Jan Vesely
  * All rights reserved.
  *
@@ -43,8 +44,14 @@
 static void remote_hw_res_enable_interrupt(ddf_fun_t *, void *, ipc_callid_t,
     ipc_call_t *);
+static void remote_hw_res_dma_channel_setup(ddf_fun_t *, void *, ipc_callid_t,
+    ipc_call_t *);
+static void remote_hw_res_dma_channel_remain(ddf_fun_t *, void *, ipc_callid_t,
+    ipc_call_t *);
 
 static remote_iface_func_ptr_t remote_hw_res_iface_ops [] = {
-	&remote_hw_res_get_resource_list,
-	&remote_hw_res_enable_interrupt
+	[HW_RES_GET_RESOURCE_LIST] = &remote_hw_res_get_resource_list,
+	[HW_RES_ENABLE_INTERRUPT] = &remote_hw_res_enable_interrupt,
+	[HW_RES_DMA_CHANNEL_SETUP] = &remote_hw_res_dma_channel_setup,
+	[HW_RES_DMA_CHANNEL_REMAIN] = &remote_hw_res_dma_channel_remain,
 };
 
@@ -94,4 +101,37 @@
 }
 
+static void remote_hw_res_dma_channel_setup(ddf_fun_t *fun, void *ops,
+    ipc_callid_t callid, ipc_call_t *call)
+{
+	hw_res_ops_t *hw_res_ops = ops;
+
+	if (hw_res_ops->dma_channel_setup == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+	const unsigned channel = DEV_IPC_GET_ARG1(*call) & 0xffff;
+	const uint8_t  mode = DEV_IPC_GET_ARG1(*call) >> 16;
+	const uint32_t address = DEV_IPC_GET_ARG2(*call);
+	const uint32_t size = DEV_IPC_GET_ARG3(*call);
+
+	const int ret = hw_res_ops->dma_channel_setup(
+	    fun, channel, address, size, mode);
+	async_answer_0(callid, ret);
+}
+
+static void remote_hw_res_dma_channel_remain(ddf_fun_t *fun, void *ops,
+    ipc_callid_t callid, ipc_call_t *call)
+{
+	hw_res_ops_t *hw_res_ops = ops;
+
+	if (hw_res_ops->dma_channel_setup == NULL) {
+		async_answer_0(callid, ENOTSUP);
+		return;
+	}
+	const unsigned channel = DEV_IPC_GET_ARG1(*call);
+	size_t remain = 0;
+	const int ret = hw_res_ops->dma_channel_remain(fun, channel, &remain);
+	async_answer_1(callid, ret, remain);
+}
 /**
  * @}
Index: uspace/lib/drv/include/audio_mixer_iface.h
===================================================================
--- uspace/lib/drv/include/audio_mixer_iface.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/drv/include/audio_mixer_iface.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup libdrv
+ * @addtogroup usb
+ * @{
+ */
+/** @file
+ * @brief Audio mixer control interface.
+ */
+
+#ifndef LIBDRV_AUDIO_MIXER_IFACE_H_
+#define LIBDRV_AUDIO_MIXER_IFACE_H_
+
+#include <async.h>
+#include <stdbool.h>
+
+#include "ddf/driver.h"
+
+int audio_mixer_get_info(async_exch_t *, const char **, unsigned *);
+int audio_mixer_get_item_info(async_exch_t *, unsigned,
+    const char **, unsigned *);
+int audio_mixer_get_item_level(async_exch_t *, unsigned, unsigned *);
+int audio_mixer_set_item_level(async_exch_t *, unsigned, unsigned);
+
+
+/** Audio mixer communication interface. */
+typedef struct {
+	int (*get_info)(ddf_fun_t *, const char **, unsigned *);
+	int (*get_item_info)(ddf_fun_t *, unsigned, const char **, unsigned *);
+	int (*get_item_level)(ddf_fun_t *, unsigned, unsigned *);
+	int (*set_item_level)(ddf_fun_t *, unsigned, unsigned);
+} audio_mixer_iface_t;
+
+#endif
+/**
+ * @}
+ */
Index: uspace/lib/drv/include/audio_pcm_iface.h
===================================================================
--- uspace/lib/drv/include/audio_pcm_iface.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/drv/include/audio_pcm_iface.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+/** @addtogroup libdrv
+ * @addtogroup usb
+ * @{
+ */
+/** @file
+ * @brief Audio PCM buffer interface.
+ */
+
+#ifndef LIBDRV_AUDIO_PCM_IFACE_H_
+#define LIBDRV_AUDIO_PCM_IFACE_H_
+
+#include <async.h>
+#include <stdbool.h>
+#include <loc.h>
+#include <pcm/sample_format.h>
+
+#include "ddf/driver.h"
+
+typedef enum {
+	/** Device is capable of audio capture */
+	AUDIO_CAP_CAPTURE,
+	/** Device is capable of audio playback */
+	AUDIO_CAP_PLAYBACK,
+	/** Maximum size of device buffer */
+	AUDIO_CAP_MAX_BUFFER,
+	/** Device is capable of providing accurate buffer position info */
+	AUDIO_CAP_BUFFER_POS,
+	/** Device is capable of event based playback or capture */
+	AUDIO_CAP_INTERRUPT,
+	/** Minimal size of playback/record fragment */
+	AUDIO_CAP_INTERRUPT_MIN_FRAMES,
+	/** Maximum size of playback/record fragment */
+	AUDIO_CAP_INTERRUPT_MAX_FRAMES,
+} audio_cap_t;
+
+typedef enum {
+	PCM_EVENT_PLAYBACK_STARTED = IPC_FIRST_USER_METHOD,
+	PCM_EVENT_CAPTURE_STARTED,
+	PCM_EVENT_FRAMES_PLAYED,
+	PCM_EVENT_FRAMES_CAPTURED,
+	PCM_EVENT_PLAYBACK_TERMINATED,
+	PCM_EVENT_CAPTURE_TERMINATED,
+} pcm_event_t;
+
+const char *audio_pcm_cap_str(audio_cap_t);
+const char *audio_pcm_event_str(pcm_event_t);
+
+typedef async_sess_t audio_pcm_sess_t;
+
+audio_pcm_sess_t *audio_pcm_open(const char *);
+audio_pcm_sess_t *audio_pcm_open_default(void);
+audio_pcm_sess_t *audio_pcm_open_service(service_id_t service);
+void audio_pcm_close(audio_pcm_sess_t *);
+
+int audio_pcm_get_info_str(audio_pcm_sess_t *, const char **);
+int audio_pcm_test_format(audio_pcm_sess_t *, unsigned *, unsigned *,
+    pcm_sample_format_t *);
+int audio_pcm_query_cap(audio_pcm_sess_t *, audio_cap_t);
+int audio_pcm_register_event_callback(audio_pcm_sess_t *,
+    async_client_conn_t, void *);
+int audio_pcm_unregister_event_callback(audio_pcm_sess_t *);
+
+int audio_pcm_get_buffer(audio_pcm_sess_t *, void **, size_t *);
+int audio_pcm_get_buffer_pos(audio_pcm_sess_t *, size_t *);
+int audio_pcm_release_buffer(audio_pcm_sess_t *);
+
+int audio_pcm_start_playback_fragment(audio_pcm_sess_t *, unsigned,
+    unsigned, unsigned, pcm_sample_format_t);
+int audio_pcm_last_playback_fragment(audio_pcm_sess_t *);
+
+int audio_pcm_start_playback(audio_pcm_sess_t *,
+    unsigned, unsigned, pcm_sample_format_t);
+int audio_pcm_stop_playback_immediate(audio_pcm_sess_t *);
+int audio_pcm_stop_playback(audio_pcm_sess_t *);
+
+int audio_pcm_start_capture_fragment(audio_pcm_sess_t *, unsigned,
+    unsigned, unsigned, pcm_sample_format_t);
+int audio_pcm_last_capture_fragment(audio_pcm_sess_t *);
+
+int audio_pcm_start_capture(audio_pcm_sess_t *,
+    unsigned, unsigned, pcm_sample_format_t);
+int audio_pcm_stop_capture_immediate(audio_pcm_sess_t *);
+int audio_pcm_stop_capture(audio_pcm_sess_t *);
+
+/** Audio pcm communication interface. */
+typedef struct {
+	int (*get_info_str)(ddf_fun_t *, const char **);
+	int (*test_format)(ddf_fun_t *, unsigned *, unsigned *,
+	    pcm_sample_format_t *);
+	unsigned (*query_cap)(ddf_fun_t *, audio_cap_t);
+	int (*get_buffer_pos)(ddf_fun_t *, size_t *);
+	int (*get_buffer)(ddf_fun_t *, void **, size_t *);
+	int (*release_buffer)(ddf_fun_t *);
+	int (*set_event_session)(ddf_fun_t *, async_sess_t *);
+	async_sess_t * (*get_event_session)(ddf_fun_t *);
+	int (*start_playback)(ddf_fun_t *, unsigned,
+	    unsigned, unsigned, pcm_sample_format_t);
+	int (*stop_playback)(ddf_fun_t *, bool);
+	int (*start_capture)(ddf_fun_t *, unsigned,
+	    unsigned, unsigned, pcm_sample_format_t);
+	int (*stop_capture)(ddf_fun_t *, bool);
+} audio_pcm_iface_t;
+
+#endif
+/**
+ * @}
+ */
Index: uspace/lib/drv/include/ops/hw_res.h
===================================================================
--- uspace/lib/drv/include/ops/hw_res.h	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/drv/include/ops/hw_res.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -44,5 +44,6 @@
 	hw_resource_list_t *(*get_resource_list)(ddf_fun_t *);
 	bool (*enable_interrupt)(ddf_fun_t *);
-	int (*dma_channel_setup)(ddf_fun_t *, unsigned, uint32_t, uint16_t, uint8_t);
+	int (*dma_channel_setup)(ddf_fun_t *, unsigned, uint32_t, uint32_t, uint8_t);
+	int (*dma_channel_remain)(ddf_fun_t *, unsigned, size_t *);
 } hw_res_ops_t;
 
Index: uspace/lib/drv/include/remote_audio_mixer.h
===================================================================
--- uspace/lib/drv/include/remote_audio_mixer.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/drv/include/remote_audio_mixer.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup libdrv
+ * @{
+ */
+/** @file
+ */
+
+#ifndef LIBDRV_REMOTE_AUDIO_MIXER_H_
+#define LIBDRV_REMOTE_AUDIO_MIXER_H_
+
+extern remote_iface_t remote_audio_mixer_iface;
+
+#endif
+
+/**
+ * @}
+ */
+
Index: uspace/lib/drv/include/remote_audio_pcm.h
===================================================================
--- uspace/lib/drv/include/remote_audio_pcm.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/drv/include/remote_audio_pcm.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * 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.
+ */
+/** @addtogroup libdrv
+ * @{
+ */
+/** @file
+ */
+
+#ifndef LIBDRV_REMOTE_AUDIO_PCM_H_
+#define LIBDRV_REMOTE_AUDIO_PCM_H_
+
+extern remote_iface_t remote_audio_pcm_iface;
+
+#endif
+
+/**
+ * @}
+ */
+
Index: uspace/lib/hound/Makefile
===================================================================
--- uspace/lib/hound/Makefile	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/hound/Makefile	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2012 Jan Vesely
+# 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 = ../..
+EXTRA_CFLAGS = -Iinclude/hound -Iinclude -I$(LIBPCM_PREFIX)/include
+LIBRARY = libhound
+
+SOURCES = \
+	src/protocol.c \
+	src/client.c
+include $(USPACE_PREFIX)/Makefile.common
+
Index: uspace/lib/hound/include/hound/client.h
===================================================================
--- uspace/lib/hound/include/hound/client.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/hound/include/hound/client.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+/** @addtogroup libhound
+ * @addtogroup audio
+ * @{
+ */
+/** @file
+ * @brief Audio PCM buffer interface.
+ */
+
+#ifndef LIBHOUND_CLIENT_H_
+#define LIBHOUND_CLIENT_H_
+
+#include <async.h>
+#include <stdbool.h>
+#include <pcm/format.h>
+#include <hound/protocol.h>
+
+#define HOUND_DEFAULT_TARGET "default"
+#define HOUND_ALL_TARGETS "all"
+
+typedef struct hound_context hound_context_t;
+typedef struct hound_stream hound_stream_t;
+
+hound_context_t * hound_context_create_playback(const char *name,
+    pcm_format_t format, size_t bsize);
+hound_context_t * hound_context_create_capture(const char *name,
+    pcm_format_t format, size_t bsize);
+void hound_context_destroy(hound_context_t *hound);
+
+int hound_context_set_main_stream_params(hound_context_t *hound,
+    pcm_format_t format, size_t bsize);
+
+int hound_context_get_available_targets(hound_context_t *hound,
+    const char ***names, size_t *count);
+int hound_context_get_connected_targets(hound_context_t *hound,
+    const char ***names, size_t *count);
+
+int hound_context_connect_target(hound_context_t *hound, const char* target);
+int hound_context_disconnect_target(hound_context_t *hound, const char* target);
+
+hound_stream_t *hound_stream_create(hound_context_t *hound, unsigned flags,
+    pcm_format_t format, size_t bsize);
+void hound_stream_destroy(hound_stream_t *stream);
+
+int hound_stream_write(hound_stream_t *stream, const void *data, size_t size);
+int hound_stream_read(hound_stream_t *stream, void *data, size_t size);
+int hound_stream_drain(hound_stream_t *stream);
+
+int hound_write_main_stream(hound_context_t *hound,
+    const void *data, size_t size);
+int hound_read_main_stream(hound_context_t *hound, void *data, size_t size);
+int hound_write_replace_main_stream(hound_context_t *hound,
+    const void *data, size_t size);
+int hound_write_immediate(hound_context_t *hound,
+    pcm_format_t format, const void *data, size_t size);
+
+#endif
Index: uspace/lib/hound/include/hound/protocol.h
===================================================================
--- uspace/lib/hound/include/hound/protocol.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/hound/include/hound/protocol.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2013 Jan Vesely
+ * 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.
+ */
+/** @addtogroup libhound
+ * @addtogroup audio
+ * @{
+ */
+/** @file
+ * @brief Audio PCM buffer interface.
+ */
+
+#ifndef LIBHOUND_PROTOCOL_H_
+#define LIBHOUND_PROTOCOL_H_
+
+#include <async.h>
+#include <errno.h>
+#include <pcm/format.h>
+
+extern const char *HOUND_SERVICE;
+
+typedef enum {
+	HOUND_SINK_APPS = 0x1,
+	HOUND_SINK_DEVS = 0x2,
+	HOUND_SOURCE_APPS = 0x4,
+	HOUND_SOURCE_DEVS = 0x8,
+	HOUND_CONNECTED = 0x10,
+
+	HOUND_STREAM_DRAIN_ON_EXIT = 0x1,
+	HOUND_STREAM_IGNORE_UNDERFLOW = 0x2,
+	HOUND_STREAM_IGNORE_OVERFLOW = 0x4,
+} hound_flags_t;
+
+typedef async_sess_t hound_sess_t;
+typedef intptr_t hound_context_id_t;
+
+/**
+ * Check context id for errors.
+ * @param id Context id
+ * @return Error code.
+ */
+static inline int hound_context_id_err(hound_context_id_t id)
+{
+	return id > 0 ? EOK : (id == 0 ? ENOENT : id);
+}
+
+hound_sess_t *hound_service_connect(const char *service);
+void hound_service_disconnect(hound_sess_t *sess);
+
+hound_context_id_t hound_service_register_context(hound_sess_t *sess,
+    const char *name, bool record);
+int hound_service_unregister_context(hound_sess_t *sess, hound_context_id_t id);
+
+int hound_service_get_list(hound_sess_t *sess, const char ***ids, size_t *count,
+    int flags, const char *connection);
+
+/**
+ * Wrapper for list queries with no connection parameter.
+ * @param[in] sess hound daemon session.
+ * @param[out] ids list of string identifiers
+ * @param[out] count Number of elements in @p ids
+ * @param[in] flags Flags limiting the query.
+ * @return Error code.
+ */
+static inline int hound_service_get_list_all(hound_sess_t *sess,
+    const char ***ids, size_t *count, int flags)
+{
+	return hound_service_get_list(sess, ids, count, flags, NULL);
+}
+
+int hound_service_connect_source_sink(hound_sess_t *sess, const char *source,
+    const char *sink);
+int hound_service_disconnect_source_sink(hound_sess_t *sess, const char *source,
+    const char *sink);
+
+int hound_service_stream_enter(async_exch_t *exch, hound_context_id_t id,
+    int flags, pcm_format_t format, size_t bsize);
+int hound_service_stream_drain(async_exch_t *exch);
+int hound_service_stream_exit(async_exch_t *exch);
+
+int hound_service_stream_write(async_exch_t *exch, const void *data, size_t size);
+int hound_service_stream_read(async_exch_t *exch, void *data, size_t size);
+
+/* Server */
+
+/** Hound server interace structure */
+typedef struct hound_server_iface {
+	/** Create new context */
+	int (*add_context)(void *, hound_context_id_t *, const char *, bool);
+	/** Destroy existing context */
+	int (*rem_context)(void *, hound_context_id_t);
+	/** Query context direction */
+	bool (*is_record_context)(void *, hound_context_id_t);
+	/** Get string identifiers of specified objects */
+	int (*get_list)(void *, const char ***, size_t *, const char *, int);
+	/** Create connection between source and sink */
+	int (*connect)(void *, const char *, const char *);
+	/** Destroy connection between source and sink */
+	int (*disconnect)(void *, const char *, const char *);
+	/** Create new stream tied to the context */
+	int (*add_stream)(void *, hound_context_id_t, int, pcm_format_t, size_t,
+	    void **);
+	/** Destroy existing stream */
+	int (*rem_stream)(void *, void *);
+	/** Block until the stream buffer is empty */
+	int (*drain_stream)(void *);
+	/** Write new data to the stream */
+	int (*stream_data_write)(void *, const void *, size_t);
+	/** Read data from the stream */
+	int (*stream_data_read)(void *, void *, size_t);
+	void *server;
+} hound_server_iface_t;
+
+void hound_service_set_server_iface(const hound_server_iface_t *iface);
+
+void hound_connection_handler(ipc_callid_t iid, ipc_call_t *icall, void *arg);
+
+#endif
+/** @}
+ */
Index: uspace/lib/hound/include/hound/server.h
===================================================================
--- uspace/lib/hound/include/hound/server.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/hound/include/hound/server.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+/** @addtogroup libhound
+ * @addtogroup audio
+ * @{
+ */
+/** @file
+ * @brief Audio PCM buffer interface.
+ */
+
+#ifndef LIBHOUND_SERVER_H_
+#define LIBHOUND_SERVER_H_
+
+#include <async.h>
+#include <loc.h>
+
+typedef void (*dev_change_callback_t)(void);
+typedef int (*device_callback_t)(service_id_t, const char *);
+
+int hound_server_register(const char *name, service_id_t *id);
+void hound_server_unregister(service_id_t id);
+int hound_server_set_device_change_callback(dev_change_callback_t cb);
+int hound_server_devices_iterate(device_callback_t callback);
+
+#endif
Index: uspace/lib/hound/src/client.c
===================================================================
--- uspace/lib/hound/src/client.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/hound/src/client.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,496 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup libhound
+ * @addtogroup audio
+ * @{
+ */
+/** @file
+ * Common USB functions.
+ */
+#include <adt/list.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <loc.h>
+#include <str.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <libarch/types.h>
+
+#include "protocol.h"
+#include "client.h"
+
+/** Stream structure */
+typedef struct hound_stream {
+	/** link in context's list */
+	link_t link;
+	/** audio data format fo the stream */
+	pcm_format_t format;
+	/** IPC exchange representing the stream (in STREAM MODE) */
+	async_exch_t *exch;
+	/** parent context */
+	hound_context_t *context;
+	/** Stream flags */
+	int flags;
+} hound_stream_t;
+
+/**
+ * Linked list isntacen helper function.
+ * @param l link
+ * @return hound stream isntance.
+ */
+static inline hound_stream_t * hound_stream_from_link(link_t *l)
+{
+	return l ? list_get_instance(l, hound_stream_t, link) : NULL;
+}
+
+/** Hound client context structure */
+typedef struct hound_context {
+	/** Audio session */
+	hound_sess_t *session;
+	/** context name, reported to the daemon */
+	const char *name;
+	/** True if the instance is record context */
+	bool record;
+	/** List of associated streams */
+	list_t stream_list;
+	/** Main stream helper structure */
+	struct {
+		hound_stream_t *stream;
+		pcm_format_t format;
+		size_t bsize;
+	} main;
+	/** Assigned context id */
+	hound_context_id_t id;
+} hound_context_t;
+
+/**
+ * Alloc and initialize context structure.
+ * @param name Base for the real context name, will add task id.
+ * @param record True if the new context should capture audio data.
+ * @param format PCM format of the main pipe.
+ * @param bsize Server size buffer size of the main stream.
+ * @return valid pointer to initialized structure on success, NULL on failure
+ */
+static hound_context_t *hound_context_create(const char *name, bool record,
+    pcm_format_t format, size_t bsize)
+{
+	hound_context_t *new_context = malloc(sizeof(hound_context_t));
+	if (new_context) {
+		char *cont_name;
+		int ret = asprintf(&cont_name, "%" PRIu64 ":%s",
+		    task_get_id(), name);
+		if (ret < 0) {
+			free(new_context);
+			return NULL;
+		}
+		list_initialize(&new_context->stream_list);
+		new_context->name = cont_name;
+		new_context->record = record;
+		new_context->session = hound_service_connect(HOUND_SERVICE);
+		new_context->main.stream = NULL;
+		new_context->main.format = format;
+		new_context->main.bsize = bsize;
+		if (!new_context->session) {
+			free(new_context->name);
+			free(new_context);
+			return NULL;
+		}
+		new_context->id = hound_service_register_context(
+		    new_context->session, new_context->name, record);
+		if (hound_context_id_err(new_context->id) != EOK) {
+			hound_service_disconnect(new_context->session);
+			free(new_context->name);
+			free(new_context);
+			return NULL;
+		}
+	}
+	return new_context;
+}
+
+/**
+ * Playback context helper function.
+ * @param name Base for the real context name, will add task id.
+ * @param format PCM format of the main pipe.
+ * @param bsize Server size buffer size of the main stream.
+ * @return valid pointer to initialized structure on success, NULL on failure
+ */
+hound_context_t * hound_context_create_playback(const char *name,
+    pcm_format_t format, size_t bsize)
+{
+	return hound_context_create(name, false, format, bsize);
+}
+
+/**
+ * Record context helper function.
+ * @param name Base for the real context name, will add task id.
+ * @param format PCM format of the main pipe.
+ * @param bsize Server size buffer size of the main stream.
+ * @return valid pointer to initialized structure on success, NULL on failure
+ */
+hound_context_t * hound_context_create_capture(const char *name,
+    pcm_format_t format, size_t bsize)
+{
+	return hound_context_create(name, true, format, bsize);
+}
+
+/**
+ * Correctly dispose of the hound context structure.
+ * @param hound context to remove.
+ *
+ * The function will destroy all associated streams first. Pointers
+ * to these structures will become invalid and the function will block
+ * if any of these stream needs to be drained first.
+ */
+void hound_context_destroy(hound_context_t *hound)
+{
+	assert(hound);
+
+	while (!list_empty(&hound->stream_list)) {
+		link_t *first = list_first(&hound->stream_list);
+		hound_stream_t *stream = hound_stream_from_link(first);
+		hound_stream_destroy(stream);
+	}
+
+	hound_service_unregister_context(hound->session, hound->id);
+	hound_service_disconnect(hound->session);
+	free(hound->name);
+	free(hound);
+}
+
+/**
+ * Get a list of possible connection targets.
+ * @param[in] hound Hound context.
+ * @param[out] names list of target string ids.
+ * @param[out] count Number of elements in @p names list
+ * @return Error code.
+ *
+ * The function will return deice sinks or source based on the context type.
+ */
+int hound_context_get_available_targets(hound_context_t *hound,
+    const char ***names, size_t *count)
+{
+	assert(hound);
+	assert(names);
+	assert(count);
+	return hound_service_get_list_all(hound->session, names, count,
+	    hound->record ? HOUND_SOURCE_DEVS : HOUND_SINK_DEVS);
+}
+
+/**
+ * Get a list of targets connected to the context.
+ * @param[in] hound Hound context.
+ * @param[out] names list of target string ids.
+ * @param[out] count Number of elements in @p names list
+ * @return Error code.
+ */
+int hound_context_get_connected_targets(hound_context_t *hound,
+    const char ***names, size_t *count)
+{
+	assert(hound);
+	assert(names);
+	assert(count);
+	return hound_service_get_list(hound->session, names, count,
+	    HOUND_CONNECTED | (hound->record ?
+	        HOUND_SOURCE_DEVS : HOUND_SINK_DEVS), hound->name);
+}
+
+/**
+ * Create a new connection to the target.
+ * @param hound Hound context.
+ * @param target String identifier of the desired target.
+ * @return Error code.
+ *
+ * The function recognizes special 'HOUND_DEFAULT_TARGET' and will
+ * connect to the first possible target if it is passed this value.
+ */
+int hound_context_connect_target(hound_context_t *hound, const char* target)
+{
+	assert(hound);
+	assert(target);
+
+	const char **tgt = NULL;
+	size_t count = 1;
+	int ret = EOK;
+	if (str_cmp(target, HOUND_DEFAULT_TARGET) == 0) {
+		ret = hound_context_get_available_targets(hound, &tgt, &count);
+		if (ret != EOK)
+			return ret;
+		target = tgt[0];
+	}
+	//TODO handle all-targets
+
+	if (hound->record) {
+		ret = hound_service_connect_source_sink(
+		    hound->session, target, hound->name);
+	} else {
+		ret = hound_service_connect_source_sink(
+		    hound->session, hound->name, target);
+	}
+	if (tgt)
+		free(tgt[0]);
+	free(tgt);
+	return ret;
+}
+
+/**
+ * Destroy a connection to the target.
+ * @param hound Hound context.
+ * @param target String identifier of the desired target.
+ * @return Error code.
+ */
+int hound_context_disconnect_target(hound_context_t *hound, const char* target)
+{
+	assert(hound);
+	assert(target);
+	//TODO handle all-targets
+	if (hound->record) {
+		return hound_service_disconnect_source_sink(
+		    hound->session, target, hound->name);
+	} else {
+		return hound_service_disconnect_source_sink(
+		    hound->session, hound->name, target);
+	}
+}
+
+/**
+ * Create a new stream associated with the context.
+ * @param hound Hound context.
+ * @param flags new stream flags.
+ * @param format new stream PCM format.
+ * @param bzise new stream server side buffer size (in bytes)
+ * @return Valid pointer to a stream instance, NULL on failure.
+ */
+hound_stream_t *hound_stream_create(hound_context_t *hound, unsigned flags,
+    pcm_format_t format, size_t bsize)
+{
+	assert(hound);
+	async_exch_t *stream_exch = async_exchange_begin(hound->session);
+	if (!stream_exch)
+		return NULL;
+	hound_stream_t *new_stream = malloc(sizeof(hound_stream_t));
+	if (new_stream) {
+		link_initialize(&new_stream->link);
+		new_stream->exch = stream_exch;
+		new_stream->format = format;
+		new_stream->context = hound;
+		new_stream->flags = flags;
+		const int ret = hound_service_stream_enter(new_stream->exch,
+		    hound->id, flags, format, bsize);
+		if (ret != EOK) {
+			async_exchange_end(new_stream->exch);
+			free(new_stream);
+			return NULL;
+		}
+		list_append(&new_stream->link, &hound->stream_list);
+	}
+	return new_stream;
+}
+
+/**
+ * Destroy existing stream
+ * @param stream The stream to destroy.
+ *
+ * Function will wait until the server side buffer is empty if the
+ * HOUND_STREAM_DRAIN_ON_EXIT flag was set on creation.
+ */
+void hound_stream_destroy(hound_stream_t *stream)
+{
+	if (stream) {
+		if (stream->flags & HOUND_STREAM_DRAIN_ON_EXIT)
+			hound_service_stream_drain(stream->exch);
+		hound_service_stream_exit(stream->exch);
+		async_exchange_end(stream->exch);
+		list_remove(&stream->link);
+		free(stream);
+	}
+}
+
+/**
+ * Send new data to a stream.
+ * @param stream The target stream
+ * @param data data buffer
+ * @param size size of the @p data buffer.
+ * @return error code.
+ */
+int hound_stream_write(hound_stream_t *stream, const void *data, size_t size)
+{
+	assert(stream);
+	if (!data || size == 0)
+		return EBADMEM;
+	return hound_service_stream_write(stream->exch, data, size);
+}
+
+/**
+ * Get data from a stream.
+ * @param stream The target stream.
+ * @param data data buffer.
+ * @param size size of the @p data buffer.
+ * @return error code.
+ */
+int hound_stream_read(hound_stream_t *stream, void *data, size_t size)
+{
+	assert(stream);
+	if (!data || size == 0)
+		return EBADMEM;
+	return hound_service_stream_read(stream->exch, data, size);
+}
+
+/**
+ * Wait until the server side buffer is empty.
+ * @param stream The stream that shoulod be drained.
+ * @return Error code.
+ */
+int hound_stream_drain(hound_stream_t *stream)
+{
+	assert(stream);
+	return hound_service_stream_drain(stream->exch);
+}
+
+/**
+ * Main stream getter function.
+ * @param hound Houndcontext.
+ * @return Valid stream pointer, NULL on failure.
+ *
+ * The function will create new stream, or return a pointer to the exiting one
+ * if it exists.
+ */
+static hound_stream_t * hound_get_main_stream(hound_context_t *hound)
+{
+	assert(hound);
+	if (!hound->main.stream)
+		hound->main.stream = hound_stream_create(hound,
+		    HOUND_STREAM_DRAIN_ON_EXIT,hound->main.format,
+		    hound->main.bsize);
+	return hound->main.stream;
+}
+
+/**
+ * Send new data to the main stream.
+ * @param stream The target stream
+ * @param data data buffer
+ * @param size size of the @p data buffer.
+ * @return error code.
+ */
+int hound_write_main_stream(hound_context_t *hound,
+    const void *data, size_t size)
+{
+	assert(hound);
+	if (hound->record)
+		return EINVAL;
+
+	hound_stream_t *mstream = hound_get_main_stream(hound);
+	if (!mstream)
+		return ENOMEM;
+	return hound_stream_write(mstream, data, size);
+}
+
+/**
+ * Get data from the main stream.
+ * @param stream The target stream
+ * @param data data buffer
+ * @param size size of the @p data buffer.
+ * @return error code.
+ */
+int hound_read_main_stream(hound_context_t *hound, void *data, size_t size)
+{
+	assert(hound);
+	if (!hound->record)
+		return EINVAL;
+	hound_stream_t *mstream = hound_get_main_stream(hound);
+	if (!mstream)
+		return ENOMEM;
+	return hound_stream_read(mstream, data, size);
+}
+
+/**
+ * Destroy the old main stream and replace it with a new one with fresh data.
+ * @param hound Hound context.
+ * @param data data buffer.
+ * @param size size of the @p data buffer.
+ * @return error code.
+ *
+ * NOT IMPLEMENTED
+ */
+int hound_write_replace_main_stream(hound_context_t *hound,
+    const void *data, size_t size)
+{
+	assert(hound);
+	if (!data || size == 0)
+		return EBADMEM;
+	// TODO implement
+	return ENOTSUP;
+}
+
+/**
+ * Destroy the old main stream and replace it with a new one using new params.
+ * @param hound Hound context.
+ * @param channels
+ * @return error code.
+ *
+ * NOT IMPLEMENTED
+ */
+int hound_context_set_main_stream_params(hound_context_t *hound,
+    pcm_format_t format, size_t bsize)
+{
+	assert(hound);
+	// TODO implement
+	return ENOTSUP;
+}
+
+/**
+ * Write data immediately to a new stream, and wait for it to drain.
+ * @param hound Hound context.
+ * @param format pcm data format.
+ * @param data data buffer
+ * @param size @p data buffer size
+ * @return Error code.
+ *
+ * This functnion creates a new stream writes the data, ti waits for the stream
+ * to drain and destroys it before returning.
+ */
+int hound_write_immediate(hound_context_t *hound, pcm_format_t format,
+    const void *data, size_t size)
+{
+	assert(hound);
+	if (hound->record)
+		return EINVAL;
+	hound_stream_t *tmpstream = hound_stream_create(hound, 0, format, size);
+	if (!tmpstream)
+		return ENOMEM;
+	const int ret = hound_stream_write(tmpstream, data, size);
+	if (ret == EOK) {
+		//TODO drain?
+		hound_service_stream_drain(tmpstream->exch);
+	}
+	hound_stream_destroy(tmpstream);
+	return ret;
+}
+/**
+ * @}
+ */
Index: uspace/lib/hound/src/protocol.c
===================================================================
--- uspace/lib/hound/src/protocol.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/hound/src/protocol.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,781 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup libhound
+ * @addtogroup audio
+ * @{
+ */
+/** @file
+ * Common USB functions.
+ */
+#include <adt/list.h>
+#include <errno.h>
+#include <loc.h>
+#include <macros.h>
+#include <str.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <libarch/types.h>
+
+#include "protocol.h"
+#include "client.h"
+#include "server.h"
+
+enum ipc_methods {
+	/** Create new context representation on the server side */
+	IPC_M_HOUND_CONTEXT_REGISTER = IPC_FIRST_USER_METHOD,
+	/** Release existing context representation on the server side */
+	IPC_M_HOUND_CONTEXT_UNREGISTER,
+	/** Request list of objects */
+	IPC_M_HOUND_GET_LIST,
+	/** Create new connection */
+	IPC_M_HOUND_CONNECT,
+	/** Destroy connection */
+	IPC_M_HOUND_DISCONNECT,
+	/** Switch IPC pipe to stream mode */
+	IPC_M_HOUND_STREAM_ENTER,
+	/** Switch IPC pipe back to general mode */
+	IPC_M_HOUND_STREAM_EXIT,
+	/** Wait until there is no data in the stream */
+	IPC_M_HOUND_STREAM_DRAIN,
+};
+
+
+/** PCM format conversion helper structure */
+typedef union {
+	struct {
+		uint16_t rate;
+		uint8_t channels;
+		uint8_t format;
+	} f __attribute__((packed));
+	sysarg_t arg;
+} format_convert_t;
+
+
+/****
+ * CLIENT
+ ****/
+
+/** Well defined service name */
+const char *HOUND_SERVICE = "audio/hound";
+
+/**
+ * Start a new audio session.
+ * @param service Named service typically 'HOUND_SERVICE' constant.
+ * @return Valid session on success, NULL on failure.
+ */
+hound_sess_t *hound_service_connect(const char *service)
+{
+	service_id_t id = 0;
+	const int ret =
+	    loc_service_get_id(service, &id, IPC_FLAG_BLOCKING);
+	if (ret != EOK)
+		return NULL;
+	return loc_service_connect(EXCHANGE_PARALLEL, id, IPC_FLAG_BLOCKING);
+}
+
+/**
+ * End an existing audio session.
+ * @param sess The session.
+ */
+void hound_service_disconnect(hound_sess_t *sess)
+{
+	if (sess)
+		async_hangup(sess);
+}
+
+/**
+ * Register a named application context to the audio server.
+ * @param sess Valid audio session.
+ * @param name Valid string identifier
+ * @param record True if the application context wishes to receive data.
+ * @return Valid ID on success, Error code on failure.
+ */
+hound_context_id_t hound_service_register_context(hound_sess_t *sess,
+    const char *name, bool record)
+{
+	assert(sess);
+	assert(name);
+	ipc_call_t call;
+	async_exch_t *exch = async_exchange_begin(sess);
+	aid_t mid =
+	    async_send_1(exch, IPC_M_HOUND_CONTEXT_REGISTER, record, &call);
+	int ret = mid ? EOK : EPARTY;
+
+	if (ret == EOK)
+		ret = async_data_write_start(exch, name, str_size(name));
+	else
+		async_forget(mid);
+
+	if (ret == EOK)
+		async_wait_for(mid, (sysarg_t *)&ret);
+
+	async_exchange_end(exch);
+	return ret == EOK ? (hound_context_id_t)IPC_GET_ARG1(call) : ret;
+}
+
+/**
+ * Remove application context from the server's list.
+ * @param sess Valid audio session.
+ * @param id Valid context id.
+ * @return Error code.
+ */
+int hound_service_unregister_context(hound_sess_t *sess, hound_context_id_t id)
+{
+	assert(sess);
+	async_exch_t *exch = async_exchange_begin(sess);
+	const int ret =
+	    async_req_1_0(exch, IPC_M_HOUND_CONTEXT_UNREGISTER, id);
+	async_exchange_end(exch);
+	return ret;
+}
+
+/**
+ * Retrieve a list of server side actors.
+ * @param[in] sess Valid audio session.
+ * @param[out] ids list of string identifiers.
+ * @param[out] count Number of elements int the @p ids list.
+ * @param[in] flags list requirements.
+ * @param[in] connection name of target actor. Used only if the list should
+ *            contain connected actors.
+ * @retval Error code.
+ */
+int hound_service_get_list(hound_sess_t *sess, const char ***ids, size_t *count,
+    int flags, const char *connection)
+{
+	assert(sess);
+	assert(ids);
+	assert(count);
+
+	if (connection && !(flags & HOUND_CONNECTED))
+		return EINVAL;
+
+	async_exch_t *exch = async_exchange_begin(sess);
+	if (!exch)
+		return ENOMEM;
+
+	ipc_call_t res_call;
+	aid_t mid = async_send_3(exch, IPC_M_HOUND_GET_LIST, flags, *count,
+	    (bool)connection, &res_call);
+
+	int ret = EOK;
+	if (mid && connection)
+		ret = async_data_write_start(exch, connection,
+		    str_size(connection));
+
+	if (ret == EOK)
+		async_wait_for(mid, (sysarg_t*)&ret);
+
+	if (ret != EOK) {
+		async_exchange_end(exch);
+		return ret;
+	}
+	unsigned name_count = IPC_GET_ARG1(res_call);
+
+	/* Start receiving names */
+	const char **names = NULL;
+	if (name_count) {
+		size_t *sizes = calloc(name_count, sizeof(size_t));
+		names = calloc(name_count, sizeof(char *));
+		if (!names || !sizes)
+			ret = ENOMEM;
+
+		if (ret == EOK)
+			ret = async_data_read_start(exch, sizes,
+			    name_count * sizeof(size_t));
+		for (unsigned i = 0; i < name_count && ret == EOK; ++i) {
+			char *name = malloc(sizes[i] + 1);
+			if (name) {
+				memset(name, 0, sizes[i] + 1);
+				ret = async_data_read_start(exch, name, sizes[i]);
+				names[i] = name;
+			} else {
+				ret = ENOMEM;
+			}
+		}
+		free(sizes);
+	}
+	async_exchange_end(exch);
+	if (ret != EOK) {
+		for (unsigned i = 0; i < name_count; ++i)
+			free(names[i]);
+		free(names);
+	} else {
+		*ids = names;
+		*count = name_count;
+	}
+	return ret;
+}
+
+/**
+ * Create a new connection between a source and a sink.
+ * @param sess Valid audio session.
+ * @param source Source name, valid string.
+ * @param sink Sink name, valid string.
+ * @return Error code.
+ */
+int hound_service_connect_source_sink(hound_sess_t *sess, const char *source,
+    const char *sink)
+{
+	assert(sess);
+	assert(source);
+	assert(sink);
+
+	async_exch_t *exch = async_exchange_begin(sess);
+	if (!exch)
+		return ENOMEM;
+	ipc_call_t call;
+	aid_t id = async_send_0(exch, IPC_M_HOUND_CONNECT, &call);
+	int ret = id ? EOK : EPARTY;
+	if (ret == EOK)
+		ret = async_data_write_start(exch, source, str_size(source));
+	if (ret == EOK)
+		ret = async_data_write_start(exch, sink, str_size(sink));
+	async_wait_for(id, (sysarg_t*)&ret);
+	async_exchange_end(exch);
+	return ret;
+}
+
+/**
+ * Destroy an existing connection between a source and a sink.
+ * @param sess Valid audio session.
+ * @param source Source name, valid string.
+ * @param sink Sink name, valid string.
+ * @return Error code.
+ */
+int hound_service_disconnect_source_sink(hound_sess_t *sess, const char *source,
+    const char *sink)
+{
+	assert(sess);
+	async_exch_t *exch = async_exchange_begin(sess);
+	if (!exch)
+		return ENOMEM;
+	ipc_call_t call;
+	aid_t id = async_send_0(exch, IPC_M_HOUND_DISCONNECT, &call);
+	int ret = id ? EOK : EPARTY;
+	if (ret == EOK)
+		ret = async_data_write_start(exch, source, str_size(source));
+	if (ret == EOK)
+		ret = async_data_write_start(exch, sink, str_size(sink));
+	async_wait_for(id, (sysarg_t*)&ret);
+	async_exchange_end(exch);
+	return ENOTSUP;
+}
+
+/**
+ * Switch IPC exchange to a STREAM mode.
+ * @param exch IPC exchange.
+ * @param id context id this stream should be associated with
+ * @param flags set stream properties
+ * @param format format of the new stream.
+ * @param bsize size of the server side buffer.
+ * @return Error code.
+ */
+int hound_service_stream_enter(async_exch_t *exch, hound_context_id_t id,
+    int flags, pcm_format_t format, size_t bsize)
+{
+	const format_convert_t c = { .f = {
+		.channels = format.channels,
+		.rate = format.sampling_rate / 100,
+		.format = format.sample_format,
+	}};
+	return async_req_4_0(exch, IPC_M_HOUND_STREAM_ENTER, id, flags,
+	    c.arg, bsize);
+}
+
+/**
+ * Destroy existing stream and return IPC exchange to general mode.
+ * @param exch IPC exchange.
+ * @return Error code.
+ */
+int hound_service_stream_exit(async_exch_t *exch)
+{
+	return async_req_0_0(exch, IPC_M_HOUND_STREAM_EXIT);
+}
+
+/**
+ * Wait until the server side buffer is empty.
+ * @param exch IPC exchange.
+ * @return Error code.
+ */
+int hound_service_stream_drain(async_exch_t *exch)
+{
+	return async_req_0_0(exch, IPC_M_HOUND_STREAM_DRAIN);
+}
+
+/**
+ * Write audio data to a stream.
+ * @param exch IPC exchange in STREAM MODE.
+ * @param data Audio data buffer.
+ * @size size of the buffer
+ * @return Error code.
+ */
+int hound_service_stream_write(async_exch_t *exch, const void *data, size_t size)
+{
+	return async_data_write_start(exch, data, size);
+}
+
+/**
+ * Read data from a stream.
+ * @param exch IPC exchange in STREAM MODE.
+ * @param data Audio data buffer.
+ * @size size of the buffer
+ * @return Error code.
+ */
+int hound_service_stream_read(async_exch_t *exch, void *data, size_t size)
+{
+	return async_data_read_start(exch, data, size);
+}
+
+/****
+ * SERVER
+ ****/
+
+static void hound_server_read_data(void *stream);
+static void hound_server_write_data(void *stream);
+static const hound_server_iface_t *server_iface;
+
+/**
+ * Set hound server interface implementation.
+ * @param iface Initialized Hound server interface.
+ */
+void hound_service_set_server_iface(const hound_server_iface_t *iface)
+{
+	server_iface = iface;
+}
+
+/**
+ * Server side implementation of the hound protocol. IPC connection handler.
+ * @param iid initial call id
+ * @param icall pointer to initial call structure.
+ * @param arg (unused)
+ */
+void hound_connection_handler(ipc_callid_t iid, ipc_call_t *icall, void *arg)
+{
+	/* Accept connection if there is a valid iface*/
+	if (server_iface) {
+		async_answer_0(iid, EOK);
+	} else {
+		async_answer_0(iid, ENOTSUP);
+		return;
+	}
+
+	while (1) {
+		ipc_call_t call;
+		ipc_callid_t callid = async_get_call(&call);
+		switch (IPC_GET_IMETHOD(call)) {
+		case IPC_M_HOUND_CONTEXT_REGISTER: {
+			/* check interface functions */
+			if (!server_iface || !server_iface->add_context) {
+				async_answer_0(callid, ENOTSUP);
+				break;
+			}
+			bool record = IPC_GET_ARG1(call);
+			void *name;
+
+			/* Get context name */
+			int ret =
+			    async_data_write_accept(&name, true, 0, 0, 0, 0);
+			if (ret != EOK) {
+				async_answer_0(callid, ret);
+				break;
+			}
+			hound_context_id_t id = 0;
+			ret = server_iface->add_context(server_iface->server,
+			    &id, name, record);
+			/** new context should create a copy */
+			free(name);
+			if (ret != EOK) {
+				async_answer_0(callid, ret);
+			} else {
+				async_answer_1(callid, EOK, id);
+			}
+			break;
+		}
+		case IPC_M_HOUND_CONTEXT_UNREGISTER: {
+			/* check interface functions */
+			if (!server_iface || !server_iface->rem_context) {
+				async_answer_0(callid, ENOTSUP);
+				break;
+			}
+
+			/* get id, 1st param */
+			hound_context_id_t id = IPC_GET_ARG1(call);
+			const int ret =
+			    server_iface->rem_context(server_iface->server, id);
+			async_answer_0(callid, ret);
+			break;
+		}
+		case IPC_M_HOUND_GET_LIST: {
+			/* check interface functions */
+			if (!server_iface || !server_iface->get_list) {
+				async_answer_0(callid, ENOTSUP);
+				break;
+			}
+
+			const char **list = NULL;
+			const int flags = IPC_GET_ARG1(call);
+			size_t count = IPC_GET_ARG2(call);
+			const bool conn = IPC_GET_ARG3(call);
+			char *conn_name = NULL;
+			int ret = EOK;
+
+			/* get connected actor name if provided */
+			if (conn)
+				ret = async_data_write_accept(
+				    (void**)&conn_name, true, 0, 0, 0, 0);
+
+			if (ret == EOK)
+				ret = server_iface->get_list(
+				    server_iface->server, &list, &count,
+				    conn_name, flags);
+			free(conn_name);
+
+			/* Alloc string sizes array */
+			size_t *sizes = NULL;
+			if (count)
+				sizes = calloc(count, sizeof(size_t));
+			if (count && !sizes)
+				ret = ENOMEM;
+			async_answer_1(callid, ret, count);
+
+			/* We are done */
+			if (count == 0 || ret != EOK)
+				break;
+
+			/* Prepare sizes table */
+			for (unsigned i = 0; i < count; ++i)
+				sizes[i] = str_size(list[i]);
+
+			/* Send sizes table */
+			ipc_callid_t id;
+			if (async_data_read_receive(&id, NULL)) {
+				ret = async_data_read_finalize(id, sizes,
+				    count * sizeof(size_t));
+			}
+			free(sizes);
+
+			/* Proceed to send names */
+			for (unsigned i = 0; i < count; ++i) {
+				size_t size = str_size(list[i]);
+				ipc_callid_t id;
+				if (ret == EOK &&
+				    async_data_read_receive(&id, NULL)) {
+					ret = async_data_read_finalize(id,
+					    list[i], size);
+				}
+				free(list[i]);
+			}
+			free(list);
+			break;
+		}
+		case IPC_M_HOUND_CONNECT: {
+			/* check interface functions */
+			if (!server_iface || !server_iface->connect) {
+				async_answer_0(callid, ENOTSUP);
+				break;
+			}
+
+			void *source = NULL;
+			void *sink = NULL;
+
+			/* read source name */
+			int ret =
+			    async_data_write_accept(&source, true, 0, 0, 0, 0);
+			/* read sink name */
+			if (ret == EOK)
+				ret = async_data_write_accept(&sink,
+				    true, 0, 0, 0, 0);
+
+			if (ret == EOK)
+				ret = server_iface->connect(
+				    server_iface->server, source, sink);
+			free(source);
+			free(sink);
+			async_answer_0(callid, ret);
+			break;
+		}
+		case IPC_M_HOUND_DISCONNECT: {
+			/* check interface functions */
+			if (!server_iface || !server_iface->disconnect) {
+				async_answer_0(callid, ENOTSUP);
+				break;
+			}
+
+			void *source = NULL;
+			void *sink = NULL;
+
+			/* read source name */
+			int ret =
+			    async_data_write_accept(&source, true, 0, 0, 0, 0);
+			/*read sink name */
+			if (ret == EOK)
+				ret = async_data_write_accept(&sink,
+				    true, 0, 0, 0, 0);
+			if (ret == EOK)
+				ret = server_iface->connect(
+				    server_iface->server, source, sink);
+			free(source);
+			free(sink);
+			async_answer_0(callid, ret);
+			break;
+		}
+		case IPC_M_HOUND_STREAM_ENTER: {
+			/* check interface functions */
+			if (!server_iface || !server_iface->is_record_context
+			    || !server_iface->add_stream
+			    || !server_iface->rem_stream) {
+				async_answer_0(callid, ENOTSUP);
+				break;
+			}
+
+			/* get parameters */
+			hound_context_id_t id = IPC_GET_ARG1(call);
+			const int flags = IPC_GET_ARG2(call);
+			const format_convert_t c = {.arg = IPC_GET_ARG3(call)};
+			const pcm_format_t f = {
+			    .sampling_rate = c.f.rate * 100,
+			    .channels = c.f.channels,
+			    .sample_format = c.f.format,
+			};
+			size_t bsize = IPC_GET_ARG4(call);
+
+			void *stream;
+			int ret = server_iface->add_stream(server_iface->server,
+			    id, flags, f, bsize, &stream);
+			if (ret != EOK) {
+				async_answer_0(callid, ret);
+				break;
+			}
+			const bool rec = server_iface->is_record_context(
+			    server_iface->server, id);
+			if (rec) {
+				if(server_iface->stream_data_read) {
+					async_answer_0(callid, EOK);
+					/* start answering read calls */
+					hound_server_write_data(stream);
+					server_iface->rem_stream(
+					    server_iface->server, stream);
+				} else {
+					async_answer_0(callid, ENOTSUP);
+				}
+			} else {
+				if (server_iface->stream_data_write) {
+					async_answer_0(callid, EOK);
+					/* accept write calls */
+					hound_server_read_data(stream);
+					server_iface->rem_stream(
+					    server_iface->server, stream);
+				} else {
+					async_answer_0(callid, ENOTSUP);
+				}
+			}
+			break;
+		}
+		case IPC_M_HOUND_STREAM_EXIT:
+		case IPC_M_HOUND_STREAM_DRAIN:
+			/* Stream exit/drain is only allowed in stream context*/
+			async_answer_0(callid, EINVAL);
+			break;
+		default:
+			async_answer_0(callid, ENOTSUP);
+			return;
+		}
+	}
+}
+
+/**
+ * Read data and push it to the stream.
+ * @param stream target stream, will push data there.
+ */
+static void hound_server_read_data(void *stream)
+{
+	ipc_callid_t callid;
+	ipc_call_t call;
+	size_t size = 0;
+	int ret_answer = EOK;
+	/* accept data write or drain */
+	while (async_data_write_receive_call(&callid, &call, &size)
+	    || (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN)) {
+		/* check drain first */
+		if (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN) {
+			int ret = ENOTSUP;
+			if (server_iface->drain_stream)
+				ret = server_iface->drain_stream(stream);
+			async_answer_0(callid, ret);
+			continue;
+		}
+
+		/* there was an error last time */
+		if (ret_answer != EOK) {
+			async_answer_0(callid, ret_answer);
+			continue;
+		}
+
+		char *buffer = malloc(size);
+		if (!buffer) {
+			async_answer_0(callid, ENOMEM);
+			continue;
+		}
+		const int ret = async_data_write_finalize(callid, buffer, size);
+		if (ret == EOK) {
+			/* push data to stream */
+			ret_answer = server_iface->stream_data_write(
+			    stream, buffer, size);
+		}
+	}
+	const int ret = IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_EXIT
+	    ? EOK : EINVAL;
+
+	async_answer_0(callid, ret);
+}
+
+/**
+ * Accept reads and pull data from the stream.
+ * @param stream target stream, will pull data from there.
+ */
+static void hound_server_write_data(void *stream)
+{
+
+	ipc_callid_t callid;
+	ipc_call_t call;
+	size_t size = 0;
+	int ret_answer = EOK;
+	/* accept data read and drain */
+	while (async_data_read_receive_call(&callid, &call, &size)
+	    || (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN)) {
+		/* drain does not make much sense but it is allowed */
+		if (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN) {
+			int ret = ENOTSUP;
+			if (server_iface->drain_stream)
+				ret = server_iface->drain_stream(stream);
+			async_answer_0(callid, ret);
+			continue;
+		}
+		/* there was an error last time */
+		if (ret_answer != EOK) {
+			async_answer_0(callid, ret_answer);
+			continue;
+		}
+		char *buffer = malloc(size);
+		if (!buffer) {
+			async_answer_0(callid, ENOMEM);
+			continue;
+		}
+		int ret = server_iface->stream_data_read(stream, buffer, size);
+		if (ret == EOK) {
+			ret_answer =
+			    async_data_read_finalize(callid, buffer, size);
+		}
+	}
+	const int ret = IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_EXIT
+	    ? EOK : EINVAL;
+
+	async_answer_0(callid, ret);
+}
+
+
+/***
+ * SERVER SIDE
+ ***/
+
+/**
+ * Register new hound service to the location service.
+ * @param[in] name server name
+ * @param[out] id assigned service id.
+ * @return Error code.
+ */
+int hound_server_register(const char *name, service_id_t *id)
+{
+	if (!name || !id)
+		return EINVAL;
+
+	int ret = loc_server_register(name);
+	if (ret != EOK)
+		return ret;
+
+	return loc_service_register(HOUND_SERVICE, id);
+}
+
+/**
+ * Unregister server from the location service.
+ * @param id previously assigned service id.
+ */
+void hound_server_unregister(service_id_t id)
+{
+	loc_service_unregister(id);
+}
+
+/**
+ * Set callback on device category change event.
+ * @param cb Callback function.
+ * @return Error code.
+ */
+int hound_server_set_device_change_callback(dev_change_callback_t cb)
+{
+	return loc_register_cat_change_cb(cb);
+}
+
+/**
+ * Walk through all device in the audio-pcm category.
+ * @param callback Function to call on every device.
+ * @return Error code.
+ */
+int hound_server_devices_iterate(device_callback_t callback)
+{
+	if (!callback)
+		return EINVAL;
+	static bool resolved = false;
+	static category_id_t cat_id = 0;
+
+	if (!resolved) {
+		const int ret = loc_category_get_id("audio-pcm", &cat_id,
+		    IPC_FLAG_BLOCKING);
+		if (ret != EOK)
+			return ret;
+		resolved = true;
+	}
+
+	service_id_t *svcs = NULL;
+	size_t count = 0;
+	const int ret = loc_category_get_svcs(cat_id, &svcs, &count);
+	if (ret != EOK)
+		return ret;
+
+	for (unsigned i = 0; i < count; ++i) {
+		char *name = NULL;
+		loc_service_get_name(svcs[i], &name);
+		callback(svcs[i], name);
+		free(name);
+	}
+	free(svcs);
+	return EOK;
+}
+/**
+ * @}
+ */
Index: uspace/lib/pcm/Makefile
===================================================================
--- uspace/lib/pcm/Makefile	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/pcm/Makefile	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2012 Jan Vesely
+# 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 = ../..
+EXTRA_CFLAGS = -Iinclude/pcm -Iinclude
+LIBRARY = libpcm
+
+SOURCES = \
+	src/format.c
+include $(USPACE_PREFIX)/Makefile.common
+
+
Index: uspace/lib/pcm/include/pcm/format.h
===================================================================
--- uspace/lib/pcm/include/pcm/format.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/pcm/include/pcm/format.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup audio
+ * @brief HelenOS sound server
+ * @{
+ */
+/** @file
+ */
+
+#ifndef PCM_FORMAT_H_
+#define PCM_FORMAT_H_
+
+#include <assert.h>
+#include <stdbool.h>
+#include <pcm/sample_format.h>
+
+/** Linear PCM audio parameters */
+typedef struct {
+	unsigned channels;
+	unsigned sampling_rate;
+	pcm_sample_format_t sample_format;
+} pcm_format_t;
+
+extern const pcm_format_t AUDIO_FORMAT_DEFAULT;
+extern const pcm_format_t AUDIO_FORMAT_ANY;
+
+/**
+ * Frame size helper function.
+ * @param a pointer to a PCM format structure.
+ * @return Size in bytes.
+ */
+static inline size_t pcm_format_frame_size(const pcm_format_t *a)
+{
+	return pcm_sample_format_frame_size(a->channels, a->sample_format);
+}
+
+/**
+ * Convert byte size to frame count.
+ * @param size Byte-size.
+ * @param a pointer to a PCM format structure.
+ * @return Frame count.
+ */
+static inline size_t pcm_format_size_to_frames(size_t size,
+    const pcm_format_t *a)
+{
+	return pcm_sample_format_size_to_frames(size, a->channels,
+	    a->sample_format);
+}
+
+/**
+ * Convert byte size to audio playback time.
+ * @param size Byte-size.
+ * @param a pointer to a PCM format structure.
+ * @return Number of microseconds.
+ */
+static inline useconds_t pcm_format_size_to_usec(size_t size,
+    const pcm_format_t *a)
+{
+	return pcm_sample_format_size_to_usec(size, a->sampling_rate,
+	    a->channels, a->sample_format);
+}
+
+bool pcm_format_same(const pcm_format_t *a, const pcm_format_t* b);
+
+/**
+ * Helper function, compares with ANY metaformat.
+ * @param f pointer to format structure.
+ * @return True if @p f points to ANY format, false otherwise.
+ */
+static inline bool pcm_format_is_any(const pcm_format_t *f)
+{
+	return pcm_format_same(f, &AUDIO_FORMAT_ANY);
+}
+void pcm_format_silence(void *dst, size_t size, const pcm_format_t *f);
+int pcm_format_convert_and_mix(void *dst, size_t dst_size, const void *src,
+    size_t src_size, const pcm_format_t *sf, const pcm_format_t *df);
+int pcm_format_mix(void *dst, const void *src, size_t size, const pcm_format_t *f);
+int pcm_format_convert(pcm_format_t a, void* srca, size_t sizea,
+    pcm_format_t b, void* srcb, size_t *sizeb);
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/lib/pcm/include/pcm/sample_format.h
===================================================================
--- uspace/lib/pcm/include/pcm/sample_format.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/pcm/include/pcm/sample_format.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup audio
+ * @brief PCM sample format
+ * @{
+ */
+/** @file
+ */
+
+#ifndef PCM_SAMPLE_FORMAT_H_
+#define PCM_SAMPLE_FORMAT_H_
+
+#include <stdbool.h>
+#include <time.h>
+
+/** Known and supported PCM sample formats */
+typedef enum {
+	PCM_SAMPLE_UINT8,
+	PCM_SAMPLE_SINT8,
+	PCM_SAMPLE_UINT16_LE,
+	PCM_SAMPLE_UINT16_BE,
+	PCM_SAMPLE_SINT16_LE,
+	PCM_SAMPLE_SINT16_BE,
+	PCM_SAMPLE_UINT24_LE,
+	PCM_SAMPLE_UINT24_BE,
+	PCM_SAMPLE_SINT24_LE,
+	PCM_SAMPLE_SINT24_BE,
+	PCM_SAMPLE_UINT24_32_LE,
+	PCM_SAMPLE_UINT24_32_BE,
+	PCM_SAMPLE_SINT24_32_LE,
+	PCM_SAMPLE_SINT24_32_BE,
+	PCM_SAMPLE_UINT32_LE,
+	PCM_SAMPLE_UINT32_BE,
+	PCM_SAMPLE_SINT32_LE,
+	PCM_SAMPLE_SINT32_BE,
+	PCM_SAMPLE_FLOAT32,
+	PCM_SAMPLE_FORMAT_LAST = PCM_SAMPLE_FLOAT32,
+} pcm_sample_format_t;
+
+/**
+ * Query if the format uses signed values.
+ * @param format PCM sample format.
+ * @return True if the format uses signed values, false otherwise.
+ */
+static inline bool pcm_sample_format_is_signed(pcm_sample_format_t format)
+{
+	switch(format) {
+	case PCM_SAMPLE_SINT8:
+	case PCM_SAMPLE_SINT16_LE:
+	case PCM_SAMPLE_SINT16_BE:
+	case PCM_SAMPLE_SINT24_LE:
+	case PCM_SAMPLE_SINT24_BE:
+	case PCM_SAMPLE_SINT24_32_LE:
+	case PCM_SAMPLE_SINT24_32_BE:
+	case PCM_SAMPLE_SINT32_LE:
+	case PCM_SAMPLE_SINT32_BE:
+		return true;
+	case PCM_SAMPLE_UINT8:
+	case PCM_SAMPLE_UINT16_LE:
+	case PCM_SAMPLE_UINT16_BE:
+	case PCM_SAMPLE_UINT24_LE:
+	case PCM_SAMPLE_UINT24_BE:
+	case PCM_SAMPLE_UINT24_32_LE:
+	case PCM_SAMPLE_UINT24_32_BE:
+	case PCM_SAMPLE_UINT32_LE:
+	case PCM_SAMPLE_UINT32_BE:
+	case PCM_SAMPLE_FLOAT32:
+	default:
+		return false;
+	}
+}
+
+/**
+ * Query byte-size of samples.
+ * @param format PCM sample format.
+ * @return Size in bytes of a single sample.
+ */
+static inline size_t pcm_sample_format_size(pcm_sample_format_t format)
+{
+	switch(format) {
+	case PCM_SAMPLE_UINT8:
+	case PCM_SAMPLE_SINT8:
+		return 1;
+	case PCM_SAMPLE_UINT16_LE:
+	case PCM_SAMPLE_UINT16_BE:
+	case PCM_SAMPLE_SINT16_LE:
+	case PCM_SAMPLE_SINT16_BE:
+		return 2;
+	case PCM_SAMPLE_UINT24_LE:
+	case PCM_SAMPLE_UINT24_BE:
+	case PCM_SAMPLE_SINT24_LE:
+	case PCM_SAMPLE_SINT24_BE:
+		return 3;
+	case PCM_SAMPLE_UINT24_32_LE:
+	case PCM_SAMPLE_UINT24_32_BE:
+	case PCM_SAMPLE_SINT24_32_LE:
+	case PCM_SAMPLE_SINT24_32_BE:
+	case PCM_SAMPLE_UINT32_LE:
+	case PCM_SAMPLE_UINT32_BE:
+	case PCM_SAMPLE_SINT32_LE:
+	case PCM_SAMPLE_SINT32_BE:
+	case PCM_SAMPLE_FLOAT32:
+		return 4;
+	default:
+		return 0;
+	}
+}
+
+/**
+ * Query sie of the entire frame.
+ * @param channels Number of samples in every frame.
+ * @param format PCM sample format.
+ * @return Size in bytes.
+ */
+static inline size_t pcm_sample_format_frame_size(unsigned channels,
+    pcm_sample_format_t format)
+{
+	return pcm_sample_format_size(format) * channels;
+}
+
+/**
+ * Count number of frames that fit into a buffer (even incomplete frames).
+ * @param size Size of the buffer.
+ * @param channels Number of samples in every frame.
+ * @param format PCM sample format.
+ * @return Number of frames (even incomplete).
+ */
+static inline size_t pcm_sample_format_size_to_frames(size_t size,
+    unsigned channels, pcm_sample_format_t format)
+{
+	const size_t frame_size = pcm_sample_format_frame_size(channels, format);
+	return (size + frame_size - 1) / frame_size;
+}
+
+/**
+ * Convert byte size to time.
+ * @param size Size of the buffer.
+ * @param sample_rate Samples per second.
+ * @param channels Number of samples in every frame.
+ * @param format PCM sample format.
+ * @return Number of useconds of audio data.
+ */
+static inline useconds_t pcm_sample_format_size_to_usec(size_t size,
+    unsigned sample_rate, unsigned channels, pcm_sample_format_t format)
+{
+	const unsigned long long frames =
+	    pcm_sample_format_size_to_frames(size, channels, format);
+	return (frames * 1000000ULL) / sample_rate;
+}
+
+/**
+ * Get readable name of a sample format.
+ * @param format PCM sample format.
+ * @return Valid string representation.
+ */
+static inline const char * pcm_sample_format_str(pcm_sample_format_t format)
+{
+	switch(format) {
+	case PCM_SAMPLE_UINT8:
+		return "8 bit unsinged";
+	case PCM_SAMPLE_SINT8:
+		return "8 bit singed";
+	case PCM_SAMPLE_UINT16_LE:
+		return "16 bit unsigned(LE)";
+	case PCM_SAMPLE_SINT16_LE:
+		return "16 bit singed(LE)";
+	case PCM_SAMPLE_UINT16_BE:
+		return "16 bit unsigned(BE)";
+	case PCM_SAMPLE_SINT16_BE:
+		return "16 bit signed(BE)";
+	case PCM_SAMPLE_UINT24_LE:
+		return "24 bit unsigned(LE)";
+	case PCM_SAMPLE_SINT24_LE:
+		return "24 bit signed(LE)";
+	case PCM_SAMPLE_UINT24_BE:
+		return "24 bit unsigned(BE)";
+	case PCM_SAMPLE_SINT24_BE:
+		return "24 bit signed(BE)";
+	case PCM_SAMPLE_UINT24_32_LE:
+		return "24 bit(4byte aligned) unsigned(LE)";
+	case PCM_SAMPLE_UINT24_32_BE:
+		return "24 bit(4byte aligned) unsigned(BE)";
+	case PCM_SAMPLE_SINT24_32_LE:
+		return "24 bit(4byte aligned) signed(LE)";
+	case PCM_SAMPLE_SINT24_32_BE:
+		return "24 bit(4byte aligned) signed(BE)";
+	case PCM_SAMPLE_UINT32_LE:
+		return "32 bit unsigned(LE)";
+	case PCM_SAMPLE_UINT32_BE:
+		return "32 bit unsigned(BE)";
+	case PCM_SAMPLE_SINT32_LE:
+		return "32 bit signed(LE)";
+	case PCM_SAMPLE_SINT32_BE:
+		return "32 bit signed(BE)";
+	case PCM_SAMPLE_FLOAT32:
+		return "32 bit float";
+	default:
+		return "Unknown sample format";
+	}
+}
+
+#endif
+
+/**
+ * @}
+ */
+
Index: uspace/lib/pcm/src/format.c
===================================================================
--- uspace/lib/pcm/src/format.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/lib/pcm/src/format.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,335 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup audio
+ * @brief HelenOS sound server
+ * @{
+ */
+/** @file
+ */
+
+#include <assert.h>
+#include <byteorder.h>
+#include <errno.h>
+#include <macros.h>
+#include <stdio.h>
+
+#include "format.h"
+
+#define uint8_t_le2host(x) (x)
+#define host2uint8_t_le(x) (x)
+#define uint8_t_be2host(x) (x)
+#define host2uint8_t_be(x) (x)
+
+#define int8_t_le2host(x) (x)
+#define host2int8_t_le(x) (x)
+
+#define int16_t_le2host(x) uint16_t_le2host(x)
+#define host2int16_t_le(x) host2uint16_t_le(x)
+
+#define int32_t_le2host(x) uint32_t_le2host(x)
+#define host2int32_t_le(x) host2uint32_t_le(x)
+
+#define int8_t_be2host(x) (x)
+#define host2int8_t_be(x) (x)
+
+#define int16_t_be2host(x) uint16_t_be2host(x)
+#define host2int16_t_be(x) host2uint16_t_be(x)
+
+#define int32_t_be2host(x) uint32_t_be2host(x)
+#define host2int32_t_be(x) host2uint32_t_be(x)
+
+// TODO float endian?
+#define float_le2host(x) (x)
+#define float_be2host(x) (x)
+
+#define host2float_le(x) (x)
+#define host2float_be(x) (x)
+
+#define from(x, type, endian) (float)(type ## _ ## endian ## 2host(x))
+#define to(x, type, endian) (float)(host2 ## type ## _ ## endian(x))
+
+/** Default linear PCM format */
+const pcm_format_t AUDIO_FORMAT_DEFAULT = {
+	.channels = 2,
+	.sampling_rate = 44100,
+	.sample_format = PCM_SAMPLE_SINT16_LE,
+	};
+
+/** Special ANY PCM format.
+ * This format is used if the real format is no know or important.
+ */
+const pcm_format_t AUDIO_FORMAT_ANY = {
+	.channels = 0,
+	.sampling_rate = 0,
+	.sample_format = 0,
+	};
+
+static float get_normalized_sample(const void *buffer, size_t size,
+    unsigned frame, unsigned channel, const pcm_format_t *f);
+
+/**
+ * Compare PCM format attribtues.
+ * @param a Format description.
+ * @param b Format description.
+ * @return True if a and b describe the same format, false otherwise.
+ */
+bool pcm_format_same(const pcm_format_t *a, const pcm_format_t* b)
+{
+	assert(a);
+	assert(b);
+	return
+	    a->sampling_rate == b->sampling_rate &&
+	    a->channels == b->channels &&
+	    a->sample_format == b->sample_format;
+}
+
+/**
+ * Fill audio buffer with silence in the specified format.
+ * @param dst Destination audio buffer.
+ * @param size Size of the destination audio buffer.
+ * @param f Pointer to the format description.
+ */
+void pcm_format_silence(void *dst, size_t size, const pcm_format_t *f)
+{
+#define SET_NULL(type, endian, nullv) \
+do { \
+	type *buffer = dst; \
+	const size_t sample_count = size / sizeof(type); \
+	for (unsigned i = 0; i < sample_count; ++i) { \
+		buffer[i] = to((type)nullv, type, endian); \
+	} \
+} while (0)
+
+	switch (f->sample_format) {
+	case PCM_SAMPLE_UINT8:
+		SET_NULL(uint8_t, le, INT8_MIN); break;
+	case PCM_SAMPLE_SINT8:
+		SET_NULL(int8_t, le, 0); break;
+	case PCM_SAMPLE_UINT16_LE:
+		SET_NULL(uint16_t, le, INT16_MIN); break;
+	case PCM_SAMPLE_SINT16_LE:
+		SET_NULL(int16_t, le, 0); break;
+	case PCM_SAMPLE_UINT16_BE:
+		SET_NULL(uint16_t, be, INT16_MIN); break;
+	case PCM_SAMPLE_SINT16_BE:
+		SET_NULL(int16_t, be, 0); break;
+	case PCM_SAMPLE_UINT32_LE:
+		SET_NULL(uint32_t, le, INT32_MIN); break;
+	case PCM_SAMPLE_SINT32_LE:
+		SET_NULL(int32_t, le, 0); break;
+	case PCM_SAMPLE_UINT32_BE:
+		SET_NULL(uint32_t, be, INT32_MIN); break;
+	case PCM_SAMPLE_SINT32_BE:
+		SET_NULL(int32_t, le, 0); break;
+	case PCM_SAMPLE_UINT24_32_LE:
+	case PCM_SAMPLE_SINT24_32_LE:
+	case PCM_SAMPLE_UINT24_32_BE:
+	case PCM_SAMPLE_SINT24_32_BE:
+	case PCM_SAMPLE_UINT24_LE:
+	case PCM_SAMPLE_SINT24_LE:
+	case PCM_SAMPLE_UINT24_BE:
+	case PCM_SAMPLE_SINT24_BE:
+	case PCM_SAMPLE_FLOAT32:
+	default: ;
+	}
+#undef SET_NULL
+}
+
+/**
+ * Mix audio data of the same format and size.
+ * @param dst Destination buffer
+ * @param src Source buffer
+ * @param size Size of both the destination and the source buffer
+ * @param f Pointer to the format descriptor.
+ * @return Error code.
+ */
+int pcm_format_mix(void *dst, const void *src, size_t size, const pcm_format_t *f)
+{
+	return pcm_format_convert_and_mix(dst, size, src, size, f, f);
+}
+
+/**
+ * Add and mix audio data.
+ * @param dst Destination audio buffer
+ * @param dst_size Size of the destination buffer
+ * @param src Source audio buffer
+ * @param src_size Size of the source buffer.
+ * @param sf Pointer to the source format descriptor.
+ * @param df Pointer to the destination format descriptor.
+ * @return Error code.
+ *
+ * Buffers must contain entire frames. Destination buffer is always filled.
+ * If there are not enough data in the source buffer silent data is assumed.
+ */
+int pcm_format_convert_and_mix(void *dst, size_t dst_size, const void *src,
+    size_t src_size, const pcm_format_t *sf, const pcm_format_t *df)
+{
+	if (!dst || !src || !sf || !df)
+		return EINVAL;
+	const size_t src_frame_size = pcm_format_frame_size(sf);
+	if ((src_size % src_frame_size) != 0)
+		return EINVAL;
+
+	const size_t dst_frame_size = pcm_format_frame_size(df);
+	if ((dst_size % dst_frame_size) != 0)
+		return EINVAL;
+
+	/* This is so ugly it eats kittens, and puppies, and ducklings,
+	 * and all little fluffy things...
+	 */
+#define LOOP_ADD(type, endian, low, high) \
+do { \
+	const unsigned frame_count = dst_size / dst_frame_size; \
+	for (size_t i = 0; i < frame_count; ++i) { \
+		for (unsigned j = 0; j < df->channels; ++j) { \
+			const float a = \
+			    get_normalized_sample(dst, dst_size, i, j, df);\
+			const float b = \
+			    get_normalized_sample(src, src_size, i, j, sf);\
+			float c = (a + b); \
+			if (c < -1.0) c = -1.0; \
+			if (c > 1.0) c = 1.0; \
+			c += 1.0; \
+			c *= ((float)(type)high - (float)(type)low) / 2; \
+			c += (float)(type)low; \
+			type *dst_buf = dst; \
+			const unsigned pos = i * df->channels  + j; \
+			if (pos < (dst_size / sizeof(type))) \
+				dst_buf[pos] = to((type)c, type, endian); \
+		} \
+	} \
+} while (0)
+
+	switch (df->sample_format) {
+	case PCM_SAMPLE_UINT8:
+		LOOP_ADD(uint8_t, le, UINT8_MIN, UINT8_MAX); break;
+	case PCM_SAMPLE_SINT8:
+		LOOP_ADD(uint8_t, le, INT8_MIN, INT8_MAX); break;
+	case PCM_SAMPLE_UINT16_LE:
+		LOOP_ADD(uint16_t, le, UINT16_MIN, UINT16_MAX); break;
+	case PCM_SAMPLE_SINT16_LE:
+		LOOP_ADD(int16_t, le, INT16_MIN, INT16_MAX); break;
+	case PCM_SAMPLE_UINT16_BE:
+		LOOP_ADD(uint16_t, be, UINT16_MIN, UINT16_MAX); break;
+	case PCM_SAMPLE_SINT16_BE:
+		LOOP_ADD(int16_t, be, INT16_MIN, INT16_MAX); break;
+	case PCM_SAMPLE_UINT24_32_LE:
+	case PCM_SAMPLE_UINT32_LE: // TODO this are not right for 24bit
+		LOOP_ADD(uint32_t, le, UINT32_MIN, UINT32_MAX); break;
+	case PCM_SAMPLE_SINT24_32_LE:
+	case PCM_SAMPLE_SINT32_LE:
+		LOOP_ADD(int32_t, le, INT32_MIN, INT32_MAX); break;
+	case PCM_SAMPLE_UINT24_32_BE:
+	case PCM_SAMPLE_UINT32_BE:
+		LOOP_ADD(uint32_t, be, UINT32_MIN, UINT32_MAX); break;
+	case PCM_SAMPLE_SINT24_32_BE:
+	case PCM_SAMPLE_SINT32_BE:
+		LOOP_ADD(int32_t, be, INT32_MIN, INT32_MAX); break;
+	case PCM_SAMPLE_UINT24_LE:
+	case PCM_SAMPLE_SINT24_LE:
+	case PCM_SAMPLE_UINT24_BE:
+	case PCM_SAMPLE_SINT24_BE:
+	case PCM_SAMPLE_FLOAT32:
+	default:
+		return ENOTSUP;
+	}
+	return EOK;
+#undef LOOP_ADD
+}
+
+/**
+ * Converts all sample formats to float <-1,1>
+ * @param buffer Audio data
+ * @param size Size of the buffer
+ * @param frame Index of the frame to read
+ * @param channel Channel within the frame
+ * @param f Pointer to a format descriptor
+ * @return Normalized sample <-1,1>, 0.0 if the data could not be read
+ */
+static float get_normalized_sample(const void *buffer, size_t size,
+    unsigned frame, unsigned channel, const pcm_format_t *f)
+{
+	assert(f);
+	assert(buffer);
+	if (channel >= f->channels)
+		return 0.0f;
+#define GET(type, endian, low, high) \
+do { \
+	const type *src = buffer; \
+	const size_t sample_count = size / sizeof(type); \
+	const size_t sample_pos = frame * f->channels + channel; \
+	if (sample_pos >= sample_count) {\
+		return 0.0f; \
+	} \
+	float sample = from(src[sample_pos], type, endian); \
+	/* This makes it positive */ \
+	sample -= (float)(type)low; \
+	/* This makes it <0,2> */ \
+	sample /= (((float)(type)high - (float)(type)low) / 2.0f); \
+	return sample - 1.0f; \
+} while (0)
+
+	switch (f->sample_format) {
+	case PCM_SAMPLE_UINT8:
+		GET(uint8_t, le, UINT8_MIN, UINT8_MAX);
+	case PCM_SAMPLE_SINT8:
+		GET(int8_t, le, INT8_MIN, INT8_MAX);
+	case PCM_SAMPLE_UINT16_LE:
+		GET(uint16_t, le, UINT16_MIN, UINT16_MAX);
+	case PCM_SAMPLE_SINT16_LE:
+		GET(int16_t, le, INT16_MIN, INT16_MAX);
+	case PCM_SAMPLE_UINT16_BE:
+		GET(uint16_t, be, UINT16_MIN, UINT16_MAX);
+	case PCM_SAMPLE_SINT16_BE:
+		GET(int16_t, be, INT16_MIN, INT16_MAX);
+	case PCM_SAMPLE_UINT24_32_LE:
+	case PCM_SAMPLE_UINT32_LE:
+		GET(uint32_t, le, UINT32_MIN, UINT32_MAX);
+	case PCM_SAMPLE_SINT24_32_LE:
+	case PCM_SAMPLE_SINT32_LE:
+		GET(int32_t, le, INT32_MIN, INT32_MAX);
+	case PCM_SAMPLE_UINT24_32_BE:
+	case PCM_SAMPLE_UINT32_BE:
+		GET(uint32_t, be, UINT32_MIN, UINT32_MAX);
+	case PCM_SAMPLE_SINT24_32_BE:
+	case PCM_SAMPLE_SINT32_BE:
+		GET(int32_t, le, INT32_MIN, INT32_MAX);
+	case PCM_SAMPLE_UINT24_LE:
+	case PCM_SAMPLE_SINT24_LE:
+	case PCM_SAMPLE_UINT24_BE:
+	case PCM_SAMPLE_SINT24_BE:
+	case PCM_SAMPLE_FLOAT32:
+	default: ;
+	}
+	return 0;
+#undef GET
+}
+/**
+ * @}
+ */
Index: uspace/lib/posix/include/posix/time.h
===================================================================
--- uspace/lib/posix/include/posix/time.h	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/posix/include/posix/time.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -87,4 +87,7 @@
 extern void __POSIX_DEF__(tzset)(void);
 
+/* Time */
+extern time_t __POSIX_DEF__(time)(time_t *t);
+
 /* Broken-down Time */
 extern struct tm *__POSIX_DEF__(gmtime_r)(const time_t *restrict timer,
Index: uspace/lib/posix/source/time.c
===================================================================
--- uspace/lib/posix/source/time.c	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/posix/source/time.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -74,4 +74,17 @@
 	posix_daylight = 0;
 	posix_timezone = 0;
+}
+
+/**
+ * Get the time in seconds
+ *
+ * @param t If t is non-NULL, the return value is also stored in the memory
+ *          pointed to by t.
+ * @return  On success, the value of time in seconds since the Epoch
+ *          is returned. On error, (time_t)-1 is returned.
+ */
+time_t posix_time(time_t *t)
+{
+	return time(t);
 }
 
Index: uspace/lib/softfloat/softfloat.c
===================================================================
--- uspace/lib/softfloat/softfloat.c	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/softfloat/softfloat.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -1265,4 +1265,14 @@
 }
 
+float __aeabi_i2f(int i)
+{
+	return __floatsisf(i);
+}
+
+float __aeabi_ui2f(int i)
+{
+	return __floatunsisf(i);
+}
+
 double __aeabi_i2d(int i)
 {
@@ -1280,4 +1290,9 @@
 }
 
+int __aeabi_f2uiz(float a)
+{
+	return __fixunssfsi(a);
+}
+
 int __aeabi_d2iz(double a)
 {
@@ -1288,4 +1303,24 @@
 {
 	return __fixunsdfsi(a);
+}
+
+int __aeabi_fcmpge(float a, float b)
+{
+	return __gesf2(a, b);
+}
+
+int __aeabi_fcmpgt(float a, float b)
+{
+	return __gtsf2(a, b);
+}
+
+int __aeabi_fcmplt(float a, float b)
+{
+	return __ltsf2(a, b);
+}
+
+int __aeabi_fcmpeq(float a, float b)
+{
+	return __eqsf2(a, b);
 }
 
Index: uspace/lib/softfloat/softfloat.h
===================================================================
--- uspace/lib/softfloat/softfloat.h	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/lib/softfloat/softfloat.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -204,4 +204,6 @@
 
 /* ARM EABI */
+extern float __aeabi_i2f(int);
+extern float __aeabi_ui2f(int);
 extern double __aeabi_i2d(int);
 extern double __aeabi_ui2d(unsigned int);
@@ -209,5 +211,11 @@
 
 extern int __aeabi_f2iz(float);
+extern int __aeabi_f2uiz(float);
 extern int __aeabi_d2iz(double);
+
+extern int __aeabi_fcmpge(float, float);
+extern int __aeabi_fcmpgt(float, float);
+extern int __aeabi_fcmplt(float, float);
+extern int __aeabi_fcmpeq(float, float);
 
 extern int __aeabi_dcmpge(double, double);
Index: uspace/srv/audio/hound/Makefile
===================================================================
--- uspace/srv/audio/hound/Makefile	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/Makefile	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,56 @@
+#
+# Copyright (c) 2012 Jan Vesely
+# 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 = ../../..
+BINARY = hound
+
+EXTRA_CFLAGS = \
+	-DNAME="\"hound\"" \
+	-I$(LIBDRV_PREFIX)/include \
+	-I$(LIBHOUND_PREFIX)/include \
+	-I$(LIBPCM_PREFIX)/include
+
+LIBS = \
+	$(LIBDRV_PREFIX)/libdrv.a \
+	$(LIBHOUND_PREFIX)/libhound.a \
+	$(LIBPCM_PREFIX)/libpcm.a
+
+SOURCES = \
+	audio_data.c \
+	audio_device.c \
+	audio_sink.c \
+	audio_source.c \
+	connection.c \
+	hound.c \
+	hound_ctx.c \
+	iface.c \
+	main.c
+
+
+include $(USPACE_PREFIX)/Makefile.common
+
Index: uspace/srv/audio/hound/audio_data.c
===================================================================
--- uspace/srv/audio/hound/audio_data.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/audio_data.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,315 @@
+/*
+ * Copyright (c) 2013 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup audio
+ * @brief HelenOS sound server
+ * @{
+ */
+/** @file
+ */
+
+#include <macros.h>
+#include <malloc.h>
+
+#include "audio_data.h"
+#include "log.h"
+
+/**
+ * Create reference counted buffer out of ordinary data buffer.
+ * @param data audio buffer
+ * @param size Size of the @p data buffer.
+ * @param fomart audio data format.
+ * @return pointer to valid audio data structre, NULL on failure.
+ */
+audio_data_t *audio_data_create(const void *data, size_t size,
+    pcm_format_t format)
+{
+	audio_data_t *adata = malloc(sizeof(audio_data_t));
+	if (adata) {
+		unsigned overflow = size % pcm_format_frame_size(&format);
+		if (overflow)
+			log_warning("Data not a multiple of frame size, "
+			    "clipping.");
+
+		adata->data = data;
+		adata->size = size - overflow;
+		adata->format = format;
+		atomic_set(&adata->refcount, 1);
+	}
+	return adata;
+}
+
+/**
+ * Get a new reference to the audio data buffer.
+ * @param adata The audio data buffer.
+ */
+void audio_data_addref(audio_data_t *adata)
+{
+	assert(adata);
+	assert(atomic_get(&adata->refcount) > 0);
+	atomic_inc(&adata->refcount);
+}
+
+/**
+ * Release a reference to the audio data buffer.
+ * @param adata The audio data buffer.
+ */
+void audio_data_unref(audio_data_t *adata)
+{
+	assert(adata);
+	assert(atomic_get(&adata->refcount) > 0);
+	atomic_count_t refc = atomic_predec(&adata->refcount);
+	if (refc == 0) {
+		free(adata->data);
+		free(adata);
+	}
+}
+
+/* Data link helpers */
+
+/** Audio data buffer list helper structure. */
+typedef struct {
+	link_t link;
+	audio_data_t *adata;
+	size_t position;
+} audio_data_link_t;
+
+/** List instance helper function.
+ * @param l link
+ * @return valid pointer to data link structure, NULL on failure.
+ */
+static inline audio_data_link_t * audio_data_link_list_instance(link_t *l)
+{
+	return l ? list_get_instance(l, audio_data_link_t, link) : NULL;
+}
+
+/**
+ * Create a new audio data link.
+ * @param adata Audio data to store.
+ * @return Valid pointer to a new audio data link structure, NULL on failure.
+ */
+static audio_data_link_t *audio_data_link_create(audio_data_t *adata)
+{
+	assert(adata);
+	audio_data_link_t *link = malloc(sizeof(audio_data_link_t));
+	if (link) {
+		audio_data_addref(adata);
+		link->adata = adata;
+		link->position = 0;
+	}
+	return link;
+}
+
+/**
+ * Destroy audio data link.
+ * @param link The link to destroy.
+ *
+ * Releases data reference.
+ */
+static void audio_data_link_destroy(audio_data_link_t *link)
+{
+	assert(link);
+	assert(!link_in_use(&link->link));
+	audio_data_unref(link->adata);
+	free(link);
+}
+
+/**
+ * Data link buffer start helper function.
+ * @param alink audio data link
+ * @return pointer to the beginning of data buffer.
+ */
+static inline const void * audio_data_link_start(audio_data_link_t *alink)
+{
+	assert(alink);
+	assert(alink->adata);
+	return alink->adata->data + alink->position;
+}
+
+/**
+ * Data link remaining size getter.
+ * @param alink audio data link
+ * @return Remaining size of valid data in the buffer.
+ */
+static inline size_t audio_data_link_remain_size(audio_data_link_t *alink)
+{
+	assert(alink);
+	assert(alink->adata);
+	assert(alink->position <= alink->adata->size);
+	return alink->adata->size - alink->position;
+}
+
+
+/**
+ * Data link remaining frames getter.
+ * @param alink audio data link
+ * @return Number of remaining frames in the buffer.
+ */
+static inline size_t audio_data_link_available_frames(audio_data_link_t *alink)
+{
+	assert(alink);
+	assert(alink->adata);
+	return pcm_format_size_to_frames(audio_data_link_remain_size(alink),
+	    &alink->adata->format);
+}
+
+/* Audio Pipe */
+
+/**
+ * Initialize audio pipe structure.
+ * @param pipe The pipe structure to initialize.
+ */
+void audio_pipe_init(audio_pipe_t *pipe)
+{
+	assert(pipe);
+	list_initialize(&pipe->list);
+	fibril_mutex_initialize(&pipe->guard);
+	pipe->frames = 0;
+	pipe->bytes = 0;
+}
+
+/**
+ * Destroy all data in a pipe.
+ * @param pipe The audio pipe to clean.
+ */
+void audio_pipe_fini(audio_pipe_t *pipe)
+{
+	assert(pipe);
+	while (!list_empty(&pipe->list)) {
+		audio_data_t *adata = audio_pipe_pop(pipe);
+		audio_data_unref(adata);
+	}
+}
+
+/**
+ * Add new audio data to a pipe.
+ * @param pipe The target pipe.
+ * @param data The data.
+ * @return Error code.
+ */
+int audio_pipe_push(audio_pipe_t *pipe, audio_data_t *data)
+{
+	assert(pipe);
+	assert(data);
+	audio_data_link_t *alink = audio_data_link_create(data);
+	if (!alink)
+		return ENOMEM;
+
+	fibril_mutex_lock(&pipe->guard);
+	list_append(&alink->link, &pipe->list);
+	pipe->bytes += audio_data_link_remain_size(alink);
+	pipe->frames += audio_data_link_available_frames(alink);
+	fibril_mutex_unlock(&pipe->guard);
+	return EOK;
+}
+
+/**
+ * Retrieve data form a audio pipe.
+ * @param pipe THe target pipe.
+ * @return Valid pointer to audio data, NULL if the pipe was empty.
+ */
+audio_data_t *audio_pipe_pop(audio_pipe_t *pipe)
+{
+	assert(pipe);
+	fibril_mutex_lock(&pipe->guard);
+	audio_data_t *adata = NULL;
+	link_t *l = list_first(&pipe->list);
+	if (l) {
+		audio_data_link_t *alink = audio_data_link_list_instance(l);
+		list_remove(&alink->link);
+		pipe->bytes -= audio_data_link_remain_size(alink);
+		pipe->frames -= audio_data_link_available_frames(alink);
+		adata = alink->adata;
+		audio_data_addref(adata);
+		audio_data_link_destroy(alink);
+	}
+	fibril_mutex_unlock(&pipe->guard);
+	return adata;
+}
+
+
+/**
+ * Use data store in a pipe and mix it into the provided buffer.
+ * @param pipe The piep that should provide data.
+ * @param data Target buffer.
+ * @param size Target buffer size.
+ * @param format Target data format.
+ * @return Size of the target buffer used, Error code on failure.
+ */
+ssize_t audio_pipe_mix_data(audio_pipe_t *pipe, void *data,
+    size_t size, const pcm_format_t *f)
+{
+	assert(pipe);
+
+	const size_t dst_frame_size = pcm_format_frame_size(f);
+	size_t needed_frames = pcm_format_size_to_frames(size, f);
+	size_t copied_size = 0;
+
+	fibril_mutex_lock(&pipe->guard);
+	while (needed_frames > 0 && !list_empty(&pipe->list)) {
+		/* Get first audio chunk */
+		link_t *l = list_first(&pipe->list);
+		audio_data_link_t *alink = audio_data_link_list_instance(l);
+
+		/* Get audio chunk metadata */
+		const size_t src_frame_size =
+		    pcm_format_frame_size(&alink->adata->format);
+		const size_t available_frames =
+		    audio_data_link_available_frames(alink);
+		const size_t copy_frames = min(available_frames, needed_frames);
+		const size_t dst_copy_size = copy_frames * dst_frame_size;
+		const size_t src_copy_size = copy_frames * src_frame_size;
+
+		assert(src_copy_size <= audio_data_link_remain_size(alink));
+
+		/* Copy audio data */
+		pcm_format_convert_and_mix(data, dst_copy_size,
+		    audio_data_link_start(alink), src_copy_size,
+		    &alink->adata->format, f);
+
+		/* Update values */
+		needed_frames -= copy_frames;
+		copied_size += dst_copy_size;
+		data += dst_copy_size;
+		alink->position += src_copy_size;
+		pipe->bytes -= src_copy_size;
+		pipe->frames -= copy_frames;
+		if (audio_data_link_remain_size(alink) == 0) {
+			list_remove(&alink->link);
+			audio_data_link_destroy(alink);
+		} else {
+			assert(needed_frames == 0);
+		}
+	}
+	fibril_mutex_unlock(&pipe->guard);
+	return copied_size;
+}
+
+/**
+ * @}
+ */
Index: uspace/srv/audio/hound/audio_data.h
===================================================================
--- uspace/srv/audio/hound/audio_data.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/audio_data.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2013 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup audio
+ * @brief HelenOS sound server
+ * @{
+ */
+/** @file
+ */
+
+#ifndef AUDIO_DATA_H_
+#define AUDIO_DATA_H_
+
+#include <adt/list.h>
+#include <atomic.h>
+#include <errno.h>
+#include <fibril_synch.h>
+#include <pcm/format.h>
+
+/** Reference counted audio buffer */
+typedef struct {
+	/** Audio data */
+	const void *data;
+	/** Size of the buffer pointer to by data */
+	size_t size;
+	/** Format of the audio data */
+	pcm_format_t format;
+	/** Reference counter */
+	atomic_t refcount;
+} audio_data_t;
+
+/** Audio data pipe structure */
+typedef struct {
+	/** List of audio data buffers */
+	list_t list;
+	/** Total size of all buffers */
+	size_t bytes;
+	/** Total frames stored in all buffers */
+	size_t frames;
+	/** List access synchronization */
+	fibril_mutex_t guard;
+} audio_pipe_t;
+
+audio_data_t * audio_data_create(const void *data, size_t size,
+    pcm_format_t format);
+void audio_data_addref(audio_data_t *adata);
+void audio_data_unref(audio_data_t *adata);
+
+void audio_pipe_init(audio_pipe_t *pipe);
+void audio_pipe_fini(audio_pipe_t *pipe);
+
+int audio_pipe_push(audio_pipe_t *pipe, audio_data_t *data);
+audio_data_t *audio_pipe_pop(audio_pipe_t *pipe);
+
+ssize_t audio_pipe_mix_data(audio_pipe_t *pipe, void *buffer, size_t size,
+    const pcm_format_t *f);
+
+/**
+ * Total bytes getter.
+ * @param pipe The audio pipe.
+ * @return Total size of buffer stored in the pipe.
+ */
+static inline size_t audio_pipe_bytes(audio_pipe_t *pipe)
+{
+	assert(pipe);
+	return pipe->bytes;
+}
+
+/**
+ * Total bytes getter.
+ * @param pipe The audio pipe.
+ * @return Total number of frames stored in the pipe.
+ */
+static inline size_t audio_pipe_frames(audio_pipe_t *pipe)
+{
+	assert(pipe);
+	return pipe->frames;
+}
+
+/**
+ * Push data form buffer directly to pipe.
+ * @param pipe The target pipe.
+ * @param data audio buffer.
+ * @param size size of the @p data buffer
+ * @param f format of the audio data.
+ * @return Error code.
+ *
+ * Reference counted buffer is created automatically.
+ */
+static inline int audio_pipe_push_data(audio_pipe_t *pipe,
+    const void *data, size_t size, pcm_format_t f)
+{
+	audio_data_t *adata = audio_data_create(data, size, f);
+	if (adata) {
+		const int ret = audio_pipe_push(pipe, adata);
+		audio_data_unref(adata);
+		return ret;
+	}
+	return ENOMEM;
+}
+
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/srv/audio/hound/audio_device.c
===================================================================
--- uspace/srv/audio/hound/audio_device.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/audio_device.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/**
+ * @addtogroup audio
+ * @brief HelenOS sound server.
+ * @{
+ */
+/** @file
+ */
+
+#include <assert.h>
+#include <async.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <loc.h>
+#include <str.h>
+#include <str_error.h>
+
+
+#include "audio_device.h"
+#include "log.h"
+
+/* hardwired to provide ~21ms per fragment */
+#define BUFFER_PARTS   16
+
+static int device_sink_connection_callback(audio_sink_t *sink, bool new);
+static int device_source_connection_callback(audio_source_t *source, bool new);
+static void device_event_callback(ipc_callid_t iid, ipc_call_t *icall, void *arg);
+static int device_check_format(audio_sink_t* sink);
+static int get_buffer(audio_device_t *dev);
+static int release_buffer(audio_device_t *dev);
+static void advance_buffer(audio_device_t *dev, size_t size);
+static inline bool is_running(audio_device_t *dev)
+{
+	assert(dev);
+	/* we release buffer on stop so this should be enough */
+	return (bool)dev->buffer.base;
+}
+
+/**
+ * Initialize audio device structure.
+ * @param dev The structure to initialize.
+ * @param id Location service id of the device driver.
+ * @param name Name of the device.
+ * @return Error code.
+ */
+int audio_device_init(audio_device_t *dev, service_id_t id, const char *name)
+{
+	assert(dev);
+	link_initialize(&dev->link);
+	dev->id = id;
+	dev->name = str_dup(name);
+	dev->sess = audio_pcm_open_service(id);
+	if (!dev->sess) {
+		log_debug("Failed to connect to device \"%s\"", name);
+		return ENOMEM;
+	}
+
+	audio_sink_init(&dev->sink, name, dev, device_sink_connection_callback,
+	    device_check_format, NULL, &AUDIO_FORMAT_ANY);
+	audio_source_init(&dev->source, name, dev,
+	    device_source_connection_callback, NULL, &AUDIO_FORMAT_ANY);
+
+	/* Init buffer members */
+	dev->buffer.base = NULL;
+	dev->buffer.position = NULL;
+	dev->buffer.size = 0;
+	dev->buffer.fragment_size = 0;
+
+	log_verbose("Initialized device (%p) '%s' with id %" PRIun ".",
+	    dev, dev->name, dev->id);
+
+	return EOK;
+}
+
+/**
+ * Restore resource cplaimed during initialization.
+ * @param dev The device to release.
+ *
+ * NOT IMPLEMENTED
+ */
+void audio_device_fini(audio_device_t *dev)
+{
+	//TODO implement;
+}
+
+/**
+ * Get device provided audio source.
+ * @param dev Th device.
+ * @return pointer to aa audio source structure, NULL if the device is not
+ *         capable of capturing audio.
+ */
+audio_source_t * audio_device_get_source(audio_device_t *dev)
+{
+	assert(dev);
+	if (audio_pcm_query_cap(dev->sess, AUDIO_CAP_CAPTURE))
+		return &dev->source;
+	return NULL;
+}
+
+/**
+ * Get device provided audio sink.
+ * @param dev Th device.
+ * @return pointer to aa audio source structure, NULL if the device is not
+ *         capable of audio playback.
+ */
+audio_sink_t * audio_device_get_sink(audio_device_t *dev)
+{
+	assert(dev);
+	if (audio_pcm_query_cap(dev->sess, AUDIO_CAP_PLAYBACK))
+		return &dev->sink;
+	return NULL;
+}
+
+/**
+ * Handle connection addition and removal.
+ * @param sink audio sink that is connected or disconnected.
+ * @param new True of a connection was added, false otherwise.
+ * @return Error code.
+ *
+ * Starts playback on first connection. Stops playback when there are no
+ * connections.
+ */
+static int device_sink_connection_callback(audio_sink_t* sink, bool new)
+{
+	assert(sink);
+	audio_device_t *dev = sink->private_data;
+	if (new && list_count(&sink->connections) == 1) {
+		log_verbose("First connection on device sink '%s'", sink->name);
+
+		int ret = get_buffer(dev);
+		if (ret != EOK) {
+			log_error("Failed to get device buffer: %s",
+			    str_error(ret));
+			return ret;
+		}
+		audio_pcm_register_event_callback(dev->sess,
+		    device_event_callback, dev);\
+
+		/* Fill the buffer first. Fill the first two fragments,
+		 * so that we stay one fragment ahead */
+		pcm_format_silence(dev->buffer.base, dev->buffer.size,
+		    &dev->sink.format);
+		//TODO add underrun detection.
+		const size_t size = dev->buffer.fragment_size * 2;
+		/* We never cross the end of the buffer here */
+		audio_sink_mix_inputs(&dev->sink, dev->buffer.position, size);
+		advance_buffer(dev, size);
+
+		const unsigned frames = dev->buffer.fragment_size /
+		    pcm_format_frame_size(&dev->sink.format);
+		log_verbose("Fragment frame count %u", frames);
+		ret = audio_pcm_start_playback_fragment(dev->sess, frames,
+		    dev->sink.format.channels, dev->sink.format.sampling_rate,
+		    dev->sink.format.sample_format);
+		if (ret != EOK) {
+			log_error("Failed to start playback: %s",
+			    str_error(ret));
+			release_buffer(dev);
+			return ret;
+		}
+	}
+	if (list_count(&sink->connections) == 0) {
+		assert(!new);
+		log_verbose("Removed last connection on device sink '%s'",
+		    sink->name);
+		int ret = audio_pcm_stop_playback(dev->sess);
+		if (ret != EOK) {
+			log_error("Failed to stop playback: %s",
+			    str_error(ret));
+			return ret;
+		}
+	}
+	return EOK;
+}
+
+/**
+ * Handle connection addition and removal.
+ * @param source audio source that is connected or disconnected.
+ * @param new True of a connection was added, false otherwise.
+ * @return Error code.
+ *
+ * Starts capture on first connection. Stops capture when there are no
+ * connections.
+ */
+static int device_source_connection_callback(audio_source_t *source, bool new)
+{
+	assert(source);
+	audio_device_t *dev = source->private_data;
+	if (new && list_count(&source->connections) == 1) {
+		int ret = get_buffer(dev);
+		if (ret != EOK) {
+			log_error("Failed to get device buffer: %s",
+			    str_error(ret));
+			return ret;
+		}
+
+		//TODO set and test format
+
+		const unsigned frames = dev->buffer.fragment_size /
+		    pcm_format_frame_size(&dev->sink.format);
+		ret = audio_pcm_start_capture_fragment(dev->sess, frames,
+		    dev->source.format.channels,
+		    dev->source.format.sampling_rate,
+		    dev->source.format.sample_format);
+		if (ret != EOK) {
+			log_error("Failed to start recording: %s",
+			    str_error(ret));
+			release_buffer(dev);
+			return ret;
+		}
+	}
+	if (list_count(&source->connections) == 0) { /* Disconnected */
+		assert(!new);
+		int ret = audio_pcm_stop_capture_immediate(dev->sess);
+		if (ret != EOK) {
+			log_error("Failed to start recording: %s",
+			    str_error(ret));
+			return ret;
+		}
+	}
+
+	return EOK;
+}
+
+/**
+ * Audio device event handler.
+ * @param iid initial call id.
+ * @param icall initial call structure.
+ * @param arg (unused)
+ */
+static void device_event_callback(ipc_callid_t iid, ipc_call_t *icall, void *arg)
+{
+	/* Answer initial request */
+	async_answer_0(iid, EOK);
+	audio_device_t *dev = arg;
+	assert(dev);
+	while (1) {
+		ipc_call_t call;
+		ipc_callid_t callid = async_get_call(&call);
+		async_answer_0(callid, EOK);
+		switch(IPC_GET_IMETHOD(call)) {
+		case PCM_EVENT_FRAMES_PLAYED: {
+			struct timeval time1;
+			getuptime(&time1);
+			//TODO add underrun detection.
+			/* We never cross the end of the buffer here */
+			audio_sink_mix_inputs(&dev->sink, dev->buffer.position,
+			    dev->buffer.fragment_size);
+			advance_buffer(dev, dev->buffer.fragment_size);
+			struct timeval time2;
+			getuptime(&time2);
+			log_verbose("Time to mix sources: %li\n",
+			    tv_sub(&time2, &time1));
+			break;
+		}
+		case PCM_EVENT_CAPTURE_TERMINATED: {
+			log_verbose("Capture terminated");
+			dev->source.format = AUDIO_FORMAT_ANY;
+			const int ret = release_buffer(dev);
+			if (ret != EOK) {
+				log_error("Failed to release buffer: %s",
+				    str_error(ret));
+			}
+			audio_pcm_unregister_event_callback(dev->sess);
+			break;
+		}
+		case PCM_EVENT_PLAYBACK_TERMINATED: {
+			log_verbose("Playback Terminated");
+			dev->sink.format = AUDIO_FORMAT_ANY;
+			const int ret = release_buffer(dev);
+			if (ret != EOK) {
+				log_error("Failed to release buffer: %s",
+				    str_error(ret));
+			}
+			audio_pcm_unregister_event_callback(dev->sess);
+			break;
+		}
+		case PCM_EVENT_FRAMES_CAPTURED: {
+			const int ret = audio_source_push_data(&dev->source,
+			    dev->buffer.position, dev->buffer.fragment_size);
+			advance_buffer(dev, dev->buffer.fragment_size);
+			if (ret != EOK)
+				log_warning("Failed to push recorded data");
+			break;
+		}
+		case 0:
+			log_info("Device event callback hangup");
+			return;
+		}
+
+	}
+}
+
+/**
+ * Test format against hardware limits.
+ * @param sink audio playback device.
+ * @return Error code.
+ */
+static int device_check_format(audio_sink_t* sink)
+{
+	assert(sink);
+	audio_device_t *dev = sink->private_data;
+	assert(dev);
+	/* Check whether we are running */
+	if (is_running(dev))
+		return EBUSY;
+	log_verbose("Checking format on sink %s", sink->name);
+	return audio_pcm_test_format(dev->sess, &sink->format.channels,
+	    &sink->format.sampling_rate, &sink->format.sample_format);
+}
+
+/**
+ * Get access to device buffer.
+ * @param dev Audio device.
+ * @return Error code.
+ */
+static int get_buffer(audio_device_t *dev)
+{
+	assert(dev);
+	if (!dev->sess) {
+		log_debug("No connection to device");
+		return EIO;
+	}
+	if (dev->buffer.base) {
+		log_debug("We already have a buffer");
+		return EBUSY;
+	}
+
+	/* Ask for largest buffer possible */
+	size_t preferred_size = 0;
+
+	const int ret = audio_pcm_get_buffer(dev->sess, &dev->buffer.base,
+	    &preferred_size);
+	if (ret == EOK) {
+		dev->buffer.size = preferred_size;
+		dev->buffer.fragment_size = dev->buffer.size / BUFFER_PARTS;
+		dev->buffer.position = dev->buffer.base;
+	}
+	return ret;
+
+}
+
+/**
+ * Surrender access to device buffer.
+ * @param dev Audio device.
+ * @return Error code.
+ */
+static int release_buffer(audio_device_t *dev)
+{
+	assert(dev);
+	assert(dev->buffer.base);
+
+	const int ret = audio_pcm_release_buffer(dev->sess);
+	if (ret == EOK) {
+		dev->buffer.base = NULL;
+		dev->buffer.size = 0;
+		dev->buffer.position = NULL;
+	} else {
+		log_warning("Failed to release buffer: %s", str_error(ret));
+	}
+	return ret;
+}
+
+/**
+ * Move buffer position pointer.
+ * @param dev Audio device.
+ * @param size number of bytes to move forward
+ */
+static void advance_buffer(audio_device_t *dev, size_t size)
+{
+	assert(dev);
+	assert(dev->buffer.position >= dev->buffer.base);
+	assert(dev->buffer.position < (dev->buffer.base + dev->buffer.size));
+	dev->buffer.position += size;
+	if (dev->buffer.position == (dev->buffer.base + dev->buffer.size))
+		dev->buffer.position = dev->buffer.base;
+}
+/**
+ * @}
+ */
Index: uspace/srv/audio/hound/audio_device.h
===================================================================
--- uspace/srv/audio/hound/audio_device.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/audio_device.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup audio
+ * @brief HelenOS sound server
+ * @{
+ */
+/** @file
+ */
+
+#ifndef AUDIO_DEVICE_H_
+#define AUDIO_DEVICE_H_
+
+#include <adt/list.h>
+#include <async.h>
+#include <fibril_synch.h>
+#include <errno.h>
+#include <ipc/loc.h>
+#include <audio_pcm_iface.h>
+
+#include "audio_source.h"
+#include "audio_sink.h"
+
+/** Audio device structure. */
+typedef struct {
+	/** Link in hound's device list */
+	link_t link;
+	/** Location service id of the audio driver */
+	service_id_t id;
+	/** IPC session to the device driver. */
+	audio_pcm_sess_t *sess;
+	/** Device name */
+	char *name;
+	/** Device buffer */
+	struct {
+		void *base;
+		size_t size;
+		void *position;
+		size_t fragment_size;
+	} buffer;
+	/** Capture device abstraction. */
+	audio_source_t source;
+	/** Playback device abstraction. */
+	audio_sink_t sink;
+} audio_device_t;
+
+/** Linked list instance helper */
+static inline audio_device_t * audio_device_list_instance(link_t *l)
+{
+	return l ? list_get_instance(l, audio_device_t, link) : NULL;
+};
+
+int audio_device_init(audio_device_t *dev, service_id_t id, const char *name);
+void audio_device_fini(audio_device_t *dev);
+audio_source_t * audio_device_get_source(audio_device_t *dev);
+audio_sink_t * audio_device_get_sink(audio_device_t *dev);
+int audio_device_recorded_data(audio_device_t *dev, void **base, size_t *size);
+int audio_device_available_buffer(audio_device_t *dev, void **base, size_t *size);
+
+#endif
+
+/**
+ * @}
+ */
+
Index: uspace/srv/audio/hound/audio_sink.c
===================================================================
--- uspace/srv/audio/hound/audio_sink.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/audio_sink.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup audio
+ * @brief HelenOS sound server
+ * @{
+ */
+/** @file
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <str.h>
+#include <str_error.h>
+
+#include "audio_sink.h"
+#include "connection.h"
+#include "log.h"
+
+
+/**
+ * Initialize audio sink structure.
+ * @param sink The structure to initialize.
+ * @param name string identifier
+ * @param private_data backend data
+ * @param connection_change connect/disconnect callback
+ * @param check_format format testing callback
+ * @param data_available trigger data prcoessing
+ * @param f sink format
+ * @return Error code.
+ */
+int audio_sink_init(audio_sink_t *sink, const char *name, void *private_data,
+    int (*connection_change)(audio_sink_t *, bool),
+    int (*check_format)(audio_sink_t *), int (*data_available)(audio_sink_t *),
+    const pcm_format_t *f)
+{
+	assert(sink);
+	if (!name)
+		return EINVAL;
+	link_initialize(&sink->link);
+	list_initialize(&sink->connections);
+	sink->name = str_dup(name);
+	if (!sink->name)
+		return ENOMEM;
+	sink->private_data = private_data;
+	sink->format = *f;
+	sink->connection_change = connection_change;
+	sink->check_format = check_format;
+	sink->data_available = data_available;
+	log_verbose("Initialized sink (%p) '%s'", sink, sink->name);
+	return EOK;
+}
+
+/**
+ * Release resources claimed by initialization.
+ * @param sink the structure to release.
+ */
+void audio_sink_fini(audio_sink_t *sink)
+{
+	assert(sink);
+	assert(list_empty(&sink->connections));
+	assert(!sink->private_data);
+	free(sink->name);
+	sink->name = NULL;
+}
+
+/**
+ * Set audio sink format and check with backend,
+ * @param sink The target sink isntance.
+ * @param format Th new format.
+ * @return Error code.
+ */
+int audio_sink_set_format(audio_sink_t *sink, const pcm_format_t *format)
+{
+	assert(sink);
+	assert(format);
+	if (!pcm_format_is_any(&sink->format)) {
+		log_debug("Sink %s already has a format", sink->name);
+		return EEXISTS;
+	}
+	const pcm_format_t old_format = sink->format;
+
+	if (pcm_format_is_any(format)) {
+		log_verbose("Setting DEFAULT format for sink %s", sink->name);
+		sink->format = AUDIO_FORMAT_DEFAULT;
+	} else {
+		sink->format = *format;
+	}
+	if (sink->check_format) {
+		const int ret = sink->check_format(sink);
+		if (ret != EOK && ret != ELIMIT) {
+			log_debug("Format check failed on sink %s", sink->name);
+			sink->format = old_format;
+			return ret;
+		}
+	}
+	log_verbose("Set format for sink %s: %u channel(s), %uHz, %s",
+	    sink->name, format->channels, format->sampling_rate,
+	    pcm_sample_format_str(format->sample_format));
+	return EOK;
+}
+
+/**
+ * Pull data from all connections and add the mix to the destination.
+ * @param sink The sink to mix data.
+ * @param dest Destination buffer.
+ * @param size size of the @p dest buffer.
+ * @return Error code.
+ */
+void audio_sink_mix_inputs(audio_sink_t *sink, void* dest, size_t size)
+{
+	assert(sink);
+	assert(dest);
+
+	pcm_format_silence(dest, size, &sink->format);
+	list_foreach(sink->connections, it) {
+		connection_t * conn = connection_from_sink_list(it);
+		const int ret = connection_add_source_data(
+		    conn, dest, size, sink->format);
+		if (ret != EOK) {
+			log_warning("Failed to mix source %s: %s",
+			    connection_source_name(conn), str_error(ret));
+		}
+	}
+}
+
+
+/**
+ * @}
+ */
Index: uspace/srv/audio/hound/audio_sink.h
===================================================================
--- uspace/srv/audio/hound/audio_sink.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/audio_sink.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup audio
+ * @brief HelenOS sound server
+ * @{
+ */
+/** @file
+ */
+
+#ifndef AUDIO_SINK_H_
+#define AUDIO_SINK_H_
+
+#include <adt/list.h>
+#include <async.h>
+#include <stdbool.h>
+#include <fibril.h>
+#include <pcm/format.h>
+
+#include "audio_source.h"
+
+typedef struct audio_sink audio_sink_t;
+
+/** Audio sink abstraction structure */
+struct audio_sink {
+	/** Link in hound's sink list */
+	link_t link;
+	/** List of all related connections */
+	list_t connections;
+	/** Sink's name */
+	const char *name;
+	/** Consumes data in this format */
+	pcm_format_t format;
+	/** Backend data */
+	void *private_data;
+	/** Connect/disconnect callback */
+	int (*connection_change)(audio_sink_t *, bool);
+	/** Backend callback to check data */
+	int (*check_format)(audio_sink_t *);
+	/** new data notifier */
+	int (*data_available)(audio_sink_t *);
+};
+
+/**
+ * List instance helper.
+ * @param l link
+ * @return pointer to a sink structure, NULL on failure.
+ */
+static inline audio_sink_t * audio_sink_list_instance(link_t *l)
+{
+	return l ? list_get_instance(l, audio_sink_t, link) : NULL;
+}
+
+int audio_sink_init(audio_sink_t *sink, const char *name, void *private_data,
+    int (*connection_change)(audio_sink_t *, bool),
+    int (*check_format)(audio_sink_t *), int (*data_available)(audio_sink_t *),
+    const pcm_format_t *f);
+
+void audio_sink_fini(audio_sink_t *sink);
+int audio_sink_set_format(audio_sink_t *sink, const pcm_format_t *format);
+void audio_sink_mix_inputs(audio_sink_t *sink, void* dest, size_t size);
+
+#endif
+/**
+ * @}
+ */
Index: uspace/srv/audio/hound/audio_source.c
===================================================================
--- uspace/srv/audio/hound/audio_source.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/audio_source.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup audio
+ * @brief HelenOS sound server
+ * @{
+ */
+/** @file
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <macros.h>
+#include <stdlib.h>
+#include <str.h>
+#include <str_error.h>
+
+#include "audio_data.h"
+#include "audio_source.h"
+#include "audio_sink.h"
+#include "connection.h"
+#include "log.h"
+
+/**
+ * Initialize audio source strcture.
+ * @param source The structure to initialize.
+ * @param name String identifier of the audio source.
+ * @param data Backend data.
+ * @param connection_change Connect/disconnect callback.
+ * @param update_available_data Data request callback.
+ * @return Error code.
+ */
+int audio_source_init(audio_source_t *source, const char *name, void *data,
+    int (*connection_change)(audio_source_t *, bool new),
+    int (*update_available_data)(audio_source_t *, size_t),
+    const pcm_format_t *f)
+{
+	assert(source);
+	if (!name || !f) {
+		log_debug("Incorrect parameters.");
+		return EINVAL;
+	}
+	link_initialize(&source->link);
+	list_initialize(&source->connections);
+	source->name = str_dup(name);
+	if (!source->name)
+		return ENOMEM;
+	source->private_data = data;
+	source->connection_change = connection_change;
+	source->update_available_data = update_available_data;
+	source->format = *f;
+	log_verbose("Initialized source (%p) '%s'", source, source->name);
+	return EOK;
+}
+
+/**
+ * Release resources claimed by initialization.
+ * @param source The structure to cleanup.
+ */
+void audio_source_fini(audio_source_t *source)
+{
+	assert(source);
+	free(source->name);
+	source->name = NULL;
+}
+/**
+ * Push data to all connections.
+ * @param source The source of the data.
+ * @param dest Destination buffer.
+ * @param size size of the @p dest buffer.
+ * @return Error code.
+ */
+int audio_source_push_data(audio_source_t *source, const void *data,
+    size_t size)
+{
+	assert(source);
+	assert(data);
+
+	audio_data_t *adata = audio_data_create(data, size, source->format);
+	if (!adata)
+		return ENOMEM;
+
+	list_foreach(source->connections, it) {
+		connection_t *conn = connection_from_source_list(it);
+		const int ret = connection_push_data(conn, adata);
+		if (ret != EOK) {
+			log_warning("Failed push data to %s: %s",
+			    connection_sink_name(conn), str_error(ret));
+		}
+	}
+	audio_data_unref(adata);
+	return EOK;
+}
+
+/**
+ * @}
+ */
Index: uspace/srv/audio/hound/audio_source.h
===================================================================
--- uspace/srv/audio/hound/audio_source.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/audio_source.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup audio
+ * @brief HelenOS sound server
+ * @{
+ */
+/** @file
+ */
+
+#ifndef AUDIO_SOURCE_H_
+#define AUDIO_SOURCE_H_
+
+#include <adt/list.h>
+#include <pcm/format.h>
+
+typedef struct audio_source audio_source_t;
+
+/** Audio data source abstraction structure */
+struct audio_source {
+	/** Link in hound's sources list */
+	link_t link;
+	/** List of connections */
+	list_t connections;
+	/** String identifier */
+	const char *name;
+	/** audio data format */
+	pcm_format_t format;
+	/** backend data */
+	void *private_data;
+	/** Callback for connection and disconnection */
+	int (*connection_change)(audio_source_t *source, bool added);
+	/** Ask backend for more data */
+	int (*update_available_data)(audio_source_t *source, size_t size);
+};
+
+/**
+ * List instance helper.
+ * @param l link
+ * @return pointer to a source structure, NULL on failure.
+ */
+static inline audio_source_t * audio_source_list_instance(link_t *l)
+{
+	return l ? list_get_instance(l, audio_source_t, link) : NULL;
+}
+
+int audio_source_init(audio_source_t *source, const char *name, void *data,
+    int (*connection_change)(audio_source_t *, bool),
+    int (*update_available_data)(audio_source_t *, size_t),
+    const pcm_format_t *f);
+void audio_source_fini(audio_source_t *source);
+int audio_source_push_data(audio_source_t *source, const void *data,
+    size_t size);
+static inline const pcm_format_t *audio_source_format(const audio_source_t *s)
+{
+	assert(s);
+	return &s->format;
+}
+#endif
+
+/**
+ * @}
+ */
Index: uspace/srv/audio/hound/connection.c
===================================================================
--- uspace/srv/audio/hound/connection.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/connection.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2013 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup audio
+ * @brief HelenOS sound server
+ * @{
+ */
+/** @file
+ */
+
+#include <malloc.h>
+#include <errno.h>
+
+#include "log.h"
+#include "connection.h"
+
+/**
+ * Create connection between source and sink.
+ * @param source Valid source structure.
+ * @param sink Valid sink structure.
+ * @return pointer to a valid connection structure, NULL on failure.
+ *
+ * Reports new connection to both the source and sink.
+ */
+connection_t *connection_create(audio_source_t *source, audio_sink_t *sink)
+{
+	assert(source);
+	assert(sink);
+	connection_t *conn = malloc(sizeof(connection_t));
+	if (conn) {
+		audio_pipe_init(&conn->fifo);
+		link_initialize(&conn->source_link);
+		link_initialize(&conn->sink_link);
+		link_initialize(&conn->hound_link);
+		conn->sink = sink;
+		conn->source = source;
+		list_append(&conn->source_link, &source->connections);
+		list_append(&conn->sink_link, &sink->connections);
+		audio_sink_set_format(sink, audio_source_format(source));
+		if (source->connection_change)
+			source->connection_change(source, true);
+		if (sink->connection_change)
+			sink->connection_change(sink, true);
+		log_debug("CONNECTED: %s -> %s", source->name, sink->name);
+	}
+	return conn;
+}
+
+/**
+ * Destroy existing connection
+ * @param connection The connection to destroy.
+ *
+ * Disconnects from both the source and the sink.
+ */
+void connection_destroy(connection_t *connection)
+{
+	assert(connection);
+	assert(!link_in_use(&connection->hound_link));
+	list_remove(&connection->source_link);
+	list_remove(&connection->sink_link);
+	if (connection->sink && connection->sink->connection_change)
+		connection->sink->connection_change(connection->sink, false);
+	if (connection->source && connection->source->connection_change)
+		connection->source->connection_change(connection->source, false);
+	audio_pipe_fini(&connection->fifo);
+	log_debug("DISCONNECTED: %s -> %s",
+	    connection->source->name, connection->sink->name);
+	free(connection);
+}
+
+/**
+ * Update and mix data provided by the source.
+ * @param connection the connection to add.
+ * @param data Destination audio buffer.
+ * @param size size of the destination audio buffer.
+ * @param format format of the destination audio buffer.
+ */
+ssize_t connection_add_source_data(connection_t *connection, void *data,
+    size_t size, pcm_format_t format)
+{
+	assert(connection);
+	if (!data)
+		return EBADMEM;
+	const size_t needed_frames = pcm_format_size_to_frames(size, &format);
+	if (needed_frames > audio_pipe_frames(&connection->fifo) &&
+	    connection->source->update_available_data) {
+		log_debug("Asking source to provide more data");
+		connection->source->update_available_data(
+		    connection->source, size);
+	}
+	log_verbose("Data available after update: %zu",
+	    audio_pipe_bytes(&connection->fifo));
+	ssize_t ret =
+	    audio_pipe_mix_data(&connection->fifo, data, size, &format);
+	if (ret != (ssize_t)size)
+		log_warning("Connection failed to provide enough data %zd/%zu",
+		    ret, size);
+	return ret > 0 ? EOK : ret;
+}
+/**
+ * Add new data to the connection buffer.
+ * @param connection Target conneciton.
+ * @aparam adata Reference counted audio data buffer.
+ * @return Error code.
+ */
+int connection_push_data(connection_t *connection,
+    audio_data_t *adata)
+{
+	assert(connection);
+	assert(adata);
+	const int ret = audio_pipe_push(&connection->fifo, adata);
+	if (ret == EOK && connection->sink->data_available)
+		connection->sink->data_available(connection->sink);
+	return ret;
+}
+
+/**
+ * @}
+ */
Index: uspace/srv/audio/hound/connection.h
===================================================================
--- uspace/srv/audio/hound/connection.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/connection.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2013 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup audio
+ * @brief HelenOS sound server
+ * @{
+ */
+/** @file
+ */
+
+#ifndef CONNECTION_H_
+#define CONNECTION_H_
+
+#include <assert.h>
+#include <adt/list.h>
+#include <pcm/format.h>
+
+#include "audio_data.h"
+#include "audio_source.h"
+#include "audio_sink.h"
+
+/** Source->sink connection structure */
+typedef struct {
+	/** Source's connections link */
+	link_t source_link;
+	/** Sink's connections link */
+	link_t sink_link;
+	/** Hound's connections link */
+	link_t hound_link;
+	/** audio data pipe */
+	audio_pipe_t fifo;
+	/** Target sink */
+	audio_sink_t *sink;
+	/** Target source */
+	audio_source_t *source;
+} connection_t;
+
+/**
+ * List instance helper.
+ * @param l link
+ * @return pointer to a connection structure, NULL on failure.
+ */
+static inline connection_t * connection_from_source_list(link_t *l)
+{
+	return l ? list_get_instance(l, connection_t, source_link) : NULL;
+}
+
+/**
+ * List instance helper.
+ * @param l link
+ * @return pointer to a connection structure, NULL on failure.
+ */
+static inline connection_t * connection_from_sink_list(link_t *l)
+{
+	return l ? list_get_instance(l, connection_t, sink_link) : NULL;
+}
+
+/**
+ * List instance helper.
+ * @param l link
+ * @return pointer to a connection structure, NULL on failure.
+ */
+static inline connection_t * connection_from_hound_list(link_t *l)
+{
+	return l ? list_get_instance(l, connection_t, hound_link) : NULL;
+}
+
+connection_t *connection_create(audio_source_t *source, audio_sink_t *sink);
+void connection_destroy(connection_t *connection);
+
+ssize_t connection_add_source_data(connection_t *connection, void *data,
+    size_t size, pcm_format_t format);
+
+int connection_push_data(connection_t *connection, audio_data_t *adata);
+
+/**
+ * Source name getter.
+ * @param connection Connection to the source.
+ * @param valid string identifier, "no source" or "unnamed source" on failure.
+ */
+static inline const char *connection_source_name(connection_t *connection)
+{
+	assert(connection);
+	if (connection->source && connection->source->name)
+		return connection->source->name;
+	// TODO assert?
+	return connection->source ? "unnamed source" : "no source";
+}
+
+/**
+ * Sink name getter.
+ * @param connection Connection to the sink.
+ * @param valid string identifier, "no source" or "unnamed source" on failure.
+ */
+static inline const char *connection_sink_name(connection_t *connection)
+{
+	assert(connection);
+	if (connection->sink && connection->sink->name)
+		return connection->sink->name;
+	// TODO assert?
+	return connection->source ? "unnamed sink" : "no sink";
+}
+
+
+#endif
+/**
+ * @}
+ */
Index: uspace/srv/audio/hound/hound.c
===================================================================
--- uspace/srv/audio/hound/hound.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/hound.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,585 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/**
+ * @addtogroup audio
+ * @brief HelenOS sound server.
+ * @{
+ */
+/** @file
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "hound.h"
+#include "audio_device.h"
+#include "audio_sink.h"
+#include "audio_source.h"
+#include "connection.h"
+#include "log.h"
+#include "errno.h"
+#include "str_error.h"
+
+#define FIND_BY_NAME(type) \
+do { \
+	assert(list); \
+	assert(name); \
+	list_foreach(*list, it) { \
+		audio_ ## type ## _t *dev = \
+		    audio_ ## type ## _list_instance(it); \
+		if (str_cmp(name, dev->name) == 0) { \
+			log_debug("%s with name '%s' is in the list", \
+			    #type, name); \
+			return dev; \
+		} \
+	} \
+	return NULL; \
+} while (0)
+
+/**
+ * Search devices by name.
+ * @param name String identifier.
+ * @return Pointer to the found device, NULL on failure.
+ */
+static audio_device_t * find_device_by_name(list_t *list, const char *name)
+{
+	FIND_BY_NAME(device);
+}
+
+/**
+ * Search sources by name.
+ * @param name String identifier.
+ * @return Pointer to the found source, NULL on failure.
+ */
+static audio_source_t * find_source_by_name(list_t *list, const char *name)
+{
+	FIND_BY_NAME(source);
+}
+
+/**
+ * Search sinks by name.
+ * @param name String identifier.
+ * @return Pointer to the found sink, NULL on failure.
+ */
+static audio_sink_t * find_sink_by_name(list_t *list, const char *name)
+{
+	FIND_BY_NAME(sink);
+}
+
+static int hound_disconnect_internal(hound_t *hound, const char* source_name, const char* sink_name);
+
+/**
+ * Remove provided sink.
+ * @param hound The hound structure.
+ * @param sink Target sink to remove.
+ *
+ * This function has to be called with the list_guard lock held.
+ */
+static void hound_remove_sink_internal(hound_t *hound, audio_sink_t *sink)
+{
+	assert(hound);
+	assert(sink);
+	assert(fibril_mutex_is_locked(&hound->list_guard));
+	log_verbose("Removing sink '%s'.", sink->name);
+	if (!list_empty(&sink->connections))
+		log_warning("Removing sink '%s' while still connected.", sink->name);
+	while (!list_empty(&sink->connections)) {
+		connection_t *conn =
+		    connection_from_sink_list(list_first(&sink->connections));
+		list_remove(&conn->hound_link);
+		connection_destroy(conn);
+	}
+	list_remove(&sink->link);
+}
+
+/**
+ * Remove provided source.
+ * @param hound The hound structure.
+ * @param sink Target source to remove.
+ *
+ * This function has to be called with the guard lock held.
+ */
+static void hound_remove_source_internal(hound_t *hound, audio_source_t *source)
+{
+	assert(hound);
+	assert(source);
+	log_verbose("Removing source '%s'.", source->name);
+	if (!list_empty(&source->connections))
+		log_warning("Removing source '%s' while still connected.", source->name);
+	while (!list_empty(&source->connections)) {
+		connection_t *conn =
+		    connection_from_source_list(list_first(&source->connections));
+		assert(conn);
+		list_remove(&conn->hound_link);
+		connection_destroy(conn);
+	}
+	list_remove(&source->link);
+}
+
+/**
+ * Initialize hound structure.
+ * @param hound The structure to initialize.
+ * @return Error code.
+ */
+int hound_init(hound_t *hound)
+{
+	assert(hound);
+	fibril_mutex_initialize(&hound->list_guard);
+	list_initialize(&hound->devices);
+	list_initialize(&hound->contexts);
+	list_initialize(&hound->sources);
+	list_initialize(&hound->sinks);
+	list_initialize(&hound->connections);
+	return EOK;
+}
+
+/**
+ * Add a new application context.
+ * @param hound Hound structure.
+ * @param ctx Context to add.
+ * @return Error code.
+ */
+int hound_add_ctx(hound_t *hound, hound_ctx_t *ctx)
+{
+	log_info("Trying to add context %p", ctx);
+	assert(hound);
+	if (!ctx)
+		return EINVAL;
+	fibril_mutex_lock(&hound->list_guard);
+	list_append(&ctx->link, &hound->contexts);
+	fibril_mutex_unlock(&hound->list_guard);
+	int ret = EOK;
+	if (ret == EOK && ctx->source)
+		ret = hound_add_source(hound, ctx->source);
+	if (ret == EOK && ctx->sink)
+		ret = hound_add_sink(hound, ctx->sink);
+	if (ret != EOK) {
+		fibril_mutex_lock(&hound->list_guard);
+		list_remove(&ctx->link);
+		fibril_mutex_unlock(&hound->list_guard);
+	}
+	return ret;
+}
+
+/**
+ * Remove existing application context.
+ * @param hound Hound structure.
+ * @param ctx Context to remove.
+ * @return Error code.
+ */
+int hound_remove_ctx(hound_t *hound, hound_ctx_t *ctx)
+{
+	assert(hound);
+	if (!ctx)
+		return EINVAL;
+	if (!list_empty(&ctx->streams))
+		return EBUSY;
+	fibril_mutex_lock(&hound->list_guard);
+	list_remove(&ctx->link);
+	if (ctx->source)
+		hound_remove_source_internal(hound, ctx->source);
+	if (ctx->sink)
+		hound_remove_sink_internal(hound, ctx->sink);
+	fibril_mutex_unlock(&hound->list_guard);
+	return EOK;
+}
+
+/**
+ * Search registered contexts for the matching id.
+ * @param hound The hound structure.
+ * @param id Requested id.
+ * @return Pointer to the found structure, NULL on failure.
+ */
+hound_ctx_t *hound_get_ctx_by_id(hound_t *hound, hound_context_id_t id)
+{
+	assert(hound);
+
+	fibril_mutex_lock(&hound->list_guard);
+	hound_ctx_t *res = NULL;
+	list_foreach(hound->contexts, it) {
+		hound_ctx_t *ctx = hound_ctx_from_link(it);
+		if (hound_ctx_get_id(ctx) == id) {
+			res = ctx;
+			break;
+		}
+	}
+	fibril_mutex_unlock(&hound->list_guard);
+	return res;
+}
+
+/**
+ * Add a new device.
+ * @param hound The hound structure.
+ * @param id Locations service id representing the device driver.
+ * @param name String identifier.
+ * @return Error code.
+ */
+int hound_add_device(hound_t *hound, service_id_t id, const char *name)
+{
+	log_verbose("Adding device \"%s\", service: %zu", name, id);
+
+	assert(hound);
+	if (!name || !id) {
+		log_debug("Incorrect parameters.");
+		return EINVAL;
+	}
+
+	list_foreach(hound->devices, it) {
+		audio_device_t *dev = audio_device_list_instance(it);
+		if (dev->id == id) {
+			log_debug("Device with id %zu is already present", id);
+			return EEXISTS;
+		}
+	}
+
+	audio_device_t *dev = find_device_by_name(&hound->devices, name);
+	if (dev) {
+		log_debug("Device with name %s is already present", name);
+		return EEXISTS;
+	}
+
+	dev = malloc(sizeof(audio_device_t));
+	if (!dev) {
+		log_debug("Failed to malloc device structure.");
+		return ENOMEM;
+	}
+
+	const int ret = audio_device_init(dev, id, name);
+	if (ret != EOK) {
+		log_debug("Failed to initialize new audio device: %s",
+			str_error(ret));
+		free(dev);
+		return ret;
+	}
+
+	list_append(&dev->link, &hound->devices);
+	log_info("Added new device: '%s'", dev->name);
+
+	audio_source_t *source = audio_device_get_source(dev);
+	if (source) {
+		const int ret = hound_add_source(hound, source);
+		if (ret != EOK) {
+			log_debug("Failed to add device source: %s",
+			    str_error(ret));
+			audio_device_fini(dev);
+			return ret;
+		}
+		log_verbose("Added source: '%s'.", source->name);
+	}
+
+	audio_sink_t *sink = audio_device_get_sink(dev);
+	if (sink) {
+		const int ret = hound_add_sink(hound, sink);
+		if (ret != EOK) {
+			log_debug("Failed to add device sink: %s",
+			    str_error(ret));
+			audio_device_fini(dev);
+			return ret;
+		}
+		log_verbose("Added sink: '%s'.", sink->name);
+	}
+
+	if (!source && !sink)
+		log_warning("Neither sink nor source on device '%s'.", name);
+
+	return ret;
+}
+
+/**
+ * Register a new source.
+ * @param hound The hound structure.
+ * @param source A new source to add.
+ * @return Error code.
+ */
+int hound_add_source(hound_t *hound, audio_source_t *source)
+{
+	assert(hound);
+	if (!source || !source->name || str_cmp(source->name, "default") == 0) {
+		log_debug("Invalid source specified.");
+		return EINVAL;
+	}
+	fibril_mutex_lock(&hound->list_guard);
+	if (find_source_by_name(&hound->sources, source->name)) {
+		log_debug("Source by that name already exists");
+		fibril_mutex_unlock(&hound->list_guard);
+		return EEXISTS;
+	}
+	list_append(&source->link, &hound->sources);
+	fibril_mutex_unlock(&hound->list_guard);
+	return EOK;
+}
+
+/**
+ * Register a new sink.
+ * @param hound The hound structure.
+ * @param sink A new sink to add.
+ * @return Error code.
+ */
+int hound_add_sink(hound_t *hound, audio_sink_t *sink)
+{
+	assert(hound);
+	if (!sink || !sink->name || str_cmp(sink->name, "default") == 0) {
+		log_debug("Invalid source specified.");
+		return EINVAL;
+	}
+	fibril_mutex_lock(&hound->list_guard);
+	if (find_sink_by_name(&hound->sinks, sink->name)) {
+		log_debug("Sink by that name already exists");
+		fibril_mutex_unlock(&hound->list_guard);
+		return EEXISTS;
+	}
+	list_append(&sink->link, &hound->sinks);
+	fibril_mutex_unlock(&hound->list_guard);
+	return EOK;
+}
+
+/**
+ * Remove a registered source.
+ * @param hound The hound structure.
+ * @param source A registered source to remove.
+ * @return Error code.
+ */
+int hound_remove_source(hound_t *hound, audio_source_t *source)
+{
+	assert(hound);
+	if (!source)
+		return EINVAL;
+	fibril_mutex_lock(&hound->list_guard);
+	hound_remove_source_internal(hound, source);
+	fibril_mutex_unlock(&hound->list_guard);
+	return EOK;
+}
+
+/**
+ * Remove a registered sink.
+ * @param hound The hound structure.
+ * @param sink A registered sink to remove.
+ * @return Error code.
+ */
+int hound_remove_sink(hound_t *hound, audio_sink_t *sink)
+{
+	assert(hound);
+	if (!sink)
+		return EINVAL;
+	fibril_mutex_lock(&hound->list_guard);
+	hound_remove_sink_internal(hound, sink);
+	fibril_mutex_unlock(&hound->list_guard);
+	return EOK;
+}
+
+/**
+ * List all registered sources.
+ * @param[in] hound The hound structure.
+ * @param[out] list List of the string identifiers.
+ * @param[out] size Number of identifiers int he @p list.
+ * @return Error code.
+ */
+int hound_list_sources(hound_t *hound, const char ***list, size_t *size)
+{
+	assert(hound);
+	if (!list || !size)
+		return EINVAL;
+
+	fibril_mutex_lock(&hound->list_guard);
+	const size_t count = list_count(&hound->sources);
+	if (count == 0) {
+		*list = NULL;
+		*size = 0;
+		fibril_mutex_unlock(&hound->list_guard);
+		return EOK;
+	}
+	const char **names = calloc(count, sizeof(char *));
+	int ret = names ? EOK : ENOMEM;
+	for (size_t i = 0; i < count && ret == EOK; ++i) {
+		link_t *slink = list_nth(&hound->sources, i);
+		audio_source_t *source = audio_source_list_instance(slink);
+		names[i] = str_dup(source->name);
+		if (names[i])
+			ret = ENOMEM;
+	}
+	if (ret == EOK) {
+		*size = count;
+		*list = names;
+	} else {
+		for (size_t i = 0; i < count; ++i)
+			free(names[i]);
+		free(names);
+	}
+	fibril_mutex_unlock(&hound->list_guard);
+	return ret;
+}
+
+/**
+ * List all registered sinks.
+ * @param[in] hound The hound structure.
+ * @param[out] list List of the string identifiers.
+ * @param[out] size Number of identifiers int he @p list.
+ * @return Error code.
+ */
+int hound_list_sinks(hound_t *hound, const char ***list, size_t *size)
+{
+	assert(hound);
+	if (!list || !size)
+		return EINVAL;
+
+	fibril_mutex_lock(&hound->list_guard);
+	const size_t count = list_count(&hound->sinks);
+	if (count == 0) {
+		*list = NULL;
+		*size = 0;
+		fibril_mutex_unlock(&hound->list_guard);
+		return EOK;
+	}
+	const char **names = calloc(count, sizeof(char *));
+	int ret = names ? EOK : ENOMEM;
+	for (size_t i = 0; i < count && ret == EOK; ++i) {
+		link_t *slink = list_nth(&hound->sinks, i);
+		audio_sink_t *sink = audio_sink_list_instance(slink);
+		names[i] = str_dup(sink->name);
+		if (!names[i])
+			ret = ENOMEM;
+	}
+	if (ret == EOK) {
+		*size = count;
+		*list = names;
+	} else {
+		for (size_t i = 0; i < count; ++i)
+			free(names[i]);
+		free(names);
+	}
+	fibril_mutex_unlock(&hound->list_guard);
+	return ret;
+}
+
+/**
+ * List all connections
+ * @param[in] hound The hound structure.
+ * @param[out] sources List of the source string identifiers.
+ * @param[out] sinks List of the sinks string identifiers.
+ * @param[out] size Number of identifiers int he @p list.
+ * @return Error code.
+ *
+ * Lists include duplicit name entries. The order of entries is important,
+ * identifiers with the same index are connected.
+ */
+int hound_list_connections(hound_t *hound, const char ***sources,
+    const char ***sinks, size_t *size)
+{
+	fibril_mutex_lock(&hound->list_guard);
+	fibril_mutex_unlock(&hound->list_guard);
+	return ENOTSUP;
+}
+
+/**
+ * Create and register a new connection.
+ * @param hound The hound structure.
+ * @param source_name Source's string id.
+ * @param sink_name Sink's string id.
+ * @return Error code.
+ */
+int hound_connect(hound_t *hound, const char* source_name, const char* sink_name)
+{
+	assert(hound);
+	log_verbose("Connecting '%s' to '%s'.", source_name, sink_name);
+	fibril_mutex_lock(&hound->list_guard);
+
+	audio_source_t *source =
+	    audio_source_list_instance(list_first(&hound->sources));
+	if (str_cmp(source_name, "default") != 0)
+	    source = find_source_by_name(&hound->sources, source_name);
+
+	audio_sink_t *sink =
+	    audio_sink_list_instance(list_first(&hound->sinks));
+	if (str_cmp(sink_name, "default") != 0)
+	    sink = find_sink_by_name(&hound->sinks, sink_name);
+
+	if (!source || !sink) {
+		fibril_mutex_unlock(&hound->list_guard);
+		log_debug("Source (%p), or sink (%p) not found", source, sink);
+		return ENOENT;
+	}
+	connection_t *conn = connection_create(source, sink);
+	if (!conn) {
+		fibril_mutex_unlock(&hound->list_guard);
+		log_debug("Failed to create connection");
+		return ENOMEM;
+	}
+	list_append(&conn->hound_link, &hound->connections);
+	fibril_mutex_unlock(&hound->list_guard);
+	return EOK;
+}
+
+/**
+ * Find and destroy connection between source and sink.
+ * @param hound The hound structure.
+ * @param source_name Source's string id.
+ * @param sink_name Sink's string id.
+ * @return Error code.
+ */
+int hound_disconnect(hound_t *hound, const char* source_name, const char* sink_name)
+{
+	assert(hound);
+	fibril_mutex_lock(&hound->list_guard);
+	const int ret = hound_disconnect_internal(hound, source_name, sink_name);
+	fibril_mutex_unlock(&hound->list_guard);
+	return ret;
+}
+
+/**
+ * Internal disconnect helper.
+ * @param hound The hound structure.
+ * @param source_name Source's string id.
+ * @param sink_name Sink's string id.
+ * @return Error code.
+ *
+ * This function has to be called with the list_guard lock held.
+ */
+static int hound_disconnect_internal(hound_t *hound, const char* source_name,
+    const char* sink_name)
+{
+	assert(hound);
+	assert(fibril_mutex_is_locked(&hound->list_guard));
+	log_debug("Disconnecting '%s' to '%s'.", source_name, sink_name);
+
+	list_foreach_safe(hound->connections, it, next) {
+		connection_t *conn = connection_from_hound_list(it);
+		if (str_cmp(connection_source_name(conn), source_name) == 0 ||
+		    str_cmp(connection_sink_name(conn), sink_name) == 0) {
+		    log_debug("Removing %s -> %s", connection_source_name(conn),
+		        connection_sink_name(conn));
+		    list_remove(it);
+		    connection_destroy(conn);
+		}
+	}
+
+	return EOK;
+}
+/**
+ * @}
+ */
Index: uspace/srv/audio/hound/hound.h
===================================================================
--- uspace/srv/audio/hound/hound.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/hound.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup audio
+ * @brief HelenOS sound server
+ * @{
+ */
+/** @file
+ */
+
+#ifndef HOUND_H_
+#define HOUND_H_
+
+#include <async.h>
+#include <adt/list.h>
+#include <ipc/loc.h>
+#include <errno.h>
+#include <fibril_synch.h>
+#include <pcm/format.h>
+#include <hound/protocol.h>
+
+#include "hound_ctx.h"
+#include "audio_source.h"
+#include "audio_sink.h"
+
+/** The main Hound structure */
+typedef struct {
+	/** List access guard */
+	fibril_mutex_t list_guard;
+	/** enumerated devices */
+	list_t devices;
+	/** registered contexts */
+	list_t contexts;
+	/** Provided sources */
+	list_t sources;
+	/** Provided sinks */
+	list_t sinks;
+	/** Existing connections. */
+	list_t connections;
+} hound_t;
+
+int hound_init(hound_t *hound);
+int hound_add_ctx(hound_t *hound, hound_ctx_t *ctx);
+int hound_remove_ctx(hound_t *hound, hound_ctx_t *ctx);
+hound_ctx_t *hound_get_ctx_by_id(hound_t *hound, hound_context_id_t id);
+
+int hound_add_device(hound_t *hound, service_id_t id, const char* name);
+int hound_add_source(hound_t *hound, audio_source_t *source);
+int hound_add_sink(hound_t *hound, audio_sink_t *sink);
+int hound_list_sources(hound_t *hound, const char ***list, size_t *size);
+int hound_list_sinks(hound_t *hound, const char ***list, size_t *size);
+int hound_list_connections(hound_t *hound, const char ***sources,
+    const char ***sinks, size_t *size);
+int hound_remove_source(hound_t *hound, audio_source_t *source);
+int hound_remove_sink(hound_t *hound, audio_sink_t *sink);
+int hound_connect(hound_t *hound, const char* source_name, const char* sink_name);
+int hound_disconnect(hound_t *hound, const char* source_name, const char* sink_name);
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/srv/audio/hound/hound_ctx.c
===================================================================
--- uspace/srv/audio/hound/hound_ctx.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/hound_ctx.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,492 @@
+/*
+ * Copyright (c) 2013 Jan Vesely
+ * 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.
+ */
+
+/**
+ * @addtogroup audio
+ * @brief HelenOS sound server.
+ * @{
+ */
+/** @file
+ */
+
+#include <malloc.h>
+#include <macros.h>
+#include <errno.h>
+#include <str_error.h>
+
+#include "hound_ctx.h"
+#include "audio_data.h"
+#include "connection.h"
+#include "log.h"
+
+static int update_data(audio_source_t *source, size_t size);
+static int new_data(audio_sink_t *sink);
+
+/**
+ * Allocate and initialize hound context structure.
+ * @param name String identifier.
+ * @return Pointer to a new context structure, NULL on failure
+ *
+ * Creates record context structure.
+ */
+hound_ctx_t *hound_record_ctx_get(const char *name)
+{
+	hound_ctx_t *ctx = malloc(sizeof(hound_ctx_t));
+	if (ctx) {
+		link_initialize(&ctx->link);
+		list_initialize(&ctx->streams);
+		fibril_mutex_initialize(&ctx->guard);
+		ctx->source = NULL;
+		ctx->sink = malloc(sizeof(audio_sink_t));
+		if (!ctx->sink) {
+			free(ctx);
+			return NULL;
+		}
+		const int ret = audio_sink_init(ctx->sink, name, ctx, NULL,
+		    NULL, new_data, &AUDIO_FORMAT_DEFAULT);
+		if (ret != EOK) {
+			free(ctx->sink);
+			free(ctx);
+			return NULL;
+		}
+	}
+	return ctx;
+}
+
+/**
+ * Allocate and initialize hound context structure.
+ * @param name String identifier.
+ * @return Pointer to a new context structure, NULL on failure
+ *
+ * Creates record context structure.
+ */
+hound_ctx_t *hound_playback_ctx_get(const char *name)
+{
+	hound_ctx_t *ctx = malloc(sizeof(hound_ctx_t));
+	if (ctx) {
+		link_initialize(&ctx->link);
+		list_initialize(&ctx->streams);
+		fibril_mutex_initialize(&ctx->guard);
+		ctx->sink = NULL;
+		ctx->source = malloc(sizeof(audio_source_t));
+		if (!ctx->source) {
+			free(ctx);
+			return NULL;
+		}
+		const int ret = audio_source_init(ctx->source, name, ctx, NULL,
+		    update_data, &AUDIO_FORMAT_DEFAULT);
+		if (ret != EOK) {
+			free(ctx->source);
+			free(ctx);
+			return NULL;
+		}
+	}
+	return ctx;
+}
+
+/**
+ * Destroy existing context structure.
+ * @param ctx hound cotnext to destroy.
+ */
+void hound_ctx_destroy(hound_ctx_t *ctx)
+{
+	assert(ctx);
+	assert(!link_in_use(&ctx->link));
+	assert(list_empty(&ctx->streams));
+	if (ctx->source)
+		audio_source_fini(ctx->source);
+	if (ctx->sink)
+		audio_sink_fini(ctx->sink);
+	free(ctx->source);
+	free(ctx->sink);
+	free(ctx);
+}
+
+/**
+ * Retrieve associated context id.
+ * @param ctx hound context.
+ * @return context id of the context.
+ */
+hound_context_id_t hound_ctx_get_id(hound_ctx_t *ctx)
+{
+	assert(ctx);
+	return (hound_context_id_t)ctx;
+}
+
+/**
+ * Query playback/record status of a hound context.
+ * @param ctx Hound context.
+ * @return True if ctx  is a recording context.
+ */
+bool hound_ctx_is_record(hound_ctx_t *ctx)
+{
+	assert(ctx);
+	return ctx->source == NULL;
+}
+
+
+/*
+ * STREAMS
+ */
+
+/** Hound stream structure. */
+typedef struct hound_ctx_stream {
+	/** Hound context streams link */
+	link_t link;
+	/** Audio data pipe */
+	audio_pipe_t fifo;
+	/** Parent context */
+	hound_ctx_t *ctx;
+	/** Stream data format */
+	pcm_format_t format;
+	/** Stream modifiers */
+	int flags;
+	/** Maximum allowed buffer size */
+	size_t allowed_size;
+	/** Fifo access synchronization */
+	fibril_mutex_t guard;
+	/** buffer status change condition */
+	fibril_condvar_t change;
+} hound_ctx_stream_t;
+
+/**
+ * List instance helper.
+ * @param l link
+ * @return pointer to a hound context structure, NULL on failure.
+ */
+static inline hound_ctx_stream_t *hound_ctx_stream_from_link(link_t *l)
+{
+	return l ? list_get_instance(l, hound_ctx_stream_t, link) : NULL;
+}
+
+/**
+ * New stream append helper.
+ * @param ctx hound context.
+ * @param stream A new stream.
+ */
+static inline void stream_append(hound_ctx_t *ctx, hound_ctx_stream_t *stream)
+{
+	assert(ctx);
+	assert(stream);
+	fibril_mutex_lock(&ctx->guard);
+	list_append(&stream->link, &ctx->streams);
+	if (list_count(&ctx->streams) == 1) {
+		if (ctx->source && list_count(&ctx->source->connections) == 0)
+			ctx->source->format = stream->format;
+	}
+	fibril_mutex_unlock(&ctx->guard);
+}
+
+/**
+ * Push new data to stream, do not block.
+ * @param stream The target stream.
+ * @param adata The new data.
+ * @return Error code.
+ */
+static int stream_push_data(hound_ctx_stream_t *stream, audio_data_t *adata)
+{
+	assert(stream);
+	assert(adata);
+
+	if (stream->allowed_size && adata->size > stream->allowed_size)
+		return EINVAL;
+
+	fibril_mutex_lock(&stream->guard);
+	if (stream->allowed_size &&
+	    (audio_pipe_bytes(&stream->fifo) + adata->size
+	        > stream->allowed_size)) {
+		fibril_mutex_unlock(&stream->guard);
+		return EOVERFLOW;
+
+	}
+
+	const int ret = audio_pipe_push(&stream->fifo, adata);
+	fibril_mutex_unlock(&stream->guard);
+	if (ret == EOK)
+		fibril_condvar_signal(&stream->change);
+	return ret;
+}
+
+/**
+ * Old stream remove helper.
+ * @param ctx hound context.
+ * @param stream An old stream.
+ */
+static inline void stream_remove(hound_ctx_t *ctx, hound_ctx_stream_t *stream)
+{
+	assert(ctx);
+	assert(stream);
+	fibril_mutex_lock(&ctx->guard);
+	list_remove(&stream->link);
+	fibril_mutex_unlock(&ctx->guard);
+}
+
+/**
+ * Create new stream.
+ * @param ctx Assocaited hound context.
+ * @param flags Stream modidfiers.
+ * @param format PCM data format.
+ * @param buffer_size Maximum allowed buffer size.
+ * @return Pointer to a new stream structure, NULL on failure.
+ */
+hound_ctx_stream_t *hound_ctx_create_stream(hound_ctx_t *ctx, int flags,
+	pcm_format_t format, size_t buffer_size)
+{
+	assert(ctx);
+	hound_ctx_stream_t *stream = malloc(sizeof(hound_ctx_stream_t));
+	if (stream) {
+		audio_pipe_init(&stream->fifo);
+		link_initialize(&stream->link);
+		fibril_mutex_initialize(&stream->guard);
+		fibril_condvar_initialize(&stream->change);
+		stream->ctx = ctx;
+		stream->flags = flags;
+		stream->format = format;
+		stream->allowed_size = buffer_size;
+		stream_append(ctx, stream);
+		log_verbose("CTX: %p added stream; flags:%#x ch: %u r:%u f:%s",
+		    ctx, flags, format.channels, format.sampling_rate,
+		    pcm_sample_format_str(format.sample_format));
+	}
+	return stream;
+}
+
+/**
+ * Destroy existing stream structure.
+ * @param stream The stream to destroy.
+ *
+ * The function will print warning if there are data in the buffer.
+ */
+void hound_ctx_destroy_stream(hound_ctx_stream_t *stream)
+{
+	if (stream) {
+		stream_remove(stream->ctx, stream);
+		if (audio_pipe_bytes(&stream->fifo))
+			log_warning("Destroying stream with non empty buffer");
+		log_verbose("CTX: %p remove stream (%zu/%zu); "
+		    "flags:%#x ch: %u r:%u f:%s",
+		    stream->ctx, audio_pipe_bytes(&stream->fifo),
+		    stream->allowed_size, stream->flags,
+		    stream->format.channels, stream->format.sampling_rate,
+		    pcm_sample_format_str(stream->format.sample_format));
+		audio_pipe_fini(&stream->fifo);
+		free(stream);
+	}
+}
+
+/**
+ * Write new data to a stream.
+ * @param stream The destination stream.
+ * @param data audio data buffer.
+ * @param size size of the @p data buffer.
+ * @return Error code.
+ */
+int hound_ctx_stream_write(hound_ctx_stream_t *stream, const void *data,
+    size_t size)
+{
+	assert(stream);
+
+	if (stream->allowed_size && size > stream->allowed_size)
+		return EINVAL;
+
+	fibril_mutex_lock(&stream->guard);
+	while (stream->allowed_size &&
+	    (audio_pipe_bytes(&stream->fifo) + size > stream->allowed_size)) {
+	    fibril_condvar_wait(&stream->change, &stream->guard);
+
+	}
+
+	const int ret =
+	    audio_pipe_push_data(&stream->fifo, data, size, stream->format);
+	fibril_mutex_unlock(&stream->guard);
+	if (ret == EOK)
+		fibril_condvar_signal(&stream->change);
+	return ret;
+}
+
+/**
+ * Read data from a buffer.
+ * @param stream The source buffer.
+ * @param data Destination data buffer.
+ * @param size Size of the @p data buffer.
+ * @return Error code.
+ */
+int hound_ctx_stream_read(hound_ctx_stream_t *stream, void *data, size_t size)
+{
+	assert(stream);
+
+	if (stream->allowed_size && size > stream->allowed_size)
+		return EINVAL;
+
+	fibril_mutex_lock(&stream->guard);
+	while (audio_pipe_bytes(&stream->fifo) < size) {
+	    fibril_condvar_wait(&stream->change, &stream->guard);
+	}
+
+	pcm_format_silence(data, size, &stream->format);
+	const int ret =
+	    audio_pipe_mix_data(&stream->fifo, data, size, &stream->format);
+	fibril_mutex_unlock(&stream->guard);
+	if (ret == EOK)
+		fibril_condvar_signal(&stream->change);
+	return ret;
+}
+
+/**
+ * Add (mix) stream data to the destination buffer.
+ * @param stream The source stream.
+ * @param data Destination audio buffer.
+ * @param size Size of the @p data buffer.
+ * @param format Destination data format.
+ * @return Size of the destination buffer touch with stream's data,
+ *         error code on failure.
+ */
+ssize_t hound_ctx_stream_add_self(hound_ctx_stream_t *stream, void *data,
+    size_t size, const pcm_format_t *f)
+{
+	assert(stream);
+	fibril_mutex_lock(&stream->guard);
+	const int ret = audio_pipe_mix_data(&stream->fifo, data, size, f);
+	fibril_condvar_signal(&stream->change);
+	fibril_mutex_unlock(&stream->guard);
+	return ret;
+}
+
+/**
+ * Block until the stream's buffer is empty.
+ * @param stream Target stream.
+ */
+void hound_ctx_stream_drain(hound_ctx_stream_t *stream)
+{
+	assert(stream);
+	log_debug("Draining stream");
+	fibril_mutex_lock(&stream->guard);
+	while (audio_pipe_bytes(&stream->fifo))
+		fibril_condvar_wait(&stream->change, &stream->guard);
+	fibril_mutex_unlock(&stream->guard);
+}
+
+/**
+ * Update context data.
+ * @param source Source abstraction.
+ * @param size Required size in source's format.
+ * @return error code.
+ *
+ * Mixes data from all streams and pushes it to all connections.
+ */
+int update_data(audio_source_t *source, size_t size)
+{
+	assert(source);
+	assert(source->private_data);
+	hound_ctx_t *ctx = source->private_data;
+	void *buffer = malloc(size);
+	if (!buffer)
+		return ENOMEM;
+	audio_data_t *adata = audio_data_create(buffer, size, source->format);
+	if (!adata) {
+		free(buffer);
+		return ENOMEM;
+	}
+	log_verbose("CTX: %p: Mixing %u streams", ctx,
+	    list_count(&ctx->streams));
+	pcm_format_silence(buffer, size, &source->format);
+	fibril_mutex_lock(&ctx->guard);
+	list_foreach(ctx->streams, it) {
+		hound_ctx_stream_t *stream = hound_ctx_stream_from_link(it);
+		ssize_t copied = hound_ctx_stream_add_self(
+		    stream, buffer, size, &source->format);
+		if (copied != (ssize_t)size)
+			log_warning("Not enough data in stream buffer");
+	}
+	log_verbose("CTX: %p. Pushing audio to %u connections", ctx,
+	    list_count(&source->connections));
+	list_foreach(source->connections, it) {
+		connection_t *conn = connection_from_source_list(it);
+		connection_push_data(conn, adata);
+	}
+	fibril_mutex_unlock(&ctx->guard);
+	return EOK;
+}
+
+int new_data(audio_sink_t *sink)
+{
+	assert(sink);
+	assert(sink->private_data);
+	hound_ctx_t *ctx = sink->private_data;
+
+	fibril_mutex_lock(&ctx->guard);
+
+	/* count available data */
+	size_t available_frames = -1;  /* this is ugly.... */
+	list_foreach(sink->connections, it) {
+		connection_t *conn = connection_from_source_list(it);
+		available_frames = min(available_frames,
+		    audio_pipe_frames(&conn->fifo));
+	}
+
+	const size_t bsize =
+	    available_frames * pcm_format_frame_size(&sink->format);
+	void *buffer = malloc(bsize);
+	if (!buffer) {
+		fibril_mutex_unlock(&ctx->guard);
+		return ENOMEM;
+	}
+	audio_data_t *adata = audio_data_create(buffer, bsize, sink->format);
+	if (!adata) {
+		fibril_mutex_unlock(&ctx->guard);
+		free(buffer);
+		return ENOMEM;
+	}
+
+	/* mix data */
+	pcm_format_silence(buffer, bsize, &sink->format);
+	list_foreach(sink->connections, it) {
+		connection_t *conn = connection_from_source_list(it);
+		/* This should not trigger data update on the source */
+		const size_t copied = connection_add_source_data(
+		    conn, buffer, bsize, sink->format);
+		if (copied != bsize)
+			log_error("Copied less than advertised data, "
+			    "something is wrong");
+	}
+	/* push to all streams */
+	list_foreach(ctx->streams, it) {
+		hound_ctx_stream_t *stream = hound_ctx_stream_from_link(it);
+		const int ret = stream_push_data(stream, adata);
+		if (ret != EOK)
+			log_error("Failed to push data to stream: %s",
+				str_error(ret));
+	}
+	audio_data_unref(adata);
+	fibril_mutex_unlock(&ctx->guard);
+	return ENOTSUP;
+}
+
+/**
+ * @}
+ */
Index: uspace/srv/audio/hound/hound_ctx.h
===================================================================
--- uspace/srv/audio/hound/hound_ctx.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/hound_ctx.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2013 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup audio
+ * @brief HelenOS sound server
+ * @{
+ */
+/** @file
+ */
+
+#ifndef HOUND_CTX_H_
+#define HOUND_CTX_H_
+
+#include <adt/list.h>
+#include <hound/protocol.h>
+#include <fibril_synch.h>
+
+#include "audio_source.h"
+#include "audio_sink.h"
+
+/** Application context structure */
+typedef struct {
+	/** Hound's contexts link */
+	link_t link;
+	/** List of streams */
+	list_t streams;
+	/** Provided audio source abstraction */
+	audio_source_t *source;
+	/** Provided audio sink abstraction */
+	audio_sink_t *sink;
+	/** List access synchronization */
+	fibril_mutex_t guard;
+} hound_ctx_t;
+
+/**
+ * List instance helper.
+ * @param l link
+ * @return pointer to a hound context structure, NULL on failure.
+ */
+static inline hound_ctx_t *hound_ctx_from_link(link_t *l)
+{
+	return l ? list_get_instance(l, hound_ctx_t, link) : NULL;
+}
+
+typedef struct hound_ctx_stream hound_ctx_stream_t;
+
+
+hound_ctx_t *hound_record_ctx_get(const char *name);
+hound_ctx_t *hound_playback_ctx_get(const char *name);
+void hound_ctx_destroy(hound_ctx_t *context);
+
+hound_context_id_t hound_ctx_get_id(hound_ctx_t *ctx);
+bool hound_ctx_is_record(hound_ctx_t *ctx);
+
+hound_ctx_stream_t *hound_ctx_create_stream(hound_ctx_t *ctx, int flags,
+	pcm_format_t format, size_t buffer_size);
+void hound_ctx_destroy_stream(hound_ctx_stream_t *stream);
+
+int hound_ctx_stream_write(hound_ctx_stream_t *stream, const void *buffer,
+    size_t size);
+int hound_ctx_stream_read(hound_ctx_stream_t *stream, void *buffer, size_t size);
+ssize_t hound_ctx_stream_add_self(hound_ctx_stream_t *stream, void *data,
+    size_t size, const pcm_format_t *f);
+void hound_ctx_stream_drain(hound_ctx_stream_t *stream);
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/srv/audio/hound/iface.c
===================================================================
--- uspace/srv/audio/hound/iface.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/iface.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2013 Jan Vesely
+ * 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.
+ */
+
+/**
+ * @addtogroup audio
+ * @brief HelenOS sound server.
+ * @{
+ */
+/** @file
+ */
+
+#include <errno.h>
+#include <hound/protocol.h>
+#include <inttypes.h>
+#include <malloc.h>
+
+#include "hound.h"
+#include "hound_ctx.h"
+#include "log.h"
+
+static int iface_add_context(void *server, hound_context_id_t *id,
+    const char *name, bool record)
+{
+	assert(server);
+	assert(id);
+	assert(name);
+
+	hound_ctx_t *ctx = record ? hound_record_ctx_get(name) :
+	    hound_playback_ctx_get(name);
+	if (!ctx)
+		return ENOMEM;
+
+	const int ret = hound_add_ctx(server, ctx);
+	if (ret != EOK)
+		hound_ctx_destroy(ctx);
+	else
+		*id = hound_ctx_get_id(ctx);
+	return ret;
+}
+
+static int iface_rem_context(void *server, hound_context_id_t id)
+{
+	assert(server);
+	hound_ctx_t *ctx = hound_get_ctx_by_id(server, id);
+	if (!ctx)
+		return EINVAL;
+	int ret = hound_remove_ctx(server, ctx);
+	if (ret != EOK)
+		return ret;
+	hound_ctx_destroy(ctx);
+	log_info("%s: %p, %#" PRIxn, __FUNCTION__, server, id);
+	return EOK;
+}
+
+static bool iface_is_record_context(void *server, hound_context_id_t id)
+{
+	assert(server);
+	hound_ctx_t *ctx = hound_get_ctx_by_id(server, id);
+	if (!ctx)
+		return false;
+	return hound_ctx_is_record(ctx);
+}
+
+static int iface_get_list(void *server, const char ***list, size_t *size,
+    const char *connection, int flags)
+{
+	log_info("%s: %p, %zu, %s, %#x\n", __FUNCTION__, server, *size,
+	    connection, flags);
+	if ((flags & (HOUND_SINK_DEVS | HOUND_SINK_APPS)) != 0)
+		return hound_list_sinks(server, list, size);
+	if ((flags & (HOUND_SOURCE_DEVS | HOUND_SOURCE_APPS)) != 0)
+		return hound_list_sources(server, list, size);
+	return ENOTSUP;
+}
+
+static int iface_connect(void *server, const char *source, const char *sink)
+{
+	log_info("%s: %p, %s -> %s", __FUNCTION__, server, source, sink);
+	return hound_connect(server, source, sink);
+}
+
+static int iface_disconnect(void *server, const char *source, const char *sink)
+{
+	log_info("%s: %p, %s -> %s", __FUNCTION__, server, source, sink);
+	return hound_disconnect(server, source, sink);
+}
+
+static int iface_add_stream(void *server, hound_context_id_t id, int flags,
+    pcm_format_t format, size_t size, void **data)
+{
+	assert(data);
+	assert(server);
+
+	log_verbose("%s: %p, %" PRIxn " %x ch:%u r:%u f:%s", __FUNCTION__,
+	    server, id, flags, format.channels, format.sampling_rate,
+	    pcm_sample_format_str(format.sample_format));
+	hound_ctx_t *ctx = hound_get_ctx_by_id(server, id);
+	if (!ctx)
+		return ENOENT;
+	hound_ctx_stream_t *stream =
+	    hound_ctx_create_stream(ctx, flags, format, size);
+	if (!stream)
+		return ENOMEM;
+	*data = stream;
+	return EOK;
+}
+
+static int iface_rem_stream(void *server, void *stream)
+{
+	hound_ctx_destroy_stream(stream);
+	return EOK;
+}
+
+static int iface_drain_stream(void *stream)
+{
+	hound_ctx_stream_drain(stream);
+	return EOK;
+}
+
+static int iface_stream_data_read(void *stream, void *buffer, size_t size)
+{
+	return hound_ctx_stream_read(stream, buffer, size);
+}
+
+static int iface_stream_data_write(void *stream, const void *buffer, size_t size)
+{
+	return hound_ctx_stream_write(stream, buffer, size);
+}
+
+hound_server_iface_t hound_iface = {
+	.add_context = iface_add_context,
+	.rem_context = iface_rem_context,
+	.is_record_context = iface_is_record_context,
+	.get_list = iface_get_list,
+	.connect = iface_connect,
+	.disconnect = iface_disconnect,
+	.add_stream = iface_add_stream,
+	.rem_stream = iface_rem_stream,
+	.drain_stream = iface_drain_stream,
+	.stream_data_write = iface_stream_data_write,
+	.stream_data_read = iface_stream_data_read,
+	.server = NULL,
+};
Index: uspace/srv/audio/hound/log.h
===================================================================
--- uspace/srv/audio/hound/log.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/log.h	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/** @addtogroup audio
+ * @brief HelenOS sound server
+ * @{
+ */
+/** @file
+ */
+
+#ifndef LOG_H_
+#define LOG_H_
+
+#ifndef NAME
+#define NAME "NONAME"
+#endif
+
+#include <stdio.h>
+
+#define log_fatal(msg, ...) printf(NAME ": Fatal: " msg "\n", ##__VA_ARGS__);
+#define log_error(msg, ...) printf(NAME ": Error: " msg "\n", ##__VA_ARGS__);
+#define log_warning(msg, ...) printf(NAME ": Warn: " msg "\n", ##__VA_ARGS__);
+#define log_info(msg, ...) printf(NAME ": Info: " msg "\n", ##__VA_ARGS__);
+#define log_debug(msg, ...) printf("%s: Debug: %s: " msg "\n", NAME, __FUNCTION__, ##__VA_ARGS__);
+#define log_verbose(msg, ...) printf("%s: %s: " msg "\n", NAME, __FUNCTION__, ##__VA_ARGS__);
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/srv/audio/hound/main.c
===================================================================
--- uspace/srv/audio/hound/main.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
+++ uspace/srv/audio/hound/main.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * 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.
+ */
+
+/**
+ * @addtogroup audio
+ * @brief HelenOS sound server.
+ * @{
+ */
+/** @file
+ */
+
+#include <async.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <str_error.h>
+#include <hound/server.h>
+#include <hound/protocol.h>
+
+#include "hound.h"
+
+#define NAMESPACE "audio"
+#define NAME "hound"
+#define CATEGORY "audio-pcm"
+
+#include "log.h"
+
+extern hound_server_iface_t hound_iface;
+
+static hound_t hound;
+
+static int device_callback(service_id_t id, const char *name)
+{
+	return hound_add_device(&hound, id, name);
+}
+
+static void scan_for_devices(void)
+{
+	hound_server_devices_iterate(device_callback);
+}
+
+int main(int argc, char **argv)
+{
+	printf("%s: HelenOS sound service\n", NAME);
+
+	int ret = hound_init(&hound);
+	if (ret != EOK) {
+		log_fatal("Failed to initialize hound structure: %s",
+		    str_error(ret));
+		return -ret;
+	}
+
+	hound_iface.server = &hound;
+	hound_service_set_server_iface(&hound_iface);
+	async_set_client_connection(hound_connection_handler);
+
+	service_id_t id = 0;
+	ret = hound_server_register(NAME, &id);
+	if (ret != EOK) {
+		log_fatal("Failed to register server: %s", str_error(ret));
+		return -ret;
+	}
+
+	ret = hound_server_set_device_change_callback(scan_for_devices);
+	if (ret != EOK) {
+		log_fatal("Failed to register for device changes: %s",
+		    str_error(ret));
+		hound_server_unregister(id);
+		return -ret;
+	}
+	log_info("Running with service id %" PRIun, id);
+
+	scan_for_devices();
+	task_retval(0);
+	async_manager();
+	return 0;
+}
+
+/**
+ * @}
+ */
Index: uspace/srv/hid/compositor/compositor.c
===================================================================
--- uspace/srv/hid/compositor/compositor.c	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/srv/hid/compositor/compositor.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -2172,6 +2172,8 @@
 	/* Establish input bidirectional connection. */
 	rc = input_connect(input_svc);
-	if (rc != EOK)
+	if (rc != EOK) {
+		printf("%s: Failed to connect to input service.\n", NAME);
 		return rc;
+	}
 
 	/* Create viewports and connect them to visualizers. */
@@ -2179,4 +2181,5 @@
 	rc = loc_category_get_id("visualizer", &cat_id, IPC_FLAG_BLOCKING);
 	if (rc != EOK) {
+		printf("%s: Failed to get visualizer category.\n", NAME);
 		input_disconnect();
 		return -1;
@@ -2187,4 +2190,5 @@
 	rc = loc_category_get_svcs(cat_id, &svcs, &svcs_cnt);
 	if (rc != EOK || svcs_cnt == 0) {
+		printf("%s: Failed to get visualizer category services.\n", NAME);
 		input_disconnect();
 		return -1;
@@ -2203,4 +2207,5 @@
 	
 	if (list_empty(&viewport_list)) {
+		printf("%s: Failed to get viewports.\n", NAME);
 		input_disconnect();
 		return -1;
Index: uspace/srv/locsrv/locsrv.c
===================================================================
--- uspace/srv/locsrv/locsrv.c	(revision 6ad185dcbc744d0932bb7ac850b8e826a4164329)
+++ uspace/srv/locsrv/locsrv.c	(revision 8c34acf7ac4a88980ef19ea35292034545fddcde)
@@ -1377,4 +1377,7 @@
 	categ_dir_add_cat(&cdir, cat);
 	
+	cat = category_new("audio-pcm");
+	categ_dir_add_cat(&cdir, cat);
+	
 	return true;
 }
