Index: HelenOS.config
===================================================================
--- HelenOS.config	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ HelenOS.config	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -58,4 +58,5 @@
 
 % Machine type
+@ "gta02" GTA02 / Neo FreeRunner
 @ "testarm" GXEmul Testarm
 @ "integratorcp" Integratorcp
@@ -389,5 +390,5 @@
 % Output device class
 @ "generic" Monitor or serial line
-! [PLATFORM=arm32&MACHINE=integratorcp] CONFIG_HID_OUT (choice)
+! [PLATFORM=arm32&(MACHINE=gta02|MACHINE=integratorcp)] CONFIG_HID_OUT (choice)
 
 % Output device class
@@ -437,4 +438,10 @@
 ! [(CONFIG_HID_IN=generic|CONFIG_HID_IN=serial)&PLATFORM=ia64&MACHINE=i460GX] CONFIG_NS16550 (y/n)
 
+% Support for Samsung S3C24XX on-chip UART
+! [(CONFIG_HID_OUT=generic|CONFIG_HID_OUT=serial)&PLATFORM=arm32&MACHINE=gta02] CONFIG_S3C24XX_UART (y/n)
+
+% Support for Samsung S3C24XX on-chip interrupt controller
+! [PLATFORM=arm32&MACHINE=gta02] CONFIG_S3C24XX_IRQC (y)
+
 % Support for Z8530 controller
 ! [(CONFIG_HID_IN=generic|CONFIG_HID_IN=keyboard)&PLATFORM=sparc64&MACHINE=generic] CONFIG_Z8530 (y/n)
@@ -465,5 +472,5 @@
 
 % Serial line input module
-! [CONFIG_DSRLNIN=y|(PLATFORM=ia64&MACHINE=i460GX&CONFIG_NS16550=y)|(PLATFORM=ia64&MACHINE=ski)|(PLATFORM=sparc64&MACHINE=serengeti&CONFIG_SGCN_KBD=y)|(PLATFORM=sparc64&PROCESSOR=sun4v)] CONFIG_SRLN (y)
+! [CONFIG_DSRLNIN=y|(PLATFORM=arm32&MACHINE=gta02)|(PLATFORM=ia64&MACHINE=i460GX&CONFIG_NS16550=y)|(PLATFORM=ia64&MACHINE=ski)|(PLATFORM=sparc64&MACHINE=serengeti&CONFIG_SGCN_KBD=y)|(PLATFORM=sparc64&PROCESSOR=sun4v)] CONFIG_SRLN (y)
 
 % EGA support
@@ -491,4 +498,6 @@
 @ "1152x720"
 @ "1152x864"
+@ "1280x720"
+@ "1280x800"
 @ "1280x960"
 @ "1280x1024"
@@ -537,2 +546,4 @@
 ! CONFIG_BAREBONE (n/y)
 
+% Line debugging information
+! [CONFIG_STRIP_BINARIES!=y] CONFIG_LINE_DEBUG (n/y)
Index: boot/Makefile
===================================================================
--- boot/Makefile	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/Makefile	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -48,5 +48,5 @@
 endif
 ifeq ($(RDFMT),fat)
-	$(MKFAT) $(DIST_PATH) $@
+	$(MKFAT) 1048576 $(DIST_PATH) $@
 endif
 
@@ -72,5 +72,5 @@
 
 clean_dist:
-	rm -f $(INITRD).fs $(INITRD).img $(COMPS_H) $(COMPS_C) $(LINK) *.co
+	rm -f $(INITRD).fs $(INITRD).img $(COMPS_H) $(COMPS_C) $(LINK) $(LINK).comp *.co
 	rm -f $(USPACE_PATH)/dist/srv/*
 	rm -f $(USPACE_PATH)/dist/app/*
Index: boot/Makefile.build
===================================================================
--- boot/Makefile.build	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/Makefile.build	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -63,4 +63,11 @@
 endif
 
+ifeq ($(CONFIG_LINE_DEBUG),y)
+	GCC_CFLAGS += -g
+	ICC_CFLAGS += -g
+	SUNCC_CFLAGS += -g
+	CLANG_CFLAGS += -g
+endif
+
 ifeq ($(COMPILER),gcc_native)
 	CFLAGS = $(GCC_CFLAGS) $(EXTRA_CFLAGS)
@@ -106,4 +113,7 @@
 	$(LD) -N $(LFLAGS) -T $(LINK) -M -Map $(MAP) -o $@ $(COMPONENT_OBJECTS) $(OBJECTS)
 
+$(LINK): $(LINK).comp $(DEPEND)
+	$(GCC) $(DEFS) $(GCC_CFLAGS) -D__ASM__ -D__LINKER__ -E -x c $(LINK).comp | grep -v "^\#" > $(LINK)
+
 %.o: %.S $(DEPEND)
 	$(CC) $(DEFS) $(CFLAGS) -D__ASM__ -c $< -o $@
Index: boot/Makefile.common
===================================================================
--- boot/Makefile.common	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/Makefile.common	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -55,4 +55,5 @@
 MKTMPFS = $(TOOLS_PATH)/mktmpfs.py
 MKFAT = $(TOOLS_PATH)/mkfat.py
+MKUIMAGE = $(TOOLS_PATH)/mkuimage.py
 
 JOBFILE = $(ROOT_PATH)/tools/jobfile.py
Index: boot/Makefile.uboot
===================================================================
--- boot/Makefile.uboot	(revision 20235a356988dff15276fd66d391230fc9b32591)
+++ boot/Makefile.uboot	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -0,0 +1,46 @@
+#
+# 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.
+#
+
+.PHONY: all clean
+
+include Makefile.common
+
+IMAGE_NAME=HelenOS-$(RELEASE)
+BIN_OUTPUT=image.bin
+
+all: $(POST_OUTPUT)
+
+$(BIN_OUTPUT): $(BOOT_OUTPUT)
+	$(OBJCOPY) -O binary $< $@
+
+$(POST_OUTPUT): $(BIN_OUTPUT)
+	$(MKUIMAGE) -name "$(IMAGE_NAME)" -laddr 0x30008000 -saddr 0x30008000 \
+    $< $@
+
+clean:
+	rm -f $(BIN_OUTPUT)
Index: boot/arch/arm32/Makefile.inc
===================================================================
--- boot/arch/arm32/Makefile.inc	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/arch/arm32/Makefile.inc	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -27,4 +27,10 @@
 #
 
+ifeq ($(MACHINE), gta02)
+	BOOT_OUTPUT = image.boot
+	POST_OUTPUT = $(ROOT_PATH)/uImage.bin
+	POSTBUILD = Makefile.uboot
+endif
+
 BFD_NAME = elf32-littlearm
 BFD_OUTPUT = $(BFD_NAME)
@@ -35,5 +41,7 @@
 PAGE_SIZE = 4096
 
-RD_SRVS_ESSENTIAL +=
+RD_SRVS_ESSENTIAL += \
+	$(USPACE_PATH)/srv/hid/s3c24xx_ts/s3c24ts \
+	$(USPACE_PATH)/srv/hw/char/s3c24xx_uart/s3c24ser
 
 RD_SRVS_NON_ESSENTIAL += \
@@ -47,4 +55,5 @@
 	$(COMPS_C) \
 	genarch/src/division.c \
+	generic/src/memstr.c \
 	generic/src/printf_core.c \
 	generic/src/vprintf.c \
Index: boot/arch/arm32/_link.ld.in
===================================================================
--- boot/arch/arm32/_link.ld.in	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/arch/arm32/_link.ld.in	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -1,11 +1,13 @@
+#include <arch/arch.h>
+
 ENTRY(start)
 
 SECTIONS {
-	. = 0x0000;
+	. = BOOT_BASE;
 	.text : {
 		*(BOOTSTRAP);
 		*(.text);
 	}
-	. = 0x8000;
+	. = BOOT_BASE + 0x8000;
 	.data : {
 		*(BOOTPT);      /* bootstrap page table */
Index: boot/arch/arm32/include/arch.h
===================================================================
--- boot/arch/arm32/include/arch.h	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/arch/arm32/include/arch.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -36,5 +36,15 @@
 #define PTL0_ENTRY_SIZE  4
 
-#define BOOT_OFFSET  0xa00000
+/*
+ * Address where the boot stage image starts (beginning of usable physical
+ * memory).
+ */
+#ifdef MACHINE_gta02
+#define BOOT_BASE	0x30008000
+#else
+#define BOOT_BASE	0x00000000
+#endif
+
+#define BOOT_OFFSET	(BOOT_BASE + 0xa00000)
 
 #ifndef __ASM__
Index: boot/arch/arm32/include/main.h
===================================================================
--- boot/arch/arm32/include/main.h	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/arch/arm32/include/main.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -1,4 +1,5 @@
 /*
  * Copyright (c) 2007 Michal Kebrt
+ * Copyright (c) 2010 Jiri Svoboda
  * All rights reserved.
  *
@@ -38,11 +39,21 @@
 
 /** Address where characters to be printed are expected. */
-#ifdef MACHINE_testarm
-	#define VIDEORAM_ADDRESS  0x10000000
-#endif
 
-#ifdef MACHINE_integratorcp
-	#define VIDEORAM_ADDRESS  0x16000000
-#endif
+/** GTA02 serial console UART register addresses.
+ *
+ * This is UART channel 2 of the S3C24xx CPU
+ */
+#define GTA02_SCONS_UTRSTAT	0x50008010
+#define GTA02_SCONS_UTXH	0x50008020
+
+/* Bits in UTXH register */
+#define S3C24XX_UTXH_TX_EMPTY	0x00000004
+
+
+/** GXemul testarm serial console output register */
+#define TESTARM_SCONS_ADDR 	0x10000000
+
+/** IntegratorCP serial console output register */
+#define ICP_SCONS_ADDR 		0x16000000
 
 extern void bootstrap(void);
Index: boot/arch/arm32/src/asm.S
===================================================================
--- boot/arch/arm32/src/asm.S	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/arch/arm32/src/asm.S	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -35,5 +35,4 @@
 .global boot_stack
 .global halt
-.global memcpy
 .global jump_to_kernel
 
@@ -55,63 +54,4 @@
 	b halt
 
-memcpy:
-	add r3, r1, #3
-	bic r3, r3, #3
-	cmp r1, r3
-	stmdb sp!, {r4, r5, lr}
-	mov r5, r0
-	beq 4f
-	
-	1:
-		cmp r2, #0
-		movne ip, #0
-		beq 3f
-	
-	2:
-		ldrb r3, [ip, r1]
-		strb r3, [ip, r0]
-		add ip, ip, #1
-		cmp ip, r2
-		bne 2b
-	
-	3:
-		mov r0, r5
-		ldmia sp!, {r4, r5, pc}
-	
-	4:
-		add r3, r0, #3
-		bic r3, r3, #3
-		cmp r0, r3
-		bne 1b
-		movs r4, r2, lsr #2
-		moveq lr, r4
-		beq 6f
-		mov lr, #0
-		mov ip, lr
-	
-	5:
-		ldr r3, [ip, r1]
-		add lr, lr, #1
-		cmp lr, r4
-		str r3, [ip, r0]
-		add ip, ip, #4
-		bne 5b
-	
-	6:
-		ands r4, r2, #3
-		beq 3b
-		mov r3, lr, lsl #2
-		add r0, r3, r0
-		add ip, r3, r1
-		mov r2, #0
-	
-	7:
-		ldrb r3, [r2, ip]
-		strb r3, [r2, r0]
-		add r2, r2, #1
-		cmp r2, r4
-		bne 7b
-		b 3b
-
 jump_to_kernel:
 	#
Index: boot/arch/arm32/src/putchar.c
===================================================================
--- boot/arch/arm32/src/putchar.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/arch/arm32/src/putchar.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -2,4 +2,5 @@
  * Copyright (c) 2007 Michal Kebrt
  * Copyright (c) 2009 Vineeth Pillai
+ * Copyright (c) 2010 Jiri Svoboda
  * All rights reserved.
  *
@@ -40,13 +41,84 @@
 #include <str.h>
 
+#ifdef MACHINE_gta02
+
+/** Send a byte to the gta02 serial console.
+ *
+ * @param byte		Byte to send.
+ */
+static void scons_sendb_gta02(uint8_t byte)
+{
+	volatile uint32_t *utrstat;
+	volatile uint32_t *utxh;
+
+	utrstat = (volatile uint32_t *) GTA02_SCONS_UTRSTAT;
+	utxh    = (volatile uint32_t *) GTA02_SCONS_UTXH;
+
+	/* Wait until transmitter is empty. */
+	while ((*utrstat & S3C24XX_UTXH_TX_EMPTY) == 0)
+		;
+
+	/* Transmit byte. */
+	*utxh = (uint32_t) byte;
+}
+
+#endif
+
+#ifdef MACHINE_testarm
+
+/** Send a byte to the GXemul testarm serial console.
+ *
+ * @param byte		Byte to send.
+ */
+static void scons_sendb_testarm(uint8_t byte)
+{
+	*((volatile uint8_t *) TESTARM_SCONS_ADDR) = byte;
+}
+
+#endif
+
+#ifdef MACHINE_integratorcp
+
+/** Send a byte to the IntegratorCP serial console.
+ *
+ * @param byte		Byte to send.
+ */
+static void scons_sendb_icp(uint8_t byte)
+{
+	*((volatile uint8_t *) ICP_SCONS_ADDR) = byte;
+}
+
+#endif
+
+/** Send a byte to the serial console.
+ *
+ * @param byte		Byte to send.
+ */
+static void scons_sendb(uint8_t byte)
+{
+#ifdef MACHINE_gta02
+	scons_sendb_gta02(byte);
+#endif
+#ifdef MACHINE_testarm
+	scons_sendb_testarm(byte);
+#endif
+#ifdef MACHINE_integratorcp
+	scons_sendb_icp(byte);
+#endif
+}
+
+/** Display a character
+ *
+ * @param ch	Character to display
+ */
 void putchar(const wchar_t ch)
 {
 	if (ch == '\n')
-		*((volatile char *) VIDEORAM_ADDRESS) = '\r';
-	
+		scons_sendb('\r');
+
 	if (ascii_check(ch))
-		*((volatile char *) VIDEORAM_ADDRESS) = ch;
+		scons_sendb((uint8_t) ch);
 	else
-		*((volatile char *) VIDEORAM_ADDRESS) = U_SPECIAL;
+		scons_sendb(U_SPECIAL);
 }
 
Index: boot/arch/ia64/Makefile.inc
===================================================================
--- boot/arch/ia64/Makefile.inc	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/arch/ia64/Makefile.inc	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -49,4 +49,5 @@
 	genarch/src/division.c \
 	generic/src/balloc.c \
+	generic/src/memstr.c \
 	generic/src/printf_core.c \
 	generic/src/vprintf.c \
Index: boot/arch/ia64/src/asm.S
===================================================================
--- boot/arch/ia64/src/asm.S	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/arch/ia64/src/asm.S	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -1,5 +1,5 @@
 #
 # Copyright (c) 2006 Martin Decky
-# Copyright (c) 2006 Jakub Jermar 
+# Copyright (c) 2006 Jakub Jermar
 # All rights reserved.
 #
@@ -39,76 +39,4 @@
 	br halt
 
-memcpy:
-	alloc loc0 = ar.pfs, 3, 1, 0, 0
-
-	adds r14 = 7, in1
-	mov r2 = ar.lc
-	mov r8 = in0 ;;
-	and r14 = -8, r14 ;;
-	cmp.ne p6, p7 = r14, in1
-(p7)	br.cond.dpnt 3f ;;
-0:
-	cmp.ne p6, p7 = 0, in2
-(p7)	br.cond.dpnt 2f ;;
-(p6)	adds r14 = -1, in2
-(p6)	mov r16 = r0
-(p6)	mov r17 = r0 ;;
-(p6)	mov ar.lc = r14
-1:
-	add r14 = r16, in1 
-	add r15 = r16, in0
-	adds r17 = 1, r17 ;;
-	ld1 r14 = [r14]
-	mov r16 = r17 ;;
-	st1 [r15] = r14
-	br.cloop.sptk.few 1b ;;
-2:
-	mov ar.lc = r2
-	mov ar.pfs = loc0
-	br.ret.sptk.many rp
-3:
-	adds r14 = 7, in0 ;;
-	and r14 = -8, r14 ;;
-	cmp.eq p6, p7 = r14, in0
-(p7)	br.cond.dptk 0b
-	shr.u r18 = in2, 3 ;;
-	cmp.ne p6, p7 = 0, r18
-(p7)	br.cond.dpnt 5f	;;
-(p6)	adds r14 = -1, r18
-(p6)	mov r16 = r0
-(p6)	mov r17 = r0 ;;
-(p6)	mov ar.lc = r14
-4:
-	shladd r14 = r16, 3, r0
-	adds r16 = 1, r17 ;;
-	add r15 = in1, r14
-	add r14 = in0, r14
-	mov r17 = r16 ;;
-	ld8 r15 = [r15] ;;
-	st8 [r14] = r15
-	br.cloop.sptk.few 4b
-5:
-	and r15 = 7, in2
-	shladd r14 = r18, 3, r0
-	mov r16 = r0
-	mov r18 = r0 ;;
-	cmp.eq p6, p7 = 0, r15
-	add in0 = r14, in0
-	adds r15 = -1, r15
-	add r17 = r14, in1 
-(p6)	br.cond.dpnt 2b ;;
-	mov ar.lc = r15
-6:
-	add r14 = r16, r17
-	add r15 = r16, in0
-	adds r16 = 1, r18 ;;
-	ld1 r14 = [r14]
-	mov r18 = r16 ;;
-	st1 [r15] = r14
-	br.cloop.sptk.few 6b ;;
-	mov ar.lc = r2
-	mov ar.pfs = loc0
-	br.ret.sptk.many rp
-
 jump_to_kernel:
 	alloc loc0 = ar.pfs, 1, 1, 0, 0
Index: boot/arch/mips32/Makefile.inc
===================================================================
--- boot/arch/mips32/Makefile.inc	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/arch/mips32/Makefile.inc	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -61,4 +61,5 @@
 	genarch/src/division.c \
 	genarch/src/multiplication.c \
+	generic/src/memstr.c \
 	generic/src/printf_core.c \
 	generic/src/vprintf.c \
Index: boot/arch/mips32/src/asm.S
===================================================================
--- boot/arch/mips32/src/asm.S	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/arch/mips32/src/asm.S	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -36,5 +36,4 @@
 .global start
 .global halt
-.global memcpy
 .global jump_to_kernel
 
@@ -127,70 +126,4 @@
 	nop
 
-memcpy:
-	addiu $v0, $a1, 3
-	li $v1, -4
-	and $v0, $v0, $v1
-	beq $a1, $v0, 3f
-	move $t0, $a0
-	move $t2, $a0
-	
-	0:
-		beq $a2, $zero, 2f
-		move $a3, $zero
-	
-	1:
-		addu $v0, $a1, $a3
-		lbu $a0, 0($v0)
-		addu $v1, $t0, $a3
-		addiu $a3, $a3, 1
-		bne $a3, $a2, 1b
-		sb $a0, 0($v1)
-	
-	2:
-		jr $ra
-		move $v0, $t2
-	
-	3:
-		addiu $v0, $a0, 3
-		and $v0, $v0, $v1
-		bne $a0, $v0, 0b
-		srl $t1, $a2, 2
-		
-		beq $t1, $zero, 5f
-		move $a3, $zero
-		
-		move $a3, $zero
-		move $a0, $zero
-	
-	4:
-		addu $v0, $a1, $a0
-		lw $v1, 0($v0)
-		addiu $a3, $a3, 1
-		addu $v0, $t0, $a0
-		sw $v1, 0($v0)
-		bne $a3, $t1, 4b
-		addiu $a0, $a0, 4
-	
-	5:
-		andi $a2, $a2, 0x3
-		beq $a2, $zero, 2b
-		nop
-		
-		sll $v0, $a3, 2
-		addu $t1, $v0, $t0
-		move $a3, $zero
-		addu $t0, $v0, $a1
-	
-	6:
-		addu $v0, $t0, $a3
-		lbu $a0, 0($v0)
-		addu $v1, $t1, $a3
-		addiu $a3, $a3, 1
-		bne $a3, $a2, 6b
-		sb $a0, 0($v1)
-		
-		jr $ra
-		move $v0, $t2
-
 jump_to_kernel:
 	#
Index: boot/arch/ppc32/Makefile.inc
===================================================================
--- boot/arch/ppc32/Makefile.inc	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/arch/ppc32/Makefile.inc	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -56,4 +56,5 @@
 	genarch/src/multiplication.c \
 	generic/src/balloc.c \
