x86_64: Syscall/Sysenter/int 0x80 (XCC)
authorBarret Rhoden <brho@cs.berkeley.edu>
Sat, 29 Jun 2013 00:23:01 +0000 (17:23 -0700)
committerBarret Rhoden <brho@cs.berkeley.edu>
Wed, 3 Jul 2013 00:18:00 +0000 (17:18 -0700)
Userspace can make system calls with either syscall (aka sysenter in the
codebase, 'syscall' is pretty generic) or int 0x80.

Rebuild the toolchain to use this, though _start probably doesn't work well as
is.  If you really want to test off this commit, write your own C program,
define _start, and compile with a non-cross gcc:

$ gcc minihello.c -o minihello -static -nostdinc -nostartfiles

kern/arch/x86/entry64.S
kern/arch/x86/process64.c
kern/arch/x86/ros/mmu64.h
kern/arch/x86/ros/syscall64.h
kern/arch/x86/ros/trapframe64.h
kern/arch/x86/trap.c
kern/arch/x86/trap64.c
kern/arch/x86/trap64.h
kern/arch/x86/trapentry64.S
kern/arch/x86/x86.h
user/parlib/include/x86/vcore64.h

index fe911f2..3900f81 100644 (file)
@@ -155,8 +155,8 @@ gdt64:
        SEG_NULL
        SEG_CODE_64(0)          # kernel code segment
        SEG_DATA_64(0)          # kernel data segment
-       SEG_CODE_64(3)          # user code segment
        SEG_DATA_64(3)          # user data segment
+       SEG_CODE_64(3)          # user code segment
        SEG_NULL                        # these two nulls are a placeholder for the TSS
        SEG_NULL                        # these two nulls are a placeholder for the TSS
 .globl gdt64desc
index 145320e..315057d 100644 (file)
@@ -8,40 +8,15 @@
 #include <assert.h>
 #include <stdio.h>
 
