Index: uspace/srv/fs/fat/fat_ops.c
===================================================================
--- uspace/srv/fs/fat/fat_ops.c	(revision db4ec8d433d5a57e9661bceeab3b1ffff2adb1ff)
+++ uspace/srv/fs/fat/fat_ops.c	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -1527,6 +1527,25 @@
 void fat_sync(ipc_callid_t rid, ipc_call_t *request)
 {
-	/* Dummy implementation */
-	ipc_answer_0(rid, EOK);
+	dev_handle_t dev_handle = (dev_handle_t) IPC_GET_ARG1(*request);
+	fs_index_t index = (fs_index_t) IPC_GET_ARG2(*request);
+	
+	fs_node_t *fn;
+	int rc = fat_node_get(&fn, dev_handle, index);
+	if (rc != EOK) {
+		ipc_answer_0(rid, rc);
+		return;
+	}
+	if (!fn) {
+		ipc_answer_0(rid, ENOENT);
+		return;
+	}
+	
+	fat_node_t *nodep = FAT_NODE(fn);
+	
+	nodep->dirty = true;
+	rc = fat_node_sync(nodep);
+	
+	fat_node_put(fn);
+	ipc_answer_0(rid, rc);
 }
 
Index: uspace/srv/fs/tmpfs/tmpfs_ops.c
===================================================================
--- uspace/srv/fs/tmpfs/tmpfs_ops.c	(revision db4ec8d433d5a57e9661bceeab3b1ffff2adb1ff)
+++ uspace/srv/fs/tmpfs/tmpfs_ops.c	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -736,5 +736,8 @@
 void tmpfs_sync(ipc_callid_t rid, ipc_call_t *request)
 {
-	/* Dummy implementation */
+	/*
+	 * TMPFS keeps its data structures always consistent,
+	 * thus the sync operation is a no-op.
+	 */
 	ipc_answer_0(rid, EOK);
 }
Index: uspace/srv/hid/console/gcons.c
===================================================================
--- uspace/srv/hid/console/gcons.c	(revision db4ec8d433d5a57e9661bceeab3b1ffff2adb1ff)
+++ uspace/srv/hid/console/gcons.c	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -286,4 +286,7 @@
 	ssize_t nx = (ssize_t) mouse_x + dx;
 	ssize_t ny = (ssize_t) mouse_y + dy;
+
+	if (!use_gcons)
+		return;
 	
 	mouse_x = (size_t) limit(nx, 0, xres);
Index: uspace/srv/hid/fb/fb.c
===================================================================
--- uspace/srv/hid/fb/fb.c	(revision db4ec8d433d5a57e9661bceeab3b1ffff2adb1ff)
+++ uspace/srv/hid/fb/fb.c	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -1347,6 +1347,6 @@
 {
 	mouse_hide();
-	pointer_x = x;
-	pointer_y = y;
+	pointer_x = x % screen.xres;
+	pointer_y = y % screen.yres;
 	mouse_show();
 }
Index: uspace/srv/hid/kbd/Makefile
===================================================================
--- uspace/srv/hid/kbd/Makefile	(revision db4ec8d433d5a57e9661bceeab3b1ffff2adb1ff)
+++ uspace/srv/hid/kbd/Makefile	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -60,6 +60,6 @@
 	ifeq ($(MACHINE),gta02)
 		SOURCES += \
-			port/dummy.c \
-			ctl/pc.c
+			port/chardev.c \
+			ctl/stty.c
 	endif
 	ifeq ($(MACHINE),testarm)
Index: uspace/srv/hid/kbd/port/chardev.c
===================================================================
--- uspace/srv/hid/kbd/port/chardev.c	(revision db4ec8d433d5a57e9661bceeab3b1ffff2adb1ff)
+++ uspace/srv/hid/kbd/port/chardev.c	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -41,4 +41,5 @@
 #include <kbd.h>
 #include <vfs/vfs.h>
+#include <sys/stat.h>
 #include <fcntl.h>
 #include <errno.h>
@@ -50,21 +51,41 @@
 #define NAME "kbd"
 
