Check block extra_len in checkb()
[akaros.git] / kern / src / kthread.c
index 1436472..173722d 100644 (file)
@@ -11,6 +11,8 @@
 #include <pmap.h>
 #include <smp.h>
 #include <schedule.h>
+#include <kstack.h>
+#include <arch/uaccess.h>
 
 uintptr_t get_kstack(void)
 {
@@ -106,7 +108,7 @@ void restart_kthread(struct kthread *kthread)
                pcpui->cur_proc = kthread->proc;
        }
        /* Finally, restart our thread */
-       pop_kernel_ctx(&kthread->context);
+       longjmp(&kthread->context, 1);
 }
 
 /* Kmsg handler to launch/run a kthread.  This must be a routine message, since
@@ -179,6 +181,26 @@ void kthread_yield(void)
        sem_down(sem);
 }
 
+void kthread_usleep(uint64_t usec)
+{
+       ERRSTACK(1);
+       /* TODO: classic ksched issue: where do we want the wake up to happen? */
+       struct timer_chain *tchain = &per_cpu_info[core_id()].tchain;
+       struct rendez rv;
+
+       int ret_zero(void *ignored)
+       {
+               return 0;
+       }
+
+       /* "discard the error" style (we run the conditional code) */
+       if (!waserror()) {
+               rendez_init(&rv);
+               rendez_sleep_timeout(&rv, ret_zero, 0, usec);
+       }
+       poperror();
+}
+
 static void __ktask_wrapper(uint32_t srcid, long a0, long a1, long a2)
 {
        ERRSTACK(1);
@@ -186,7 +208,7 @@ static void __ktask_wrapper(uint32_t srcid, long a0, long a1, long a2)
        void *arg = (void*)a1;
        char *name = (char*)a2;
        struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
-       assert(pcpui->cur_kthread->is_ktask);
+       assert(is_ktask(pcpui->cur_kthread));
        pcpui->cur_kthread->name = name;
        /* There are some rendezs out there that aren't wrapped.  Though no one can
         * abort them.  Yet. */
@@ -218,8 +240,8 @@ void check_poison(char *msg)
 {
 #ifdef CONFIG_KTHREAD_POISON
        struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
-       assert(pcpui->cur_kthread && pcpui->cur_kthread->stacktop);
-       if (*kstack_bottom_addr(pcpui->cur_kthread->stacktop) != 0xdeadbeef) {
+       if (pcpui->cur_kthread && pcpui->cur_kthread->stacktop &&
+           (*kstack_bottom_addr(pcpui->cur_kthread->stacktop) != 0xdeadbeef)) {
                printk("\nBad kthread canary, msg: %s\n", msg);
                panic("");
        }
@@ -259,6 +281,9 @@ void sem_init_irqsave(struct semaphore *sem, int signals)
 bool sem_trydown(struct semaphore *sem)
 {
        bool ret = FALSE;
+       /* lockless peek */
+       if (sem->nr_signals <= 0)
+               return ret;
        spin_lock(&sem->lock);
        if (sem->nr_signals > 0) {
                sem->nr_signals--;
@@ -274,19 +299,31 @@ bool sem_trydown(struct semaphore *sem)
  * signal is already there is not optimized. */
 void sem_down(struct semaphore *sem)
 {
-       volatile bool blocking = TRUE;  /* signal to short circuit when restarting*/
        struct kthread *kthread, *new_kthread;
        register uintptr_t new_stacktop;
        struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
+       bool irqs_were_on = irq_is_enabled();
 
        assert(can_block(pcpui));
        /* Make sure we aren't holding any locks (only works if SPINLOCK_DEBUG) */
-       assert(!pcpui->lock_depth);
+       if (pcpui->lock_depth)
+               panic("Kthread tried to sleep, with lockdepth %d\n", pcpui->lock_depth);
        assert(pcpui->cur_kthread);
        /* Try to down the semaphore.  If there is a signal there, we can skip all
         * of the sleep prep and just return. */
+#ifdef CONFIG_SEM_SPINWAIT
+       for (int i = 0; i < CONFIG_SEM_SPINWAIT_NR_LOOPS; i++) {
+               if (sem_trydown(sem))
+                       goto block_return_path;
+               cpu_relax();
+       }
+#else
        if (sem_trydown(sem))
                goto block_return_path;
+#endif
+#ifdef CONFIG_SEM_TRACE_BLOCKERS
+       TRACEME();
+#endif
        /* We're probably going to sleep, so get ready.  We'll check again later. */
        kthread = pcpui->cur_kthread;
        /* We need to have a spare slot for restart, so we also use it when
@@ -299,16 +336,16 @@ void sem_down(struct semaphore *sem)
                new_kthread = pcpui->spare;
                new_stacktop = new_kthread->stacktop;
                pcpui->spare = 0;
-               /* Based on how we set is_ktask (in PRKM), we'll usually have a spare
-                * with is_ktask set, even though the default setting is off.  The
-                * reason is that the launching of blocked kthreads also uses PRKM, and
-                * that KMSG (__launch_kthread) doesn't return.  Thus the soon-to-be
-                * spare kthread, that is launching another, has is_ktask set. */
-               new_kthread->is_ktask = FALSE;
+               /* The old flags could have KTH_IS_KTASK set.  The reason is that the
+                * launching of blocked kthreads also uses PRKM, and that KMSG
+                * (__launch_kthread) doesn't return.  Thus the soon-to-be spare
+                * kthread, that is launching another, has flags & KTH_IS_KTASK set. */
+               new_kthread->flags = KTH_DEFAULT_FLAGS;
                new_kthread->proc = 0;
                new_kthread->name = 0;
        } else {
                new_kthread = __kthread_zalloc();
+               new_kthread->flags = KTH_DEFAULT_FLAGS;
                new_stacktop = get_kstack();
                new_kthread->stacktop = new_stacktop;
 #ifdef CONFIG_KTHREAD_POISON
@@ -330,53 +367,45 @@ void sem_down(struct semaphore *sem)
 #endif /* CONFIG_KTHREAD_POISON */
        /* Kthreads that are ktasks are not related to any process, and do not need
         * to work in a process's address space.  They can operate in any address
-        * space that has the kernel mapped (like boot_pgdir, or any pgdir).
+        * space that has the kernel mapped (like boot_pgdir, or any pgdir).  Some
+        * ktasks may switch_to, at which point they do care about the address
+        * space and must maintain a reference.
         *
-        * Other kthreads need to stay in the process context (if there is one), but
-        * we want the core (which could be a vcore) to stay in the context too.  In
-        * the future, we could check owning_proc. If it isn't set, we could leave
-        * the process context and transfer the refcnt to kthread->proc. */
-       if (!kthread->is_ktask) {
+        * Normal kthreads need to stay in the process context, but we want the core
+        * (which could be a vcore) to stay in the context too. */
+       if (kthread->flags & KTH_SAVE_ADDR_SPACE) {
                kthread->proc = current;
-               if (kthread->proc)      /* still could be none, like during init */
-                       proc_incref(kthread->proc, 1);
+               assert(kthread->proc);
+               /* In the future, we could check owning_proc. If it isn't set, we could
+                * clear current and transfer the refcnt to kthread->proc. */
+               proc_incref(kthread->proc, 1);
        } else {
                kthread->proc = 0;
        } 
-       /* Save the context, toggle blocking for the reactivation */
-       save_kernel_ctx(&kthread->context);
-       if (!blocking)
+       if (setjmp(&kthread->context))
                goto block_return_path;
-       blocking = FALSE;                                       /* for when it starts back up */
-       /* Down the semaphore.  We need this to be inline.  If we're sleeping, once
-        * we unlock the kthread could be started up again and can return and start
-        * trashing this function's stack, hence the weird control flow. */
        spin_lock(&sem->lock);
        if (sem->nr_signals-- <= 0) {
                TAILQ_INSERT_TAIL(&sem->waiters, kthread, link);
-               debug_downed_sem(sem);
+               debug_downed_sem(sem);  /* need to debug after inserting */
                /* At this point, we know we'll sleep and change stacks later.  Once we
                 * unlock, we could have the kthread restarted (possibly on another
                 * core), so we need to disable irqs until we are on our new stack.
                 * Otherwise, if we take an IRQ, we'll be using our stack while another
                 * core is using it (restarted kthread).  Basically, disabling irqs
-                * allows us to atomically unlock and 'yield'. */
+                * allows us to atomically unlock and 'yield'.  Also, IRQs might have
+                * already been disabled if this was an irqsave sem. */
                disable_irq();
-       } else {                                                        /* we didn't sleep */
-               debug_downed_sem(sem);
-               goto unwind_sleep_prep;
+               spin_unlock(&sem->lock);
+               /* Switch to the core's default stack.  After this, don't use local
+                * variables. */
+               set_stack_pointer(new_stacktop);
+               smp_idle();                                                     /* reenables irqs eventually */
+               assert(0);
        }
-       spin_unlock(&sem->lock);
-       /* Switch to the core's default stack.  After this, don't use local
-        * variables.  TODO: we shouldn't be using new_stacktop either, can't always
-        * trust the register keyword (AFAIK). */
-       set_stack_pointer(new_stacktop);
-       smp_idle();                                                     /* reenables irqs eventually */
-       /* smp_idle never returns */
-       assert(0);
-unwind_sleep_prep:
        /* We get here if we should not sleep on sem (the signal beat the sleep).
-        * Note we are not optimizing for cases where the signal won. */
+        * We debug_downed_sem since we actually downed it - just didn't sleep. */
+       debug_downed_sem(sem);
        spin_unlock(&sem->lock);
        printd("[kernel] Didn't sleep, unwinding...\n");
        /* Restore the core's current and default stacktop */
@@ -395,6 +424,12 @@ unwind_sleep_prep:
 #endif /* CONFIG_KTHREAD_POISON */
 block_return_path:
        printd("[kernel] Returning from being 'blocked'! at %llu\n", read_tsc());
+       /* restart_kthread and longjmp did not reenable IRQs.  We need to make sure
+        * irqs are on if they were on when we started to block.  If they were
+        * already on and we short-circuited the block, it's harmless to reenable
+        * them. */
+       if (irqs_were_on)
+               enable_irq();
        return;
 }
 
@@ -514,6 +549,7 @@ void print_sem_info(struct semaphore *sem)
        TAILQ_FOREACH(kth_i, &sem->waiters, link)
                printk("\tKthread %p (%s), proc %d (%p), sysc %p\n", kth_i, kth_i->name,
                       kth_i->proc ? kth_i->proc->pid : 0, kth_i->proc, kth_i->sysc);
+       printk("\n");
        spin_unlock_irqsave(&sem->lock);
 }
 
@@ -694,7 +730,8 @@ void cv_broadcast_irqsave(struct cond_var *cv, int8_t *irq_state)
        enable_irqsave(irq_state);
 }
 
-/* Helper, aborts and releases a CLE.  dereg_ spinwaits on abort_in_progress. */
+/* Helper, aborts and releases a CLE.  dereg_ spinwaits on abort_in_progress.
+ * This can throw a PF */
 static void __abort_and_release_cle(struct cv_lookup_elm *cle)
 {
        int8_t irq_state = 0;
@@ -725,8 +762,10 @@ static void __abort_and_release_cle(struct cv_lookup_elm *cle)
  *   all the memory for CLE is safe */
 bool abort_sysc(struct proc *p, struct syscall *sysc)
 {
+       ERRSTACK(1);
        struct cv_lookup_elm *cle;
        int8_t irq_state = 0;
+
        spin_lock_irqsave(&p->abort_list_lock);
        TAILQ_FOREACH(cle, &p->abortable_sleepers, link) {
                if (cle->sysc == sysc) {
@@ -739,20 +778,28 @@ bool abort_sysc(struct proc *p, struct syscall *sysc)
        spin_unlock_irqsave(&p->abort_list_lock);
        if (!cle)
                return FALSE;
-       __abort_and_release_cle(cle);
+       if (!waserror())        /* discard error */
+               __abort_and_release_cle(cle);
+       poperror();
        return TRUE;
 }
 
-/* This will abort any abortabls at the time the call was started.  New
- * abortables could be registered concurrently.  The main caller I see for this
- * is proc_destroy(), so DYING will be set, and new abortables will quickly
- * abort and dereg when they see their proc is DYING. */
-void abort_all_sysc(struct proc *p)
+/* This will abort any abortables at the time the call was started for which
+ * should_abort(cle, arg) returns true.  New abortables could be registered
+ * concurrently.  The original for this is proc_destroy(), so DYING will be set,
+ * and new abortables will quickly abort and dereg when they see their proc is
+ * DYING. */
+static int __abort_all_sysc(struct proc *p,
+                            bool (*should_abort)(struct cv_lookup_elm*, void*),
+                            void *arg)
 {
+       ERRSTACK(1);
        struct cv_lookup_elm *cle;
        int8_t irq_state = 0;
        struct cv_lookup_tailq abortall_list;
-       struct proc *old_proc = switch_to(p);
+       uintptr_t old_proc = switch_to(p);
+       int ret = 0;
+
        /* Concerns: we need to not remove them from their original list, since
         * concurrent wake ups will cause a dereg, which will remove from the list.
         * We also can't touch freed memory, so we need a refcnt to keep cles
@@ -760,13 +807,54 @@ void abort_all_sysc(struct proc *p)
        TAILQ_INIT(&abortall_list);
        spin_lock_irqsave(&p->abort_list_lock);
        TAILQ_FOREACH(cle, &p->abortable_sleepers, link) {
+               if (!should_abort(cle, arg))
+                       continue;
                atomic_inc(&cle->abort_in_progress);
                TAILQ_INSERT_HEAD(&abortall_list, cle, abortall_link);
+               ret++;
        }
        spin_unlock_irqsave(&p->abort_list_lock);
-       TAILQ_FOREACH(cle, &abortall_list, abortall_link)
-               __abort_and_release_cle(cle);
+       if (!waserror()) { /* discard error */
+               TAILQ_FOREACH(cle, &abortall_list, abortall_link)
+                       __abort_and_release_cle(cle);
+       }
+       poperror();
        switch_back(p, old_proc);
+       return ret;
+}
+
+static bool always_abort(struct cv_lookup_elm *cle, void *arg)
+{
+       return TRUE;
+}
+
+void abort_all_sysc(struct proc *p)
+{
+       __abort_all_sysc(p, always_abort, 0);
+}
+
+/* cle->sysc could be a bad pointer.  we can either use copy_from_user (btw,
+ * we're already in their addr space) or we can use a waserror in
+ * __abort_all_sysc().  Both options are fine.  I went with it here for a couple
+ * reasons.  It is only this abort function pointer that accesses sysc, though
+ * that could change.  Our syscall aborting isn't plugged into a broader error()
+ * handler yet, which means we'd want to poperror instead of nexterror in
+ * __abort_all_sysc, and that would required int ret getting a volatile flag. */
+static bool sysc_uses_fd(struct cv_lookup_elm *cle, void *fd)
+{
+       struct syscall local_sysc;
+       int err;
+
+       err = copy_from_user(&local_sysc, cle->sysc, sizeof(struct syscall));
+       /* Trigger an abort on error */
+       if (err)
+               return TRUE;
+       return syscall_uses_fd(&local_sysc, (int)(long)fd);
+}
+
+int abort_all_sysc_fd(struct proc *p, int fd)
+{
+       return __abort_all_sysc(p, sysc_uses_fd, (void*)(long)fd);
 }
 
 /* Being on the abortable list means that the CLE, KTH, SYSC, and CV are valid
@@ -779,12 +867,11 @@ void __reg_abortable_cv(struct cv_lookup_elm *cle, struct cond_var *cv)
        cle->cv = cv;
        cle->kthread = pcpui->cur_kthread;
        /* Could be a ktask.  Can build in support for aborting these later */
-       if (cle->kthread->is_ktask) {
+       if (is_ktask(cle->kthread)) {
                cle->sysc = 0;
                return;
        }
        cle->sysc = cle->kthread->sysc;
-       assert(cle->sysc);
        cle->proc = pcpui->cur_proc;
        atomic_init(&cle->abort_in_progress, 0);
        spin_lock_irqsave(&cle->proc->abort_list_lock);
@@ -799,7 +886,7 @@ void __reg_abortable_cv(struct cv_lookup_elm *cle, struct cond_var *cv)
  * CV lock.  So if we hold the CV lock, we can deadlock (circular dependency).*/
 void dereg_abortable_cv(struct cv_lookup_elm *cle)
 {
-       if (cle->kthread->is_ktask)
+       if (is_ktask(cle->kthread))
                return;
        assert(cle->proc);
        spin_lock_irqsave(&cle->proc->abort_list_lock);
@@ -815,11 +902,48 @@ void dereg_abortable_cv(struct cv_lookup_elm *cle)
  * this with things for ktasks in the future. */
 bool should_abort(struct cv_lookup_elm *cle)
 {
-       if (cle->kthread->is_ktask)
+       struct syscall local_sysc;
+       int err;
+
+       if (is_ktask(cle->kthread))
                return FALSE;
        if (cle->proc && (cle->proc->state == PROC_DYING))
                return TRUE;
-       if (cle->sysc && (atomic_read(&cle->sysc->flags) & SC_ABORT))
-               return TRUE;
+       if (cle->sysc) {
+               assert(cle->proc && (cle->proc == current));
+               err = copy_from_user(&local_sysc, cle->sysc,
+                                    offsetof(struct syscall, flags) +
+                                    sizeof(cle->sysc->flags));
+               /* just go ahead and abort if there was an error */
+               if (err || (atomic_read(&local_sysc.flags) & SC_ABORT))
+                       return TRUE;
+       }
        return FALSE;
 }
+
+/* Sometimes the kernel needs to switch out of process context and into a
+ * 'process-less' kernel thread.  This is basically a ktask.  We use this mostly
+ * when performing file ops as the kernel.  It's nasty, and all uses of this
+ * probably should be removed.  (TODO: KFOP). */
+uintptr_t switch_to_ktask(void)
+{
+       struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
+       struct kthread *kth = pcpui->cur_kthread;
+
+       if (is_ktask(kth))
+               return 0;
+       /* We leave the SAVE_ADDR_SPACE flag on.  Now we're basically a ktask that
+        * cares about its addr space, since we need to return to it (not that we're
+        * leaving). */
+       kth->flags |= KTH_IS_KTASK;
+       return 1;
+}
+
+void switch_back_from_ktask(uintptr_t old_ret)
+{
+       struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
+       struct kthread *kth = pcpui->cur_kthread;
+
+       if (old_ret)
+               kth->flags &= ~KTH_IS_KTASK;
+}