+
+/* 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;
+ /* At this point, we have a handle on the syscall that we want to abort (via
+ * the cle), and we know none of the memory will disappear on us (deregers
+ * wait on the flag). So we'll signal ABORT, which rendez will pick up next
+ * time it is awake. Then we make sure it is awake with a broadcast. */
+ atomic_or(&cle->sysc->flags, SC_ABORT);
+ cmb(); /* flags write before signal; atomic op provided CPU mb */
+ cv_broadcast_irqsave(cle->cv, &irq_state);
+ cmb(); /* broadcast writes before abort flag; atomic op provided CPU mb */
+ atomic_dec(&cle->abort_in_progress);
+}
+
+/* Attempts to abort p's sysc. It will only do so if the sysc lookup succeeds,
+ * so we can handle "guesses" for syscalls that might not be sleeping. This
+ * style of "do it if you know you can" is the best way here - anything else
+ * runs into situations where you don't know if the memory is safe to touch or
+ * not (we're doing a lookup via pointer address, and only dereferencing if that
+ * succeeds). Even something simple like letting userspace write SC_ABORT is
+ * very hard for them, since they don't know a sysc's state for sure (under the
+ * current system).
+ *
+ * Here are the rules:
+ * - if you're flagged SC_ABORT, you don't sleep
+ * - if you sleep, you're on the list
+ * - if you are on the list or abort_in_progress is set, CV is signallable, and
+ * 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) {
+ /* Note: we could have multiple aborters, so we need to use a
+ * numeric refcnt instead of a flag. */
+ atomic_inc(&cle->abort_in_progress);
+ break;
+ }
+ }
+ spin_unlock_irqsave(&p->abort_list_lock);
+ if (!cle)
+ return FALSE;
+ if (!waserror()) /* discard error */
+ __abort_and_release_cle(cle);
+ poperror();
+ return TRUE;
+}
+
+/* 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.
+ *
+ * One caller for this is proc_destroy(), in which case DYING_ABORT will be set,
+ * and new abortables will quickly abort and dereg when they see their proc is
+ * DYING_ABORT. */
+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;
+ 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
+ * around. */
+ 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);
+ 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
+ * memory. The lock ordering is {CV lock, list_lock}. Callers to this *will*
+ * have CV held. This is done to avoid excessive locking in places like
+ * rendez_sleep, which want to check the condition before registering. */
+void __reg_abortable_cv(struct cv_lookup_elm *cle, struct cond_var *cv)
+{
+ struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
+ cle->cv = cv;
+ cle->kthread = pcpui->cur_kthread;
+ /* Could be a ktask. Can build in support for aborting these later */
+ if (is_ktask(cle->kthread)) {
+ cle->sysc = 0;
+ return;
+ }
+ cle->sysc = cle->kthread->sysc;
+ cle->proc = pcpui->cur_proc;
+ atomic_init(&cle->abort_in_progress, 0);
+ spin_lock_irqsave(&cle->proc->abort_list_lock);
+ TAILQ_INSERT_HEAD(&cle->proc->abortable_sleepers, cle, link);
+ spin_unlock_irqsave(&cle->proc->abort_list_lock);
+}
+
+/* We're racing with the aborter too, who will hold the flag in cle to protect
+ * its ref on our cle. While the lock ordering is CV, list, callers to this
+ * must *not* have the cv lock held. The reason is this waits on a successful
+ * abort_sysc, which is trying to cv_{signal,broadcast}, which could wait on the
+ * CV lock. So if we hold the CV lock, we can deadlock (circular dependency).*/
+void dereg_abortable_cv(struct cv_lookup_elm *cle)
+{
+ if (is_ktask(cle->kthread))
+ return;
+ assert(cle->proc);
+ spin_lock_irqsave(&cle->proc->abort_list_lock);
+ TAILQ_REMOVE(&cle->proc->abortable_sleepers, cle, link);
+ spin_unlock_irqsave(&cle->proc->abort_list_lock);
+ /* If we won the race and yanked it out of the list before abort claimed it,
+ * this will already be FALSE. */
+ while (atomic_read(&cle->abort_in_progress))
+ cpu_relax();
+}
+
+/* Helper to sleepers to know if they should abort or not. I'll probably extend
+ * this with things for ktasks in the future. */
+bool should_abort(struct cv_lookup_elm *cle)
+{
+ struct syscall local_sysc;
+ int err;
+
+ if (is_ktask(cle->kthread))
+ return FALSE;
+ if (cle->proc && (cle->proc->state == PROC_DYING_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;
+}