+/** List of devices to try connecting to. */
+static const char *in_devs[] = {
+	"/dev/char/ps2a",
+	"/dev/char/s3c24ser"
+};
+
+static const int num_devs = sizeof(in_devs) / sizeof(in_devs[0]);
+
 int kbd_port_init(void)
 {
-	const char *input = "/dev/char/ps2a";
 	int input_fd;
+	int i;
 
-	printf(NAME ": open %s\n", input);
+	input_fd = -1;
+	for (i = 0; i < num_devs; i++) {
+		struct stat s;
 
-	input_fd = open(input, O_RDONLY);
+		if (stat(in_devs[i], &s) == EOK)
+			break;
+	}
+
+	if (i >= num_devs) {
+		printf(NAME ": Could not find any suitable input device.\n");
+		return -1;
+	}
+
+	input_fd = open(in_devs[i], O_RDONLY);
 	if (input_fd < 0) {
-		printf(NAME ": Failed opening %s (%d)\n", input, input_fd);
-		return false;
+		printf(NAME ": failed opening device %s (%d).\n", in_devs[i],
+		    input_fd);
+		return -1;
 	}
 
 	dev_phone = fd_phone(input_fd);
 	if (dev_phone < 0) {
-		printf(NAME ": Failed to connect to device\n");
-		return false;
+		printf(NAME ": Failed connecting to device\n");
+		return -1;
 	}
 
@@ -73,5 +94,5 @@
 	if (ipc_connect_to_me(dev_phone, 0, 0, 0, &phonehash) != 0) {
 		printf(NAME ": Failed to create callback from device\n");
-		return false;
+		return -1;
 	}
 
Index: uspace/srv/hid/kbd/port/i8042.h
===================================================================
--- uspace/srv/hid/kbd/port/i8042.h	(revision db4ec8d433d5a57e9661bceeab3b1ffff2adb1ff)
+++ 	(revision )
@@ -1,55 +1,0 @@
-/*
- * Copyright (c) 2006 Josef Cejka
- * 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 kbd_port
- * @ingroup  kbd
- * @{
- */
-
-/** @file
- * @brief i8042 port driver.
- */
-
-#ifndef KBD_PORT_i8042_H_
-#define KBD_PORT_i8042_H_
-
-#include <libarch/ddi.h>
-#include <libarch/types.h>
-
-struct i8042 {
-	ioport8_t data;
-	uint8_t pad[3];
-	ioport8_t status;
-} __attribute__ ((packed));
-typedef struct i8042 i8042_t;
-
-#endif
-
-/**
- * @}
- */ 
Index: uspace/srv/hid/s3c24xx_ts/Makefile
===================================================================
--- uspace/srv/hid/s3c24xx_ts/Makefile	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
+++ uspace/srv/hid/s3c24xx_ts/Makefile	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -0,0 +1,37 @@
+#
+# Copyright (c) 2010 Jiri Svoboda
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# - Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# - The name of the author may not be used to endorse or promote products
+#   derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+USPACE_PREFIX = ../../..
+
+# Need to use short name because of FAT 8+3 limit
+BINARY = s3c24ts
+
+SOURCES = \
+	s3c24xx_ts.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/srv/hid/s3c24xx_ts/s3c24xx_ts.c
===================================================================
--- uspace/srv/hid/s3c24xx_ts/s3c24xx_ts.c	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
+++ uspace/srv/hid/s3c24xx_ts/s3c24xx_ts.c	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -0,0 +1,408 @@
+/*
+ * Copyright (c) 2010 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup mouse
+ * @{
+ */
+/**
+ * @file
+ * @brief Samsung Samsung S3C24xx on-chip ADC and touch-screen interface driver.
+ *
+ * This interface is present on the Samsung S3C24xx CPU (on the gta02 platform).
+ */
+
+#include <ddi.h>
+#include <libarch/ddi.h>
+#include <devmap.h>
+#include <io/console.h>
+#include <vfs/vfs.h>
+#include <ipc/ipc.h>
+#include <ipc/mouse.h>
+#include <async.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysinfo.h>
+#include <errno.h>
+
+#include "s3c24xx_ts.h"
+
+#define NAME "s3c24ser"
+#define NAMESPACE "hid_in"
+
+static irq_cmd_t ts_irq_cmds[] = {
+	{
+		.cmd = CMD_ACCEPT
+	}
+};
+
+static irq_code_t ts_irq_code = {
+	sizeof(ts_irq_cmds) / sizeof(irq_cmd_t),
+	ts_irq_cmds
+};
+
+/** S3C24xx touchscreen instance structure */
+static s3c24xx_ts_t *ts;
+
+static void s3c24xx_ts_connection(ipc_callid_t iid, ipc_call_t *icall);
+static void s3c24xx_ts_irq_handler(ipc_callid_t iid, ipc_call_t *call);
+static void s3c24xx_ts_pen_down(s3c24xx_ts_t *ts);
+static void s3c24xx_ts_pen_up(s3c24xx_ts_t *ts);
+static void s3c24xx_ts_eoc(s3c24xx_ts_t *ts);
+static int s3c24xx_ts_init(s3c24xx_ts_t *ts);
+static void s3c24xx_ts_wait_for_int_mode(s3c24xx_ts_t *ts, ts_updn_t updn);
+static void s3c24xx_ts_convert_samples(int smp0, int smp1, int *x, int *y);
+static int lin_map_range(int v, int i0, int i1, int o0, int o1);
+
+int main(int argc, char *argv[])
+{
+	int rc;
+
+	printf(NAME ": S3C24xx touchscreen driver\n");
+
+	rc = devmap_driver_register(NAME, s3c24xx_ts_connection);
+	if (rc < 0) {
+		printf(NAME ": Unable to register driver.\n");
+		return -1;
+	}
+
+	ts = malloc(sizeof(s3c24xx_ts_t));
+	if (ts == NULL)
+		return -1;
+
+	if (s3c24xx_ts_init(ts) != EOK)
+		return -1;
+
+	rc = devmap_device_register(NAMESPACE "/mouse", &ts->dev_handle);
+	if (rc != EOK) {
+		devmap_hangup_phone(DEVMAP_DRIVER);
+		printf(NAME ": Unable to register device %s.\n",
+		    NAMESPACE "/mouse");
+		return -1;
+	}
+
+	printf(NAME ": Registered device %s.\n", NAMESPACE "/mouse");
+
+	printf(NAME ": Accepting connections\n");
+	task_retval(0);
+	async_manager();
+
+	/* Not reached */
+	return 0;
+}
+
+/** Initialize S3C24xx touchscreen interface. */
+static int s3c24xx_ts_init(s3c24xx_ts_t *ts)
+{
+	void *vaddr;
+	sysarg_t inr;
+
+	inr = S3C24XX_TS_INR;
+	ts->paddr = S3C24XX_TS_ADDR;
+
+	if (pio_enable((void *) ts->paddr, sizeof(s3c24xx_adc_io_t),
+	    &vaddr) != 0)
+		return -1;
+
+	ts->io = vaddr;
+	ts->client_phone = -1;
+	ts->state = ts_wait_pendown;
+	ts->last_x = 0;
+	ts->last_y = 0;
+
+	printf(NAME ": device at physical address 0x%x, inr %d.\n",
+	    ts->paddr, inr);
+
+	async_set_interrupt_received(s3c24xx_ts_irq_handler);
+	ipc_register_irq(inr, device_assign_devno(), 0, &ts_irq_code);
+
+	s3c24xx_ts_wait_for_int_mode(ts, updn_down);
+
+	return EOK;
+}
+
+/** Switch interface to wait for interrupt mode.
+ *
+ * In this mode we receive an interrupt when pen goes up/down, depending
+ * on @a updn.
+ *
+ * @param ts	Touchscreen instance
+ * @param updn	@c updn_up to wait for pen up, @c updn_down to wait for pen
+ *		down.
+ */
+static void s3c24xx_ts_wait_for_int_mode(s3c24xx_ts_t *ts, ts_updn_t updn)
+{
+	uint32_t con, tsc;
+
+	/*
+	 * Configure ADCCON register
+	 */
+
+	con = pio_read_32(&ts->io->con);
+
+	/* Disable standby, disable start-by-read, clear manual start bit */
+	con = con & ~(ADCCON_STDBM | ADCCON_READ_START | ADCCON_ENABLE_START);
+
+	/* Set prescaler value 0xff, XP for input. */
+	con = con | (ADCCON_PRSCVL(0xff) << 6) | ADCCON_SEL_MUX(SMUX_XP);
+
+	/* Enable prescaler. */
+	con = con | ADCCON_PRSCEN;
+
+ 	pio_write_32(&ts->io->con, con);
+
+	/*
+	 * Configure ADCTSC register
+	 */
+
+	tsc = pio_read_32(&ts->io->tsc);
+
+	/* Select whether waiting for pen up or pen down. */
+	if (updn == updn_up)
+		tsc |= ADCTSC_DSUD_UP;
+	else
+		tsc &= ~ADCTSC_DSUD_UP;
+
+	/*
+	 * Enable XP pull-up and disable all drivers except YM. This is
+	 * according to the manual. This gives us L on XP input when touching
+	 * and (pulled up to) H when not touching.
+	 */
+	tsc = tsc & ~(ADCTSC_XM_ENABLE | ADCTSC_AUTO_PST |
+	    ADCTSC_PULLUP_DISABLE);
+	tsc = tsc | ADCTSC_YP_DISABLE | ADCTSC_XP_DISABLE | ADCTSC_YM_ENABLE;
+
+	/* Select wait-for-interrupt mode. */
+	tsc = (tsc & ~ADCTSC_XY_PST_MASK) | ADCTSC_XY_PST_WAITINT;
+
+	pio_write_32(&ts->io->tsc, tsc);
+}
+
+/** Handle touchscreen interrupt */
+static void s3c24xx_ts_irq_handler(ipc_callid_t iid, ipc_call_t *call)
+{
+	ts_updn_t updn;
+
+	(void) iid; (void) call;
+
+	/* Read up/down interrupt flags. */
+	updn = pio_read_32(&ts->io->updn);
+
+	if (updn & (ADCUPDN_TSC_DN | ADCUPDN_TSC_UP)) {
+		/* Clear up/down interrupt flags. */
+		pio_write_32(&ts->io->updn, updn &
+		    ~(ADCUPDN_TSC_DN | ADCUPDN_TSC_UP));
+	}
+
+	if (updn & ADCUPDN_TSC_DN) {
+		/* Pen-down interrupt */
+		s3c24xx_ts_pen_down(ts);
+	} else if (updn & ADCUPDN_TSC_UP) {
+		/* Pen-up interrupt */
+		s3c24xx_ts_pen_up(ts);
+	} else {
+		/* Presumably end-of-conversion interrupt */
+
+		/* Check end-of-conversion flag. */
+		if ((pio_read_32(&ts->io->con) & ADCCON_ECFLG) == 0) {
+			printf(NAME ": Unrecognized ts int.\n");
+			return;
+		}
+
+		if (ts->state != ts_sample_pos) {
+			/*
+			 * We got an extra interrupt ater switching to
+			 * wait for interrupt mode.
+			 */
+			return;
+		}
+
+		/* End-of-conversion interrupt */
+		s3c24xx_ts_eoc(ts);
+	}
+}
+
+/** Handle pen-down interrupt.
+ *
+ * @param ts	Touchscreen instance
+ */
+static void s3c24xx_ts_pen_down(s3c24xx_ts_t *ts)
+{
+	/* Pen-down interrupt */
+
+	ts->state = ts_sample_pos;
+
+	/* Enable auto xy-conversion mode */
+	pio_write_32(&ts->io->tsc, (pio_read_32(&ts->io->tsc)
+	    & ~3) | 4);
+
+	/* Start the conversion. */
+	pio_write_32(&ts->io->con, pio_read_32(&ts->io->con)
+	    | ADCCON_ENABLE_START);
+}
+
+/** Handle pen-up interrupt.
+ *
+ * @param ts	Touchscreen instance
+ */
+static void s3c24xx_ts_pen_up(s3c24xx_ts_t *ts)
+{
+	int button, press;
+
+	/* Pen-up interrupt */
+
+	ts->state = ts_wait_pendown;
+
+	button = 1;
+	press = 0;
+	async_msg_2(ts->client_phone, MEVENT_BUTTON, button, press);
+
+	s3c24xx_ts_wait_for_int_mode(ts, updn_down);
+}
+
+/** Handle end-of-conversion interrupt.
+ *
+ * @param ts	Touchscreen instance
+ */
+static void s3c24xx_ts_eoc(s3c24xx_ts_t *ts)
+{
+	uint32_t data;
+	int button, press;
+	int smp0, smp1;
+	int x_pos, y_pos;
+	int dx, dy;
+
+	ts->state = ts_wait_penup;
+
+	/* Read in sampled data. */
+
+	data = pio_read_32(&ts->io->dat0);
+	smp0 = data & 0x3ff;
+
+	data = pio_read_32(&ts->io->dat1);
+	smp1 = data & 0x3ff;
+
+	/* Convert to screen coordinates. */
+	s3c24xx_ts_convert_samples(smp0, smp1, &x_pos, &y_pos);
+
+	printf("s0: 0x%03x, s1:0x%03x -> x:%d,y:%d\n", smp0, smp1,
+	    x_pos, y_pos);
+
+	/* Get differences. */
+	dx = x_pos - ts->last_x;
+	dy = y_pos - ts->last_y;
+
+	button = 1;
+	press = 1;
+
+	/* Send notifications to client. */
+	async_msg_2(ts->client_phone, MEVENT_MOVE, dx, dy);
+	async_msg_2(ts->client_phone, MEVENT_BUTTON, button, press);
+
+	ts->last_x = x_pos;
+	ts->last_y = y_pos;
+
+	s3c24xx_ts_wait_for_int_mode(ts, updn_up);
+}
+
+/** Convert sampled data to screen coordinates. */
+static void s3c24xx_ts_convert_samples(int smp0, int smp1, int *x, int *y)
+{
+	/*
+	 * The orientation and display dimensions are GTA02-specific and the
+	 * calibration values might even specific to the individual piece
+	 * of hardware.
+	 *
+	 * The calibration values can be obtained by touching corners
+	 * of the screen with the stylus and noting the sampled values.
+	 */
+	*x = lin_map_range(smp1, 0xa1, 0x396, 0, 479);
+	*y = lin_map_range(smp0, 0x69, 0x38a, 639, 0);
+}
+
+/** Map integer from one range to another range in a linear fashion.
+ *
+ * i0 < i1 is required. i0 is mapped to o0, i1 to o1. If o1 < o0, then the
+ * mapping will be descending. If v is outside of [i0, i1], it is clamped.
+ *
+ * @param v	Value to map.
+ * @param i0	Lower bound of input range.
+ * @param i1	Upper bound of input range.
+ * @param o0	First bound of output range.
+ * @param o1	Second bound of output range.
+ *
+ * @return	Mapped value ov, o0 <= ov <= o1.
+ */
+static int lin_map_range(int v, int i0, int i1, int o0, int o1)
+{
+	if (v < i0)
+		v = i0;
+
+	if (v > i1)
+		v = i1;
+
+	return o0 + (o1 - o0) * (v - i0) / (i1 - i0);
+}
+
+/** Handle mouse client connection. */
+static void s3c24xx_ts_connection(ipc_callid_t iid, ipc_call_t *icall)
+{
+	ipc_callid_t callid;
+	ipc_call_t call;
+	int retval;
+
+	ipc_answer_0(iid, EOK);
+
+	while (1) {
+		callid = async_get_call(&call);
+		switch (IPC_GET_METHOD(call)) {
+		case IPC_M_PHONE_HUNGUP:
+			if (ts->client_phone != -1) {
+				ipc_hangup(ts->client_phone);
+				ts->client_phone = -1;
+			}
+
+			ipc_answer_0(callid, EOK);
+			return;
+		case IPC_M_CONNECT_TO_ME:
+			if (ts->client_phone != -1) {
+				retval = ELIMIT;
+				break;
+			}
+			ts->client_phone = IPC_GET_ARG5(call);
+			retval = 0;
+			break;
+		default:
+			retval = EINVAL;
+		}
+		ipc_answer_0(callid, retval);
+	}
+}
+
+/** @}
+ */
Index: uspace/srv/hid/s3c24xx_ts/s3c24xx_ts.h
===================================================================
--- uspace/srv/hid/s3c24xx_ts/s3c24xx_ts.h	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
+++ uspace/srv/hid/s3c24xx_ts/s3c24xx_ts.h	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2010 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup genarch
+ * @{
+ */
+/**
+ * @file
+ * @brief Samsung S3C24xx on-chip ADC and touch-screen interface driver.
+ */
+
+#ifndef S3C24XX_TS_H_
+#define S3C24XX_TS_H_
+
+#include <sys/types.h>
+
+/** S3C24xx ADC and touch-screen I/O */
+typedef struct {
+	uint32_t con;
+	uint32_t tsc;
+	uint32_t dly;
+	uint32_t dat0;
+	uint32_t dat1;
+	uint32_t updn;
+} s3c24xx_adc_io_t;
+
+/* Fields in ADCCON register */
+#define ADCCON_ECFLG		0x8000
+#define ADCCON_PRSCEN		0x4000
+
+#define ADCCON_PRSCVL(val)	(((val) & 0xff) << 6)
+
+#define ADCCON_SEL_MUX(smux)	(((smux) & 7) << 3)
+
+#define ADCCON_STDBM		0x0004
+#define ADCCON_READ_START	0x0002
+#define ADCCON_ENABLE_START	0x0001
+
+/* Values for ADCCON_SEL_MUX */
+#define SMUX_AIN0		0
+#define SMUX_AIN1		1
+#define SMUX_AIN2		2
+#define SMUX_AIN3		3
+#define SMUX_YM			4
+#define SMUX_YP			5
+#define SMUX_XM			6
+#define SMUX_XP			7
+
+
+/* Fields in ADCTSC register */
+#define ADCTSC_DSUD_UP		0x0100
+#define ADCTSC_YM_ENABLE	0x0080
+#define ADCTSC_YP_DISABLE	0x0040
+#define ADCTSC_XM_ENABLE	0x0020
+#define ADCTSC_XP_DISABLE	0x0010
+#define ADCTSC_PULLUP_DISABLE	0x0008
+#define ADCTSC_AUTO_PST		0x0004
+
+#define ADCTSC_XY_PST_NOOP	0x0000
+#define ADCTSC_XY_PST_X		0x0001
+#define ADCTSC_XY_PST_Y		0x0002
+#define ADCTSC_XY_PST_WAITINT	0x0003
+#define ADCTSC_XY_PST_MASK	0x0003
+
+/* Fields in ADCDAT0, ADCDAT1 registers */
+#define ADCDAT_UPDOWN		0x8000
+#define ADCDAT_AUTO_PST		0x4000
+
+/* Fields in ADCUPDN register */
+#define ADCUPDN_TSC_UP		0x0002
+#define ADCUPDN_TSC_DN		0x0001
+
+/** Touchscreen interrupt number */
+#define S3C24XX_TS_INR		31
+
+/** Touchscreen I/O address */
+#define S3C24XX_TS_ADDR		0x58000000
+
+typedef enum {
+	ts_wait_pendown,
+	ts_sample_pos,
+	ts_wait_penup
+} ts_state_t;
+
+typedef enum {
+	updn_up,
+	updn_down
+} ts_updn_t;
+
+/** S3C24xx touchscreen driver instance */
+typedef struct {
+	/** Physical device address */
+	uintptr_t paddr;
+
+	/** Device I/O structure */
+	s3c24xx_adc_io_t *io;
+
+	/** Callback phone to the client */
+	int client_phone;
+
+	/** Device handle */
+	dev_handle_t dev_handle;
+
+	/** Device/driver state */
+	ts_state_t state;
+
+	/** Previous position reported to client. */
+	int last_x;
+	int last_y;
+} s3c24xx_ts_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/hw/char/s3c24xx_uart/Makefile
===================================================================
--- uspace/srv/hw/char/s3c24xx_uart/Makefile	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
+++ uspace/srv/hw/char/s3c24xx_uart/Makefile	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -0,0 +1,38 @@
+#
+# Copyright (c) 2005 Martin Decky
+# Copyright (c) 2007 Jakub Jermar
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# - Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# - The name of the author may not be used to endorse or promote products
+#   derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+USPACE_PREFIX = ../../../..
+
+# Need to use short name because of FAT 8+3 limit
+BINARY = s3c24ser
+
+SOURCES = \
+	s3c24xx_uart.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/srv/hw/char/s3c24xx_uart/s3c24xx_uart.c
===================================================================
--- uspace/srv/hw/char/s3c24xx_uart/s3c24xx_uart.c	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
+++ uspace/srv/hw/char/s3c24xx_uart/s3c24xx_uart.c	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 2010 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup driver_serial
+ * @{
+ */
+/**
+ * @file
+ * @brief Samsung S3C24xx on-chip UART driver.
+ *
+ * This UART is present on the Samsung S3C24xx CPU (on the gta02 platform).
+ */
+
+#include <ddi.h>
+#include <libarch/ddi.h>
+#include <devmap.h>
+#include <ipc/ipc.h>
+#include <ipc/char.h>
+#include <async.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sysinfo.h>
+#include <errno.h>
+
+#include "s3c24xx_uart.h"
+
+#define NAME "s3c24ser"
+#define NAMESPACE "char"
+
+static irq_cmd_t uart_irq_cmds[] = {
+	{
+		.cmd = CMD_ACCEPT
+	}
+};
+
+static irq_code_t uart_irq_code = {
+	sizeof(uart_irq_cmds) / sizeof(irq_cmd_t),
+	uart_irq_cmds
+};
+
+/** S3C24xx UART instance structure */
+static s3c24xx_uart_t *uart;
+
+static void s3c24xx_uart_connection(ipc_callid_t iid, ipc_call_t *icall);
+static void s3c24xx_uart_irq_handler(ipc_callid_t iid, ipc_call_t *call);
+static int s3c24xx_uart_init(s3c24xx_uart_t *uart);
+static void s3c24xx_uart_sendb(s3c24xx_uart_t *uart, uint8_t byte);
+
+int main(int argc, char *argv[])
+{
+	int rc;
+
+	printf(NAME ": S3C24xx on-chip UART driver\n");
+
+	rc = devmap_driver_register(NAME, s3c24xx_uart_connection);
+	if (rc < 0) {
+		printf(NAME ": Unable to register driver.\n");
+		return -1;
+	}
+
+	uart = malloc(sizeof(s3c24xx_uart_t));
+	if (uart == NULL)
+		return -1;
+
+	if (s3c24xx_uart_init(uart) != EOK)
+		return -1;
+
+	rc = devmap_device_register(NAMESPACE "/" NAME, &uart->dev_handle);
+	if (rc != EOK) {
+		devmap_hangup_phone(DEVMAP_DRIVER);
+		printf(NAME ": Unable to register device %s.\n");
+		return -1;
+	}
+
+	printf(NAME ": Registered device %s.\n", NAMESPACE "/" NAME);
+
+	printf(NAME ": Accepting connections\n");
+	task_retval(0);
+	async_manager();
+
+	/* Not reached */
+	return 0;
+}
+
+/** Character device connection handler. */
+static void s3c24xx_uart_connection(ipc_callid_t iid, ipc_call_t *icall)
+{
+	ipc_callid_t callid;
+	ipc_call_t call;
+	ipcarg_t method;
+	int retval;
+
+	/* Answer the IPC_M_CONNECT_ME_TO call. */
+	ipc_answer_0(iid, EOK);
+
+	while (1) {
+		callid = async_get_call(&call);
+		method = IPC_GET_METHOD(call);
+		switch (method) {
+		case IPC_M_PHONE_HUNGUP:
+			/* The other side has hung up. */
+			ipc_answer_0(callid, EOK);
+			return;
+		case IPC_M_CONNECT_TO_ME:
+			printf(NAME ": creating callback connection\n");
+			uart->client_phone = IPC_GET_ARG5(call);
+			retval = 0;
+			break;
+		case CHAR_WRITE_BYTE:
+			printf(NAME ": write %d to device\n",
+			    IPC_GET_ARG1(call));
+			s3c24xx_uart_sendb(uart, (uint8_t) IPC_GET_ARG1(call));
+			retval = 0;
+			break;
+		default:
+			retval = EINVAL;
+			break;
+		}
+		ipc_answer_0(callid, retval);
+	}
+}
+
+static void s3c24xx_uart_irq_handler(ipc_callid_t iid, ipc_call_t *call)
+{
+	(void) iid; (void) call;
+
+	while ((pio_read_32(&uart->io->ufstat) & S3C24XX_UFSTAT_RX_COUNT) != 0) {
+		uint32_t data = pio_read_32(&uart->io->urxh) & 0xff;
+		uint32_t status = pio_read_32(&uart->io->uerstat);
+
+		if (uart->client_phone != -1) {
+			async_msg_1(uart->client_phone, CHAR_NOTIF_BYTE,
+			    data);
+		}
+
+		if (status != 0)
+			printf(NAME ": Error status 0x%x\n", status);
+	}
+}
+
+/** Initialize S3C24xx on-chip UART. */
+static int s3c24xx_uart_init(s3c24xx_uart_t *uart)
+{
+	void *vaddr;
+	sysarg_t inr;
+
+	if (sysinfo_get_value("s3c24xx_uart.address.physical",
+	    &uart->paddr) != EOK)
+		return -1;
+
+	if (pio_enable((void *) uart->paddr, sizeof(s3c24xx_uart_io_t),
+	    &vaddr) != 0)
+		return -1;
+
+	if (sysinfo_get_value("s3c24xx_uart.inr", &inr) != EOK)
+		return -1;
+
+	uart->io = vaddr;
+	uart->client_phone = -1;
+
+	printf(NAME ": device at physical address 0x%x, inr %d.\n",
+	    uart->paddr, inr);
+
+	async_set_interrupt_received(s3c24xx_uart_irq_handler);
+
+	ipc_register_irq(inr, device_assign_devno(), 0, &uart_irq_code);
+
+	/* Enable FIFO, Tx trigger level: empty, Rx trigger level: 1 byte. */
+	pio_write_32(&uart->io->ufcon, UFCON_FIFO_ENABLE |
+	    UFCON_TX_FIFO_TLEVEL_EMPTY | UFCON_RX_FIFO_TLEVEL_1B);
+
+	/* Set RX interrupt to pulse mode */
+	pio_write_32(&uart->io->ucon,
+	    pio_read_32(&uart->io->ucon) & ~UCON_RX_INT_LEVEL);
+
+	return EOK;
+}
+
+/** Send a byte to the UART. */
+static void s3c24xx_uart_sendb(s3c24xx_uart_t *uart, uint8_t byte)
+{
+	/* Wait for space becoming available in Tx FIFO. */
+	while ((pio_read_32(&uart->io->ufstat) & S3C24XX_UFSTAT_TX_FULL) != 0)
+		;
+
+	pio_write_32(&uart->io->utxh, byte);
+}
+
+/** @}
+ */
Index: uspace/srv/hw/char/s3c24xx_uart/s3c24xx_uart.h
===================================================================
--- uspace/srv/hw/char/s3c24xx_uart/s3c24xx_uart.h	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
+++ uspace/srv/hw/char/s3c24xx_uart/s3c24xx_uart.h	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2010 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup genarch
+ * @{
+ */
+/**
+ * @file
+ * @brief Samsung S3C24xx on-chip UART driver.
+ */
+
+#ifndef S3C24XX_UART_H_
+#define S3C24XX_UART_H_
+
+#include <sys/types.h>
+
+/** S3C24xx UART I/O */
+typedef struct {
+	uint32_t ulcon;
+	uint32_t ucon;
+	uint32_t ufcon;
+	uint32_t umcon;
+
+	uint32_t utrstat;
+	uint32_t uerstat;
+	uint32_t ufstat;
+	uint32_t umstat;
+
+	uint32_t utxh;
+	uint32_t urxh;
+
+	uint32_t ubrdiv;
+} s3c24xx_uart_io_t;
+
+/* Bits in UTRSTAT register */
+#define S3C24XX_UTRSTAT_TX_EMPTY	0x4
+#define S3C24XX_UTRSTAT_RDATA		0x1
+
+/* Bits in UFSTAT register */
+#define S3C24XX_UFSTAT_TX_FULL		0x4000
+#define S3C24XX_UFSTAT_RX_FULL		0x0040
+#define S3C24XX_UFSTAT_RX_COUNT		0x002f
+
+/* Bits in UCON register */
+#define UCON_RX_INT_LEVEL		0x100
+
+/* Bits in UFCON register */
+#define UFCON_TX_FIFO_TLEVEL_EMPTY	0x00
+#define UFCON_RX_FIFO_TLEVEL_1B		0x00
+#define UFCON_FIFO_ENABLE		0x01
+
+
+/** S3C24xx UART instance */
+typedef struct {
+	/** Physical device address */
+	uintptr_t paddr;
+
+	/** Device I/O structure */
+	s3c24xx_uart_io_t *io;
+
+	/** Callback phone to the client */
+	int client_phone;
+
+	/** Device handle */
+	dev_handle_t dev_handle;
+} s3c24xx_uart_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/vfs/vfs.h
===================================================================
--- uspace/srv/vfs/vfs.h	(revision db4ec8d433d5a57e9661bceeab3b1ffff2adb1ff)
+++ uspace/srv/vfs/vfs.h	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -169,5 +169,5 @@
 
 extern int vfs_grab_phone(fs_handle_t);
-extern void vfs_release_phone(int);
+extern void vfs_release_phone(fs_handle_t, int);
 
 extern fs_handle_t fs_name_to_handle(char *, bool);
Index: uspace/srv/vfs/vfs_lookup.c
===================================================================
--- uspace/srv/vfs/vfs_lookup.c	(revision db4ec8d433d5a57e9661bceeab3b1ffff2adb1ff)
+++ uspace/srv/vfs/vfs_lookup.c	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -168,5 +168,5 @@
 	ipcarg_t rc;
 	async_wait_for(req, &rc);
-	vfs_release_phone(phone);
+	vfs_release_phone(root->fs_handle, phone);
 	
 	fibril_mutex_lock(&plb_mutex);
@@ -215,5 +215,5 @@
 	ipcarg_t rc;
 	async_wait_for(req, &rc);
-	vfs_release_phone(phone);
+	vfs_release_phone(result->triplet.fs_handle, phone);
 	
 	if (rc == EOK) {
Index: uspace/srv/vfs/vfs_node.c
===================================================================
--- uspace/srv/vfs/vfs_node.c	(revision db4ec8d433d5a57e9661bceeab3b1ffff2adb1ff)
+++ uspace/srv/vfs/vfs_node.c	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -133,5 +133,5 @@
 		    (ipcarg_t)node->dev_handle, (ipcarg_t)node->index);
 		assert(rc == EOK);
-		vfs_release_phone(phone);
+		vfs_release_phone(node->fs_handle, phone);
 	}
 	if (free_vfs_node)
Index: uspace/srv/vfs/vfs_ops.c
===================================================================
--- uspace/srv/vfs/vfs_ops.c	(revision db4ec8d433d5a57e9661bceeab3b1ffff2adb1ff)
+++ uspace/srv/vfs/vfs_ops.c	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -131,5 +131,5 @@
 			if (rc != EOK) {
 				async_wait_for(msg, NULL);
-				vfs_release_phone(phone);
+				vfs_release_phone(fs_handle, phone);
 				fibril_rwlock_write_unlock(&namespace_rwlock);
 				ipc_answer_0(rid, rc);
@@ -137,5 +137,5 @@
 			}
 			async_wait_for(msg, &rc);
-			vfs_release_phone(phone);
+			vfs_release_phone(fs_handle, phone);
 			
 			if (rc != EOK) {
@@ -196,6 +196,6 @@
 	if (rc != EOK) {
 		async_wait_for(msg, NULL);
-		vfs_release_phone(mountee_phone);
-		vfs_release_phone(phone);
+		vfs_release_phone(fs_handle, mountee_phone);
+		vfs_release_phone(mp_res.triplet.fs_handle, phone);
 		/* Mount failed, drop reference to mp_node. */
 		if (mp_node)
@@ -206,5 +206,5 @@
 	}
 
-	vfs_release_phone(mountee_phone);
+	vfs_release_phone(fs_handle, mountee_phone);
 	
 	/* send the mount options */
@@ -212,5 +212,5 @@
 	if (rc != EOK) {
 		async_wait_for(msg, NULL);
-		vfs_release_phone(phone);
+		vfs_release_phone(mp_res.triplet.fs_handle, phone);
 		/* Mount failed, drop reference to mp_node. */
 		if (mp_node)
@@ -221,5 +221,5 @@
 	}
 	async_wait_for(msg, &rc);
-	vfs_release_phone(phone);
+	vfs_release_phone(mp_res.triplet.fs_handle, phone);
 	
 	if (rc == EOK) {
@@ -423,5 +423,5 @@
 		rc = async_req_1_0(phone, VFS_OUT_UNMOUNTED,
 		    mr_node->dev_handle);
-		vfs_release_phone(phone);
+		vfs_release_phone(mr_node->fs_handle, phone);
 		if (rc != EOK) {
 			fibril_rwlock_write_unlock(&namespace_rwlock);
@@ -460,5 +460,5 @@
 		rc = async_req_2_0(phone, VFS_OUT_UNMOUNT, mp_node->dev_handle,
 		    mp_node->index);
-		vfs_release_phone(phone);
+		vfs_release_phone(mp_node->fs_handle, phone);
 		if (rc != EOK) {
 			fibril_rwlock_write_unlock(&namespace_rwlock);
@@ -716,5 +716,5 @@
 	async_wait_for(msg, &rc);
 	
-	vfs_release_phone(fs_phone);
+	vfs_release_phone(file->node->fs_handle, fs_phone);
 	fibril_mutex_unlock(&file->lock);
 	
@@ -747,5 +747,5 @@
 		async_wait_for(msg, &rc);
 		
-		vfs_release_phone(fs_phone);
+		vfs_release_phone(file->node->fs_handle, fs_phone);
 		fibril_mutex_unlock(&file->lock);
 		
@@ -846,5 +846,5 @@
 	}
 	
-	vfs_release_phone(fs_phone);
+	vfs_release_phone(file->node->fs_handle, fs_phone);
 	
 	size_t bytes = IPC_GET_ARG1(answer);
@@ -970,5 +970,5 @@
 	rc = async_req_4_0(fs_phone, VFS_OUT_TRUNCATE, (ipcarg_t) dev_handle,
 	    (ipcarg_t) index, LOWER32(size), UPPER32(size));
-	vfs_release_phone(fs_phone);
+	vfs_release_phone(fs_handle, fs_phone);
 	return (int)rc;
 }
@@ -1026,5 +1026,5 @@
 	ipc_forward_fast(callid, fs_phone, 0, 0, 0, IPC_FF_ROUTE_FROM_ME);
 	async_wait_for(msg, &rc);
-	vfs_release_phone(fs_phone);
+	vfs_release_phone(file->node->fs_handle, fs_phone);
 
 	fibril_mutex_unlock(&file->lock);
@@ -1077,5 +1077,5 @@
 	ipcarg_t rv;
 	async_wait_for(msg, &rv);
-	vfs_release_phone(fs_phone);
+	vfs_release_phone(node->fs_handle, fs_phone);
 
 	ipc_answer_0(rid, rv);
Index: uspace/srv/vfs/vfs_register.c
===================================================================
--- uspace/srv/vfs/vfs_register.c	(revision db4ec8d433d5a57e9661bceeab3b1ffff2adb1ff)
+++ uspace/srv/vfs/vfs_register.c	(revision 7431b66531a16a53dda757d3e07d0ac028368f72)
@@ -39,5 +39,7 @@
 #include <ipc/services.h>
 #include <async.h>
+#include <async_rel.h>
 #include <fibril.h>
+#include <fibril_synch.h>
 #include <errno.h>
 #include <stdio.h>
@@ -46,5 +48,4 @@
 #include <ctype.h>
 #include <bool.h>
-#include <fibril_synch.h>
 #include <adt/list.h>
 #include <as.h>
@@ -252,4 +253,6 @@
 int vfs_grab_phone(fs_handle_t handle)
 {
+	link_t *cur;
+	fs_info_t *fs;
 	int phone;
 
@@ -262,6 +265,4 @@
 	 */
 	fibril_mutex_lock(&fs_head_lock);
-	link_t *cur;
-	fs_info_t *fs;
 	for (cur = fs_head.next; cur != &fs_head; cur = cur->next) {
 		fs = list_get_instance(cur, fs_info_t, fs_link);
@@ -269,5 +270,5 @@
 			fibril_mutex_unlock(&fs_head_lock);
 			fibril_mutex_lock(&fs->phone_lock);
-			phone = ipc_connect_me_to(fs->phone, 0, 0, 0);
+			phone = async_relation_create(fs->phone);
 			fibril_mutex_unlock(&fs->phone_lock);
 
@@ -284,8 +285,23 @@
  * @param phone		Phone to FS task.
  */
-void vfs_release_phone(int phone)
-{
-	/* TODO: implement connection caching */
-	ipc_hangup(phone);
+void vfs_release_phone(fs_handle_t handle, int phone)
+{
+	link_t *cur;
+	fs_info_t *fs;
+
+	fibril_mutex_lock(&fs_head_lock);
+	for (cur = fs_head.next; cur != &fs_head; cur = cur->next) {
+		fs = list_get_instance(cur, fs_info_t, fs_link);
+		if (fs->fs_handle == handle) {
+			fibril_mutex_unlock(&fs_head_lock);
+			fibril_mutex_lock(&fs->phone_lock);
+			async_relation_destroy(fs->phone, phone);
+			fibril_mutex_unlock(&fs->phone_lock);
+			return;
+		}
+	}
+	/* should not really get here */
+	abort();
+	fibril_mutex_unlock(&fs_head_lock);
 }
 
