Adds reset_alarm_abs()
authorBarret Rhoden <brho@cs.berkeley.edu>
Wed, 13 Nov 2013 02:10:36 +0000 (18:10 -0800)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 16 Jan 2014 19:36:29 +0000 (11:36 -0800)
Alarm resets will allow a caller to update the time at which the alarm
will fire.  This way, you can increase the time of an alarm, while
keeping the system armed.  It is equivalent to unset_alarm; set_alarm;,
except that there is only one resetting of the tchain interrupt.

There is no guarantee that the alarm hasn't already fired.  If it has,
it will get reinserted into the tchain.

This isn't super-necessary for the kernel, since resetting the IRQ is
pretty easy.  But userspace will have alarm code that is highly similar
(and will use k/s/alarm.c as a base) and resetting the IRQ takes a
syscall from userspace.

If we need _rel and _inc, we can add those easily.

kern/include/alarm.h
kern/src/alarm.c
kern/src/testing.c

index d1b9e07..d2a153c 100644 (file)
@@ -94,6 +94,8 @@ void set_awaiter_inc(struct alarm_waiter *waiter, uint64_t usleep);
 void __set_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter);
 void set_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter);
 bool unset_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter);
+void reset_alarm_abs(struct timer_chain *tchain, struct alarm_waiter *waiter,
+                     uint64_t abs_time);
 /* Blocks on the alarm waiter */
 int sleep_on_awaiter(struct alarm_waiter *waiter);
 /* Interrupt handlers needs to call this.  Don't call it directly. */
index cf1fd70..29db9d9 100644 (file)
@@ -148,10 +148,10 @@ void trigger_tchain(struct timer_chain *tchain)
        spin_unlock(&tchain->lock);
 }
 
-/* Sets the alarm.  If it is a kthread-style alarm (func == 0), sleep on it
- * later.  This version assumes you have the lock held.  That only makes sense
- * from alarm handlers, which are called with this lock held from IRQ context */
-void __set_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter)
+/* Helper, inserts the waiter into the tchain, returning TRUE if we still need
+ * to reset the tchain interrupt.  Caller holds the lock. */
+static bool __insert_awaiter(struct timer_chain *tchain,
+                             struct alarm_waiter *waiter)
 {
        struct alarm_waiter *i, *temp;
        /* This will fail if you don't set a time */
@@ -164,7 +164,7 @@ void __set_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter)
                tchain->latest_time = waiter->wake_up_time;
                TAILQ_INSERT_HEAD(&tchain->waiters, waiter, next);
                /* Need to turn on the timer interrupt later */
-               goto reset_out;
+               return TRUE;
        }
        /* If not, either we're first, last, or in the middle.  Reset the interrupt
         * and adjust the tchain's times accordingly. */
@@ -172,7 +172,7 @@ void __set_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter)
                tchain->earliest_time = waiter->wake_up_time;
                TAILQ_INSERT_HEAD(&tchain->waiters, waiter, next);
                /* Changed the first entry; we'll need to reset the interrupt later */
-               goto reset_out;
+               return TRUE;
        }
        /* If there is a tie for last, the newer one will really go last.  We need
         * to handle equality here since the loop later won't catch it. */
@@ -180,7 +180,7 @@ void __set_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter)
                tchain->latest_time = waiter->wake_up_time;
                /* Proactively put it at the end if we know we're last */
                TAILQ_INSERT_TAIL(&tchain->waiters, waiter, next);
-               goto no_reset_out;
+               return FALSE;
        }
        /* Insert before the first one you are earlier than.  This won't scale well
         * (TODO) if we have a lot of inserts.  The proactive insert_tail up above
@@ -188,15 +188,19 @@ void __set_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter)
        TAILQ_FOREACH_SAFE(i, &tchain->waiters, next, temp) {
                if (waiter->wake_up_time < i->wake_up_time) {
                        TAILQ_INSERT_BEFORE(i, waiter, next);
-                       goto no_reset_out;
+                       return FALSE;
                }
        }
        panic("Could not find a spot for awaiter %p\n", waiter);
-reset_out:
-       reset_tchain_interrupt(tchain);
-no_reset_out:
-       ;
-       /* TODO: could put some debug stuff here */
+}
+
+/* Sets the alarm.  If it is a kthread-style alarm (func == 0), sleep on it
+ * later.  This version assumes you have the lock held.  That only makes sense
+ * from alarm handlers, which are called with this lock held from IRQ context */
+void __set_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter)
+{
+       if (__insert_awaiter(tchain, waiter))
+               reset_tchain_interrupt(tchain);
 }
 
 /* Sets the alarm.  Don't call this from an alarm handler, since you already
@@ -208,23 +212,14 @@ void set_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter)
        spin_unlock_irqsave(&tchain->lock);
 }
 
-/* Removes waiter from the tchain before it goes off.  Returns TRUE if we
- * disarmed before the alarm went off, FALSE if it already fired. */
-bool unset_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter)
+/* Helper, rips the waiter from the tchain, knowing that it is on the list.
+ * Returns TRUE if the tchain interrupt needs to be reset.  Callers hold the
+ * lock. */
+static bool __remove_awaiter(struct timer_chain *tchain,
+                             struct alarm_waiter *waiter)
 {
        struct alarm_waiter *temp;
        bool reset_int = FALSE;         /* whether or not to reset the interrupt */
-
-       spin_lock_irqsave(&tchain->lock);
-       if (waiter->has_fired) {
-               /* the alarm has already gone off.  its not even on this tchain's list,
-                * though the concurrent change to has_fired (specifically, the setting
-                * of it to TRUE), happens under the tchain's lock.  As a side note, the
-                * code that sets it to FALSE is called when the waiter is on no chain,
-                * so there is no race on that. */
-               spin_unlock_irqsave(&tchain->lock);
-               return FALSE;
-       }
        /* Need to make sure earliest and latest are set, in case we're mucking with
         * the first and/or last element of the chain. */
        if (TAILQ_FIRST(&tchain->waiters) == waiter) {
@@ -237,12 +232,49 @@ bool unset_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter)
                tchain->latest_time = (temp) ? temp->wake_up_time : ALARM_POISON_TIME;
        }
        TAILQ_REMOVE(&tchain->waiters, waiter, next);