+	generic/src/memstr.c \
 	generic/src/printf_core.c \
 	generic/src/vprintf.c \
Index: boot/arch/ppc32/src/asm.S
===================================================================
--- boot/arch/ppc32/src/asm.S	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/arch/ppc32/src/asm.S	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -60,5 +60,4 @@
 .global start
 .global halt
-.global memcpy
 .global jump_to_kernel
 .global real_mode
@@ -78,60 +77,4 @@
 halt:
 	b halt
-
-memcpy:
-	srwi. r7, r5, 3
-	addi r6, r3, -4
-	addi r4, r4, -4
-	beq 2f
-	
-	andi. r0, r6, 3
-	mtctr r7
-	bne 5f
-	
-	1:
-		lwz r7, 4(r4)
-		lwzu r8, 8(r4)
-		stw r7, 4(r6)
-		stwu r8, 8(r6)
-		bdnz 1b
-		
-		andi. r5, r5, 7
-	
-	2:
-		cmplwi 0, r5, 4
-		blt 3f
-		
-		lwzu r0, 4(r4)
-		addi r5, r5, -4
-		stwu r0, 4(r6)
-	
-	3:
-		cmpwi 0, r5, 0
-		beqlr
-		mtctr r5
-		addi r4, r4, 3
-		addi r6, r6, 3
-	
-	4:
-		lbzu r0, 1(r4)
-		stbu r0, 1(r6)
-		bdnz 4b
-		blr
-	
-	5:
-		subfic r0, r0, 4
-		mtctr r0
-	
-	6:
-		lbz r7, 4(r4)
-		addi r4, r4, 1
-		stb r7, 4(r6)
-		addi r6, r6, 1
-		bdnz 6b
-		subf r5, r0, r5
-		rlwinm. r7, r5, 32-3, 3, 31
-		beq 2b
-		mtctr r7
-		b 1b
 
 jump_to_kernel:
Index: boot/arch/sparc64/Makefile.inc
===================================================================
--- boot/arch/sparc64/Makefile.inc	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/arch/sparc64/Makefile.inc	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -54,4 +54,5 @@
 	genarch/src/ofw_tree.c \
 	generic/src/balloc.c \
+	generic/src/memstr.c \
 	generic/src/printf_core.c \
 	generic/src/vprintf.c \
Index: boot/arch/sparc64/src/asm.S
===================================================================
--- boot/arch/sparc64/src/asm.S	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ boot/arch/sparc64/src/asm.S	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -42,5 +42,4 @@
 .global start
 .global halt
-.global memcpy
 .global jump_to_kernel
 
@@ -98,73 +97,4 @@
 	ba %xcc, halt
 	nop
-
-memcpy:
-	! Save dst
-	
-	mov %o0, %o3
-	add %o1, 7, %g1
-	and %g1, -8, %g1
-	cmp %o1, %g1
-	be,pn %xcc, 3f
-	add %o0, 7, %g1
-	mov 0, %g3
-	
-	0:
-		brz,pn %o2, 2f
-		mov 0, %g2
-	
-	1:
-		ldub [%g3 + %o1], %g1
-		add %g2, 1, %g2
-		cmp %o2, %g2
-		stb %g1, [%g3 + %o0]
-		bne,pt %xcc, 1b
-		mov %g2, %g3
-	
-	2:
-		! Exit point
-		
-		jmp %o7 + 8
-		mov %o3, %o0
-	
-	3:
-		and %g1, -8, %g1
-		cmp %o0, %g1
-		bne,pt %xcc, 0b
-		mov 0, %g3
-		srlx %o2, 3, %g4
-		brz,pn %g4, 5f
-		mov 0, %g5
-	
-	4:
-		sllx %g3, 3, %g2
-		add %g5, 1, %g3
-		ldx [%o1 + %g2], %g1
-		mov %g3, %g5
-		cmp %g4, %g3
-		bne,pt %xcc, 4b
-		stx %g1, [%o0 + %g2]
-	
-	5:
-		and %o2, 7, %o2
-		brz,pn %o2, 2b
-		sllx %g4, 3, %g1
-		mov 0, %g2
-		add %g1, %o0, %o0
-		add %g1, %o1, %g4
-		mov 0, %g3
-	
-	6:
-		ldub [%g2 + %g4], %g1
-		stb %g1, [%g2 + %o0]
-		add %g3, 1, %g2
-		cmp %o2, %g2
-		bne,pt %xcc, 6b
-		mov %g2, %g3
-	
-	! Exit point
-	
-	jmp %o7 + 8
-	mov %o3, %o0
 
 jump_to_kernel:
Index: boot/generic/src/memstr.c
===================================================================
--- boot/generic/src/memstr.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
+++ boot/generic/src/memstr.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+#include <memstr.h>
+#include <typedefs.h>
+
+/** Copy block of memory.
+ *
+ * Copy cnt bytes from src address to dst address.The source and destination
+ * memory areas cannot overlap.
+ *
+ * @param src		Source address to copy from.
+ * @param dst		Destination address to copy to.
+ * @param cnt		Number of bytes to copy.
+ *
+ * @return		Destination address.
+ */
+void *memcpy(void *dst, const void *src, size_t cnt)
+{
+	size_t i;
+
+	for (i = 0; i < cnt; i++)
+		((uint8_t *) dst)[i] = ((uint8_t *) src)[i];
+
+	return dst;
+}
+
+/** @}
+ */
Index: contrib/conf/ia32-qe.sh
===================================================================
--- contrib/conf/ia32-qe.sh	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ contrib/conf/ia32-qe.sh	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -5,5 +5,5 @@
 # Create a disk image if it does not exist
 if [ ! -f "$DISK_IMG" ]; then
-	tools/mkfat.py uspace/dist/data "$DISK_IMG"
+	tools/mkfat.py 1048576 uspace/dist/data "$DISK_IMG"
 fi
 
Index: contrib/conf/mips32-gx.sh
===================================================================
--- contrib/conf/mips32-gx.sh	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ contrib/conf/mips32-gx.sh	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -5,5 +5,5 @@
 # Create a disk image if it does not exist
 if [ ! -f "$DISK_IMG" ]; then
-	tools/mkfat.py uspace/dist/data "$DISK_IMG"
+	tools/mkfat.py 1048576 uspace/dist/data "$DISK_IMG"
 fi
 
Index: defaults/arm32/gta02/Makefile.config
===================================================================
--- defaults/arm32/gta02/Makefile.config	(revision 20235a356988dff15276fd66d391230fc9b32591)
+++ defaults/arm32/gta02/Makefile.config	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -0,0 +1,14 @@
+# Machine type
+MACHINE = gta02
+
+# RAM disk format
+RDFMT = tmpfs
+
+# Support for userspace debuggers
+CONFIG_UDEBUG = n
+
+# Compile kernel tests
+CONFIG_TEST = n
+
+# Barebone build with essential binaries only
+CONFIG_BAREBONE = y
Index: kernel/Makefile
===================================================================
--- kernel/Makefile	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/Makefile	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -120,4 +120,11 @@
 ifeq ($(CONFIG_LTO),y)
 	GCC_CFLAGS += -flto
+endif
+
+ifeq ($(CONFIG_LINE_DEBUG),y)
+	GCC_CFLAGS += -g
+	ICC_CFLAGS += -g
+	SUNCC_CFLAGS += -g
+	CLANG_CFLAGS += -g
 endif
 
@@ -401,5 +408,9 @@
 
 $(DISASM): $(RAW)
+ifeq ($(CONFIG_LINE_DEBUG),y)
+	$(OBJDUMP) -d -S $< > $@
+else
 	$(OBJDUMP) -d $< > $@
+endif
 
 $(RAW): $(LINK) $(ARCH_OBJECTS) $(GENARCH_OBJECTS) $(GENERIC_OBJECTS) $(SYMTAB_OBJECTS)
Index: kernel/arch/amd64/_link.ld.in
===================================================================
--- kernel/arch/amd64/_link.ld.in	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/amd64/_link.ld.in	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -53,4 +53,17 @@
 	}
 	
+#ifdef CONFIG_LINE_DEBUG
+	.comment 0 : { *(.comment); }
+	.debug_abbrev 0 : { *(.debug_abbrev); }
+	.debug_aranges 0 : { *(.debug_aranges); }
+	.debug_info 0 : { *(.debug_info); }
+	.debug_line 0 : { *(.debug_line); }
+	.debug_loc 0 : { *(.debug_loc); }
+	.debug_pubnames 0 : { *(.debug_pubnames); }
+	.debug_pubtypes 0 : { *(.debug_pubtypes); }
+	.debug_ranges 0 : { *(.debug_ranges); }
+	.debug_str 0 : { *(.debug_str); }
+#endif
+	
 	/DISCARD/ : {
 		*(*);
Index: kernel/arch/amd64/include/pm.h
===================================================================
--- kernel/arch/amd64/include/pm.h	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/amd64/include/pm.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -65,6 +65,5 @@
 #endif /* CONFIG_FB */
 
-#define gdtselector(des)  ((des) << 3)
-#define idtselector(des)  ((des) << 4)
+#define GDT_SELECTOR(des)  ((des) << 3)
 
 #define PL_KERNEL  0
@@ -168,5 +167,4 @@
 
 extern ptr_16_64_t gdtr;
-extern ptr_16_32_t bootstrap_gdtr;
 extern ptr_16_32_t protected_ap_gdtr;
 
Index: kernel/arch/amd64/src/asm.S
===================================================================
--- kernel/arch/amd64/src/asm.S	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/amd64/src/asm.S	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -244,5 +244,5 @@
 	 */
 	xorq %rdx, %rdx
-	cmpq $(gdtselector(KTEXT_DES)), ISTATE_OFFSET_CS(%rsp)
+	cmpq $(GDT_SELECTOR(KTEXT_DES)), ISTATE_OFFSET_CS(%rsp)
 	cmovnzq %rdx, %rbp
 
Index: kernel/arch/amd64/src/boot/boot.S
===================================================================
--- kernel/arch/amd64/src/boot/boot.S	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/amd64/src/boot/boot.S	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -85,5 +85,5 @@
 	
 	/* Kernel data + stack */
-	movw $gdtselector(KDATA_DES), %cx
+	movw $GDT_SELECTOR(KDATA_DES), %cx
 	movw %cx, %es
 	movw %cx, %ds
@@ -94,9 +94,9 @@
 	 * when _visible_ part of GS does not point to user-mode segment.
 	 */
-	movw $gdtselector(UDATA_DES), %cx
+	movw $GDT_SELECTOR(UDATA_DES), %cx
 	movw %cx, %fs
 	movw %cx, %gs
 	
-	jmpl $gdtselector(KTEXT32_DES), $multiboot_meeting_point
+	jmpl $GDT_SELECTOR(KTEXT32_DES), $multiboot_meeting_point
 	multiboot_meeting_point:
 	
@@ -182,5 +182,5 @@
 	
 	/* At this point we are in compatibility mode */
-	jmpl $gdtselector(KTEXT_DES), $start64
+	jmpl $GDT_SELECTOR(KTEXT_DES), $start64
 
 /** Print string to EGA display (in light red) and halt.
@@ -645,7 +645,6 @@
 .section K_DATA_START, "aw", @progbits
 
-.global bootstrap_gdtr
 bootstrap_gdtr:
-	.word gdtselector(GDT_ITEMS)
+	.word GDT_SELECTOR(GDT_ITEMS)
 	.long KA2PA(gdt)
 
Index: kernel/arch/amd64/src/boot/vesa_ret.inc
===================================================================
--- kernel/arch/amd64/src/boot/vesa_ret.inc	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/amd64/src/boot/vesa_ret.inc	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -7,5 +7,5 @@
 	
 	/* Kernel data + stack */
-	movw $gdtselector(KDATA_DES), %cx
+	movw $GDT_SELECTOR(KDATA_DES), %cx
 	movw %cx, %es
 	movw %cx, %ds
@@ -17,7 +17,7 @@
 	 */
 	
-	movw $gdtselector(UDATA_DES), %cx
+	movw $GDT_SELECTOR(UDATA_DES), %cx
 	movw %cx, %fs
 	movw %cx, %gs
 	
-	jmpl $gdtselector(KTEXT32_DES), $vesa_meeting_point
+	jmpl $GDT_SELECTOR(KTEXT32_DES), $vesa_meeting_point
Index: kernel/arch/amd64/src/ddi/ddi.c
===================================================================
--- kernel/arch/amd64/src/ddi/ddi.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/amd64/src/ddi/ddi.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -153,5 +153,5 @@
 	tss_descriptor_t *tss_desc = (tss_descriptor_t *) &gdt_p[TSS_DES];
 	tss_desc->type = AR_TSS;
-	tr_load(gdtselector(TSS_DES));
+	tr_load(GDT_SELECTOR(TSS_DES));
 	
 	/*
Index: kernel/arch/amd64/src/pm.c
===================================================================
--- kernel/arch/amd64/src/pm.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/amd64/src/pm.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -171,5 +171,5 @@
 
 		d->unused = 0;
-		d->selector = gdtselector(KTEXT_DES);
+		d->selector = GDT_SELECTOR(KTEXT_DES);
 
 		d->present = 1;
@@ -291,5 +291,5 @@
 	 * to its own TSS. We just need to load the TR register.
 	 */
-	tr_load(gdtselector(TSS_DES));
+	tr_load(GDT_SELECTOR(TSS_DES));
 }
 
Index: kernel/arch/amd64/src/smp/ap.S
===================================================================
--- kernel/arch/amd64/src/smp/ap.S	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/amd64/src/smp/ap.S	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -61,13 +61,13 @@
 	orl $1, %eax
 	movl %eax, %cr0     # switch to protected mode
-	jmpl $gdtselector(KTEXT32_DES), $jump_to_kernel - BOOT_OFFSET + AP_BOOT_OFFSET
+	jmpl $GDT_SELECTOR(KTEXT32_DES), $jump_to_kernel - BOOT_OFFSET + AP_BOOT_OFFSET
 
 jump_to_kernel:
 .code32
-	movw $gdtselector(KDATA_DES), %ax
+	movw $GDT_SELECTOR(KDATA_DES), %ax
 	movw %ax, %ds
 	movw %ax, %es
 	movw %ax, %ss
-	movw $gdtselector(UDATA_DES), %ax
+	movw $GDT_SELECTOR(UDATA_DES), %ax
 	movw %ax, %gs
 	
@@ -94,5 +94,5 @@
 	
 	# At this point we are in compatibility mode
-	jmpl $gdtselector(KTEXT_DES), $start64 - BOOT_OFFSET + AP_BOOT_OFFSET
+	jmpl $GDT_SELECTOR(KTEXT_DES), $start64 - BOOT_OFFSET + AP_BOOT_OFFSET
 
 .code64
Index: kernel/arch/amd64/src/syscall.c
===================================================================
--- kernel/arch/amd64/src/syscall.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/amd64/src/syscall.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -58,6 +58,6 @@
 	 */
 	write_msr(AMD_MSR_STAR,
-	    ((uint64_t)(gdtselector(KDATA_DES) | PL_USER) << 48) |
-	    ((uint64_t)(gdtselector(KTEXT_DES) | PL_KERNEL) << 32));
+	    ((uint64_t) (GDT_SELECTOR(KDATA_DES) | PL_USER) << 48) |
+	    ((uint64_t) (GDT_SELECTOR(KTEXT_DES) | PL_KERNEL) << 32));
 	write_msr(AMD_MSR_LSTAR, (uint64_t)syscall_entry);
 	/* Mask RFLAGS on syscall 
Index: kernel/arch/amd64/src/userspace.c
===================================================================
--- kernel/arch/amd64/src/userspace.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/amd64/src/userspace.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -65,8 +65,8 @@
 		"xorq %%rdi, %%rdi\n"
 		"iretq\n"
-		:: [udata_des] "i" (gdtselector(UDATA_DES) | PL_USER),
+		:: [udata_des] "i" (GDT_SELECTOR(UDATA_DES) | PL_USER),
 		   [stack_size] "r" (kernel_uarg->uspace_stack + THREAD_STACK_SIZE),
 		   [ipl] "r" (ipl),
-		   [utext_des] "i" (gdtselector(UTEXT_DES) | PL_USER),
+		   [utext_des] "i" (GDT_SELECTOR(UTEXT_DES) | PL_USER),
 		   [entry] "r" (kernel_uarg->uspace_entry),
 		   [uarg] "r" (kernel_uarg->uspace_uarg)
Index: kernel/arch/arm32/Makefile.inc
===================================================================
--- kernel/arch/arm32/Makefile.inc	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/arm32/Makefile.inc	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -60,4 +60,8 @@
 	arch/$(KARCH)/src/ras.c
 
+ifeq ($(MACHINE),gta02)
+	ARCH_SOURCES += arch/$(KARCH)/src/mach/gta02/gta02.c
+endif
+
 ifeq ($(MACHINE),testarm)
 	ARCH_SOURCES += arch/$(KARCH)/src/mach/testarm/testarm.c
Index: kernel/arch/arm32/_link.ld.in
===================================================================
--- kernel/arch/arm32/_link.ld.in	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/arm32/_link.ld.in	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -7,5 +7,9 @@
  */
 
+#ifdef MACHINE_gta02
+#define KERNEL_LOAD_ADDRESS 0xb0a08000
+#else
 #define KERNEL_LOAD_ADDRESS 0x80a00000
+#endif
 
 OUTPUT_ARCH(arm)
Index: kernel/arch/arm32/include/mach/gta02/gta02.h
===================================================================
--- kernel/arch/arm32/include/mach/gta02/gta02.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
+++ kernel/arch/arm32/include/mach/gta02/gta02.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -0,0 +1,51 @@
+/*
+ * 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 arm32gta02 GTA02
+ *  @brief Openmoko GTA02 platform.
+ *  @ingroup arm32
+ * @{
+ */
+/** @file
+ *  @brief Openmoko GTA02 platform driver.
+ */
+
+#ifndef KERN_arm32_gta02_H_
+#define KERN_arm32_gta02_H_
+
+#include <arch/machine_func.h>
+
+extern struct arm_machine_ops gta02_machine_ops;
+
+/** Size of GTA02 IRQ number range (starting from 0) */
+#define GTA02_IRQ_COUNT 32
+
+#endif
+
+/** @}
+ */
Index: kernel/arch/arm32/include/mach/integratorcp/integratorcp.h
===================================================================
--- kernel/arch/arm32/include/mach/integratorcp/integratorcp.h	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/arm32/include/mach/integratorcp/integratorcp.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -103,8 +103,12 @@
 extern void icp_cpu_halt(void);
 extern void icp_irq_exception(unsigned int, istate_t *);
-extern uintptr_t icp_get_memory_size(void);
+extern void icp_get_memory_extents(uintptr_t *, uintptr_t *);
 extern void icp_frame_init(void);
+extern size_t icp_get_irq_count(void);
 
 extern struct arm_machine_ops icp_machine_ops;
+
+/** Size of IntegratorCP IRQ number range (starting from 0) */
+#define ICP_IRQ_COUNT 8
 
 #endif
Index: kernel/arch/arm32/include/mach/testarm/testarm.h
===================================================================
--- kernel/arch/arm32/include/mach/testarm/testarm.h	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/arm32/include/mach/testarm/testarm.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -42,10 +42,8 @@
 #include <arch/machine_func.h>
 
-/** Last interrupt number (beginning from 0) whose status is probed
- * from interrupt controller
- */
-#define GXEMUL_IRQC_MAX_IRQ  8
-#define GXEMUL_KBD_IRQ       2
-#define GXEMUL_TIMER_IRQ     4
+/** Size of GXemul IRQ number range (starting from 0) */
+#define GXEMUL_IRQ_COUNT	32
+#define GXEMUL_KBD_IRQ		2
+#define GXEMUL_TIMER_IRQ	4
 
 /** Timer frequency */
@@ -73,6 +71,7 @@
 extern void gxemul_cpu_halt(void);
 extern void gxemul_irq_exception(unsigned int, istate_t *);
-extern uintptr_t gxemul_get_memory_size(void);
+extern void gxemul_get_memory_extents(uintptr_t *, uintptr_t *);
 extern void gxemul_frame_init(void);
+extern size_t gxemul_get_irq_count(void);
 
 extern struct arm_machine_ops gxemul_machine_ops;