-/* TODO: handle user and kernel contexts */
 void proc_pop_ctx(struct user_context *ctx)
 {
-       struct hw_trapframe *tf = &ctx->tf.hw_tf;
-       assert(ctx->type == ROS_HW_CTX);
-
-       /* Bug with this whole idea (TODO: (TLSV))*/
-       /* Load the LDT for this process.  Slightly ghetto doing it here. */
-       /* copy-in and check the LDT location.  the segmentation hardware writes the
-        * accessed bit, so we want the memory to be in the user-writeable area. */
-       segdesc_t *ldt = current->procdata->ldt;
-       ldt = (segdesc_t*)MIN((uintptr_t)ldt, UWLIM - LDT_SIZE);
-       /* Only set up the ldt if a pointer to the ldt actually exists */
-#if 0 /* think about how to do TLS.  need better seg macros too */
-       if(ldt != NULL) {
-               segdesc_t *my_gdt = per_cpu_info[core_id()].gdt;
-               /* TODO: 64b issues here.  need to redo this anyways.  Considering how
-                * slow userspace TLS changes are (70ns), I might opt for just changing
-                * FS base, either via fast syscall or in userspace on newer versions */
-               segdesc_t ldt_temp = SEG_SYS(STS_LDT, (uint32_t)ldt, LDT_SIZE, 3);
-               my_gdt[GD_LDT >> 3] = ldt_temp;
-               asm volatile("lldt %%ax" :: "a"(GD_LDT));
-       }
-#endif
-
-       /* In case they are enabled elsewhere.  We can't take an interrupt in these
-        * routines, due to how they play with the kernel stack pointer. */
        disable_irq();
-       write_msr(MSR_GS_BASE, (uint64_t)tf->tf_gsbase);
-       write_msr(MSR_FS_BASE, (uint64_t)tf->tf_fsbase);
-       /* If the process entered the kernel via sysenter, we need to leave via
-        * sysexit.  sysenter trapframes have 0 for a CS, which is pushed in
-        * sysenter_handler. */
-       if (tf->tf_cs) {
+       /* for both HW and SW, note we pass an offset into the TF, beyond the fs and
+        * gs bases */
+       if (ctx->type == ROS_HW_CTX) {
+               struct hw_trapframe *tf = &ctx->tf.hw_tf;
+               write_msr(MSR_GS_BASE, (uint64_t)tf->tf_gsbase);
+               write_msr(MSR_FS_BASE, (uint64_t)tf->tf_fsbase);
                asm volatile ("movq %0, %%rsp;          "
                              "popq %%rax;              "
                              "popq %%rbx;              "
@@ -61,39 +36,34 @@ void proc_pop_ctx(struct user_context *ctx)
                              "addq $0x10, %%rsp;       "
                              "iretq                    "
                              : : "g" (&tf->tf_rax) : "memory");
-               panic("iret failed");  /* mostly to placate the compiler */
+               panic("iretq failed");
        } else {
-               /* Return path of sysexit.  See sysenter_handler's asm for details.
-                * One difference is that this tf could be somewhere other than a stack
-                * (like in a struct proc).  We need to make sure esp is valid once
-                * interrupts are turned on (which would happen on popfl normally), so
-                * we need to save and restore a decent esp (the current one).  We need
-                * a place to save it that is accessible after we change the stack
-                * pointer to the tf *and* that is specific to this core/instance of
-                * sysexit.  The simplest and nicest is to use the tf_esp, which we
-                * can just pop.  Incidentally, the value in oesp would work too.
-                * To prevent popfl from turning interrupts on, we hack the tf's eflags
-                * so that we have a chance to change esp to a good value before
-                * interrupts are enabled.  The other option would be to throw away the
-                * eflags, but that's less desirable. */
-               tf->tf_rflags &= !FL_IF;
-               tf->tf_rsp = read_sp();
-//             asm volatile ("movl %0,%%esp;           "
-//                           "popal;                   "
-//                           "popl %%gs;               "
-//                           "popl %%fs;               "
-//                           "popl %%es;               "
-//                           "popl %%ds;               "
-//                           "addl $0x10,%%esp;        "
-//                           "popfl;                   "
-//                           "movl %%ebp,%%ecx;        "
-//                           "popl %%esp;              "
-//                           "sti;                     "
-//                           "sysexit                  "
-//                           : : "g" (&tf->tf_rax) : "memory");
-               // keep in mind, we can take an interrupt in here (depending on what GS
-               // tricks there are)
-               panic("sysexit failed");  /* mostly to placate your mom */
+               struct sw_trapframe *tf = &ctx->tf.sw_tf;
+               write_msr(MSR_GS_BASE, (uint64_t)tf->tf_gsbase);
+               write_msr(MSR_FS_BASE, (uint64_t)tf->tf_fsbase);
+               /* We need to 0 out any registers that aren't part of the sw_tf and that
+                * we won't use/clobber on the out-path.  While these aren't part of the
+                * sw_tf, we also don't want to leak any kernel register content. */
+               asm volatile ("movq %0, %%rsp;          "
+                             "movq $0, %%rax;          "
+                                         "movq $0, %%rdx;          "
+                                         "movq $0, %%rsi;          "
+                                         "movq $0, %%rdi;          "
+                                         "movq $0, %%r8;           "
+                                         "movq $0, %%r9;           "
+                                         "movq $0, %%r10;          "
+                             "popq %%rbx;              "
+                             "popq %%rbp;              "
+                             "popq %%r12;              "
+                             "popq %%r13;              "
+                             "popq %%r14;              "
+                             "popq %%r15;              "
+                                         "movq %1, %%r11;          "
+                             "popq %%rcx;              "
+                             "popq %%rsp;              "
+                             "rex.w sysret             "
+                             : : "g"(&tf->tf_rbx), "i"(FL_IF) : "memory");
+               panic("sysret failed");
        }
 }
 
index 360fa87..b905b6d 100644 (file)
@@ -305,8 +305,10 @@ typedef unsigned long pde_t;
 #define GD_NULL                        0x00    /* NULL descriptor */
 #define GD_KT                  0x08    /* kernel text */
 #define GD_KD                  0x10    /* kernel data */
-#define GD_UT                  0x18    /* user text */
-#define GD_UD                  0x20    /* user data */
+/* syscall/sysret wants UD before UT, but KT before KD.  it really wants UT32,
+ * UD, UT64.  anyways... */
+#define GD_UD                  0x18    /* user data */
+#define GD_UT                  0x20    /* user text */
 #define GD_TSS                 0x28    /* Task segment selector */
 #define GD_TSS2                        0x30    /* Placeholder, TSS is 2-descriptors wide */
 /* These two aren't in the GDT yet (might never be) */
index 60c5dfe..421321b 100644 (file)
 
 static inline intreg_t __syscall_sysenter(uintreg_t a0, uintreg_t a1)
 {
-       /* The kernel clobbers ecx, so we save it manually. */
        intreg_t ret = 0;
-       #if 0
-       asm volatile ("  pushl %%ecx;        "
-                     "  pushl %%edx;        "
-                     "  pushl %%ebp;        "
-                     "  movl %%esp, %%ebp;  "
-                     "  leal 1f, %%edx;     "
-                     "  sysenter;           "
-                     "1:                    "
-                     "  popl %%ebp;         "
-                     "  popl %%edx;         "
-                     "  popl %%ecx;         "
+       /* we're calling using the amd function call abi.  this asm and the kernel
+        * will save the callee-saved state.  We'll use the clobber list to force
+        * the compiler to save caller-saved state.  As with uthread code, you need
+        * to make sure you have one ABI-compliant, non-inlined function call
+        * between any floating point ops and this.
+        *
+        * Note that syscall doesn't save the stack pointer - using rdx for that.
+        * The kernel will restore it for us. */
+       asm volatile ("movq %%rsp, %%rdx;       "
+                     "syscall;                 "
                      : "=a" (ret)
-                     : "a" (a0),
+                     : "D" (a0),
                        "S" (a1)
-                     : "cc", "memory");
-       #endif
+                     : "cc", "memory", "rcx", "rdx", "r8", "r9", "r10", "r11");
        return ret;
 }
 
 static inline intreg_t __syscall_trap(uintreg_t a0, uintreg_t a1)
 {
        intreg_t ret;
-
-       #if 0
        /* If you change this, change pop_user_ctx() */
        asm volatile("int %1"
                     : "=a" (ret)
                     : "i" (T_SYSCALL),
-                      "a" (a0),
-                      "d" (a1)
+                      "D" (a0),
+                      "S" (a1)
                     : "cc", "memory");
-       #endif
        return ret;
 }
 
index 8513ecb..1e34aeb 100644 (file)
@@ -41,15 +41,19 @@ struct hw_trapframe {
 };
 
 struct sw_trapframe {
-       uint32_t tf_ebp;
-       uint32_t tf_ebx;
-       uint32_t tf_esi;
-       uint32_t tf_edi;
-       uint32_t tf_esp;
-       uint32_t tf_eip;
+       uint64_t tf_gsbase;
+       uint64_t tf_fsbase;
+       uint64_t tf_rbx;
+       uint64_t tf_rbp;
+       uint64_t tf_r12;
+       uint64_t tf_r13;
+       uint64_t tf_r14;
+       uint64_t tf_r15;
+       uint64_t tf_rip;
+       uint64_t tf_rsp;
        uint32_t tf_mxcsr;
        uint16_t tf_fpucw;
-       uint16_t tf_gs;         /* something to track TLS is callee-saved (sort of) */
+       uint16_t tf_padding0;
 };
 
 #endif /* ROS_INC_ARCH_TRAPFRAME64_H */
index 583ac16..a817365 100644 (file)
@@ -272,6 +272,7 @@ static void trap_dispatch(struct hw_trapframe *hw_tf)
                        // check for userspace, for now
                        assert(hw_tf->tf_cs != GD_KT);
                        /* Set up and run the async calls */
+                       /* TODO: this is using the wrong reg1 for traps for 32 bit */
                        prep_syscalls(current,
                                      (struct syscall*)x86_get_sysenter_arg0(hw_tf),
                                                  (unsigned int)x86_get_sysenter_arg1(hw_tf));
@@ -495,6 +496,26 @@ register_interrupt_handler(handler_t TP(TV(t)) table[],
        table[int_num].data = data;
 }
 
+/* It's a moderate pain in the ass to put these in bit-specific files (header
+ * hell with the set_current_ helpers) */
+#ifdef CONFIG_X86_64
+void sysenter_callwrapper(struct syscall *sysc, unsigned long count,
+                          struct sw_trapframe *sw_tf)
+{
+       struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
+       set_current_ctx_sw(pcpui, sw_tf);
+       /* Once we've set_current_ctx, we can enable interrupts.  This used to be
+        * mandatory (we had immediate KMSGs that would muck with cur_ctx).  Now it
+        * should only help for sanity/debugging. */
+       enable_irq();
+       /* Set up and run the async calls */
+       prep_syscalls(current, sysc, count);
+       /* If you use pcpui again, reread it, since you might have migrated */
+       proc_restartcore();
+}
+
+#else
+
 /* This is called from sysenter's asm, with the tf on the kernel stack. */
 /* TODO: use a sw_tf for sysenter */
 void sysenter_callwrapper(struct hw_trapframe *hw_tf)
@@ -514,6 +535,7 @@ void sysenter_callwrapper(struct hw_trapframe *hw_tf)
        /* If you use pcpui again, reread it, since you might have migrated */
        proc_restartcore();
 }
