diff --git a/arch/i386/dispatch.h b/arch/i386/dispatch.h
index 2237d612d650c42ce91ce5652e90cb50b4301a74..be55edd8a159313d9773bbfdac00f59690ef04d2 100644
--- a/arch/i386/dispatch.h
+++ b/arch/i386/dispatch.h
@@ -84,7 +84,7 @@ public:
 
 	/** \brief Syscall to start idle loop (in ring 0) */
 	static forceinline void idle(void) {
-		syscall(&idle_loop, 0, true);
+		syscall<true>(&idle_loop);
 	}
 
 	/** \brief The idle loop
diff --git a/arch/i386/machine.cc b/arch/i386/machine.cc
index d1a65a8f1913afe24f44a99f32a240d94c196dc1..af8b063d02c3e8865e233bda05557f1381304791 100644
--- a/arch/i386/machine.cc
+++ b/arch/i386/machine.cc
@@ -11,6 +11,6 @@ noinline void __OS_trigger_syscall(uint8_t irq) {
 }
 
 void Machine::trigger_interrupt_from_user(uint8_t irq) {
-	arch::syscall(__OS_trigger_syscall, irq, true);
+	arch::syscall<true>(__OS_trigger_syscall, irq);
 }
 
diff --git a/arch/i386/syscall.cc b/arch/i386/syscall.cc
index 7a928150b0f0e0f6ca82d1432e566f9871559b3d..5ce6072d93c2cccdaa9939468298560d118e3152 100644
--- a/arch/i386/syscall.cc
+++ b/arch/i386/syscall.cc
@@ -15,10 +15,10 @@ namespace arch {
 IRQ_HANDLER(IRQ_SYSCALL) {
 	// get arguments from registers
 	// also, store pointer to context in %esi before we change %esp
-	uint32_t fun, arg;
+	uint32_t fun, arg1, arg2, arg3;
 	bool direct;
 	cpu_context* ctx;
-	asm volatile("leal -4(%%esp), %0" : "=r"(ctx), "=c"(arg), "=b"(fun), "=d"(direct));
+	asm volatile("leal -4(%%esp), %0" : "=r"(ctx), "=c"(arg1), "=a"(arg2), "=D"(arg3), "=b"(fun), "=d"(direct));
 
 	// TODO: remove/reuse pushed CPU context?
 
@@ -42,13 +42,15 @@ IRQ_HANDLER(IRQ_SYSCALL) {
 			save_sp = 0; // for detecting bugs, not stricly neccessary
 		}
 
-		// put syscall argument on top kernel stack
+		// put syscall arguments on top kernel stack
 		uint32_t* sp = (uint32_t*) (&_estack_os - 2048);
-		*sp = arg;
+		*sp = arg3;
+		*(sp-1) = arg2;
+		*(sp-2) = arg1;
 
 		// push the syscall stack address/segment
 		Machine::push(GDT::USER_DATA_SEGMENT | 0x3); // push stack segment, DPL3
-		Machine::push((uint32_t)(sp) - 4); // push stack pointer above argument
+		Machine::push((uint32_t)(sp-3)); // push stack pointer above argument
 
 		// push flags, IO privilege level 3
 		Machine::push(ctx->eflags | 0x3000);
@@ -74,7 +76,7 @@ IRQ_HANDLER(IRQ_SYSCALL) {
 		// call syscall function with argument
 		// C code does not work as compiler overwrites our return address with arg
 		//void (* f)(uint32_t) = (void (*)(uint32_t))fun; f(arg);
-		asm volatile("push %0; call *%1; pop %0" :: "r"(arg), "r"(fun));
+		asm volatile("push %3; push %2; push %1; call *%0; pop %1; pop %2; pop %3" :: "r"(fun), "r"(arg1), "r"(arg2), "r"(arg3));
 
 		// restore page directory
 		asm volatile("mov %0, %%cr3" :: "S"(pd));
diff --git a/arch/i386/syscall.h b/arch/i386/syscall.h
index a21b9e8364f66b87810ae5c7af1fb20d01c73078..970732faf46e966cecce75ef592b8dff52c1daf1 100644
--- a/arch/i386/syscall.h
+++ b/arch/i386/syscall.h
@@ -13,29 +13,52 @@
 
 namespace arch {
 
+/**@{*/
 /** \brief Run specified function as syscall
  *
  * Currently, any function can be called this way. Eventually, this will be replaced
  * by an (encoded) index to a static jumptable of syscalls
  *
  * \param fun syscall function to be called
- * \param arg (optional) argument for syscall
- * \param direct execute syscall directly in IRQ handler instead of userspace (default: false)
+ * \param arg1 (optional) argument for syscall
+ * \param arg2 (optional) argument for syscall
+ * \param arg3 (optional) argument for syscall
+ * \tparam direct execute syscall directly in IRQ handler instead of userspace (default: false)
  */
-template<typename F, typename A=int>
-forceinline void syscall(F fun, A arg=0, bool direct=false) {
-	// save all registers and call syscall interrupt
-	//asm volatile("pusha; int %0; popa" :: "i"(IRQ_SYSCALL), "b"(fun), "c"(*((uint32_t*)&arg)));
-
+template<bool direct=false, typename F>
+forceinline void syscall(F fun) {
 	// use clobber instead of pusha to save and restore only required registers:
 	// gcc documentation says to list modified input in outputs and they must not be included
 	// in clobber list, but LLVM works only the other way ...
-	asm volatile("int %0" :: "i"(IRQ_SYSCALL), "b"(fun), "c"(*((uint32_t*)&arg)), "d"(direct) :
+	asm volatile("int %0" :: "i"(IRQ_SYSCALL), "b"(fun), "d"(direct) :
+		"ebx", "ecx", "eax", "edx", "ebp", "esi", "edi", "cc", "memory");
+}
+
+template<bool direct=false, typename F, typename A>
+forceinline void syscall(F fun, A arg1) {
+	asm volatile("int %0" :: "i"(IRQ_SYSCALL), "b"(fun), "c"(*((uint32_t*)&arg1)), "d"(direct) :
 		"ebx", "ecx", "eax", "edx", "ebp", "esi", "edi", "cc", "memory");
 }
 
+template<bool direct=false, typename F, typename A, typename B>
+forceinline void syscall(F fun, A arg1, B arg2) {
+	asm volatile("int %0" :: "i"(IRQ_SYSCALL), "b"(fun), "c"(*((uint32_t*)&arg1)), "d"(direct),
+		"a"(*((uint32_t*)&arg2)) :
+		"ebx", "ecx", "eax", "edx", "ebp", "esi", "edi", "cc", "memory");
+}
+
+template<bool direct=false, typename F, typename A, typename B, typename C>
+forceinline void syscall(F fun, A arg1, B arg2, C arg3) {
+	asm volatile("int %0" :: "i"(IRQ_SYSCALL), "b"(fun), "c"(*((uint32_t*)&arg1)), "d"(direct),
+		"a"(*((uint32_t*)&arg2)), "D"(*((uint32_t*)&arg3)) :
+		"ebx", "ecx", "eax", "edx", "ebp", "esi", "edi", "cc", "memory");
+}
+
+/**@}*/
+
 /** \brief Return true if calling code is running as part of a syscall */
 forceinline bool in_syscall() {
+	// TODO: determine using some (encoded) system variable instead of hardware register?
 	return (LAPIC::get_task_prio() == 128);
 }