diff --git a/src/arch/x86_64/interrupt/syscall.rs b/src/arch/x86_64/interrupt/syscall.rs
index f7f99ac220b2903d1333b21bec1a98b6c89c3caf..2f1247e5d70f31c21dcd9ad75a1893bb043f7cba 100644
--- a/src/arch/x86_64/interrupt/syscall.rs
+++ b/src/arch/x86_64/interrupt/syscall.rs
@@ -1,5 +1,90 @@
-use arch::x86_64::pti;
+use arch::{gdt, pti};
 use syscall;
+use x86::shared::msr;
+
+pub unsafe fn init() {
+    msr::wrmsr(msr::IA32_STAR, ((gdt::GDT_KERNEL_CODE as u64) << 3) << 32);
+    msr::wrmsr(msr::IA32_LSTAR, syscall_instruction as u64);
+    msr::wrmsr(msr::IA32_FMASK, 1 << 9);
+    msr::wrmsr(msr::IA32_KERNEL_GS_BASE, &gdt::TSS as *const _ as u64);
+
+    let efer = msr::rdmsr(msr::IA32_EFER);
+    msr::wrmsr(msr::IA32_EFER, efer | 1);
+}
+
+#[naked]
+pub unsafe extern fn syscall_instruction() {
+    #[inline(never)]
+    unsafe fn inner(stack: &mut SyscallStack) -> usize {
+        let rbp;
+        asm!("" : "={rbp}"(rbp) : : : "intel", "volatile");
+
+        syscall::syscall(stack.rax, stack.rdi, stack.rsi, stack.rdx, stack.r10, stack.r8, rbp, stack)
+    }
+
+    // Yes, this is magic. No, you don't need to understand
+    asm!("xchg bx, bx
+          swapgs                    // Set gs segment to TSS
+          mov gs:[28], rsp          // Save userspace rsp
+          mov rsp, gs:[4]           // Load kernel rsp
+          push 5 * 8 + 3            // Push userspace data segment
+          push qword ptr gs:[28]    // Push userspace rsp
+          mov qword ptr gs:[28], 0  // Clear userspace rsp
+          push r11                  // Push rflags
+          push 4 * 8 + 3            // Push userspace code segment
+          push rcx                  // Push userspace return pointer
+          swapgs                    // Restore gs
+          "
+          :
+          :
+          :
+          : "intel", "volatile");
+
+      // Push scratch registers
+      asm!("push rax
+           push rbx
+           push rcx
+           push rdx
+           push rdi
+           push rsi
+           push r8
+           push r9
+           push r10
+           push r11
+           push fs
+           mov r11, 0x18
+           mov fs, r11"
+           : : : : "intel", "volatile");
+
+      // Get reference to stack variables
+      let rsp: usize;
+      asm!("" : "={rsp}"(rsp) : : : "intel", "volatile");
+
+      // Map kernel
+      pti::map();
+
+      let a = inner(&mut *(rsp as *mut SyscallStack));
+
+      // Unmap kernel
+      pti::unmap();
+
+      asm!("" : : "{rax}"(a) : : "intel", "volatile");
+
+      // Interrupt return
+      asm!("pop fs
+            pop r11
+            pop r10
+            pop r9
+            pop r8
+            pop rsi
+            pop rdi
+            pop rdx
+            pop rcx
+            pop rbx
+            add rsp, 8
+            iretq"
+            : : : : "intel", "volatile");
+}
 
 #[naked]
 pub unsafe extern fn syscall() {
diff --git a/src/arch/x86_64/start.rs b/src/arch/x86_64/start.rs
index 81b12bb3bff77651943ad50a3c36a227c2851fc6..46151703ffc8b590f8df43dc8cb9e1b140a12b0a 100644
--- a/src/arch/x86_64/start.rs
+++ b/src/arch/x86_64/start.rs
@@ -90,6 +90,9 @@ pub unsafe extern fn kstart(args_ptr: *const KernelArgs) -> ! {
         // Set up IDT
         idt::init_paging();
 
+        // Set up syscall instruction
+        interrupt::syscall::init();
+
         // Test tdata and tbss
         {
             assert_eq!(TBSS_TEST_ZERO, 0);