+#endif
 
 /* Declared in x86/arch.h */
 void send_ipi(uint32_t os_coreid, uint8_t vector)
index af06e27..43334f2 100644 (file)
@@ -55,7 +55,7 @@ void print_trapframe(struct hw_trapframe *hw_tf)
         * nuts when we print/panic */
        pcpui->__lock_depth_disabled++;
        spin_lock_irqsave(&ptf_lock);
-       printk("TRAP frame at %p on core %d\n", hw_tf, core_id());
+       printk("HW TRAP frame at %p on core %d\n", hw_tf, core_id());
        printk("  rax  0x%016lx\n",           hw_tf->tf_rax);
        printk("  rbx  0x%016lx\n",           hw_tf->tf_rbx);
        printk("  rcx  0x%016lx\n",           hw_tf->tf_rcx);
@@ -74,7 +74,10 @@ void print_trapframe(struct hw_trapframe *hw_tf)
        printk("  trap 0x%08x %s\n",          hw_tf->tf_trapno,
                                              x86_trapname(hw_tf->tf_trapno));
        /* FYI: these aren't physically adjacent to trap and err */
-       printk("  gsbs 0x%016lx\n",           hw_tf->tf_gsbase);
+       if (hw_tf->tf_cs == GD_KT)
+               printk("  gsbs 0x%016lx\n",       read_msr(MSR_GS_BASE));
+       else
+               printk("  gsbs 0x%016lx\n",       hw_tf->tf_gsbase);
        printk("  fsbs 0x%016lx\n",           hw_tf->tf_fsbase);
        printk("  err  0x--------%08x\n",     hw_tf->tf_err);
        printk("  rip  0x%016lx\n",           hw_tf->tf_rip);