-       if (reset_int)
+       return reset_int;
+}
+
+/* Removes waiter from the tchain before it goes off.  Returns TRUE if we
+ * disarmed before the alarm went off, FALSE if it already fired. */
+bool unset_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter)
+{
+       spin_lock_irqsave(&tchain->lock);
+       if (waiter->has_fired) {
+               /* the alarm has already gone off.  its not even on this tchain's list,
+                * though the concurrent change to has_fired (specifically, the setting
+                * of it to TRUE), happens under the tchain's lock.  As a side note, the
+                * code that sets it to FALSE is called when the waiter is on no chain,
+                * so there is no race on that. */
+               spin_unlock_irqsave(&tchain->lock);
+               return FALSE;
+       }
+       if (__remove_awaiter(tchain, waiter))
                reset_tchain_interrupt(tchain);
        spin_unlock_irqsave(&tchain->lock);
        return TRUE;
 }
 
+/* waiter may be on the tchain, or it might have fired already and be off the
+ * tchain.  Either way, this will put the waiter on the list, set to go off at
+ * abs_time.  If you know the alarm has fired, don't call this.  Just set the
+ * awaiter, and then set_alarm() */
+void reset_alarm_abs(struct timer_chain *tchain, struct alarm_waiter *waiter,
+                     uint64_t abs_time)
+{
+       bool reset_int = FALSE;         /* whether or not to reset the interrupt */
+       spin_lock_irqsave(&tchain->lock);
+       /* We only need to remove/unset when the alarm has not fired yet.  If it
+        * has, it's like a fresh insert */
+       if (!waiter->has_fired)
+               reset_int = __remove_awaiter(tchain, waiter);
+       set_awaiter_abs(waiter, abs_time);
+       /* regardless, we need to be reinserted, which will handle has_fired */
+       if (__insert_awaiter(tchain, waiter) || reset_int)
+               reset_tchain_interrupt(tchain);
+       spin_unlock_irqsave(&tchain->lock);
+}
+
 /* Attempts to sleep on the alarm.  Could fail if you aren't allowed to kthread
  * (process limit, etc).  Don't call it on a waiter that is an event-handler. */
 int sleep_on_awaiter(struct alarm_waiter *waiter)
index 794361e..768244a 100644 (file)
@@ -1849,3 +1849,37 @@ void test_rv(void)
        assert(!rv->cv.nr_waiters);
        printk("test_rv: lots of sleepers/timeouts complete\n");
 }
+
+/* Cheap test for the alarm internal management */
+void test_alarm(void)
+{
+       uint64_t now = tsc2usec(read_tsc());
+       struct alarm_waiter await1, await2;
+       struct timer_chain *tchain = &per_cpu_info[0].tchain;
+       void shouldnt_run(struct alarm_waiter *awaiter)
+       {
+               printk("Crap, %p ran!\n", awaiter);
+       }
+       void empty_run(struct alarm_waiter *awaiter)
+       {
+               printk("Yay, %p ran (hopefully twice)!\n", awaiter);
+       }
+       /* Test basic insert, move, remove */
+       init_awaiter(&await1, shouldnt_run);
+       set_awaiter_abs(&await1, now + 1000000000);
+       set_alarm(tchain, &await1);
+       reset_alarm_abs(tchain, &await1, now + 1000000000 - 50);
+       reset_alarm_abs(tchain, &await1, now + 1000000000 + 50);
+       unset_alarm(tchain, &await1);
+       /* Test insert of one that fired already */
+       init_awaiter(&await2, empty_run);
+       set_awaiter_rel(&await2, 1);
+       set_alarm(tchain, &await2);
+       enable_irq();
+       udelay(1000);
+       reset_alarm_abs(tchain, &await2, now + 10);
+       udelay(1000);
+       unset_alarm(tchain, &await2);
+
+       printk("%s complete\n", __FUNCTION__);
+}