Index: kernel/arch/arm32/include/machine_func.h
===================================================================
--- kernel/arch/arm32/include/machine_func.h	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/arm32/include/machine_func.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -47,12 +47,13 @@
 
 struct arm_machine_ops {
-	void		(*machine_init)(void);
-	void		(*machine_timer_irq_start)(void);
-	void		(*machine_cpu_halt)(void);
-	uintptr_t	(*machine_get_memory_size)(void);
-	void		(*machine_irq_exception)(unsigned int, istate_t*);
-	void		(*machine_frame_init)(void);
-	void		(*machine_output_init)(void);
-	void		(*machine_input_init)(void);
+	void (*machine_init)(void);
+	void (*machine_timer_irq_start)(void);
+	void (*machine_cpu_halt)(void);
+	void (*machine_get_memory_extents)(uintptr_t *, uintptr_t *);
+	void (*machine_irq_exception)(unsigned int, istate_t *);
+	void (*machine_frame_init)(void);
+	void (*machine_output_init)(void);
+	void (*machine_input_init)(void);
+	size_t (*machine_get_irq_count)(void);
 };
 
@@ -74,11 +75,10 @@
 extern void machine_cpu_halt(void);
 
-
-/** Returns size of available memory.
+/** Get extents of available memory.
  *
- *  @return Size of available memory.
+ * @param start		Place to store memory start address.
+ * @param size		Place to store memory size.
  */
-extern uintptr_t machine_get_memory_size(void);
-
+extern void machine_get_memory_extents(uintptr_t *start, uintptr_t *size);
 
 /** Interrupt exception handler.
@@ -105,4 +105,6 @@
 extern void machine_input_init(void);
 
+extern size_t machine_get_irq_count(void);
+
 #endif
 
Index: kernel/arch/arm32/include/mm/frame.h
===================================================================
--- kernel/arch/arm32/include/mm/frame.h	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/arm32/include/mm/frame.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -46,8 +46,19 @@
 
 #define BOOT_PAGE_TABLE_SIZE     0x4000
-#define BOOT_PAGE_TABLE_ADDRESS  0x8000
+
+#ifdef MACHINE_gta02
+#define BOOT_PAGE_TABLE_ADDRESS  0x30010000
+#else
+#define BOOT_PAGE_TABLE_ADDRESS  0x00008000
+#endif
 
 #define BOOT_PAGE_TABLE_START_FRAME     (BOOT_PAGE_TABLE_ADDRESS >> FRAME_WIDTH)
 #define BOOT_PAGE_TABLE_SIZE_IN_FRAMES  (BOOT_PAGE_TABLE_SIZE >> FRAME_WIDTH)
+
+#ifdef MACHINE_gta02
+#define PHYSMEM_START_ADDR	0x30008000
+#else
+#define PHYSMEM_START_ADDR	0x00000000
+#endif
 
 extern uintptr_t last_frame;
Index: kernel/arch/arm32/src/exc_handler.S
===================================================================
--- kernel/arch/arm32/src/exc_handler.S	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/arm32/src/exc_handler.S	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -96,5 +96,8 @@
 	ldmfd r3!, {r4-r7}
 	stmfd r13!, {r4-r7}
-	stmfd r13!, {r13, lr}^
+	mov r4, r13
+	stmfd r4, {r13, lr}^
+	nop			/* Cannot access r13 immediately after stm(2) */
+	sub r13, r13, #8
 	stmfd r13!, {r2}
 
@@ -137,5 +140,8 @@
 
 	# return to user mode
-	ldmfd r13!, {r13, lr}^
+	mov r0, r13
+	ldmfd r0, {r13, lr}^
+	nop			/* Cannot access r13 immediately after ldm(2) */
+	add r13, r13, #8
 	b 2f
 
Index: kernel/arch/arm32/src/interrupt.c
===================================================================
--- kernel/arch/arm32/src/interrupt.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/arm32/src/interrupt.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -40,7 +40,4 @@
 #include <ddi/device.h>
 #include <interrupt.h>
-
-/** Initial size of a table holding interrupt handlers. */
-#define IRQ_COUNT 8
 
 /** Disable interrupts.
@@ -105,5 +102,9 @@
 void interrupt_init(void)
 {
-	irq_init(IRQ_COUNT, IRQ_COUNT);
+	size_t irq_count;
+
+	irq_count = machine_get_irq_count();
+	irq_init(irq_count, irq_count);
+
 	machine_timer_irq_start();
 }
Index: kernel/arch/arm32/src/mach/gta02/gta02.c
===================================================================
--- kernel/arch/arm32/src/mach/gta02/gta02.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
+++ kernel/arch/arm32/src/mach/gta02/gta02.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -0,0 +1,308 @@
+/*
+ * 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 arm32gxemul
+ * @{
+ */
+/** @file
+ *  @brief Openmoko GTA02 (Neo FreeRunner) platform driver.
+ */
+
+#include <arch/exception.h>
+#include <arch/mach/gta02/gta02.h>
+#include <arch/mm/page.h>
+#include <mm/page.h>
+#include <genarch/fb/fb.h>
+#include <genarch/fb/visuals.h>
+#include <genarch/drivers/s3c24xx_uart/s3c24xx_uart.h>
+#include <genarch/drivers/s3c24xx_irqc/s3c24xx_irqc.h>
+#include <genarch/drivers/s3c24xx_timer/s3c24xx_timer.h>
+#include <genarch/srln/srln.h>
+#include <sysinfo/sysinfo.h>
+#include <interrupt.h>
+#include <ddi/ddi.h>
+#include <ddi/device.h>
+
+#define GTA02_MEMORY_START	0x30000000	/* physical */
+#define GTA02_MEMORY_SIZE	0x08000000	/* 128 MB */
+#define GTA02_MEMORY_SKIP	0x8000
+
+/** GTA02 serial console UART address (UART S3C24XX CPU UART channel 2). */
+#define GTA02_SCONS_BASE	0x50008000
+
+/** GTA02 framebuffer base address */
+#define GTA02_FB_BASE		0x08800000
+
+/** IRQ number used for clock */
+#define GTA02_TIMER_IRQ		S3C24XX_INT_TIMER0
+
+static void gta02_init(void);
+static void gta02_timer_irq_start(void);
+static void gta02_cpu_halt(void);
+static void gta02_get_memory_extents(uintptr_t *start, uintptr_t *size);
+static void gta02_irq_exception(unsigned int exc_no, istate_t *istate);
+static void gta02_frame_init(void);
+static void gta02_output_init(void);
+static void gta02_input_init(void);
+static size_t gta02_get_irq_count(void);
+
+static void gta02_timer_irq_init(void);
+static void gta02_timer_start(void);
+static irq_ownership_t gta02_timer_irq_claim(irq_t *irq);
+static void gta02_timer_irq_handler(irq_t *irq);
+
+static outdev_t *gta02_scons_dev;
+static s3c24xx_irqc_t gta02_irqc;
+static s3c24xx_timer_t *gta02_timer;
+
+static irq_t gta02_timer_irq;
+
+struct arm_machine_ops gta02_machine_ops = {
+	gta02_init,
+	gta02_timer_irq_start,
+	gta02_cpu_halt,
+	gta02_get_memory_extents,
+	gta02_irq_exception,
+	gta02_frame_init,
+	gta02_output_init,
+	gta02_input_init,
+	gta02_get_irq_count
+};
+
+static void gta02_init(void)
+{
+	s3c24xx_irqc_regs_t *irqc_regs;
+
+	gta02_timer = (void *) hw_map(S3C24XX_TIMER_ADDRESS, PAGE_SIZE);
+	irqc_regs = (void *) hw_map(S3C24XX_IRQC_ADDRESS, PAGE_SIZE);
+
+	/* Initialize interrupt controller. */
+	s3c24xx_irqc_init(&gta02_irqc, irqc_regs);
+}
+
+static void gta02_timer_irq_start(void)
+{
+	gta02_timer_irq_init();
+	gta02_timer_start();
+}
+
+static void gta02_cpu_halt(void)
+{
+}
+
+/** Get extents of available memory.
+ *
+ * @param start		Place to store memory start address (physical).
+ * @param size		Place to store memory size.
+ */
+static void gta02_get_memory_extents(uintptr_t *start, uintptr_t *size)
+{
+	*start = GTA02_MEMORY_START + GTA02_MEMORY_SKIP;
+	*size  = GTA02_MEMORY_SIZE - GTA02_MEMORY_SKIP;
+}
+
+static void gta02_irq_exception(unsigned int exc_no, istate_t *istate)
+{
+	uint32_t inum;
+
+	/* Determine IRQ number. */
+	inum = s3c24xx_irqc_inum_get(&gta02_irqc);
+
+	/* Clear interrupt condition in the interrupt controller. */
+	s3c24xx_irqc_clear(&gta02_irqc, inum);
+
+	irq_t *irq = irq_dispatch_and_lock(inum);
+	if (irq) {
+		/* The IRQ handler was found. */
+		irq->handler(irq);
+		spinlock_unlock(&irq->lock);
+	} else {
+		/* Spurious interrupt.*/
+		printf("cpu%d: spurious interrupt (inum=%d)\n",
+		    CPU->id, inum);
+	}
+}
+
+static void gta02_frame_init(void)
+{
+}
+
+static void gta02_output_init(void)
+{
+#ifdef CONFIG_FB
+	parea_t fb_parea;
+
+	fb_properties_t prop = {
+		.addr = GTA02_FB_BASE,
+		.offset = 0,
+		.x = 480,
+		.y = 640,
+		.scan = 960,
+		.visual = VISUAL_RGB_5_6_5_LE
+	};
+
+	outdev_t *fb_dev = fb_init(&prop);
+	if (fb_dev) {
+		stdout_wire(fb_dev);
+		fb_parea.pbase = GTA02_FB_BASE;
+		fb_parea.frames = 150;
+		ddi_parea_register(&fb_parea);
+	}
+#endif
+
+	/* Initialize serial port of the debugging console. */
+	s3c24xx_uart_io_t *scons_io;
+
+	scons_io = (void *) hw_map(GTA02_SCONS_BASE, PAGE_SIZE);
+	gta02_scons_dev = s3c24xx_uart_init(scons_io, S3C24XX_INT_UART2);
+
+	if (gta02_scons_dev) {
+		/* Create output device. */
+		stdout_wire(gta02_scons_dev);
+	}
+
+	/*
+	 * This is the necessary evil until the userspace driver is entirely
+	 * self-sufficient.
+	 */
+	sysinfo_set_item_val("s3c24xx_uart", NULL, true);
+	sysinfo_set_item_val("s3c24xx_uart.inr", NULL, S3C24XX_INT_UART2);
+	sysinfo_set_item_val("s3c24xx_uart.address.physical", NULL,
+	    (uintptr_t) GTA02_SCONS_BASE);
+
+}
+
+static void gta02_input_init(void)
+{
+	s3c24xx_uart_t *scons_inst;
+
+	if (gta02_scons_dev) {
+		/* Create input device. */
+		scons_inst = (void *) gta02_scons_dev->data;
+
+		srln_instance_t *srln_instance = srln_init();
+		if (srln_instance) {
+			indev_t *sink = stdin_wire();
+			indev_t *srln = srln_wire(srln_instance, sink);
+			s3c24xx_uart_input_wire(scons_inst, srln);
+
+			/* Enable interrupts from UART2 */
+			s3c24xx_irqc_src_enable(&gta02_irqc,
+			    S3C24XX_INT_UART2);
+
+			/* Enable interrupts from UART2 RXD */
+			s3c24xx_irqc_subsrc_enable(&gta02_irqc,
+			    S3C24XX_SUBINT_RXD2);
+		}
+	}
+
+	/* Enable interrupts from ADC */
+	s3c24xx_irqc_src_enable(&gta02_irqc, S3C24XX_INT_ADC);
+
+	/* Enable interrupts from ADC sub-sources */
+	s3c24xx_irqc_subsrc_enable(&gta02_irqc, S3C24XX_SUBINT_ADC_S);
+	s3c24xx_irqc_subsrc_enable(&gta02_irqc, S3C24XX_SUBINT_TC);
+}
+
+size_t gta02_get_irq_count(void)
+{
+	return GTA02_IRQ_COUNT;
+}
+
+static void gta02_timer_irq_init(void)
+{
+	irq_initialize(&gta02_timer_irq);
+	gta02_timer_irq.devno = device_assign_devno();
+	gta02_timer_irq.inr = GTA02_TIMER_IRQ;
+	gta02_timer_irq.claim = gta02_timer_irq_claim;
+	gta02_timer_irq.handler = gta02_timer_irq_handler;
+
+	irq_register(&gta02_timer_irq);
+}
+
+static irq_ownership_t gta02_timer_irq_claim(irq_t *irq)
+{
+	return IRQ_ACCEPT;
+}
+
+static void gta02_timer_irq_handler(irq_t *irq)
+{
+	/*
+	 * We are holding a lock which prevents preemption.
+	 * Release the lock, call clock() and reacquire the lock again.
+	 */
+	spinlock_unlock(&irq->lock);
+	clock();
+	spinlock_lock(&irq->lock);
+}
+
+static void gta02_timer_start(void)
+{
+	s3c24xx_timer_t *timer = gta02_timer;
+
+	/*
+	 * See S3C2442B user manual chapter 10 (PWM Timer) for description
+	 * of timer operation. Starting a timer is described in the
+	 * section 'Timer initialization using manual update bit and
+	 * inverter bit'.
+	 */
+
+	/*
+	 * GTA02 PCLK should be 100 MHz.
+	 * Timer input freq. = PCLK / divider / (1+prescaler)
+	 * 100 MHz / 2 / (1+7) / 62500 ~= 100 Hz
+	 */
+#if HZ != 100
+#warning Other HZ than 100 not suppored.
+#endif
+
+	/* Set prescaler values. No pre-divison, no dead zone. */
+	pio_write_32(&timer->tcfg0, 7); /* prescale 1/8 */
+
+	/* No DMA request, divider value = 2 for all timers. */
+	pio_write_32(&timer->tcfg1, 0);
+
+	/* Stop all timers. */
+	pio_write_32(&timer->tcon, 0);
+
+	/* Start counting from 64k-1. Compare value is irrelevant. */
+	pio_write_32(&timer->timer[0].cntb, 62500);
+	pio_write_32(&timer->timer[0].cmpb, 0);
+
+	/* Enable interrupts from timer0 */
+	s3c24xx_irqc_src_enable(&gta02_irqc, S3C24XX_INT_TIMER0);
+
+	/* Load data from tcntb0/tcmpb0 into tcnt0/tcmp0. */
+	pio_write_32(&timer->tcon, TCON_T0_AUTO_RLD | TCON_T0_MUPDATE);
+
+	/* Start timer 0. Inverter is off. */
+	pio_write_32(&timer->tcon, TCON_T0_AUTO_RLD | TCON_T0_START);
+}
+
+/** @}
+ */
Index: kernel/arch/arm32/src/mach/integratorcp/integratorcp.c
===================================================================
--- kernel/arch/arm32/src/mach/integratorcp/integratorcp.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/arm32/src/mach/integratorcp/integratorcp.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -60,9 +60,10 @@
 	icp_timer_irq_start,
 	icp_cpu_halt,
-	icp_get_memory_size,
+	icp_get_memory_extents,
 	icp_irq_exception,
 	icp_frame_init,
 	icp_output_init,
-	icp_input_init
+	icp_input_init,
+	icp_get_irq_count
 };
 
@@ -214,16 +215,19 @@
 }
 
-/** Returns the size of emulated memory.
- *
- * @return Size in bytes.
- */
-size_t icp_get_memory_size(void) 
-{
+/** Get extents of available memory.
+ *
+ * @param start		Place to store memory start address.
+ * @param size		Place to store memory size.
+ */
+void icp_get_memory_extents(uintptr_t *start, uintptr_t *size)
+{
+	*start = 0;
+
 	if (hw_map_init_called) {
-		return (sdram[((*(uint32_t *)icp_hw_map.sdramcr & ICP_SDRAM_MASK) >> 2)]);
+		*size = (sdram[((*(uint32_t *)icp_hw_map.sdramcr &
+		    ICP_SDRAM_MASK) >> 2)]);
 	} else {
-		return SDRAM_SIZE;
-	}
-	
+		*size = SDRAM_SIZE;
+	}
 }
 
@@ -333,4 +337,8 @@
 }
 
+size_t icp_get_irq_count(void)
+{
+	return ICP_IRQ_COUNT;
+}
 
 /** @}
Index: kernel/arch/arm32/src/mach/testarm/testarm.c
===================================================================
--- kernel/arch/arm32/src/mach/testarm/testarm.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/arm32/src/mach/testarm/testarm.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -60,9 +60,10 @@
 	gxemul_timer_irq_start,
 	gxemul_cpu_halt,
-	gxemul_get_memory_size,
+	gxemul_get_memory_extents,
 	gxemul_irq_exception,
 	gxemul_frame_init,
 	gxemul_output_init,
-	gxemul_input_init
+	gxemul_input_init,
+	gxemul_get_irq_count
 };
 
@@ -126,4 +127,9 @@
 }
 
+size_t gxemul_get_irq_count(void)
+{
+	return GXEMUL_IRQ_COUNT;
+}
+
 /** Starts gxemul Real Time Clock device, which asserts regular interrupts.
  *
@@ -185,13 +191,14 @@
 }
 
-/** Returns the size of emulated memory.
- *
- * @return Size in bytes.
- */
-uintptr_t gxemul_get_memory_size(void)
-{
-        return  *((uintptr_t *) (GXEMUL_MP_ADDRESS + GXEMUL_MP_MEMSIZE_OFFSET));
-}
-
+/** Get extents of available memory.
+ *
+ * @param start		Place to store memory start address.
+ * @param size		Place to store memory size.
+ */
+void gxemul_get_memory_extents(uintptr_t *start, uintptr_t *size)
+{
+	*start = 0;
+        *size = *((uintptr_t *) (GXEMUL_MP_ADDRESS + GXEMUL_MP_MEMSIZE_OFFSET));
+}
 
 /** Returns the mask of active interrupts. */
@@ -210,5 +217,5 @@
 	unsigned int i;
 