@@ -92,6 +95,29 @@ void print_trapframe(struct hw_trapframe *hw_tf)
        static_assert(offsetof(struct per_cpu_info, stacktop) == 0);
 }
 
+void print_swtrapframe(struct sw_trapframe *sw_tf)
+{
+       static spinlock_t ptf_lock = SPINLOCK_INITIALIZER_IRQSAVE;
+       struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
+       pcpui->__lock_depth_disabled++;
+       spin_lock_irqsave(&ptf_lock);
+       printk("SW TRAP frame at %p on core %d\n", sw_tf, core_id());
+       printk("  rbx  0x%016lx\n",           sw_tf->tf_rbx);
+       printk("  rbp  0x%016lx\n",           sw_tf->tf_rbp);
+       printk("  r12  0x%016lx\n",           sw_tf->tf_r12);
+       printk("  r13  0x%016lx\n",           sw_tf->tf_r13);
+       printk("  r14  0x%016lx\n",           sw_tf->tf_r14);
+       printk("  r15  0x%016lx\n",           sw_tf->tf_r15);
+       printk("  gsbs 0x%016lx\n",           sw_tf->tf_gsbase);
+       printk("  fsbs 0x%016lx\n",           sw_tf->tf_fsbase);
+       printk("  rip  0x%016lx\n",           sw_tf->tf_rip);
+       printk("  rsp  0x%016lx\n",           sw_tf->tf_rsp);
+       printk(" mxcsr 0x%08x\n",             sw_tf->tf_mxcsr);
+       printk(" fpucw 0x%04x\n",             sw_tf->tf_fpucw);
+       spin_unlock_irqsave(&ptf_lock);
+       pcpui->__lock_depth_disabled--;
+}
+
 void page_fault_handler(struct hw_trapframe *hw_tf)
 {
        uintptr_t fault_va = rcr2();
index dc40db1..b7fbd3c 100644 (file)
@@ -14,6 +14,8 @@
 #error "Do not include arch/trap64.h directly."
 #endif
 
+void print_swtrapframe(struct sw_trapframe *sw_tf);
+
 static inline bool in_kernel(struct hw_trapframe *hw_tf)
 {
        return (hw_tf->tf_cs & ~3) == GD_KT;
@@ -60,14 +62,20 @@ static inline void x86_fake_rdtscp(struct hw_trapframe *hw_tf)
        hw_tf->tf_rcx = core_id();
 }
 
-/* TODO: use syscall.  all of this sysenter stuff is wrong  */
 static inline void x86_sysenter_init(uintptr_t stacktop)
 {
-       //write_msr(MSR_IA32_SYSENTER_CS, GD_KT);
-       //write_msr(MSR_IA32_SYSENTER_EIP, (uintptr_t) &sysenter_handler);
+       /* check amd 2:6.1.1 for details.  they have some expectations about the GDT
+        * layout. */
+       write_msr(MSR_STAR, ((((uint64_t)GD_UD - 8) | 0x3) << 48) |
+                           ((uint64_t)GD_KT << 32));
+       write_msr(MSR_LSTAR, (uintptr_t)&sysenter_handler);
+       /* Masking all flags.  when we syscall, we'll get rflags = 0 */
+       write_msr(MSR_SFMASK, 0xffffffff);
+       write_msr(IA32_EFER_MSR, read_msr(IA32_EFER_MSR) | IA32_EFER_SYSCALL);
        asm volatile ("movq %0, %%gs:0" : : "r"(stacktop));
 }
 
+/* these are used for both sysenter and traps on 32 bit */
 static inline void x86_set_sysenter_stacktop(uintptr_t stacktop)
 {
        asm volatile ("movq %0, %%gs:0" : : "r"(stacktop));
@@ -75,12 +83,12 @@ static inline void x86_set_sysenter_stacktop(uintptr_t stacktop)
 
 static inline long x86_get_sysenter_arg0(struct hw_trapframe *hw_tf)
 {
-       return hw_tf->tf_rax;   // XXX probably wrong
+       return hw_tf->tf_rdi;
 }
 
 static inline long x86_get_sysenter_arg1(struct hw_trapframe *hw_tf)
 {
-       return hw_tf->tf_rsi;   // XXX probably wrong
+       return hw_tf->tf_rsi;
 }
 
 static inline uintptr_t x86_get_stacktop_tss(struct taskstate *tss)
index 3203f6e..7ae67b9 100644 (file)
@@ -175,7 +175,7 @@ _alltraps:
        shl $32, %rdx
        orq %rax, %rdx
        pushq %rdx
-       # because we swapped gs earlier, the TF in use is now in KERN_GS_BASE
+       # because we swapped gs earlier, the user GS is now in KERN_GS_BASE
        movl $MSR_KERN_GS_BASE, %ecx
        rdmsr
        shl $32, %rdx
@@ -244,7 +244,7 @@ _allirqs:
        shl $32, %rdx
        orq %rax, %rdx
        pushq %rdx
-       # because we swapped gs earlier, the TF in use is now in KERN_GS_BASE
+       # because we swapped gs earlier, the user GS is now in KERN_GS_BASE
        movl $MSR_KERN_GS_BASE, %ecx
        rdmsr
        shl $32, %rdx
@@ -287,39 +287,53 @@ irq_all_tf:
 
 .globl sysenter_handler;
 .type sysenter_handler, @function;
-# All of the pushq zeros are to keep the trap frame looking the same as when we
-# receive a trap or an interrupt
+
 sysenter_handler:
-       cld
-       pushq $0                                # ss
-       pushq $0                                # rsp
-       pushfq                                  # eflags
-       pushq $0                                # CS == 0 lets the kernel know it was a sysenter        
-       pushq $0                                # eip
-       pushq $0                                # err 
-       pushq $T_SYSCALL                # helps with print_trapframe
-       # pushq %ds
-       # pushq %es
-       pushq %fs
-       pushq %gs
-       # pushal
-       movw $0, %ax;
-       movw %ax, %gs;
-       movw %ax, %fs;
-       movw $GD_KD, %ax
-       movw %ax, %ds
-       movw %ax, %es
-       pushq %rsp
+       # Rough plan to do a quick TLS / FS base change, never changing stacks
+#      compare rdi with a non-canon magic number (never a sysc)
+#      mov rcx to rdi (save it)
+#      cx, dx, and ax are available, can do an wrmsr on MSR_FS_BASE
+#      rsi has the new FS base
+#      mov rdi back to rcx
+#      sysret
+
+       # cld is handled by the SFMASK
+       swapgs
+       movq %gs:0, %rsp
+       # Saving the FPU callee-saved state for now.  Might be able to have the
+       # preempt handler deal with it.
+       pushq $0                        # space for mxcsr and fpucw
+       fnstcw 0x4(%rsp)
+       stmxcsr (%rsp)
+       pushq %rdx                      # rsp, saved by userspace
+       pushq %rcx                      # rip, saved by hardware
+       pushq %r15
+       pushq %r14
+       pushq %r13
+       pushq %r12
+       pushq %rbp
+       pushq %rbx
+       # save fs and gs base
+       movl $MSR_FS_BASE, %ecx
+       rdmsr
+       shl $32, %rdx
+       orq %rax, %rdx
+       pushq %rdx
+       # because we swapped gs earlier, the user GS is now in KERN_GS_BASE
+       movl $MSR_KERN_GS_BASE, %ecx
+       rdmsr
+       shl $32, %rdx
+       orq %rax, %rdx
+       pushq %rdx
+       # make sure the kernel's gs base is loaded into the KERN slot at all times
+       movl $MSR_GS_BASE, %ecx
+       rdmsr
+       movl $MSR_KERN_GS_BASE, %ecx
+       wrmsr
        movq $0, %rbp                   # so we can backtrace to this point
+       movq %rsp, %rdx
+       # arg0, rdi: struct sysc*.  arg1, rsi: count.  arg2, rdx: sw_tf
        call sysenter_callwrapper
-       popq %rsp
-       # popal
-       popq %gs
-       popq %fs
-       # popq %es
-       # popq %ds
-       addq $0x10, %rsp                # pop T_SYSCALL and the three zeros
-       popfq                                   # restore EFLAGS (and usually enables interrupts!)
-       movq %rbp, %rcx
-       sti                                             # interrupts are turned off when starting a core
-       sysexit
+       # return via pop_tf, never this path
+sysenter_spin:
+       jmp sysenter_spin
index 8aba0b1..05b5dd7 100644 (file)
 #define MSR_GS_BASE                                    0xc0000101
 #define MSR_KERN_GS_BASE                       0xc0000102
 
+#define MSR_STAR                                       0xc0000081
+#define MSR_LSTAR                                      0xc0000082
+#define MSR_CSTAR                                      0xc0000083
+#define MSR_SFMASK                                     0xc0000084
+
 /* CPUID */
 #define CPUID_PSE_SUPPORT                      0x00000008
 
index 5c60d99..0b3a651 100644 (file)
@@ -111,6 +111,7 @@ static inline void pop_hw_tf(struct hw_trapframe *tf, uint32_t vcoreid)
                      "popl %%eax;           " /* &sysc, trap arg0 */
                      "pushl %%edx;          " /* save edx, will be trap arg1 */
                      "movl $0x1,%%edx;      " /* sending one async syscall: arg1 */
+                                 TODO double check these args
                      "int %1;               " /* fire the syscall */
                      "popl %%edx;           " /* restore regs after syscall */
                      "jmp 2f;               " /* skip 1:, already popped */
@@ -167,6 +168,7 @@ static inline void pop_sw_tf(struct sw_trapframe *sw_tf, uint32_t vcoreid)
                      /* Actual syscall.  Note we don't wait on the async call.
                       * &sysc is already in eax (trap arg0). */
                      "movl $0x1,%%edx;      " /* sending one async syscall: arg1 */
+                                 TODO double check these args
                      "int %3;               " /* fire the syscall */
                      "1: ret;               " /* retaddr was pushed earlier */
                      :