Add a few more pthread compilant definitions
[akaros.git] / user / pthread / pthread.c
index 7f7f323..af7a5fc 100644 (file)
@@ -68,39 +68,132 @@ struct schedule_ops *sched_ops = &pthread_sched_ops;
 /* Static helpers */
 static void __pthread_free_stack(struct pthread_tcb *pt);
 static int __pthread_allocate_stack(struct pthread_tcb *pt);
+static void __pth_yield_cb(struct uthread *uthread, void *junk);
 
-/* Trigger a posix signal on a pthread from vcore context */
-static void __pthread_trigger_posix_signal(pthread_t thread, int signo,
-                                           struct siginfo *info)
+/* Swap the contents of two user contexts (not just their pointers). */
+static void swap_user_contexts(struct user_context *c1, struct user_context *c2)
+{
+       struct user_context temp_ctx;
+       temp_ctx = *c1;
+       *c1 = *c2;
+       *c2 = temp_ctx;
+}
+
+/* Prep a pthread to run a signal handler.  The original context of the pthread
+ * is saved, and a new context with a new stack is set up to run the signal
+ * handler the next time the pthread is run. */
+static void __pthread_prep_sighandler(struct pthread_tcb *pthread,
+                                      void (*entry)(void),
+                                      struct siginfo *info)
 {
-       int vcoreid = vcore_id();
        struct user_context *ctx;
 
-       if (((struct uthread*)thread)->flags & UTHREAD_SAVED) {
-               ctx = &thread->uthread.u_ctx;
+       pthread->sigdata = alloc_sigdata();
+       if (info != NULL)
+               pthread->sigdata->info = *info;
+       init_user_ctx(&pthread->sigdata->u_ctx,
+                     (uintptr_t)entry,
+                     (uintptr_t)pthread->sigdata->stack);
+       if (pthread->uthread.flags & UTHREAD_SAVED) {
+               ctx = &pthread->uthread.u_ctx;
+               if (pthread->uthread.flags & UTHREAD_FPSAVED) {
+                       pthread->sigdata->as = pthread->uthread.as;
+                       pthread->uthread.flags &= ~UTHREAD_FPSAVED;
+               }
        } else {
-               struct preempt_data *vcpd = vcpd_of(vcoreid);
-               assert(current_uthread == thread);
-               ctx = &vcpd->uthread_ctx;
+               assert(current_uthread == &pthread->uthread);
+               ctx = &vcpd_of(vcore_id())->uthread_ctx;
+               save_fp_state(&pthread->sigdata->as);
+       }
+       swap_user_contexts(ctx, &pthread->sigdata->u_ctx);
+}
+
+/* Restore the context saved as the result of running a signal handler on a
+ * pthread. This context will execute the next time the pthread is run. */
+static void __pthread_restore_after_sighandler(struct pthread_tcb *pthread)
+{
+       pthread->uthread.u_ctx = pthread->sigdata->u_ctx;
+       pthread->uthread.flags |= UTHREAD_SAVED;
+       if (pthread->uthread.u_ctx.type == ROS_HW_CTX) {
+               pthread->uthread.as = pthread->sigdata->as;
+               pthread->uthread.flags |= UTHREAD_FPSAVED;
        }
+       free_sigdata(pthread->sigdata);
+       pthread->sigdata = NULL;
+}
 
-       void *temp_tls_desc = get_tls_desc(vcoreid);
-       set_tls_desc(thread->uthread.tls_desc, vcoreid);
-       trigger_posix_signal(signo, info, ctx);
-       set_tls_desc(temp_tls_desc, vcoreid);
+/* Callback when yielding a pthread after upon completion of a sighandler.  We
+ * didn't save the current context on yeild, but that's ok because here we
+ * restore the original saved context of the pthread and then treat this like a
+ * normal voluntary yield. */
+static void __exit_sighandler_cb(struct uthread *uthread, void *junk)
+{
+       __pthread_restore_after_sighandler((struct pthread_tcb*)uthread);
+       __pth_yield_cb(uthread, 0);
 }
 
-static void __pthread_trigger_pending_posix_signals(pthread_t thread)
+/* Run a specific sighandler from the top of the sigdata stack. The 'info'
+ * struct is prepopulated before the call is triggered as the result of a
+ * reflected fault. */
+static void __run_sighandler()
 {
-       if (thread->sigpending) {
-               sigset_t andset = thread->sigpending & (~thread->sigmask);
+       struct pthread_tcb *me = pthread_self();
+       __sigdelset(&me->sigpending, me->sigdata->info.si_signo);
+       trigger_posix_signal(me->sigdata->info.si_signo,
+                            &me->sigdata->info,
+                            &me->sigdata->u_ctx);
+       uthread_yield(FALSE, __exit_sighandler_cb, 0);
+}
+
+/* Run through all pending sighandlers and trigger them with a NULL info field.
+ * These handlers are triggered as the result of a pthread_kill(), and thus
+ * don't require individual 'info' structs. */
+static void __run_pending_sighandlers()
+{
+       struct pthread_tcb *me = pthread_self();
+       sigset_t andset = me->sigpending & (~me->sigmask);
+       for (int i = 1; i < _NSIG; i++) {
+               if (__sigismember(&andset, i)) {
+                       __sigdelset(&me->sigpending, i);
+                       trigger_posix_signal(i, NULL, &me->sigdata->u_ctx);
+               }
+       }
+       uthread_yield(FALSE, __exit_sighandler_cb, 0);
+}
+
+/* If the given signal is unmasked, prep the pthread to run it's signal
+ * handler, but don't run it yet. In either case, make the pthread runnable
+ * again. Once the signal handler is complete, the original context will be
+ * restored and restarted. */
+static void __pthread_signal_and_restart(struct pthread_tcb *pthread,
+                                          int signo, int code, void *addr)
+{
+       if (!__sigismember(&pthread->sigmask, signo)) {
+               if (pthread->sigdata) {
+                       printf("Pthread sighandler faulted, signal: %d\n", signo);
+                       /* uthread.c already copied out the faulting ctx into the uth */
+                       print_user_context(&pthread->uthread.u_ctx);
+                       exit(-1);
+               }
+               struct siginfo info = {0};
+               info.si_signo = signo;
+               info.si_code = code;
+               info.si_addr = addr;
+               __pthread_prep_sighandler(pthread, __run_sighandler, &info);
+       }
+       pth_thread_runnable(&pthread->uthread);
+}
+
+/* If there are any pending signals, prep the pthread to run it's signal
+ * handler. The next time the pthread is run, it will pop into it's signal
+ * handler context instead of its original saved context. Once the signal
+ * handler is complete, the original context will be restored and restarted. */
+static void __pthread_prep_for_pending_posix_signals(pthread_t pthread)
+{
+       if (!pthread->sigdata && pthread->sigpending) {
+               sigset_t andset = pthread->sigpending & (~pthread->sigmask);
                if (!__sigisemptyset(&andset)) {
-                       for (int i = 1; i < _NSIG; i++) {
-                               if (__sigismember(&andset, i)) {
-                                       __sigdelset(&thread->sigpending, i);
-                                       __pthread_trigger_posix_signal(thread, i, NULL);
-                               }
-                       }
+                       __pthread_prep_sighandler(pthread, __run_pending_sighandlers, NULL);
                }
        }
 }
@@ -112,10 +205,11 @@ void __attribute__((noreturn)) pth_sched_entry(void)
 {
        uint32_t vcoreid = vcore_id();
        if (current_uthread) {
-               /* Run any pending posix signal handlers registered via pthread_kill */
-               __pthread_trigger_pending_posix_signals((pthread_t)current_uthread);
-               run_current_uthread();
+               /* Prep the pthread to run any pending posix signal handlers registered
+         * via pthread_kill once it is restored. */
+               __pthread_prep_for_pending_posix_signals((pthread_t)current_uthread);
                /* Run the thread itself */
+               run_current_uthread();
                assert(0);
        }
        /* no one currently running, so lets get someone from the ready queue */
@@ -151,8 +245,9 @@ void __attribute__((noreturn)) pth_sched_entry(void)
                        vcore_yield(FALSE);
        } while (1);
        assert(new_thread->state == PTH_RUNNABLE);
-       /* Run any pending posix signal handlers registered via pthread_kill */
-       __pthread_trigger_pending_posix_signals(new_thread);
+       /* Prep the pthread to run any pending posix signal handlers registered
+     * via pthread_kill once it is restored. */
+       __pthread_prep_for_pending_posix_signals(new_thread);
        /* Run the thread itself */
        run_uthread((struct uthread*)new_thread);
        assert(0);
@@ -305,6 +400,44 @@ void pth_thread_has_blocked(struct uthread *uthread, int flags)
                printf("For great justice!\n");
 }
 
+static void handle_div_by_zero(struct uthread *uthread, unsigned int err,
+                               unsigned long aux)
+{
+       struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
+       __pthread_signal_and_restart(pthread, SIGFPE, FPE_INTDIV, (void*)aux);
+}
+
+static void handle_gp_fault(struct uthread *uthread, unsigned int err,
+                            unsigned long aux)
+{
+       struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
+       __pthread_signal_and_restart(pthread, SIGSEGV, SEGV_ACCERR, (void*)aux);
+}
+
+static void handle_page_fault(struct uthread *uthread, unsigned int err,
+                              unsigned long aux)
+{
+       struct pthread_tcb *pthread = (struct pthread_tcb*)uthread;
+       if (!(err & PF_VMR_BACKED)) {
+               __pthread_signal_and_restart(pthread, SIGSEGV, SEGV_MAPERR, (void*)aux);
+       } else {
+               /* stitching for the event handler.  sysc -> uth, uth -> sysc */
+               uthread->local_sysc.u_data = uthread;
+               uthread->sysc = &uthread->local_sysc;
+               pthread->state = PTH_BLK_SYSC;
+               /* one downside is that we'll never check the return val of the syscall.  if
+                * we errored out, we wouldn't know til we PF'd again, and inspected the old
+                * retval/err and other sysc fields (make sure the PF is on the same addr,
+                * etc).  could run into this issue on truncated files too. */
+               syscall_async(&uthread->local_sysc, SYS_populate_va, aux, 1);
+               if (!register_evq(&uthread->local_sysc, sysc_mgmt[vcore_id()].ev_q)) {
+                       /* Lost the race with the call being done.  The kernel won't send the
+                        * event.  Just restart him. */
+                       restart_thread(&uthread->local_sysc);
+               }
+       }
+}
+
 void pth_thread_refl_fault(struct uthread *uthread, unsigned int trap_nr,
                            unsigned int err, unsigned long aux)
 {
@@ -315,38 +448,27 @@ void pth_thread_refl_fault(struct uthread *uthread, unsigned int trap_nr,
        TAILQ_REMOVE(&active_queue, pthread, next);
        mcs_pdr_unlock(&queue_lock);
 
-       if (trap_nr != 14 && trap_nr != 13) {
-               printf("Pthread has unhandled fault: %d\n", trap_nr);
-               print_user_context(&uthread->u_ctx);
-               exit(-1);
-       }
-
-       if (!(err & PF_VMR_BACKED)) {
-               if (!__sigismember(&pthread->sigmask, SIGSEGV)) {
-                       struct siginfo info = {0};
-                       info.si_signo = SIGSEGV;
-                       info.si_errno = trap_nr;
-                       info.si_code = SEGV_MAPERR;
-                       info.si_addr = (void*)aux;
-                       __pthread_trigger_posix_signal(pthread, SIGSEGV, &info);
-               }
-               pth_thread_runnable(uthread);
-               return;
-       }
-       /* stitching for the event handler.  sysc -> uth, uth -> sysc */
-       uthread->local_sysc.u_data = uthread;
-       uthread->sysc = &uthread->local_sysc;
-       pthread->state = PTH_BLK_SYSC;
-       /* one downside is that we'll never check the return val of the syscall.  if
-        * we errored out, we wouldn't know til we PF'd again, and inspected the old
-        * retval/err and other sysc fields (make sure the PF is on the same addr,
-        * etc).  could run into this issue on truncated files too. */
-       syscall_async(&uthread->local_sysc, SYS_populate_va, aux, 1);
-       if (!register_evq(&uthread->local_sysc, sysc_mgmt[vcore_id()].ev_q)) {
-               /* Lost the race with the call being done.  The kernel won't send the
-                * event.  Just restart him. */
-               restart_thread(&uthread->local_sysc);
+       /* TODO: RISCV/x86 issue! (0 is divby0, 14 is PF, etc) */
+#if defined(__i386__) || defined(__x86_64__) 
+       switch(trap_nr) {
+               case 0:
+                       handle_div_by_zero(uthread, err, aux);
+                       break;
+               case 13:
+                       handle_gp_fault(uthread, err, aux);
+                       break;
+               case 14:
+                       handle_page_fault(uthread, err, aux);
+                       break;
+               default:
+                       printf("Pthread has unhandled fault: %d\n", trap_nr);
+                       /* Note that uthread.c already copied out our ctx into the uth struct */
+                       print_user_context(&uthread->u_ctx);
+                       exit(-1);
        }
+#else
+       #error "Handling hardware faults is currently only supported on x86"
+#endif
 }
 
 void pth_preempt_pending(void)
@@ -423,6 +545,25 @@ int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize)
        return 0;
 }
 
+int pthread_attr_getstack(const pthread_attr_t *__restrict __attr,
+                                                  void **__stackaddr, size_t *__stacksize)
+{
+       *__stackaddr = __attr->stackaddr;
+       *__stacksize = __attr->stacksize;
+       return 0;
+}
+
+int pthread_getattr_np(pthread_t __th, pthread_attr_t *__attr)
+{
+       __attr->stackaddr = __th->stacktop - __th->stacksize;
+       __attr->stacksize = __th->stacksize;
+       if (__th->detached)
+               __attr->detachstate = PTHREAD_CREATE_DETACHED;
+       else
+               __attr->detachstate = PTHREAD_CREATE_JOINABLE;
+       return 0;
+}
+
 /* Do whatever init you want.  At some point call uthread_lib_init() and pass it
  * a uthread representing thread0 (int main()) */
 void pthread_lib_init(void)
@@ -532,6 +673,7 @@ int __pthread_create(pthread_t *thread, const pthread_attr_t *attr,
        pthread->joiner = 0;
        pthread->sigmask = ((pthread_t)current_uthread)->sigmask;
        __sigemptyset(&pthread->sigpending);
+       pthread->sigdata = NULL;
        /* Respect the attributes */
        if (attr) {
                if (attr->stacksize)                                    /* don't set a 0 stacksize */
@@ -1140,3 +1282,26 @@ int pthread_setspecific(pthread_key_t key, const void *value)
        return 0;
 }
 
+int pthread_mutex_timedlock (pthread_mutex_t *__restrict __mutex,
+                                       const struct timespec *__restrict
+                                       __abstime)
+{
+       fprintf(stderr, "Unsupported %s!", __FUNCTION__);
+       abort();
+       return -1;
+}
+int pthread_cond_timedwait (pthread_cond_t *__restrict __cond,
+                                  pthread_mutex_t *__restrict __mutex,
+                                  const struct timespec *__restrict __abstime)
+{
+       fprintf(stderr, "Unsupported %s!", __FUNCTION__);
+       abort();
+       return -1;
+}
+
+int pthread_cancel (pthread_t __th)
+{
+       fprintf(stderr, "Unsupported %s!", __FUNCTION__);
+       abort();
+       return -1;
+}