-	for (i = 0; i < GXEMUL_IRQC_MAX_IRQ; i++) {
+	for (i = 0; i < GXEMUL_IRQ_COUNT; i++) {
 		if (sources & (1 << i)) {
 			irq_t *irq = irq_dispatch_and_lock(i);
Index: kernel/arch/arm32/src/machine_func.c
===================================================================
--- kernel/arch/arm32/src/machine_func.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/arm32/src/machine_func.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -39,4 +39,5 @@
 
 #include <arch/machine_func.h>
+#include <arch/mach/gta02/gta02.h>
 #include <arch/mach/integratorcp/integratorcp.h>
 #include <arch/mach/testarm/testarm.h>
@@ -48,5 +49,7 @@
 void machine_ops_init(void)
 {
-#if defined(MACHINE_testarm)
+#if defined(MACHINE_gta02)
+	machine_ops = &gta02_machine_ops;
+#elif defined(MACHINE_testarm)
 	machine_ops = &gxemul_machine_ops;
 #elif defined(MACHINE_integratorcp)
@@ -77,12 +80,12 @@
 }
 
-
-/** Returns size of available memory.
+/** Get extents of available memory.
  *
- *  @return Size of available memory.
+ * @param start		Place to store memory start address.
+ * @param size		Place to store memory size.
  */
-uintptr_t machine_get_memory_size(void)
+void machine_get_memory_extents(uintptr_t *start, uintptr_t *size)
 {
-	return (machine_ops->machine_get_memory_size)();
+	(machine_ops->machine_get_memory_extents)(start, size);
 }
 
@@ -122,4 +125,10 @@
 }
 
+/** Get IRQ number range used by machine. */
+size_t machine_get_irq_count(void)
+{
+	return (machine_ops->machine_get_irq_count)();
+}
+
 /** @}
  */
Index: kernel/arch/arm32/src/mm/frame.c
===================================================================
--- kernel/arch/arm32/src/mm/frame.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/arm32/src/mm/frame.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -38,4 +38,5 @@
 #include <arch/machine_func.h>
 #include <config.h>
+#include <align.h>
 
 /** Address of the last frame in the memory. */
@@ -45,8 +46,15 @@
 void frame_arch_init(void)
 {
-	last_frame = machine_get_memory_size();
+	uintptr_t mem_start, mem_size;
+	uintptr_t first_frame;
+	uintptr_t num_frames;
+
+	machine_get_memory_extents(&mem_start, &mem_size);
+	first_frame = ALIGN_UP(mem_start, FRAME_SIZE);
+	last_frame = ALIGN_DOWN(mem_start + mem_size, FRAME_SIZE);
+	num_frames = (last_frame - first_frame) >> FRAME_WIDTH;
 	
 	/* All memory as one zone */
-	zone_create(0, ADDR2PFN(last_frame),
+	zone_create(first_frame >> FRAME_WIDTH, num_frames,
 	    BOOT_PAGE_TABLE_START_FRAME + BOOT_PAGE_TABLE_SIZE_IN_FRAMES, 0);
 	
Index: kernel/arch/arm32/src/mm/page.c
===================================================================
--- kernel/arch/arm32/src/mm/page.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/arm32/src/mm/page.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -58,5 +58,5 @@
 	uintptr_t cur;
 	/* Kernel identity mapping */
-	for (cur = 0; cur < last_frame; cur += FRAME_SIZE)
+	for (cur = PHYSMEM_START_ADDR; cur < last_frame; cur += FRAME_SIZE)
 		page_mapping_insert(AS_KERNEL, PA2KA(cur), cur, flags);
 	
@@ -68,4 +68,6 @@
 #error "Only high exception vector supported now"
 #endif
+	cur = ALIGN_DOWN(0x50008010, FRAME_SIZE);
+	page_mapping_insert(AS_KERNEL, PA2KA(cur), cur, flags);
 
 	page_table_unlock(AS_KERNEL, true);
Index: kernel/arch/arm32/src/userspace.c
===================================================================
--- kernel/arch/arm32/src/userspace.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/arm32/src/userspace.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -97,5 +97,7 @@
 		"mov sp, %[ustate]\n"
 		"msr spsr_c, %[user_mode]\n"
-		"ldmfd sp!, {r0-r12, sp, lr}^\n"
+		"ldmfd sp, {r0-r12, sp, lr}^\n"
+		"nop\n"		/* Cannot access sp immediately after ldm(2) */
+		"add sp, sp, #(15*4)\n"
 		"ldmfd sp!, {pc}^\n"
 		:: [ustate] "r" (&ustate), [user_mode] "r" (user_mode)
Index: kernel/arch/ia32/include/bios/bios.h
===================================================================
--- kernel/arch/ia32/include/bios/bios.h	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/ia32/include/bios/bios.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -38,6 +38,4 @@
 #include <typedefs.h>
 
-#define BIOS_EBDA_PTR  0x40e
-
 extern uintptr_t ebda;
 
Index: kernel/arch/ia32/include/mm/as.h
===================================================================
--- kernel/arch/ia32/include/mm/as.h	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/ia32/include/mm/as.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -27,5 +27,5 @@
  */
 
-/** @addtogroup ia32mm	
+/** @addtogroup ia32mm
  * @{
  */
Index: kernel/arch/ia32/include/mm/page.h
===================================================================
--- kernel/arch/ia32/include/mm/page.h	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/ia32/include/mm/page.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -27,5 +27,5 @@
  */
 
-/** @addtogroup ia32mm	
+/** @addtogroup ia32mm
  * @{
  */
@@ -106,5 +106,5 @@
 
 /* Set PTE flags accessors for each level. */
-#define SET_PTL1_FLAGS_ARCH(ptl0, i, x)	\
+#define SET_PTL1_FLAGS_ARCH(ptl0, i, x) \
 	set_pt_flags((pte_t *) (ptl0), (size_t) (i), (x))
 #define SET_PTL2_FLAGS_ARCH(ptl1, i, x)
Index: kernel/arch/ia32/include/pm.h
===================================================================
--- kernel/arch/ia32/include/pm.h	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/ia32/include/pm.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -58,5 +58,5 @@
 #endif /* CONFIG_FB */
 
-#define gdtselector(des)  ((des) << 3)
+#define GDT_SELECTOR(des)  ((des) << 3)
 
 #define PL_KERNEL  0
@@ -153,5 +153,4 @@
 
 extern ptr_16_32_t gdtr;
-extern ptr_16_32_t bootstrap_gdtr;
 extern ptr_16_32_t protected_ap_gdtr;
 extern tss_t *tss_p;
Index: kernel/arch/ia32/src/asm.S
===================================================================
--- kernel/arch/ia32/src/asm.S	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/ia32/src/asm.S	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -225,5 +225,5 @@
 	 * Switch to kernel selectors.
 	 */
-	movw $(gdtselector(KDATA_DES)), %ax
+	movw $(GDT_SELECTOR(KDATA_DES)), %ax
 	movw %ax, %ds
 	movw %ax, %es
@@ -304,5 +304,5 @@
 	 * Switch to kernel selectors.
 	 */
-	movl $(gdtselector(KDATA_DES)), %eax
+	movl $(GDT_SELECTOR(KDATA_DES)), %eax
 	movl %eax, %ds
 	movl %eax, %es
@@ -407,5 +407,5 @@
 	 * Switch to kernel selectors.
 	 */
-	movl $(gdtselector(KDATA_DES)), %eax
+	movl $(GDT_SELECTOR(KDATA_DES)), %eax
 	movl %eax, %ds
 	movl %eax, %es
@@ -416,5 +416,5 @@
 	 */
 	xorl %eax, %eax
-	cmpl $(gdtselector(KTEXT_DES)), ISTATE_OFFSET_CS(%esp)
+	cmpl $(GDT_SELECTOR(KTEXT_DES)), ISTATE_OFFSET_CS(%esp)
 	cmovnzl %eax, %ebp
 
Index: kernel/arch/ia32/src/bios/bios.c
===================================================================
--- kernel/arch/ia32/src/bios/bios.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/ia32/src/bios/bios.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -36,4 +36,6 @@
 #include <typedefs.h>
 
+#define BIOS_EBDA_PTR  0x40e
+
 uintptr_t ebda = 0;
 
Index: kernel/arch/ia32/src/boot/boot.S
===================================================================
--- kernel/arch/ia32/src/boot/boot.S	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/ia32/src/boot/boot.S	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -78,8 +78,8 @@
 	
 	/* Initialize Global Descriptor Table register */
-	lgdtl KA2PA(bootstrap_gdtr)
+	lgdtl bootstrap_gdtr
 	
 	/* Kernel data + stack */
-	movw $gdtselector(KDATA_DES), %cx
+	movw $GDT_SELECTOR(KDATA_DES), %cx
 	movw %cx, %es
 	movw %cx, %fs
@@ -88,5 +88,5 @@
 	movw %cx, %ss
 	
-	jmpl $gdtselector(KTEXT_DES), $multiboot_meeting_point
+	jmpl $GDT_SELECTOR(KTEXT_DES), $multiboot_meeting_point
 	multiboot_meeting_point:
 	
@@ -514,4 +514,8 @@
 page_directory:
 	.space 4096, 0
+
+bootstrap_gdtr:
+	.word GDT_SELECTOR(GDT_ITEMS)
+	.long KA2PA(gdt)
 
 grub_eax:
Index: kernel/arch/ia32/src/boot/vesa_real.inc
===================================================================
--- kernel/arch/ia32/src/boot/vesa_real.inc	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/ia32/src/boot/vesa_real.inc	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -30,5 +30,5 @@
 .code32
 vesa_init:
-	jmp $gdtselector(VESA_INIT_DES), $vesa_init_real - vesa_init
+	jmp $GDT_SELECTOR(VESA_INIT_DES), $vesa_init_real - vesa_init
 
 .code16
@@ -335,5 +335,5 @@
 		vesa_leave_real2:
 		
-			ljmpl $gdtselector(KTEXT32_DES), $(vesa_init_protected - vesa_init + VESA_INIT_SEGMENT << 4)
+			ljmpl $GDT_SELECTOR(KTEXT32_DES), $(vesa_init_protected - vesa_init + VESA_INIT_SEGMENT << 4)
 	
 	no_mode:
Index: kernel/arch/ia32/src/boot/vesa_ret.inc
===================================================================
--- kernel/arch/ia32/src/boot/vesa_ret.inc	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/ia32/src/boot/vesa_ret.inc	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -7,5 +7,5 @@
 	
 	/* Kernel data + stack */
-	movw $gdtselector(KDATA_DES), %cx
+	movw $GDT_SELECTOR(KDATA_DES), %cx
 	movw %cx, %es
 	movw %cx, %fs
@@ -14,3 +14,3 @@
 	movw %cx, %ss
 	
-	jmpl $gdtselector(KTEXT_DES), $vesa_meeting_point
+	jmpl $GDT_SELECTOR(KTEXT_DES), $vesa_meeting_point
Index: kernel/arch/ia32/src/ddi/ddi.c
===================================================================
--- kernel/arch/ia32/src/ddi/ddi.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/ia32/src/ddi/ddi.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -153,5 +153,5 @@
 	 */
 	gdt_p[TSS_DES].access = AR_PRESENT | AR_TSS | DPL_KERNEL;
-	tr_load(gdtselector(TSS_DES));
+	tr_load(GDT_SELECTOR(TSS_DES));
 	
 	/*
Index: kernel/arch/ia32/src/mm/frame.c
===================================================================
--- kernel/arch/ia32/src/mm/frame.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/ia32/src/mm/frame.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -131,7 +131,14 @@
 			if (last_frame < ALIGN_UP(new_base + new_size, FRAME_SIZE))
 				last_frame = ALIGN_UP(new_base + new_size, FRAME_SIZE);
-		}
-		
-		if (e820table[i].type == MEMMAP_MEMORY_RESERVED) {
+		} else if ((e820table[i].type == MEMMAP_MEMORY_ACPI) ||
+		    (e820table[i].type == MEMMAP_MEMORY_NVS)) {
+			/* To be safe, make the firmware zone possibly larger */
+			uint64_t new_base = ALIGN_DOWN(base, FRAME_SIZE);
+			uint64_t new_size = ALIGN_UP(size + (base - new_base),
+			    FRAME_SIZE);
+			
+			zone_create(ADDR2PFN(new_base), SIZE2FRAMES(new_size), 0,
+			    ZONE_FIRMWARE);
+		} else {
 			/* To be safe, make the reserved zone possibly larger */
 			uint64_t new_base = ALIGN_DOWN(base, FRAME_SIZE);
@@ -141,14 +148,4 @@
 			zone_create(ADDR2PFN(new_base), SIZE2FRAMES(new_size), 0,
 			    ZONE_RESERVED);
-		}
-		
-		if (e820table[i].type == MEMMAP_MEMORY_ACPI) {
-			/* To be safe, make the firmware zone possibly larger */
-			uint64_t new_base = ALIGN_DOWN(base, FRAME_SIZE);
-			uint64_t new_size = ALIGN_UP(size + (base - new_base),
-			    FRAME_SIZE);
-			
-			zone_create(ADDR2PFN(new_base), SIZE2FRAMES(new_size), 0,
-			    ZONE_FIRMWARE);
 		}
 	}
@@ -203,5 +200,5 @@
 #ifdef CONFIG_SMP
 		/* Reserve AP real mode bootstrap memory */
-		frame_mark_unavailable(AP_BOOT_OFFSET >> FRAME_WIDTH, 
+		frame_mark_unavailable(AP_BOOT_OFFSET >> FRAME_WIDTH,
 		    (hardcoded_unmapped_ktext_size +
 		    hardcoded_unmapped_kdata_size) >> FRAME_WIDTH);
Index: kernel/arch/ia32/src/pm.c
===================================================================
--- kernel/arch/ia32/src/pm.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/ia32/src/pm.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -75,6 +75,6 @@
 	/* VESA Init descriptor */
 #ifdef CONFIG_FB
-	{ 0xffff, 0, VESA_INIT_SEGMENT>>12, AR_PRESENT | AR_CODE | DPL_KERNEL, 0xf, 0, 0, 0, 0, 0 }
-#endif	
+	{ 0xffff, 0, VESA_INIT_SEGMENT >> 12, AR_PRESENT | AR_CODE | DPL_KERNEL, 0xf, 0, 0, 0, 0, 0 }
+#endif
 };
 
@@ -86,6 +86,8 @@
 
 /* gdtr is changed by kmp before next CPU is initialized */
-ptr_16_32_t bootstrap_gdtr = { .limit = sizeof(gdt), .base = KA2PA((uintptr_t) gdt) };
-ptr_16_32_t gdtr = { .limit = sizeof(gdt), .base = (uintptr_t) gdt };
+ptr_16_32_t gdtr = {
+	.limit = sizeof(gdt),
+	.base = (uintptr_t) gdt
+};
 
 void gdt_setbase(descriptor_t *d, uintptr_t base)
@@ -128,5 +130,5 @@
 
 		d->unused = 0;
-		d->selector = gdtselector(KTEXT_DES);
+		d->selector = GDT_SELECTOR(KTEXT_DES);
 
 		if (i == VECTOR_SYSCALL) {
@@ -283,5 +285,5 @@
 	 * to its own TSS. We just need to load the TR register.
 	 */
-	tr_load(gdtselector(TSS_DES));
+	tr_load(GDT_SELECTOR(TSS_DES));
 	
 	clean_IOPL_NT_flags();    /* Disable I/O on nonprivileged levels and clear NT flag. */
Index: kernel/arch/ia32/src/proc/scheduler.c
===================================================================
--- kernel/arch/ia32/src/proc/scheduler.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/ia32/src/proc/scheduler.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -67,5 +67,5 @@
 	/* Set kernel stack for CPL3 -> CPL0 switch via interrupt */
 	CPU->arch.tss->esp0 = kstk;
-	CPU->arch.tss->ss0 = gdtselector(KDATA_DES);
+	CPU->arch.tss->ss0 = GDT_SELECTOR(KDATA_DES);
 	
 	/* Set up TLS in GS register */
Index: kernel/arch/ia32/src/syscall.c
===================================================================
--- kernel/arch/ia32/src/syscall.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/ia32/src/syscall.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -45,5 +45,5 @@
 
 	/* set kernel mode CS selector */
-	write_msr(IA32_MSR_SYSENTER_CS, gdtselector(KTEXT_DES));
+	write_msr(IA32_MSR_SYSENTER_CS, GDT_SELECTOR(KTEXT_DES));
 	/* set kernel mode entry point */
 	write_msr(IA32_MSR_SYSENTER_EIP, (uint32_t) sysenter_handler);
Index: kernel/arch/ia32/src/userspace.c
===================================================================
--- kernel/arch/ia32/src/userspace.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/arch/ia32/src/userspace.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -75,11 +75,11 @@
 		"iret\n"
 		:
-		: [udata_des] "i" (gdtselector(UDATA_DES) | PL_USER),
+		: [udata_des] "i" (GDT_SELECTOR(UDATA_DES) | PL_USER),
 		  [stack_size] "r" ((uint8_t *) kernel_uarg->uspace_stack + THREAD_STACK_SIZE),
 		  [ipl] "r" (ipl),
-		  [utext_des] "i" (gdtselector(UTEXT_DES) | PL_USER),
+		  [utext_des] "i" (GDT_SELECTOR(UTEXT_DES) | PL_USER),
 		  [entry] "r" (kernel_uarg->uspace_entry),
 		  [uarg] "r" (kernel_uarg->uspace_uarg),
-		  [tls_des] "r" (gdtselector(TLS_DES))
+		  [tls_des] "r" (GDT_SELECTOR(TLS_DES))
 		: "eax");
 	
Index: kernel/genarch/Makefile.inc
===================================================================
--- kernel/genarch/Makefile.inc	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/genarch/Makefile.inc	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -90,4 +90,14 @@
 endif
 
+ifeq ($(CONFIG_S3C24XX_IRQC),y)
+	GENARCH_SOURCES += \
+		genarch/src/drivers/s3c24xx_irqc/s3c24xx_irqc.c
+endif
+
+ifeq ($(CONFIG_S3C24XX_UART),y)
+	GENARCH_SOURCES += \
+		genarch/src/drivers/s3c24xx_uart/s3c24xx_uart.c
+endif
+
 ifeq ($(CONFIG_Z8530),y)
 	GENARCH_SOURCES += \
Index: kernel/genarch/include/drivers/s3c24xx_irqc/s3c24xx_irqc.h
===================================================================
--- kernel/genarch/include/drivers/s3c24xx_irqc/s3c24xx_irqc.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
+++ kernel/genarch/include/drivers/s3c24xx_irqc/s3c24xx_irqc.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -0,0 +1,137 @@
+/*
+ * 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 interrupt controller driver.
+ */
+
+#ifndef KERN_S3C24XX_IRQC_H_
+#define KERN_S3C24XX_IRQC_H_
+
+#include <typedefs.h>
+
+/** Physical address where S3C24XX Interrupt controller is mapped */
+#define S3C24XX_IRQC_ADDRESS	0x4a000000
+
+/** S3C24xx on-chip interrupt controller registers */
+typedef struct {
+	ioport32_t srcpnd;	/**< Source pending */
+	ioport32_t intmod;	/**< Interrupt mode */
+	ioport32_t intmsk;	/**< Interrupt mask */
+	ioport32_t priority;	/**< Priority */
+	ioport32_t intpnd;	/**< Interrupt pending */
+	ioport32_t intoffset;	/**< Interrupt offset */
+	ioport32_t subsrcpnd;	/**< Sub source pending */
+	ioport32_t intsubmsk;	/** Interrupt sub mask */
+} s3c24xx_irqc_regs_t;
+
+/** S3C24xx Interrupt source numbers.
+ *
+ * These correspond to bit numbers in srcpnd, intmod, intmsk and intpnd
+ * registers as well as to the values read from the intoffset register.
+ */
+enum s3c24xx_int_source {
+	S3C24XX_INT_ADC		= 31,
+	S3C24XX_INT_RTC		= 30,
+	S3C24XX_INT_SPI1	= 29,
+	S3C24XX_INT_UART0	= 28,
+	S3C24XX_INT_IIC		= 27,
+	S3C24XX_INT_USBH	= 26,
+	S3C24XX_INT_USBD	= 25,
+	S3C24XX_INT_NFCON	= 24,
+	S3C24XX_INT_UART1	= 23,
+	S3C24XX_INT_SPI0	= 22,
+	S3C24XX_INT_SDI		= 21,
+	S3C24XX_INT_DMA3	= 20,
+	S3C24XX_INT_DMA2	= 19,
+	S3C24XX_INT_DMA1	= 18,
+	S3C24XX_INT_DMA0	= 17,
+	S3C24XX_INT_LCD		= 16,
+	S3C24XX_INT_UART2	= 15,
+	S3C24XX_INT_TIMER4	= 14,
+	S3C24XX_INT_TIMER3	= 13,
+	S3C24XX_INT_TIMER2	= 12,
+	S3C24XX_INT_TIMER1	= 11,
+	S3C24XX_INT_TIMER0	= 10,
+	S3C24XX_INT_WDT_AC97	= 9,
+	S3C24XX_INT_TICK	= 8,
+	S3C24XX_nBATT_FLT	= 7,
+	S3C24XX_INT_CAM		= 6,
+	S3C24XX_EINT8_23	= 5,
+	S3C24XX_EINT4_7		= 4,
+	S3C24XX_EINT3		= 3,
+	S3C24XX_EINT2		= 2,
+	S3C24XX_EINT1		= 1,
+	S3C24XX_EINT0		= 0
+};
+
+/** S3C24xx Interrupt sub-source numbers.
+ *
+ * These correspond to bit numbers in the intsubmsk register.
+ */
+enum s3c24xx_int_subsource {
+	S3C24XX_SUBINT_AC97	= 14,
+	S3C24XX_SUBINT_WDT	= 13,
+	S3C24XX_SUBINT_CAM_P	= 12,
+	S3C24XX_SUBINT_CAM_C	= 11,
+	S3C24XX_SUBINT_ADC_S	= 10,
+	S3C24XX_SUBINT_TC	= 9,
+	S3C24XX_SUBINT_ERR2	= 8,
+	S3C24XX_SUBINT_TXD2	= 7,
+	S3C24XX_SUBINT_RXD2	= 6,
+	S3C24XX_SUBINT_ERR1	= 5,
+	S3C24XX_SUBINT_TXD1	= 4,
+	S3C24XX_SUBINT_RXD1	= 3,
+	S3C24XX_SUBINT_ERR0	= 2,
+	S3C24XX_SUBINT_TXD0	= 1,
+	S3C24XX_SUBINT_RXD0	= 0
+};
+
+#define S3C24XX_INT_BIT(source) (1 << (source))
+#define S3C24XX_SUBINT_BIT(subsource) (1 << (subsource))
+
+typedef struct {
+	s3c24xx_irqc_regs_t *regs;
+} s3c24xx_irqc_t;
+
+extern void s3c24xx_irqc_init(s3c24xx_irqc_t *, s3c24xx_irqc_regs_t *);
+extern unsigned s3c24xx_irqc_inum_get(s3c24xx_irqc_t *);
+extern void s3c24xx_irqc_clear(s3c24xx_irqc_t *, unsigned);
+extern void s3c24xx_irqc_src_enable(s3c24xx_irqc_t *, unsigned);
+extern void s3c24xx_irqc_src_disable(s3c24xx_irqc_t *, unsigned);
+extern void s3c24xx_irqc_subsrc_enable(s3c24xx_irqc_t *, unsigned);
+extern void s3c24xx_irqc_subsrc_disable(s3c24xx_irqc_t *, unsigned);
+
+#endif
+
+/** @}
+ */
Index: kernel/genarch/include/drivers/s3c24xx_timer/s3c24xx_timer.h
===================================================================
--- kernel/genarch/include/drivers/s3c24xx_timer/s3c24xx_timer.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
+++ kernel/genarch/include/drivers/s3c24xx_timer/s3c24xx_timer.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -0,0 +1,90 @@
+/*
+ * 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 PWM timer driver.
+ */
+
+#ifndef KERN_S3C24XX_TIMER_H_
+#define KERN_S3C24XX_TIMER_H_
+
+#include <typedefs.h>
+
+/** Physical address where S3C24XX on-chip PWM timer is mapped */
+#define S3C24XX_TIMER_ADDRESS	0x51000000
+
+/** S3C24xx on-chip PWM timer registers */
+typedef struct {
+	ioport32_t tcfg0;	/**< Timer configuration register 0 */
+	ioport32_t tcfg1;	/**< Timer configuration register 1 */
+	ioport32_t tcon;	/**< Timer control register */
+
+	struct {
+		ioport32_t cntb;	/**< Count buffer register */
+		ioport32_t cmpb;	/**< Compare buffer register */
+		ioport32_t cnto;	/**< Count observation register */
+	} timer[5];
+} s3c24xx_timer_t;
+
+/** Bits in the S3C24xx PWM timer TCON register. */
+enum s3c24xx_tcon_bits {
+	TCON_T0_START		= (1 << 0),	/**< Timer 0 start */
+	TCON_T0_MUPDATE		= (1 << 1),	/**< Timer 0 manual update */
+	TCON_T0_INVERT		= (1 << 2),	/**< Timer 0 inverter on */
+	TCON_T0_AUTO_RLD	= (1 << 3),	/**< Timer 0 auto reload */
+
+	TCON_DEAD_ZONE		= (1 << 4),	/**< Dead zone enable */
+
+	TCON_T1_START		= (1 << 8),	/**< Timer 1 start */
+	TCON_T1_MUPDATE		= (1 << 9),	/**< Timer 1 manual update */
+	TCON_T1_INVERT		= (1 << 10),	/**< Timer 1 inverter on */
+	TCON_T1_AUTO_RLD	= (1 << 11),	/**< Timer 1 auto reload */
+
+	TCON_T2_START		= (1 << 12),	/**< Timer 2 start */
+	TCON_T2_MUPDATE		= (1 << 13),	/**< Timer 2 manual update */
+	TCON_T2_INVERT		= (1 << 14),	/**< Timer 2 inverter on */
+	TCON_T2_AUTO_RLD	= (1 << 15),	/**< Timer 2 auto reload */
+
+	TCON_T3_START		= (1 << 16),	/**< Timer 3 start */
+	TCON_T3_MUPDATE		= (1 << 17),	/**< Timer 3 manual update */
+	TCON_T3_INVERT		= (1 << 18),	/**< Timer 3 inverter on */
+	TCON_T3_AUTO_RLD	= (1 << 19),	/**< Timer 3 auto reload */
+
+	TCON_T4_START		= (1 << 20),	/**< Timer 4 start */
+	TCON_T4_MUPDATE		= (1 << 21),	/**< Timer 4 manual update */
+	TCON_T4_AUTO_RLD	= (1 << 22)	/**< Timer 4 auto reload */
+};
+
+#endif
+
+/** @}
+ */
Index: kernel/genarch/include/drivers/s3c24xx_uart/s3c24xx_uart.h
===================================================================
--- kernel/genarch/include/drivers/s3c24xx_uart/s3c24xx_uart.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
+++ kernel/genarch/include/drivers/s3c24xx_uart/s3c24xx_uart.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -0,0 +1,94 @@
+/*
+ * 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 KERN_S3C24XX_UART_H_
+#define KERN_S3C24XX_UART_H_
+
+#include <ddi/irq.h>
+#include <console/chardev.h>
+#include <typedefs.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 {
+	s3c24xx_uart_io_t *io;
+	indev_t *indev;
+	irq_t irq;
+} s3c24xx_uart_t;
+
+extern outdev_t *s3c24xx_uart_init(s3c24xx_uart_io_t *, inr_t inr);
+extern void s3c24xx_uart_input_wire(s3c24xx_uart_t *,
+    indev_t *);
+
+#endif
+
+/** @}
+ */
Index: kernel/genarch/src/drivers/s3c24xx_irqc/s3c24xx_irqc.c
===================================================================
--- kernel/genarch/src/drivers/s3c24xx_irqc/s3c24xx_irqc.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
+++ kernel/genarch/src/drivers/s3c24xx_irqc/s3c24xx_irqc.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -0,0 +1,141 @@
+/*
+ * 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 interrupt controller.
+ *
+ * This IRQC is present on the Samsung S3C24xx CPU (on the gta02 platform).
+ */
+
+#include <genarch/drivers/s3c24xx_irqc/s3c24xx_irqc.h>
+#include <arch/asm.h>
+
+/** Correspondence between interrupt sources and sub-sources. */
+static unsigned s3c24xx_subsrc_src[][2] = {
+	{ S3C24XX_SUBINT_CAM_P, S3C24XX_INT_CAM },
+	{ S3C24XX_SUBINT_CAM_C, S3C24XX_INT_CAM },
+	{ S3C24XX_SUBINT_ADC_S, S3C24XX_INT_ADC },
+	{ S3C24XX_SUBINT_TC, S3C24XX_INT_ADC },
+	{ S3C24XX_SUBINT_ERR2, S3C24XX_INT_UART2 },
+	{ S3C24XX_SUBINT_TXD2, S3C24XX_INT_UART2 },
+	{ S3C24XX_SUBINT_RXD2, S3C24XX_INT_UART2 },
+	{ S3C24XX_SUBINT_ERR1, S3C24XX_INT_UART1 },
+	{ S3C24XX_SUBINT_TXD1, S3C24XX_INT_UART1 },
+	{ S3C24XX_SUBINT_RXD1, S3C24XX_INT_UART1 },
+	{ S3C24XX_SUBINT_ERR0, S3C24XX_INT_UART0 },
+	{ S3C24XX_SUBINT_TXD0, S3C24XX_INT_UART0 },
+	{ S3C24XX_SUBINT_RXD0, S3C24XX_INT_UART0 }
+};
+
+/** Initialize S3C24xx interrupt controller.
+ *
+ * @param irqc	Instance structure
+ * @param regs	Register I/O structure
+ */
+void s3c24xx_irqc_init(s3c24xx_irqc_t *irqc, s3c24xx_irqc_regs_t *regs)
+{
+	irqc->regs = regs;
+
+	/* Make all interrupt sources use IRQ mode (not FIQ). */
+	pio_write_32(&regs->intmod, 0x00000000);
+
+	/* Disable all interrupt sources. */
+	pio_write_32(&regs->intmsk, 0xffffffff);
+
+	/* Disable interrupts from all sub-sources. */
+	pio_write_32(&regs->intsubmsk, 0xffffffff);
+}
+
+/** Obtain number of pending interrupt. */
+unsigned s3c24xx_irqc_inum_get(s3c24xx_irqc_t *irqc)
+{
+	return pio_read_32(&irqc->regs->intoffset);
+}
+
+/** Clear pending interrupt condition including sub-sources.
+ *
+ * Clear source and interrupt pending condition and also automatically clear
+ * any sub-source pending condition pertaining to the source.
+ */
+void s3c24xx_irqc_clear(s3c24xx_irqc_t *irqc, unsigned inum)
+{
+	unsigned src, subsrc;
+	unsigned entries, i;
+
+	entries = sizeof(s3c24xx_subsrc_src) / sizeof(s3c24xx_subsrc_src[0]);
+
+	for (i = 0; i < entries; i++) {
+		subsrc = s3c24xx_subsrc_src[i][0];
+		src = s3c24xx_subsrc_src[i][1];
+
+		if (src == inum) {
+			pio_write_32(&irqc->regs->subsrcpnd,
+			    S3C24XX_SUBINT_BIT(subsrc));
+		}
+	}
+
+	pio_write_32(&irqc->regs->srcpnd, S3C24XX_INT_BIT(inum));
+	pio_write_32(&irqc->regs->intpnd, S3C24XX_INT_BIT(inum));
+}
+
+/** Enable interrupts from the specified source. */
+void s3c24xx_irqc_src_enable(s3c24xx_irqc_t *irqc, unsigned src)
+{
+	pio_write_32(&irqc->regs->intmsk, pio_read_32(&irqc->regs->intmsk) &
+	    ~S3C24XX_INT_BIT(src));
+}
+
+/** Disable interrupts from the specified source. */
+void s3c24xx_irqc_src_disable(s3c24xx_irqc_t *irqc, unsigned src)
+{
+	pio_write_32(&irqc->regs->intmsk, pio_read_32(&irqc->regs->intmsk) |
+	    S3C24XX_INT_BIT(src));
+}
+
+/** Enable interrupts from the specified sub-source. */
+void s3c24xx_irqc_subsrc_enable(s3c24xx_irqc_t *irqc, unsigned subsrc)
+{
+	pio_write_32(&irqc->regs->intsubmsk,
+	    pio_read_32(&irqc->regs->intsubmsk) &
+	    ~S3C24XX_SUBINT_BIT(subsrc));
+}
+
+/** Disable interrupts from the specified sub-source. */
+void s3c24xx_irqc_subsrc_disable(s3c24xx_irqc_t *irqc, unsigned subsrc)
+{
+	pio_write_32(&irqc->regs->intsubmsk,
+	    pio_read_32(&irqc->regs->intsubmsk) |
+	    S3C24XX_SUBINT_BIT(subsrc));
+}
+
+/** @}
+ */
Index: kernel/genarch/src/drivers/s3c24xx_uart/s3c24xx_uart.c
===================================================================
--- kernel/genarch/src/drivers/s3c24xx_uart/s3c24xx_uart.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
+++ kernel/genarch/src/drivers/s3c24xx_uart/s3c24xx_uart.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2009 Martin Decky
+ * 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.
+ *
+ * This UART is present on the Samsung S3C24xx CPU (on the gta02 platform).
+ */
+
+#include <genarch/drivers/s3c24xx_uart/s3c24xx_uart.h>
+#include <console/chardev.h>
+#include <console/console.h>
+#include <ddi/device.h>
+#include <arch/asm.h>
+#include <mm/slab.h>
+#include <sysinfo/sysinfo.h>
+#include <str.h>
+
+static void s3c24xx_uart_sendb(outdev_t *dev, uint8_t byte)
+{
+	s3c24xx_uart_t *uart =
+	    (s3c24xx_uart_t *) dev->data;
+
+	/* 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);
+}
+
+static void s3c24xx_uart_putchar(outdev_t *dev, wchar_t ch, bool silent)
+{
+	if (!silent) {
+		if (!ascii_check(ch)) {
+			s3c24xx_uart_sendb(dev, U_SPECIAL);
+		} else {
+    			if (ch == '\n')
+				s3c24xx_uart_sendb(dev, (uint8_t) '\r');
+			s3c24xx_uart_sendb(dev, (uint8_t) ch);
+		}
+	}
+}
+
+static irq_ownership_t s3c24xx_uart_claim(irq_t *irq)
+{
+	return IRQ_ACCEPT;
+}
+
+static void s3c24xx_uart_irq_handler(irq_t *irq)
+{
+	s3c24xx_uart_t *uart = irq->instance;
+
+	while ((pio_read_32(&uart->io->ufstat) & S3C24XX_UFSTAT_RX_COUNT) != 0) {
+		uint32_t data = pio_read_32(&uart->io->urxh);
+		pio_read_32(&uart->io->uerstat);
+		indev_push_character(uart->indev, data & 0xff);
+	}
+}
+
+static outdev_operations_t s3c24xx_uart_ops = {
+	.write = s3c24xx_uart_putchar,
+	.redraw = NULL
+};
+
+outdev_t *s3c24xx_uart_init(s3c24xx_uart_io_t *io, inr_t inr)
+{
+	outdev_t *uart_dev = malloc(sizeof(outdev_t), FRAME_ATOMIC);
+	if (!uart_dev)
+		return NULL;
+
+	s3c24xx_uart_t *uart =
+	    malloc(sizeof(s3c24xx_uart_t), FRAME_ATOMIC);
+	if (!uart) {
+		free(uart_dev);
+		return NULL;
+	}
+
+	outdev_initialize("s3c24xx_uart_dev", uart_dev, &s3c24xx_uart_ops);
+	uart_dev->data = uart;
+
+	uart->io = io;
+	uart->indev = NULL;
+
+	/* Initialize IRQ structure. */
+	irq_initialize(&uart->irq);
+	uart->irq.devno = device_assign_devno();
+	uart->irq.inr = inr;
+	uart->irq.claim = s3c24xx_uart_claim;
+	uart->irq.handler = s3c24xx_uart_irq_handler;
+	uart->irq.instance = uart;
+
+	/* 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);
+
+	if (!fb_exported) {
+		/*
+		 * This is the necessary evil until the userspace driver is entirely
+		 * self-sufficient.
+		 */
+		sysinfo_set_item_val("fb", NULL, true);
+		sysinfo_set_item_val("fb.kind", NULL, 3);
+		sysinfo_set_item_val("fb.address.physical", NULL, KA2PA(io));
+
+		fb_exported = true;
+	}
+
+	return uart_dev;
+}
+
+void s3c24xx_uart_input_wire(s3c24xx_uart_t *uart, indev_t *indev)
+{
+	ASSERT(uart);
+	ASSERT(indev);
+
+	uart->indev = indev;
+	irq_register(&uart->irq);
+}
+
+/** @}
+ */
Index: kernel/generic/include/macros.h
===================================================================
--- kernel/generic/include/macros.h	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/generic/include/macros.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -47,12 +47,30 @@
  * @param s2  Start address of the second interval.
  * @param sz2 Size of the second interval.
+ *
  */
-NO_TRACE static inline int overlaps(uintptr_t s1, size_t sz1, uintptr_t s2,
-    size_t sz2)
+NO_TRACE static inline int overlaps(uint64_t s1, uint64_t sz1, uint64_t s2,
+    uint64_t sz2)
 {
-	uintptr_t e1 = s1 + sz1;
-	uintptr_t e2 = s2 + sz2;
+	uint64_t e1 = s1 + sz1;
+	uint64_t e2 = s2 + sz2;
 	
 	return ((s1 < e2) && (s2 < e1));
+}
+
+/** Return true if the second interval is within the first interval.
+ *
+ * @param s1  Start address of the first interval.
+ * @param sz1 Size of the first interval.
+ * @param s2  Start address of the second interval.
+ * @param sz2 Size of the second interval.
+ *
+ */
+NO_TRACE static inline int iswithin(uint64_t s1, uint64_t sz1, uint64_t s2,
+    uint64_t sz2)
+{
+	uint64_t e1 = s1 + sz1;
+	uint64_t e2 = s2 + sz2;
+	
+	return ((s1 <= s2) && (e1 >= e2));
 }
 
@@ -74,5 +92,5 @@
 
 /* Compute overlapping of physical addresses */
-#define PA_overlaps(x, szx, y, szy) \
+#define PA_OVERLAPS(x, szx, y, szy) \
 	overlaps(KA2PA((x)), (szx), KA2PA((y)), (szy))
 
Index: kernel/generic/src/main/main.c
===================================================================
--- kernel/generic/src/main/main.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/generic/src/main/main.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -147,5 +147,5 @@
 	size_t i;
 	for (i = 0; i < init.cnt; i++) {
-		if (PA_overlaps(config.stack_base, config.stack_size,
+		if (PA_OVERLAPS(config.stack_base, config.stack_size,
 		    init.tasks[i].addr, init.tasks[i].size))
 			config.stack_base = ALIGN_UP(init.tasks[i].addr +
@@ -155,5 +155,5 @@
 	/* Avoid placing stack on top of boot allocations. */
 	if (ballocs.size) {
-		if (PA_overlaps(config.stack_base, config.stack_size,
+		if (PA_OVERLAPS(config.stack_base, config.stack_size,
 		    ballocs.base, ballocs.size))
 			config.stack_base = ALIGN_UP(ballocs.base +
Index: kernel/generic/src/mm/frame.c
===================================================================
--- kernel/generic/src/mm/frame.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ kernel/generic/src/mm/frame.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -121,5 +121,6 @@
  *
  */
-NO_TRACE static size_t zones_insert_zone(pfn_t base, size_t count)
+NO_TRACE static size_t zones_insert_zone(pfn_t base, size_t count,
+    zone_flags_t flags)
 {
 	if (zones.count + 1 == ZONES_MAX) {
@@ -131,10 +132,23 @@
 	for (i = 0; i < zones.count; i++) {
 		/* Check for overlap */
-		if (overlaps(base, count,
-		    zones.info[i].base, zones.info[i].count)) {
-			printf("Zone (%p, %p) overlaps with zone (%p, %p)!\n",
-			    PFN2ADDR(base), PFN2ADDR(base + count),
-			    PFN2ADDR(zones.info[i].base),
-			    PFN2ADDR(zones.info[i].base + zones.info[i].count));
+		if (overlaps(zones.info[i].base, zones.info[i].count,
+		    base, count)) {
+			
+			/*
+			 * If the overlaping zones are of the same type
+			 * and the new zone is completely within the previous
+			 * one, then quietly ignore the new zone.
+			 *
+			 */
+			
+			if ((zones.info[i].flags != flags) ||
+			    (!iswithin(zones.info[i].base, zones.info[i].count,
+			    base, count))) {
+				printf("Zone (%p, %p) overlaps with previous zone (%p, %p)!\n",
+				    PFN2ADDR(base), PFN2ADDR(count),
+				    PFN2ADDR(zones.info[i].base),
+				    PFN2ADDR(zones.info[i].count));
+			}
+			
 			return (size_t) -1;
 		}
@@ -147,6 +161,7 @@
 	for (j = zones.count; j > i; j--) {
 		zones.info[j] = zones.info[j - 1];
-		zones.info[j].buddy_system->data =
-		    (void *) &zones.info[j - 1];
+		if (zones.info[j].buddy_system != NULL)
+			zones.info[j].buddy_system->data =
+			    (void *) &zones.info[j];
 	}
 	
@@ -748,6 +763,7 @@
 	for (i = z2 + 1; i < zones.count; i++) {
 		zones.info[i - 1] = zones.info[i];
-		zones.info[i - 1].buddy_system->data =
-		    (void *) &zones.info[i - 1];
+		if (zones.info[i - 1].buddy_system != NULL)
+			zones.info[i - 1].buddy_system->data =
+			    (void *) &zones.info[i - 1];
 	}
 	
@@ -898,5 +914,5 @@
 		}
 		
-		size_t znum = zones_insert_zone(start, count);
+		size_t znum = zones_insert_zone(start, count, flags);
 		if (znum == (size_t) -1) {
 			irq_spinlock_unlock(&zones.lock, true);
@@ -921,5 +937,5 @@
 	
 	/* Non-available zone */
-	size_t znum = zones_insert_zone(start, count);
+	size_t znum = zones_insert_zone(start, count, flags);
 	if (znum == (size_t) -1) {
 		irq_spinlock_unlock(&zones.lock, true);
Index: tools/mkfat.py
===================================================================
--- tools/mkfat.py	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ tools/mkfat.py	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -343,12 +343,18 @@
 def usage(prname):
 	"Print usage syntax"
-	print prname + " <PATH> <IMAGE>"
+	print prname + " <EXTRA_BYTES> <PATH> <IMAGE>"
 
 def main():
-	if (len(sys.argv) < 3):
+	if (len(sys.argv) < 4):
 		usage(sys.argv[0])
 		return
 	
-	path = os.path.abspath(sys.argv[1])
+	if (not sys.argv[1].isdigit()):
+		print "<EXTRA_BYTES> must be a number"
+		return
+	
+	extra_bytes = int(sys.argv[1])
+	
+	path = os.path.abspath(sys.argv[2])
 	if (not os.path.isdir(path)):
 		print "<PATH> must be a directory"
@@ -365,9 +371,9 @@
 	
 	# Make sure the filesystem is large enought for FAT16
-	size = subtree_size(path, cluster_size, dirent_size) + reserved_clusters * cluster_size
+	size = subtree_size(path, cluster_size, dirent_size) + reserved_clusters * cluster_size + extra_bytes
 	while (size / cluster_size < fat16_clusters):
 		if (cluster_size > sector_size):
 			cluster_size /= 2
-			size = subtree_size(path, cluster_size, dirent_size) + reserved_clusters * cluster_size
+			size = subtree_size(path, cluster_size, dirent_size) + reserved_clusters * cluster_size + extra_bytes
 		else:
 			size = fat16_clusters * cluster_size + reserved_clusters * cluster_size
@@ -381,5 +387,5 @@
 	data_start = root_start + root_size
 	
-	outf = file(sys.argv[2], "w")
+	outf = file(sys.argv[3], "w")
 	
 	boot_sector = xstruct.create(BOOT_SECTOR)
Index: tools/mkuimage.py
===================================================================
--- tools/mkuimage.py	(revision 20235a356988dff15276fd66d391230fc9b32591)
+++ tools/mkuimage.py	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -0,0 +1,160 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+
+"""
+Create legacy uImage (U-Boot image)
+"""
+
+from collections import deque
+import os
+import sys
+import xstruct
+import zlib
+
+UIMAGE_HEADER = """big:
+	uint32_t magic
+	uint32_t header_crc
+	uint32_t c_tstamp
+	uint32_t data_size
+	uint32_t load_addr
+	uint32_t start_addr
+	uint32_t data_crc
+	uint8_t os
+	uint8_t arch
+	uint8_t img_type
+	uint8_t compression
+	char img_name[32]
+"""
+
+def main():
+	args = deque(sys.argv)
+	cmd_name = args.popleft()
+	base_name = os.path.basename(cmd_name)
+	image_name = 'Noname'
+	load_addr = 0
+	start_addr = 0
+
+	while len(args) >= 2 and args[0][0] == '-':
+		opt = args.popleft()[1:]
+		optarg = args.popleft()
+
+		if opt == 'name':
+			image_name = optarg
+		elif opt == 'laddr':
+			load_addr = (int)(optarg, 0)
+		elif opt == 'saddr':
+			start_addr = (int)(optarg, 0)
+		else:
+			print base_name + ": Unrecognized option."
+			print_syntax(cmd_name)
+			return
+
+	if len(args) < 2:
+		print base_name + ": Argument missing."
+		print_syntax(cmd_name)
+		return
+
+	inf_name = args[0]
+	outf_name = args[1]
+
+	try:
+		mkuimage(inf_name, outf_name, image_name, load_addr, start_addr)
+	except:
+		os.remove(outf_name)
+		raise
+
+def mkuimage(inf_name, outf_name, image_name, load_addr, start_addr):
+	inf = file(inf_name, 'rb')
+	outf = file(outf_name, 'wb')
+
+	header = xstruct.create(UIMAGE_HEADER)
+	header_size = header.size()
+
+	#
+	# Write data
+	#
+	outf.seek(header_size, os.SEEK_SET)
+	data = inf.read()
+	data_size = inf.tell()
+	data_crc = calc_crc32(data)
+	data_tstamp = (int)(os.path.getmtime(inf_name))
+	outf.write(data)
+	data = ''
+
+	#
+	# Write header
+	#
+	outf.seek(0, os.SEEK_SET)
+
+	header.magic = 0x27051956	# uImage magic
+	header.header_crc = 0
+	header.c_tstamp = data_tstamp
+	header.data_size = data_size
+	header.load_addr = load_addr	# Address where to load image
+	header.start_addr = start_addr	# Address of entry point
+	header.data_crc = data_crc
+	header.os = 5			# Linux
+	header.arch = 2			# ARM
+	header.img_type = 2		# Kernel
+	header.compression = 0		# None
+	header.img_name = image_name
+
+	header_crc = calc_crc32(header.pack())
+	header.header_crc = header_crc
+
+	outf.write(header.pack())
+	outf.close()
+
+## Compute CRC32 of binary string.
+#
+# Works around bug in zlib.crc32() which returns signed int32 result
+# in Python < 3.0.
+#
+def calc_crc32(byteseq):
+	signed_crc = zlib.crc32(byteseq, 0)
+	if signed_crc < 0:
+		return (long(signed_crc) + 4294967296L) # 2^32L
+	else:
+		return signed_crc
+
+## Print command-line syntax.
+#
+def print_syntax(cmd):
+	print "syntax: " + cmd + " [<options>] <raw_image> <uImage>"
+	print
+	print "\traw_image\tInput image name (raw binary data)"
+	print "\tuImage\t\tOutput uImage name (U-Boot image)"
+	print
+	print "options:"
+	print "\t-name <name>\tImage name (default: 'Noname')"
+	print "\t-laddr <name>\tLoad address (default: 0x00000000)"
+	print "\t-saddr <name>\tStart address (default: 0x00000000)"
+
+if __name__ == '__main__':
+	main()
Index: tools/pack.py
===================================================================
--- tools/pack.py	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ tools/pack.py	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -191,5 +191,5 @@
 	link_in.close()
 	
-	link_out = file(os.path.join(arch_path, LINK), "w")
+	link_out = file(os.path.join(arch_path, "%s.comp" % LINK), "w")
 	link_out.write(template.replace("[[COMPONENTS]]", "\n".join(link_ctx)))
 	link_out.close()
Index: uspace/Makefile
===================================================================
--- uspace/Makefile	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/Makefile	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -66,7 +66,9 @@
 	srv/hid/adb_mouse \
 	srv/hid/char_mouse \
+	srv/hid/s3c24xx_ts \
 	srv/hid/fb \
 	srv/hid/kbd \
 	srv/hw/char/i8042 \
+	srv/hw/char/s3c24xx_uart \
 	srv/hw/netif/dp8390 \
 	srv/net/cfg \
Index: uspace/Makefile.common
===================================================================
--- uspace/Makefile.common	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/Makefile.common	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -172,5 +172,9 @@
 ifneq ($(BINARY),)
 %.disasm: $(BINARY)
+ifeq ($(CONFIG_LINE_DEBUG),y)
+	$(OBJDUMP) -d -S $< > $@
+else
 	$(OBJDUMP) -d $< > $@
+endif
 
 $(BINARY): $(LINKER_SCRIPT) $(OBJECTS) $(LIBS) $(BASE_LIBS)
Index: uspace/app/init/init.c
===================================================================
--- uspace/app/init/init.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/app/init/init.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -274,6 +274,8 @@
 	srv_start("/srv/cuda_adb");
 	srv_start("/srv/i8042");
+	srv_start("/srv/s3c24ser");
 	srv_start("/srv/adb_ms");
 	srv_start("/srv/char_ms");
+	srv_start("/srv/s3c24ts");
 	
 	spawn("/srv/fb");
Index: uspace/app/klog/klog.c
===================================================================
--- uspace/app/klog/klog.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/app/klog/klog.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -43,11 +43,15 @@
 #include <event.h>
 #include <errno.h>
+#include <str_error.h>
 #include <io/klog.h>
 
-#define NAME  "klog"
+#define NAME       "klog"
+#define LOG_FNAME  "/log/klog"
 
 /* Pointer to klog area */
 static wchar_t *klog;
 static size_t klog_length;
+
+static FILE *log;
 
 static void interrupt_received(ipc_callid_t callid, ipc_call_t *call)
@@ -58,6 +62,17 @@
 	size_t i;
 	
-	for (i = klog_len - klog_stored; i < klog_len; i++)
-		putchar(klog[(klog_start + i) % klog_length]);
+	for (i = klog_len - klog_stored; i < klog_len; i++) {
+		wchar_t ch = klog[(klog_start + i) % klog_length];
+		
+		putchar(ch);
+		
+		if (log != NULL)
+			fputc(ch, log);
+	}
+	
+	if (log != NULL) {
+		fflush(log);
+		fsync(fileno(log));
+	}
 }
 
@@ -91,4 +106,14 @@
 	}
 	
+	/*
+	 * Mode "a" would be definitively much better here, but it is
+	 * not well supported by the FAT driver.
+	 *
+	 */
+	log = fopen(LOG_FNAME, "w");
+	if (log == NULL)
+		printf("%s: Unable to create log file %s (%s)\n", NAME, LOG_FNAME,
+		    str_error(errno));
+	
 	async_set_interrupt_received(interrupt_received);
 	klog_update();
Index: uspace/lib/c/Makefile
===================================================================
--- uspace/lib/c/Makefile	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/lib/c/Makefile	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -84,4 +84,5 @@
 	generic/ipc.c \
 	generic/async.c \
+	generic/async_rel.c \
 	generic/loader.c \
 	generic/getopt.c \
Index: uspace/lib/c/generic/async_rel.c
===================================================================
--- uspace/lib/c/generic/async_rel.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
+++ uspace/lib/c/generic/async_rel.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 2010 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.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+/**
+ * This file implements simple relation support for the async framework.
+ *
+ * By the term "relation", we mean a logical data path between a client and a
+ * server over which the client can send multiple, potentially blocking,
+ * requests to the server.
+ *
+ * Clients and servers are naturally connected using IPC phones, thus an IPC
+ * phone represents a connection between a client and a server. In one
+ * connection, there can be many relations.
+ *
+ * Relations are useful in situations in which there is only one IPC connection
+ * between the client and the server, but the client wants to be able to make
+ * multiple parallel requests. Using only a single phone and without any other
+ * provisions, all requests would have to be serialized. On the other hand, the
+ * client can make as many parallel requests as there are active relations.
+ *
+ * There are several possible implementations of relations. This implementation
+ * uses additional phones to represent relations. Using phones both for the
+ * primary connection and also for its relations has several advantages:
+ *
+ * - to make a series of requests over a relation, the client can continue to
+ *   use the existing async framework APIs
+ * - the server supports relations by the virtue of spawning a new connection
+ *   fibril, just as it does for every new connection even without relations
+ * - the implementation is pretty straightforward; a very naive implementation
+ *   would be to make each request using a fresh phone (that is what we have
+ *   done in the past); a slightly better approach would be to cache connected
+ *   phones so that they can be reused by a later relation within the same
+ *   connection (that is what this implementation does)
+ *
+ * The main disadvantages of using phones to represent relations are:
+ *
+ * - if there are too many relations (even cached ones), the task may hit its
+ *   limit on the maximum number of connected phones, which could prevent the
+ *   task from making new IPC connections to other tasks
+ * - if there are too many IPC connections already, it may be impossible to
+ *   create a relation by connecting a new phone thanks to the task's limit on
+ *   the maximum number of connected phones
+ *
+ * These problems can be helped by increasing the limit on the maximum number of
+ * connected phones to some reasonable value and by limiting the number of
+ * phones cached to some fraction of this limit.
+ *
+ * The cache itself has a mechanism to close some number of unused phones if a
+ * new phone cannot be connected, but the outter world currently does not have a
+ * way to ask the phone cache to shrink.
+ *
+ * To minimize the confusion stemming from the fact that we use phones for two
+ * things (the primary IPC connection and also each relation), this file makes
+ * the distinction by using the term 'key phone' for the former and 'relation
+ * phone' for the latter. Under the hood, all phones remain equal, of course.
+ *
+ * There is a small inefficiency in that the cache repeatedly allocates and
+ * deallocated the rel_node_t structures when in fact it could keep the
+ * allocated structures around and reuse them later. But such a solution would
+ * be effectively implementing a poor man's slab allocator while it would be
+ * better to have the slab allocator ported to uspace so that everyone could
+ * benefit from it.
+ */
+
+#include <async_rel.h>
+#include <ipc/ipc.h>
+#include <fibril_synch.h>
+#include <adt/list.h>
+#include <adt/hash_table.h>
+#include <malloc.h>
+#include <errno.h>
+#include <assert.h>
+
+#define KEY_NODE_HASH_BUCKETS	16
+
+typedef struct {
+	link_t link;		/**< Key node hash table link. */
+	int key_phone;		/**< The phone serving as a key. */
+	link_t rel_head;	/**< List of open relation phones. */
+} key_node_t;
+
+typedef struct {
+	link_t rel_link;	/**< Link for the list of relation phones. */
+	link_t global_link;	/**< Link for the global list of phones. */
+	int rel_phone;		/**< Connected relation phone. */
+} rel_node_t;
+
+/**
+ * Mutex protecting the global_rel_head list and the key_node_hash hash table.
+ */
+static fibril_mutex_t async_rel_mutex;
+
+/**
+ * List of all currently unused relation phones.
+ */
+static LIST_INITIALIZE(global_rel_head);
+
+/**
+ * Hash table containing lists of available relation phones for all key
+ * phones.
+ */
+static hash_table_t key_node_hash;
+
+static hash_index_t kn_hash(unsigned long *key)
+{
+	return *key % KEY_NODE_HASH_BUCKETS;
+}
+
+static int kn_compare(unsigned long *key, hash_count_t keys, link_t *item)
+{
+	key_node_t *knp = hash_table_get_instance(item, key_node_t, link);
+
+	return *key == (unsigned long) knp->key_phone;
+}
+
+static void kn_remove_callback(link_t *item)
+{
+}
+
+static hash_table_operations_t key_node_hash_ops = {
+	.hash = kn_hash,
+	.compare = kn_compare,
+	.remove_callback = kn_remove_callback
+};
+
+/** Initialize the async_rel subsystem.
+ *
+ * Needs to be called prior to any other interface in this file.
+ */
+int async_rel_init(void)
+{
+	fibril_mutex_initialize(&async_rel_mutex);
+	list_initialize(&global_rel_head);
+	return hash_table_create(&key_node_hash, KEY_NODE_HASH_BUCKETS, 1,
+	    &key_node_hash_ops);
+}
+
+static void key_node_initialize(key_node_t *knp)
+{
+	link_initialize(&knp->link);
+	knp->key_phone = -1;
+	list_initialize(&knp->rel_head);
+}
+
+static void rel_node_initialize(rel_node_t *rnp)
+{
+	link_initialize(&rnp->rel_link);
+	link_initialize(&rnp->global_link);
+	rnp->rel_phone = -1;
+}
+
+/** Create a new relation for a connection represented by a key phone.
+ *
+ * @param key_phone	Phone representing the connection.
+ * @return		Phone representing the new relation or a negative error
+ *			code.
+ */
+int async_relation_create(int key_phone)
+{
+	unsigned long key = (unsigned long) key_phone;
+	link_t *lnk;
+	key_node_t *knp;
+	rel_node_t *rnp;
+	int rel_phone;
+
+	fibril_mutex_lock(&async_rel_mutex);
+	lnk = hash_table_find(&key_node_hash, &key);
+	if (!lnk) {
+		/*
+		 * The key node was not found in the hash table. Try to allocate
+		 * and hash in a new one.
+		 */
+		knp = (key_node_t *) malloc(sizeof(key_node_t));
+		if (!knp) {
+			/*
+			 * As a possible improvement, we could make a one-time
+			 * attempt to create a phone without trying to add the
+			 * key node into the hash.
+			 */
+			fibril_mutex_unlock(&async_rel_mutex);
+			return ENOMEM;
+		}
+		key_node_initialize(knp);
+		knp->key_phone = key_phone;
+		hash_table_insert(&key_node_hash, &key, &knp->link);
+	} else {
+		/*
+		 * Found the key node.
+		 */
+		knp = hash_table_get_instance(lnk, key_node_t, link);
+	}
+
+	if (!list_empty(&knp->rel_head)) {
+		/*
+		 * There are available relation phones for the key phone.
+		 */
+		rnp = list_get_instance(knp->rel_head.next, rel_node_t,
+		    rel_link);
+		list_remove(&rnp->rel_link);
+		list_remove(&rnp->global_link);
+		
+		rel_phone = rnp->rel_phone;
+		free(rnp);
+	} else {
+		/*
+		 * There are no available relation phones for the key phone.
+		 * Make a one-time attempt to connect a new relation phone.
+		 */
+retry:
+		rel_phone = ipc_connect_me_to(key_phone, 0, 0, 0);
+		if (rel_phone >= 0) {
+			/* success, do nothing */
+		} else if (!list_empty(&global_rel_head)) {
+			/*
+			 * We did not manage to connect a new phone. But we can
+			 * try to hangup some currently unused phones and try
+			 * again.
+			 */
+			rnp = list_get_instance(global_rel_head.next,
+			    rel_node_t, global_link);
+			list_remove(&rnp->global_link);
+			list_remove(&rnp->rel_link);
+			rel_phone = rnp->rel_phone;
+			free(rnp);
+			ipc_hangup(rel_phone);
+			goto retry;
+		} else {
+			/*
+			 * This is unfortunate. We failed both to find a cached
+			 * phone or to create a new one even after cleaning up
+			 * the cache. This is most likely due to too many key
+			 * phones being kept connected.
+			 */
+			rel_phone = ELIMIT;
+		}
+	}
+
+	fibril_mutex_unlock(&async_rel_mutex);
+	return rel_phone;
+}
+
+/** Destroy a relation.
+ *
+ * @param key_phone	Phone representing the connection.
+ * @param rel_phone	Phone representing the relation within the connection.
+ */
+void async_relation_destroy(int key_phone, int rel_phone)
+{
+	unsigned long key = (unsigned long) key_phone;
+	key_node_t *knp;
+	rel_node_t *rnp;
+	link_t *lnk;
+
+	fibril_mutex_lock(&async_rel_mutex);
+	lnk = hash_table_find(&key_node_hash, &key);
+	assert(lnk);
+	knp = hash_table_get_instance(lnk, key_node_t, link);
+	rnp = (rel_node_t *) malloc(sizeof(rel_node_t));
+	if (!rnp) {
+		/*
+		 * Being unable to remember the connected relation phone here
+		 * means that we simply hangup.
+		 */
+		fibril_mutex_unlock(&async_rel_mutex);
+		ipc_hangup(rel_phone);
+		return;
+	}
+	rel_node_initialize(rnp);
+	rnp->rel_phone = rel_phone;
+	list_append(&rnp->rel_link, &knp->rel_head);
+	list_append(&rnp->global_link, &global_rel_head);
+	fibril_mutex_unlock(&async_rel_mutex);
+}
+
+/** @}
+ */
Index: uspace/lib/c/generic/io/io.c
===================================================================
--- uspace/lib/c/generic/io/io.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/lib/c/generic/io/io.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -757,4 +757,14 @@
 }
 
+int fileno(FILE *stream)
+{
+	if (stream->klog) {
+		errno = EBADF;
+		return -1;
+	}
+	
+	return stream->fd;
+}
+
 int fphone(FILE *stream)
 {
Index: uspace/lib/c/generic/libc.c
===================================================================
--- uspace/lib/c/generic/libc.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/lib/c/generic/libc.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -50,4 +50,5 @@
 #include <ipc/ipc.h>
 #include <async.h>
+#include <async_rel.h>
 #include <as.h>
 #include <loader/pcb.h>
@@ -65,4 +66,5 @@
 	__heap_init();
 	__async_init();
+	(void) async_rel_init();
 	fibril_t *fibril = fibril_setup();
 	__tcb_set(fibril->tcb);
Index: uspace/lib/c/include/async_rel.h
===================================================================
--- uspace/lib/c/include/async_rel.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
+++ uspace/lib/c/include/async_rel.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2010 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.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef LIBC_ASYNC_REL_H_
+#define LIBC_ASYNC_REL_H_
+
+extern int async_rel_init(void);
+extern int async_relation_create(int);
+extern void async_relation_destroy(int, int);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/c/include/stdio.h
===================================================================
--- uspace/lib/c/include/stdio.h	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/lib/c/include/stdio.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -171,4 +171,5 @@
 extern off64_t ftell(FILE *);
 extern int feof(FILE *);
+extern int fileno(FILE *);
 
 extern int fflush(FILE *);
Index: uspace/srv/fs/fat/fat.h
===================================================================
--- uspace/srv/fs/fat/fat.h	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/srv/fs/fat/fat.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -48,4 +48,17 @@
 
 #define min(a, b)		((a) < (b) ? (a) : (b))
+
+/*
+ * Convenience macros for accessing some frequently used boot sector members.
+ */
+#define BPS(bs)		uint16_t_le2host((bs)->bps)
+#define SPC(bs)		(bs)->spc
+#define RSCNT(bs)	uint16_t_le2host((bs)->rscnt)
+#define FATCNT(bs)	(bs)->fatcnt
+#define SF(bs)		uint16_t_le2host((bs)->sec_per_fat)
+#define RDE(bs)		uint16_t_le2host((bs)->root_ent_max)
+#define TS(bs)		(uint16_t_le2host((bs)->totsec16) != 0 ? \
+			uint16_t_le2host((bs)->totsec16) : \
+			uint32_t_le2host(bs->totsec32))
 
 #define BS_BLOCK		0
@@ -198,4 +211,16 @@
 	unsigned		refcnt;
 	bool			dirty;
+
+	/*
+	 * Cache of the node's last and "current" cluster to avoid some
+	 * unnecessary FAT walks.
+	 */
+	/* Node's last cluster in FAT. */
+	bool		lastc_cached_valid;
+	fat_cluster_t	lastc_cached_value;
+	/* Node's "current" cluster, i.e. where the last I/O took place. */
+	bool		currc_cached_valid;
+	aoff64_t	currc_cached_bn;
+	fat_cluster_t	currc_cached_value;
 } fat_node_t;
 
Index: uspace/srv/fs/fat/fat_fat.c
===================================================================
--- uspace/srv/fs/fat/fat_fat.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/srv/fs/fat/fat_fat.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -49,4 +49,15 @@
 #include <mem.h>
 
+/*
+ * Convenience macros for computing some frequently used values from the
+ * primitive boot sector members.
+ */
+#define RDS(bs)		((sizeof(fat_dentry_t) * RDE((bs))) / BPS((bs))) + \
+			(((sizeof(fat_dentry_t) * RDE((bs))) % BPS((bs))) != 0)
+#define SSA(bs)		(RSCNT((bs)) + FATCNT((bs)) * SF((bs)) + RDS(bs))
+
+#define CLBN2PBN(bs, cl, bn) \
+	(SSA((bs)) + ((cl) - FAT_CLST_FIRST) * SPC((bs)) + (bn) % SPC((bs)))
+
 /**
  * The fat_alloc_lock mutex protects all copies of the File Allocation Table
@@ -74,12 +85,7 @@
 {
 	block_t *b;
-	unsigned bps;
-	unsigned rscnt;		/* block address of the first FAT */
 	uint16_t clusters = 0;
 	fat_cluster_t clst = firstc;
 	int rc;
-
-	bps = uint16_t_le2host(bs->bps);
-	rscnt = uint16_t_le2host(bs->rscnt);
 
 	if (firstc == FAT_CLST_RES0) {
@@ -99,8 +105,9 @@
 		if (lastc)
 			*lastc = clst;	/* remember the last cluster number */
-		fsec = (clst * sizeof(fat_cluster_t)) / bps;
-		fidx = clst % (bps / sizeof(fat_cluster_t));
+		fsec = (clst * sizeof(fat_cluster_t)) / BPS(bs);
+		fidx = clst % (BPS(bs) / sizeof(fat_cluster_t));
 		/* read FAT1 */
-		rc = block_get(&b, dev_handle, rscnt + fsec, BLOCK_FLAGS_NONE);
+		rc = block_get(&b, dev_handle, RSCNT(bs) + fsec,
+		    BLOCK_FLAGS_NONE);
 		if (rc != EOK)
 			return rc;
@@ -125,7 +132,5 @@
  * @param block		Pointer to a block pointer for storing result.
  * @param bs		Buffer holding the boot sector of the file system.
- * @param dev_handle	Device handle of the file system.
- * @param firstc	First cluster used by the file. Can be zero if the file
- *			is empty.
+ * @param nodep		FAT node.
  * @param bn		Block number.
  * @param flags		Flags passed to libblock.
@@ -134,16 +139,75 @@
  */
 int
+fat_block_get(block_t **block, struct fat_bs *bs, fat_node_t *nodep,
+    aoff64_t bn, int flags)
+{
+	fat_cluster_t firstc = nodep->firstc;
+	fat_cluster_t currc;
+	aoff64_t relbn = bn;
+	int rc;
+
+	if (!nodep->size)
+		return ELIMIT;
+
+	if (nodep->firstc == FAT_CLST_ROOT) 
+		goto fall_through;
+
+	if (((((nodep->size - 1) / BPS(bs)) / SPC(bs)) == bn / SPC(bs)) &&
+	    nodep->lastc_cached_valid) {
+	    	/*
+		 * This is a request to read a block within the last cluster
+		 * when fortunately we have the last cluster number cached.
+		 */
+		return block_get(block, nodep->idx->dev_handle,
+		    CLBN2PBN(bs, nodep->lastc_cached_value, bn), flags);
+	}
+
+	if (nodep->currc_cached_valid && bn >= nodep->currc_cached_bn) {
+		/*
+		 * We can start with the cluster cached by the previous call to
+		 * fat_block_get().
+		 */
+		firstc = nodep->currc_cached_value;
+		relbn -= (nodep->currc_cached_bn / SPC(bs)) * SPC(bs);
+	}
+
+fall_through:
+	rc = _fat_block_get(block, bs, nodep->idx->dev_handle, firstc,
+	    &currc, relbn, flags);
+	if (rc != EOK)
+		return rc;
+	
+	/*
+	 * Update the "current" cluster cache.
+	 */
+	nodep->currc_cached_valid = true;
+	nodep->currc_cached_bn = bn;
+	nodep->currc_cached_value = currc;
+
+	return rc;
+}
+
+/** Read block from file located on a FAT file system.
+ *
+ * @param block		Pointer to a block pointer for storing result.
+ * @param bs		Buffer holding the boot sector of the file system.
+ * @param dev_handle	Device handle of the file system.
+ * @param fcl		First cluster used by the file. Can be zero if the file
+ *			is empty.
+ * @param clp		If not NULL, address where the cluster containing bn
+ *			will be stored.
+ *			stored 
+ * @param bn		Block number.
+ * @param flags		Flags passed to libblock.
+ *
+ * @return		EOK on success or a negative error code.
+ */
+int
 _fat_block_get(block_t **block, fat_bs_t *bs, dev_handle_t dev_handle,
-    fat_cluster_t firstc, aoff64_t bn, int flags)
-{
-	unsigned bps;
-	unsigned rscnt;		/* block address of the first FAT */
-	unsigned rde;
-	unsigned rds;		/* root directory size */
-	unsigned sf;
-	unsigned ssa;		/* size of the system area */
+    fat_cluster_t fcl, fat_cluster_t *clp, aoff64_t bn, int flags)
+{
 	uint16_t clusters;
 	unsigned max_clusters;
-	fat_cluster_t lastc;
+	fat_cluster_t c;
 	int rc;
 
@@ -151,33 +215,25 @@
 	 * This function can only operate on non-zero length files.
 	 */
-	if (firstc == FAT_CLST_RES0)
+	if (fcl == FAT_CLST_RES0)
 		return ELIMIT;
 
-	bps = uint16_t_le2host(bs->bps);
-	rscnt = uint16_t_le2host(bs->rscnt);
-	rde = uint16_t_le2host(bs->root_ent_max);
-	sf = uint16_t_le2host(bs->sec_per_fat);
-
-	rds = (sizeof(fat_dentry_t) * rde) / bps;
-	rds += ((sizeof(fat_dentry_t) * rde) % bps != 0);
-	ssa = rscnt + bs->fatcnt * sf + rds;
-
-	if (firstc == FAT_CLST_ROOT) {
+	if (fcl == FAT_CLST_ROOT) {
 		/* root directory special case */
-		assert(bn < rds);
-		rc = block_get(block, dev_handle, rscnt + bs->fatcnt * sf + bn,
-		    flags);
+		assert(bn < RDS(bs));
+		rc = block_get(block, dev_handle,
+		    RSCNT(bs) + FATCNT(bs) * SF(bs) + bn, flags);
 		return rc;
 	}
 
-	max_clusters = bn / bs->spc;
-	rc = fat_cluster_walk(bs, dev_handle, firstc, &lastc, &clusters,
-	    max_clusters);
+	max_clusters = bn / SPC(bs);
+	rc = fat_cluster_walk(bs, dev_handle, fcl, &c, &clusters, max_clusters);
 	if (rc != EOK)
 		return rc;
 	assert(clusters == max_clusters);
 
-	rc = block_get(block, dev_handle,
-	    ssa + (lastc - FAT_CLST_FIRST) * bs->spc + bn % bs->spc, flags);
+	rc = block_get(block, dev_handle, CLBN2PBN(bs, c, bn), flags);
+
+	if (clp)
+		*clp = c;
 
 	return rc;
@@ -198,24 +254,19 @@
 int fat_fill_gap(fat_bs_t *bs, fat_node_t *nodep, fat_cluster_t mcl, aoff64_t pos)
 {
-	uint16_t bps;
-	unsigned spc;
 	block_t *b;
 	aoff64_t o, boundary;
 	int rc;
 
-	bps = uint16_t_le2host(bs->bps);
-	spc = bs->spc;
-	
-	boundary = ROUND_UP(nodep->size, bps * spc);
+	boundary = ROUND_UP(nodep->size, BPS(bs) * SPC(bs));
 
 	/* zero out already allocated space */
 	for (o = nodep->size; o < pos && o < boundary;
-	    o = ALIGN_DOWN(o + bps, bps)) {
-	    	int flags = (o % bps == 0) ?
+	    o = ALIGN_DOWN(o + BPS(bs), BPS(bs))) {
+	    	int flags = (o % BPS(bs) == 0) ?
 		    BLOCK_FLAGS_NOREAD : BLOCK_FLAGS_NONE;
-		rc = fat_block_get(&b, bs, nodep, o / bps, flags);
-		if (rc != EOK)
-			return rc;
-		memset(b->data + o % bps, 0, bps - o % bps);
+		rc = fat_block_get(&b, bs, nodep, o / BPS(bs), flags);
+		if (rc != EOK)
+			return rc;
+		memset(b->data + o % BPS(bs), 0, BPS(bs) - o % BPS(bs));
 		b->dirty = true;		/* need to sync node */
 		rc = block_put(b);
@@ -228,10 +279,10 @@
 	
 	/* zero out the initial part of the new cluster chain */
-	for (o = boundary; o < pos; o += bps) {
+	for (o = boundary; o < pos; o += BPS(bs)) {
 		rc = _fat_block_get(&b, bs, nodep->idx->dev_handle, mcl,
-		    (o - boundary) / bps, BLOCK_FLAGS_NOREAD);
-		if (rc != EOK)
-			return rc;
-		memset(b->data, 0, min(bps, pos - o));
+		    NULL, (o - boundary) / BPS(bs), BLOCK_FLAGS_NOREAD);
+		if (rc != EOK)
+			return rc;
+		memset(b->data, 0, min(BPS(bs), pos - o));
 		b->dirty = true;		/* need to sync node */
 		rc = block_put(b);
@@ -257,19 +308,13 @@
 {
 	block_t *b;
-	uint16_t bps;
-	uint16_t rscnt;
-	uint16_t sf;
 	fat_cluster_t *cp;
 	int rc;
 
-	bps = uint16_t_le2host(bs->bps);
-	rscnt = uint16_t_le2host(bs->rscnt);
-	sf = uint16_t_le2host(bs->sec_per_fat);
-
-	rc = block_get(&b, dev_handle, rscnt + sf * fatno +
-	    (clst * sizeof(fat_cluster_t)) / bps, BLOCK_FLAGS_NONE);
+	rc = block_get(&b, dev_handle, RSCNT(bs) + SF(bs) * fatno +
+	    (clst * sizeof(fat_cluster_t)) / BPS(bs), BLOCK_FLAGS_NONE);
 	if (rc != EOK)
 		return rc;
-	cp = (fat_cluster_t *)b->data + clst % (bps / sizeof(fat_cluster_t));
+	cp = (fat_cluster_t *)b->data +
+	    clst % (BPS(bs) / sizeof(fat_cluster_t));
 	*value = uint16_t_le2host(*cp);
 	rc = block_put(b);
@@ -293,20 +338,14 @@
 {
 	block_t *b;
-	uint16_t bps;
-	uint16_t rscnt;
-	uint16_t sf;
 	fat_cluster_t *cp;
 	int rc;
 
-	bps = uint16_t_le2host(bs->bps);
-	rscnt = uint16_t_le2host(bs->rscnt);
-	sf = uint16_t_le2host(bs->sec_per_fat);
-
-	assert(fatno < bs->fatcnt);
-	rc = block_get(&b, dev_handle, rscnt + sf * fatno +
-	    (clst * sizeof(fat_cluster_t)) / bps, BLOCK_FLAGS_NONE);
+	assert(fatno < FATCNT(bs));
+	rc = block_get(&b, dev_handle, RSCNT(bs) + SF(bs) * fatno +
+	    (clst * sizeof(fat_cluster_t)) / BPS(bs), BLOCK_FLAGS_NONE);
 	if (rc != EOK)
 		return rc;
-	cp = (fat_cluster_t *)b->data + clst % (bps / sizeof(fat_cluster_t));
+	cp = (fat_cluster_t *)b->data +
+	    clst % (BPS(bs) / sizeof(fat_cluster_t));
 	*cp = host2uint16_t_le(value);
 	b->dirty = true;		/* need to sync block */
@@ -364,11 +403,4 @@
     fat_cluster_t *mcl, fat_cluster_t *lcl)
 {
-	uint16_t bps;
-	uint16_t rscnt;
-	uint16_t sf;
-	uint32_t ts;
-	unsigned rde;
-	unsigned rds;
-	unsigned ssa;
 	block_t *blk;
 	fat_cluster_t *lifo;	/* stack for storing free cluster numbers */ 
@@ -380,16 +412,4 @@
 	if (!lifo)
 		return ENOMEM;
-	
-	bps = uint16_t_le2host(bs->bps);
-	rscnt = uint16_t_le2host(bs->rscnt);
-	sf = uint16_t_le2host(bs->sec_per_fat);
-	rde = uint16_t_le2host(bs->root_ent_max);
-	ts = (uint32_t) uint16_t_le2host(bs->totsec16);
-	if (ts == 0)
-		ts = uint32_t_le2host(bs->totsec32);
-
-	rds = (sizeof(fat_dentry_t) * rde) / bps;
-	rds += ((sizeof(fat_dentry_t) * rde) % bps != 0);
-	ssa = rscnt + bs->fatcnt * sf + rds;
 	
 	/*
@@ -397,16 +417,20 @@
 	 */
 	fibril_mutex_lock(&fat_alloc_lock);
-	for (b = 0, cl = 0; b < sf; b++) {
-		rc = block_get(&blk, dev_handle, rscnt + b, BLOCK_FLAGS_NONE);
+	for (b = 0, cl = 0; b < SF(bs); b++) {
+		rc = block_get(&blk, dev_handle, RSCNT(bs) + b,
+		    BLOCK_FLAGS_NONE);
 		if (rc != EOK)
 			goto error;
-		for (c = 0; c < bps / sizeof(fat_cluster_t); c++, cl++) {
+		for (c = 0; c < BPS(bs) / sizeof(fat_cluster_t); c++, cl++) {
 			/*
-			 * Check if the cluster is physically there. This check
-			 * becomes necessary when the file system is created
-			 * with fewer total sectors than how many is inferred
-			 * from the size of the file allocation table.
+			 * Check if the entire cluster is physically there.
+			 * This check becomes necessary when the file system is
+			 * created with fewer total sectors than how many is
+			 * inferred from the size of the file allocation table
+			 * or when the last cluster ends beyond the end of the
+			 * device.
 			 */
-			if ((cl >= 2) && ((cl - 2) * bs->spc + ssa >= ts)) {
+			if ((cl >= FAT_CLST_FIRST) &&
+			    CLBN2PBN(bs, cl, SPC(bs) - 1) >= TS(bs)) {
 				rc = block_put(blk);
 				if (rc != EOK)
@@ -511,33 +535,44 @@
  * @param nodep		Node representing the file.
  * @param mcl		First cluster of the cluster chain to append.
+ * @param lcl		Last cluster of the cluster chain to append.
  *
  * @return		EOK on success or a negative error code.
  */
-int fat_append_clusters(fat_bs_t *bs, fat_node_t *nodep, fat_cluster_t mcl)
+int
+fat_append_clusters(fat_bs_t *bs, fat_node_t *nodep, fat_cluster_t mcl,
+    fat_cluster_t lcl)
 {
 	dev_handle_t dev_handle = nodep->idx->dev_handle;
-	fat_cluster_t lcl;
+	fat_cluster_t lastc;
 	uint16_t numc;
 	uint8_t fatno;
 	int rc;
 
-	rc = fat_cluster_walk(bs, dev_handle, nodep->firstc, &lcl, &numc,
-	    (uint16_t) -1);
-	if (rc != EOK)
-		return rc;
-
-	if (numc == 0) {
-		/* No clusters allocated to the node yet. */
-		nodep->firstc = mcl;
-		nodep->dirty = true;		/* need to sync node */
-		return EOK;
+	if (nodep->lastc_cached_valid) {
+		lastc = nodep->lastc_cached_value;
+		nodep->lastc_cached_valid = false;
+	} else {
+		rc = fat_cluster_walk(bs, dev_handle, nodep->firstc, &lastc,
+		    &numc, (uint16_t) -1);
+		if (rc != EOK)
+			return rc;
+
+		if (numc == 0) {
+			/* No clusters allocated to the node yet. */
+			nodep->firstc = mcl;
+			nodep->dirty = true;	/* need to sync node */
+			return EOK;
+		}
 	}
 
 	for (fatno = FAT1; fatno < bs->fatcnt; fatno++) {
-		rc = fat_set_cluster(bs, nodep->idx->dev_handle, fatno, lcl,
+		rc = fat_set_cluster(bs, nodep->idx->dev_handle, fatno, lastc,
 		    mcl);
 		if (rc != EOK)
 			return rc;
 	}
+
+	nodep->lastc_cached_valid = true;
+	nodep->lastc_cached_value = lcl;
 
 	return EOK;
@@ -548,5 +583,5 @@
  * @param bs		Buffer holding the boot sector of the file system.
  * @param nodep		FAT node where the chopping will take place.
- * @param lastc		Last cluster which will remain in the node. If this
+ * @param lcl		Last cluster which will remain in the node. If this
  *			argument is FAT_CLST_RES0, then all clusters will
  *			be chopped off.
@@ -554,10 +589,17 @@
  * @return		EOK on success or a negative return code.
  */
-int fat_chop_clusters(fat_bs_t *bs, fat_node_t *nodep, fat_cluster_t lastc)
-{
-	int rc;
-
+int fat_chop_clusters(fat_bs_t *bs, fat_node_t *nodep, fat_cluster_t lcl)
+{
+	int rc;
 	dev_handle_t dev_handle = nodep->idx->dev_handle;
-	if (lastc == FAT_CLST_RES0) {
+
+	/*
+	 * Invalidate cached cluster numbers.
+	 */
+	nodep->lastc_cached_valid = false;
+	if (nodep->currc_cached_value != lcl)
+		nodep->currc_cached_valid = false;
+
+	if (lcl == FAT_CLST_RES0) {
 		/* The node will have zero size and no clusters allocated. */
 		rc = fat_free_clusters(bs, dev_handle, nodep->firstc);
@@ -570,5 +612,5 @@
 		unsigned fatno;
 
-		rc = fat_get_cluster(bs, dev_handle, FAT1, lastc, &nextc);
+		rc = fat_get_cluster(bs, dev_handle, FAT1, lcl, &nextc);
 		if (rc != EOK)
 			return rc;
@@ -576,5 +618,5 @@
 		/* Terminate the cluster chain in all copies of FAT. */
 		for (fatno = FAT1; fatno < bs->fatcnt; fatno++) {
-			rc = fat_set_cluster(bs, dev_handle, fatno, lastc,
+			rc = fat_set_cluster(bs, dev_handle, fatno, lcl,
 			    FAT_CLST_LAST1);
 			if (rc != EOK)
@@ -588,4 +630,10 @@
 	}
 
+	/*
+	 * Update and re-enable the last cluster cache.
+	 */
+	nodep->lastc_cached_valid = true;
+	nodep->lastc_cached_value = lcl;
+
 	return EOK;
 }
@@ -596,15 +644,12 @@
 	int i;
 	block_t *b;
-	unsigned bps;
-	int rc;
-
-	bps = uint16_t_le2host(bs->bps);
-	
-	for (i = 0; i < bs->spc; i++) {
-		rc = _fat_block_get(&b, bs, dev_handle, c, i,
+	int rc;
+
+	for (i = 0; i < SPC(bs); i++) {
+		rc = _fat_block_get(&b, bs, dev_handle, c, NULL, i,
 		    BLOCK_FLAGS_NOREAD);
 		if (rc != EOK)
 			return rc;
-		memset(b->data, 0, bps);
+		memset(b->data, 0, BPS(bs));
 		b->dirty = true;
 		rc = block_put(b);
Index: uspace/srv/fs/fat/fat_fat.h
===================================================================
--- uspace/srv/fs/fat/fat_fat.h	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/srv/fs/fat/fat_fat.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -64,13 +64,11 @@
     fat_cluster_t *, uint16_t *, uint16_t);
 
-#define fat_block_get(b, bs, np, bn, flags) \
-    _fat_block_get((b), (bs), (np)->idx->dev_handle, (np)->firstc, (bn), \
-    (flags))
-
+extern int fat_block_get(block_t **, struct fat_bs *, struct fat_node *,
+    aoff64_t, int);
 extern int _fat_block_get(block_t **, struct fat_bs *, dev_handle_t,
-    fat_cluster_t, aoff64_t, int);
+    fat_cluster_t, fat_cluster_t *, aoff64_t, int);
 
 extern int fat_append_clusters(struct fat_bs *, struct fat_node *,
-    fat_cluster_t);
+    fat_cluster_t, fat_cluster_t);
 extern int fat_chop_clusters(struct fat_bs *, struct fat_node *,
     fat_cluster_t);
Index: uspace/srv/fs/fat/fat_ops.c
===================================================================
--- uspace/srv/fs/fat/fat_ops.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/srv/fs/fat/fat_ops.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -60,4 +60,7 @@
 #define FS_NODE(node)	((node) ? (node)->bp : NULL)
 
+#define DPS(bs)		(BPS((bs)) / sizeof(fat_dentry_t))
+#define BPC(bs)		(BPS((bs)) * SPC((bs)))
+
 /** Mutex protecting the list of cached free FAT nodes. */
 static FIBRIL_MUTEX_INITIALIZE(ffn_mutex);
@@ -101,4 +104,9 @@
 	node->refcnt = 0;
 	node->dirty = false;
+	node->lastc_cached_valid = false;
+	node->lastc_cached_value = FAT_CLST_LAST1;
+	node->currc_cached_valid = false;
+	node->currc_cached_bn = 0;
+	node->currc_cached_value = FAT_CLST_LAST1;
 }
 
@@ -108,6 +116,4 @@
 	fat_bs_t *bs;
 	fat_dentry_t *d;
-	uint16_t bps;
-	unsigned dps;
 	int rc;
 	
@@ -115,14 +121,13 @@
 
 	bs = block_bb_get(node->idx->dev_handle);
-	bps = uint16_t_le2host(bs->bps);
-	dps = bps / sizeof(fat_dentry_t);
 	
 	/* Read the block that contains the dentry of interest. */
 	rc = _fat_block_get(&b, bs, node->idx->dev_handle, node->idx->pfc,
-	    (node->idx->pdi * sizeof(fat_dentry_t)) / bps, BLOCK_FLAGS_NONE);
+	    NULL, (node->idx->pdi * sizeof(fat_dentry_t)) / BPS(bs),
+	    BLOCK_FLAGS_NONE);
 	if (rc != EOK)
 		return rc;
 
-	d = ((fat_dentry_t *)b->data) + (node->idx->pdi % dps);
+	d = ((fat_dentry_t *)b->data) + (node->idx->pdi % DPS(bs));
 
 	d->firstc = host2uint16_t_le(node->firstc);
@@ -266,7 +271,4 @@
 	fat_dentry_t *d;
 	fat_node_t *nodep = NULL;
-	unsigned bps;
-	unsigned spc;
-	unsigned dps;
 	int rc;
 
@@ -298,11 +300,8 @@
 
 	bs = block_bb_get(idxp->dev_handle);
-	bps = uint16_t_le2host(bs->bps);
-	spc = bs->spc;
-	dps = bps / sizeof(fat_dentry_t);
 
 	/* Read the block that contains the dentry of interest. */
-	rc = _fat_block_get(&b, bs, idxp->dev_handle, idxp->pfc,
-	    (idxp->pdi * sizeof(fat_dentry_t)) / bps, BLOCK_FLAGS_NONE);
+	rc = _fat_block_get(&b, bs, idxp->dev_handle, idxp->pfc, NULL,
+	    (idxp->pdi * sizeof(fat_dentry_t)) / BPS(bs), BLOCK_FLAGS_NONE);
 	if (rc != EOK) {
 		(void) fat_node_put(FS_NODE(nodep));
@@ -310,5 +309,5 @@
 	}
 
-	d = ((fat_dentry_t *)b->data) + (idxp->pdi % dps);
+	d = ((fat_dentry_t *)b->data) + (idxp->pdi % DPS(bs));
 	if (d->attr & FAT_ATTR_SUBDIR) {
 		/* 
@@ -330,5 +329,5 @@
 			return rc;
 		}
-		nodep->size = bps * spc * clusters;
+		nodep->size = BPS(bs) * SPC(bs) * clusters;
 	} else {
 		nodep->type = FAT_FILE;
@@ -368,6 +367,4 @@
 	char name[FAT_NAME_LEN + 1 + FAT_EXT_LEN + 1];
 	unsigned i, j;
-	unsigned bps;		/* bytes per sector */
-	unsigned dps;		/* dentries per sector */
 	unsigned blocks;
 	fat_dentry_t *d;
@@ -377,7 +374,5 @@
 	fibril_mutex_lock(&parentp->idx->lock);
 	bs = block_bb_get(parentp->idx->dev_handle);
-	bps = uint16_t_le2host(bs->bps);
-	dps = bps / sizeof(fat_dentry_t);
-	blocks = parentp->size / bps;
+	blocks = parentp->size / BPS(bs);
 	for (i = 0; i < blocks; i++) {
 		rc = fat_block_get(&b, bs, parentp, i, BLOCK_FLAGS_NONE);
@@ -386,5 +381,5 @@
 			return rc;
 		}
-		for (j = 0; j < dps; j++) { 
+		for (j = 0; j < DPS(bs); j++) { 
 			d = ((fat_dentry_t *)b->data) + j;
 			switch (fat_classify_dentry(d)) {
@@ -414,5 +409,5 @@
 				fat_idx_t *idx = fat_idx_get_by_pos(
 				    parentp->idx->dev_handle, parentp->firstc,
-				    i * dps + j);
+				    i * DPS(bs) + j);
 				fibril_mutex_unlock(&parentp->idx->lock);
 				if (!idx) {
@@ -513,9 +508,7 @@
 	fat_bs_t *bs;
 	fat_cluster_t mcl, lcl;
-	uint16_t bps;
 	int rc;
 
 	bs = block_bb_get(dev_handle);
-	bps = uint16_t_le2host(bs->bps);
 	if (flags & L_DIRECTORY) {
 		/* allocate a cluster */
@@ -546,5 +539,5 @@
 		nodep->type = FAT_DIRECTORY;
 		nodep->firstc = mcl;
-		nodep->size = bps * bs->spc;
+		nodep->size = BPS(bs) * SPC(bs);
 	} else {
 		nodep->type = FAT_FILE;
@@ -609,6 +602,4 @@
 	block_t *b;
 	unsigned i, j;
-	uint16_t bps;
-	unsigned dps;
 	unsigned blocks;
 	fat_cluster_t mcl, lcl;
@@ -640,8 +631,6 @@
 	fibril_mutex_lock(&parentp->idx->lock);
 	bs = block_bb_get(parentp->idx->dev_handle);
-	bps = uint16_t_le2host(bs->bps);
-	dps = bps / sizeof(fat_dentry_t);
-
-	blocks = parentp->size / bps;
+
+	blocks = parentp->size / BPS(bs);
 
 	for (i = 0; i < blocks; i++) {
@@ -651,5 +640,5 @@
 			return rc;
 		}
-		for (j = 0; j < dps; j++) {
+		for (j = 0; j < DPS(bs); j++) {
 			d = ((fat_dentry_t *)b->data) + j;
 			switch (fat_classify_dentry(d)) {
@@ -691,5 +680,5 @@
 		return rc;
 	}
-	rc = fat_append_clusters(bs, parentp, mcl);
+	rc = fat_append_clusters(bs, parentp, mcl, lcl);
 	if (rc != EOK) {
 		(void) fat_free_clusters(bs, parentp->idx->dev_handle, mcl);
@@ -697,5 +686,5 @@
 		return rc;
 	}
-	parentp->size += bps * bs->spc;
+	parentp->size += BPS(bs) * SPC(bs);
 	parentp->dirty = true;		/* need to sync node */
 	rc = fat_block_get(&b, bs, parentp, i, BLOCK_FLAGS_NONE);
@@ -771,5 +760,5 @@
 
 	childp->idx->pfc = parentp->firstc;
-	childp->idx->pdi = i * dps + j;
+	childp->idx->pdi = i * DPS(bs) + j;
 	fibril_mutex_unlock(&childp->idx->lock);
 
@@ -793,5 +782,4 @@
 	fat_bs_t *bs;
 	fat_dentry_t *d;
-	uint16_t bps;
 	block_t *b;
 	bool has_children;
@@ -812,13 +800,12 @@
 	fibril_mutex_lock(&childp->idx->lock);
 	bs = block_bb_get(childp->idx->dev_handle);
-	bps = uint16_t_le2host(bs->bps);
 
 	rc = _fat_block_get(&b, bs, childp->idx->dev_handle, childp->idx->pfc,
-	    (childp->idx->pdi * sizeof(fat_dentry_t)) / bps,
+	    NULL, (childp->idx->pdi * sizeof(fat_dentry_t)) / BPS(bs),
 	    BLOCK_FLAGS_NONE);
 	if (rc != EOK) 
 		goto error;
 	d = (fat_dentry_t *)b->data +
-	    (childp->idx->pdi % (bps / sizeof(fat_dentry_t)));
+	    (childp->idx->pdi % (BPS(bs) / sizeof(fat_dentry_t)));
 	/* mark the dentry as not-currently-used */
 	d->name[0] = FAT_DENTRY_ERASED;
@@ -852,6 +839,4 @@
 	fat_bs_t *bs;
 	fat_node_t *nodep = FAT_NODE(fn);
-	unsigned bps;
-	unsigned dps;
 	unsigned blocks;
 	block_t *b;
@@ -866,8 +851,6 @@
 	fibril_mutex_lock(&nodep->idx->lock);
 	bs = block_bb_get(nodep->idx->dev_handle);
-	bps = uint16_t_le2host(bs->bps);
-	dps = bps / sizeof(fat_dentry_t);
-
-	blocks = nodep->size / bps;
+
+	blocks = nodep->size / BPS(bs);
 
 	for (i = 0; i < blocks; i++) {
@@ -879,5 +862,5 @@
 			return rc;
 		}
-		for (j = 0; j < dps; j++) {
+		for (j = 0; j < DPS(bs); j++) {
 			d = ((fat_dentry_t *)b->data) + j;
 			switch (fat_classify_dentry(d)) {
@@ -976,6 +959,4 @@
 	enum cache_mode cmode;
 	fat_bs_t *bs;
-	uint16_t bps;
-	uint16_t rde;
 	
 	/* Accept the mount options */
@@ -1014,9 +995,5 @@
 	bs = block_bb_get(dev_handle);
 	
-	/* Read the number of root directory entries. */
-	bps = uint16_t_le2host(bs->bps);
-	rde = uint16_t_le2host(bs->root_ent_max);
-
-	if (bps != BS_SIZE) {
+	if (BPS(bs) != BS_SIZE) {
 		block_fini(dev_handle);
 		ipc_answer_0(rid, ENOTSUP);
@@ -1025,5 +1002,5 @@
 
 	/* Initialize the block cache */
-	rc = block_cache_init(dev_handle, bps, 0 /* XXX */, cmode);
+	rc = block_cache_init(dev_handle, BPS(bs), 0 /* XXX */, cmode);
 	if (rc != EOK) {
 		block_fini(dev_handle);
@@ -1087,5 +1064,5 @@
 	rootp->refcnt = 1;
 	rootp->lnkcnt = 0;	/* FS root is not linked */
-	rootp->size = rde * sizeof(fat_dentry_t);
+	rootp->size = RDE(bs) * sizeof(fat_dentry_t);
 	rootp->idx = ridxp;
 	ridxp->nodep = rootp;
@@ -1165,5 +1142,4 @@
 	fat_node_t *nodep;
 	fat_bs_t *bs;
-	uint16_t bps;
 	size_t bytes;
 	block_t *b;
@@ -1191,5 +1167,4 @@
 
 	bs = block_bb_get(dev_handle);
-	bps = uint16_t_le2host(bs->bps);
 
 	if (nodep->type == FAT_FILE) {
@@ -1204,7 +1179,7 @@
 			(void) async_data_read_finalize(callid, NULL, 0);
 		} else {
-			bytes = min(len, bps - pos % bps);
+			bytes = min(len, BPS(bs) - pos % BPS(bs));
 			bytes = min(bytes, nodep->size - pos);
-			rc = fat_block_get(&b, bs, nodep, pos / bps,
+			rc = fat_block_get(&b, bs, nodep, pos / BPS(bs),
 			    BLOCK_FLAGS_NONE);
 			if (rc != EOK) {
@@ -1214,6 +1189,6 @@
 				return;
 			}
-			(void) async_data_read_finalize(callid, b->data + pos % bps,
-			    bytes);
+			(void) async_data_read_finalize(callid,
+			    b->data + pos % BPS(bs), bytes);
 			rc = block_put(b);
 			if (rc != EOK) {
@@ -1230,6 +1205,6 @@
 
 		assert(nodep->type == FAT_DIRECTORY);
-		assert(nodep->size % bps == 0);
-		assert(bps % sizeof(fat_dentry_t) == 0);
+		assert(nodep->size % BPS(bs) == 0);
+		assert(BPS(bs) % sizeof(fat_dentry_t) == 0);
 
 		/*
@@ -1239,6 +1214,6 @@
 		 * the position pointer accordingly.
 		 */
-		bnum = (pos * sizeof(fat_dentry_t)) / bps;
-		while (bnum < nodep->size / bps) {
+		bnum = (pos * sizeof(fat_dentry_t)) / BPS(bs);
+		while (bnum < nodep->size / BPS(bs)) {
 			aoff64_t o;
 
@@ -1247,6 +1222,6 @@
 			if (rc != EOK)
 				goto err;
-			for (o = pos % (bps / sizeof(fat_dentry_t));
-			    o < bps / sizeof(fat_dentry_t);
+			for (o = pos % (BPS(bs) / sizeof(fat_dentry_t));
+			    o < BPS(bs) / sizeof(fat_dentry_t);
 			    o++, pos++) {
 				d = ((fat_dentry_t *)b->data) + o;
@@ -1306,7 +1281,4 @@
 	size_t bytes, size;
 	block_t *b;
-	uint16_t bps;
-	unsigned spc;
-	unsigned bpc;		/* bytes per cluster */
 	aoff64_t boundary;
 	int flags = BLOCK_FLAGS_NONE;
@@ -1334,7 +1306,4 @@
 
 	bs = block_bb_get(dev_handle);
-	bps = uint16_t_le2host(bs->bps);
-	spc = bs->spc;
-	bpc = bps * spc;
 
 	/*
@@ -1345,9 +1314,9 @@
 	 * value signalizing a smaller number of bytes written. 
 	 */ 
-	bytes = min(len, bps - pos % bps);
-	if (bytes == bps)
+	bytes = min(len, BPS(bs) - pos % BPS(bs));
+	if (bytes == BPS(bs))
 		flags |= BLOCK_FLAGS_NOREAD;
 	
-	boundary = ROUND_UP(nodep->size, bpc);
+	boundary = ROUND_UP(nodep->size, BPC(bs));
 	if (pos < boundary) {
 		/*
@@ -1364,5 +1333,5 @@
 			return;
 		}
-		rc = fat_block_get(&b, bs, nodep, pos / bps, flags);
+		rc = fat_block_get(&b, bs, nodep, pos / BPS(bs), flags);
 		if (rc != EOK) {
 			(void) fat_node_put(fn);
@@ -1371,6 +1340,6 @@
 			return;
 		}
-		(void) async_data_write_finalize(callid, b->data + pos % bps,
-		    bytes);
+		(void) async_data_write_finalize(callid,
+		    b->data + pos % BPS(bs), bytes);
 		b->dirty = true;		/* need to sync block */
 		rc = block_put(b);
@@ -1396,5 +1365,5 @@
 		fat_cluster_t mcl, lcl; 
  
-		nclsts = (ROUND_UP(pos + bytes, bpc) - boundary) / bpc;
+		nclsts = (ROUND_UP(pos + bytes, BPC(bs)) - boundary) / BPC(bs);
 		/* create an independent chain of nclsts clusters in all FATs */
 		rc = fat_alloc_clusters(bs, dev_handle, nclsts, &mcl, &lcl);
@@ -1415,6 +1384,6 @@
 			return;
 		}
-		rc = _fat_block_get(&b, bs, dev_handle, lcl, (pos / bps) % spc,
-		    flags);
+		rc = _fat_block_get(&b, bs, dev_handle, lcl, NULL,
+		    (pos / BPS(bs)) % SPC(bs), flags);
 		if (rc != EOK) {
 			(void) fat_free_clusters(bs, dev_handle, mcl);
@@ -1424,6 +1393,6 @@
 			return;
 		}
-		(void) async_data_write_finalize(callid, b->data + pos % bps,
-		    bytes);
+		(void) async_data_write_finalize(callid,
+		    b->data + pos % BPS(bs), bytes);
 		b->dirty = true;		/* need to sync block */
 		rc = block_put(b);
@@ -1438,5 +1407,5 @@
 		 * node's cluster chain.
 		 */
-		rc = fat_append_clusters(bs, nodep, mcl);
+		rc = fat_append_clusters(bs, nodep, mcl, lcl);
 		if (rc != EOK) {
 			(void) fat_free_clusters(bs, dev_handle, mcl);
@@ -1462,7 +1431,4 @@
 	fat_node_t *nodep;
 	fat_bs_t *bs;
-	uint16_t bps;
-	uint8_t spc;
-	unsigned bpc;	/* bytes per cluster */
 	int rc;
 
@@ -1479,7 +1445,4 @@
 
 	bs = block_bb_get(dev_handle);
-	bps = uint16_t_le2host(bs->bps);
-	spc = bs->spc;
-	bpc = bps * spc;
 
 	if (nodep->size == size) {
@@ -1491,5 +1454,5 @@
 		 */
 		rc = EINVAL;
-	} else if (ROUND_UP(nodep->size, bpc) == ROUND_UP(size, bpc)) {
+	} else if (ROUND_UP(nodep->size, BPC(bs)) == ROUND_UP(size, BPC(bs))) {
 		/*
 		 * The node will be shrunk, but no clusters will be deallocated.
@@ -1509,5 +1472,5 @@
 			fat_cluster_t lastc;
 			rc = fat_cluster_walk(bs, dev_handle, nodep->firstc,
-			    &lastc, NULL, (size - 1) / bpc);
+			    &lastc, NULL, (size - 1) / BPC(bs));
 			if (rc != EOK)
 				goto out;
@@ -1564,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 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/srv/fs/tmpfs/tmpfs_ops.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -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 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/srv/hid/console/gcons.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -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 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/srv/hid/fb/fb.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -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 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/srv/hid/kbd/Makefile	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -58,4 +58,9 @@
 
 ifeq ($(UARCH),arm32)
+	ifeq ($(MACHINE),gta02)
+		SOURCES += \
+			port/chardev.c \
+			ctl/stty.c
+	endif
 	ifeq ($(MACHINE),testarm)
 		SOURCES += \
Index: uspace/srv/hid/kbd/port/chardev.c
===================================================================
--- uspace/srv/hid/kbd/port/chardev.c	(revision 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/srv/hid/kbd/port/chardev.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -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/s3c24xx_ts/Makefile
===================================================================
--- uspace/srv/hid/s3c24xx_ts/Makefile	(revision 20235a356988dff15276fd66d391230fc9b32591)
+++ uspace/srv/hid/s3c24xx_ts/Makefile	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -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 20235a356988dff15276fd66d391230fc9b32591)
+++ uspace/srv/hid/s3c24xx_ts/s3c24xx_ts.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -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 20235a356988dff15276fd66d391230fc9b32591)
+++ uspace/srv/hid/s3c24xx_ts/s3c24xx_ts.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -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 20235a356988dff15276fd66d391230fc9b32591)
+++ uspace/srv/hw/char/s3c24xx_uart/Makefile	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -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 20235a356988dff15276fd66d391230fc9b32591)
+++ uspace/srv/hw/char/s3c24xx_uart/s3c24xx_uart.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -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 20235a356988dff15276fd66d391230fc9b32591)
+++ uspace/srv/hw/char/s3c24xx_uart/s3c24xx_uart.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -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 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/srv/vfs/vfs.h	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -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 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/srv/vfs/vfs_lookup.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -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 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/srv/vfs/vfs_node.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -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 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/srv/vfs/vfs_ops.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -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 0c61955d1de286c187f2c43fd9ba4639dda1db5a)
+++ uspace/srv/vfs/vfs_register.c	(revision 20235a356988dff15276fd66d391230fc9b32591)
@@ -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);
 }
 
