X-Git-Url: http://akaros.cs.berkeley.edu/gitweb/?p=akaros.git;a=blobdiff_plain;f=kern%2Fsrc%2Falarm.c;h=168ceb94f034fb4abaa7fd14d420882abfe01c23;hp=df1efbae8da0519b94b21b7865dbee8c52169155;hb=45d47a9f3253bb96ce6eb657eba2e1c90cbf4975;hpb=865e03f621b01b01c413e8efc24c6a22d7a06a5e diff --git a/kern/src/alarm.c b/kern/src/alarm.c index df1efba..168ceb9 100644 --- a/kern/src/alarm.c +++ b/kern/src/alarm.c @@ -45,30 +45,16 @@ void init_timer_chain(struct timer_chain *tchain, reset_tchain_times(tchain); } -static void __init_awaiter(struct alarm_waiter *waiter) -{ - waiter->wake_up_time = ALARM_POISON_TIME; - waiter->on_tchain = FALSE; - waiter->holds_tchain_lock = FALSE; -} - void init_awaiter(struct alarm_waiter *waiter, void (*func) (struct alarm_waiter *awaiter)) { - waiter->irq_ok = FALSE; assert(func); waiter->func = func; - __init_awaiter(waiter); -} - -void init_awaiter_irq(struct alarm_waiter *waiter, - void (*func_irq) (struct alarm_waiter *awaiter, - struct hw_trapframe *hw_tf)) -{ - waiter->irq_ok = TRUE; - assert(func_irq); - waiter->func_irq = func_irq; - __init_awaiter(waiter); + waiter->wake_up_time = ALARM_POISON_TIME; + waiter->on_tchain = false; + waiter->is_running = false; + waiter->no_rearm = false; + cv_init_irqsave(&waiter->done_cv); } /* Give this the absolute time. For now, abs_time is the TSC time that you want @@ -118,23 +104,36 @@ static void reset_tchain_interrupt(struct timer_chain *tchain) } } +static void __finish_awaiter(struct alarm_waiter *waiter) +{ + int8_t irq_state = 0; + + /* Syncing with unset_alarm. They are waiting for us to tell them the + * waiter is not running. + * + * 'is_running' is set true under the tchain lock. It's checked and cleared + * under the cv_lock, but not necessarily with the tchain lock. */ + cv_lock_irqsave(&waiter->done_cv, &irq_state); + waiter->is_running = false; + /* broadcast, instead of signal. This allows us to have multiple unsetters + * concurrently. (only one of which will succeed, so YMMV.) */ + __cv_broadcast(&waiter->done_cv); + cv_unlock_irqsave(&waiter->done_cv, &irq_state); +} + static void __run_awaiter(uint32_t srcid, long a0, long a1, long a2) { struct alarm_waiter *waiter = (struct alarm_waiter*)a0; + waiter->func(waiter); + __finish_awaiter(waiter); } static void wake_awaiter(struct alarm_waiter *waiter, struct hw_trapframe *hw_tf) { - if (waiter->irq_ok) { - waiter->holds_tchain_lock = TRUE; - waiter->func_irq(waiter, hw_tf); - waiter->holds_tchain_lock = FALSE; - } else { - send_kernel_message(core_id(), __run_awaiter, (long)waiter, - 0, 0, KMSG_ROUTINE); - } + send_kernel_message(core_id(), __run_awaiter, (long)waiter, + 0, 0, KMSG_ROUTINE); } /* This is called when an interrupt triggers a tchain, and needs to wake up @@ -143,36 +142,33 @@ void __trigger_tchain(struct timer_chain *tchain, struct hw_trapframe *hw_tf) { struct alarm_waiter *i, *temp; uint64_t now = read_tsc(); - bool changed_list = FALSE; - /* why do we disable irqs here? the lock is irqsave, but we (think we) know - * the timer IRQ for this tchain won't fire again. disabling irqs is nice - * for the lock debugger. i don't want to disable the debugger completely, - * and we can't make the debugger ignore irq context code either in the - * general case. it might be nice for handlers to have IRQs disabled too.*/ + struct awaiters_tailq to_wake = TAILQ_HEAD_INITIALIZER(to_wake); + spin_lock_irqsave(&tchain->lock); TAILQ_FOREACH_SAFE(i, &tchain->waiters, next, temp) { printd("Trying to wake up %p who is due at %llu and now is %llu\n", i, i->wake_up_time, now); /* TODO: Could also do something in cases where we're close to now */ - if (i->wake_up_time <= now) { - changed_list = TRUE; - i->on_tchain = FALSE; - TAILQ_REMOVE(&tchain->waiters, i, next); - cmb(); /* enforce waking after removal */ - /* Don't touch the waiter after waking it, since it could be in use - * on another core (and the waiter can be clobbered as the kthread - * unwinds its stack). Or it could be kfreed */ - wake_awaiter(i, hw_tf); - } else { + if (i->wake_up_time > now) break; - } - } - if (changed_list) { - reset_tchain_times(tchain); + /* At this point, unset must wait until it has finished */ + i->on_tchain = false; + i->is_running = true; + TAILQ_REMOVE(&tchain->waiters, i, next); + TAILQ_INSERT_TAIL(&to_wake, i, next); } - /* Need to reset the interrupt no matter what */ + reset_tchain_times(tchain); reset_tchain_interrupt(tchain); spin_unlock_irqsave(&tchain->lock); + + TAILQ_FOREACH_SAFE(i, &to_wake, next, temp) { + /* Don't touch the waiter after waking it, since it could be in use on + * another core (and the waiter can be clobbered as the kthread unwinds + * its stack). Or it could be kfreed. Technically, the waiter hasn't + * finished until we cleared is_running and unlocked the cv lock. */ + TAILQ_REMOVE(&to_wake, i, next); + wake_awaiter(i, hw_tf); + } } /* Helper, inserts the waiter into the tchain, returning TRUE if we still need @@ -181,9 +177,7 @@ 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 */ - assert(waiter->wake_up_time != ALARM_POISON_TIME); - assert(!waiter->on_tchain); + waiter->on_tchain = TRUE; /* Either the list is empty, or not. */ if (TAILQ_EMPTY(&tchain->waiters)) { @@ -221,23 +215,24 @@ static bool __insert_awaiter(struct timer_chain *tchain, panic("Could not find a spot for awaiter %p\n", waiter); } -static void __set_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter) -{ - if (__insert_awaiter(tchain, waiter)) - reset_tchain_interrupt(tchain); -} - /* Sets the alarm. If it is a kthread-style alarm (func == 0), sleep on it * later. */ void set_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter) { - if (waiter->holds_tchain_lock) { - __set_alarm(tchain, waiter); - } else { - spin_lock_irqsave(&tchain->lock); - __set_alarm(tchain, waiter); + assert(waiter->wake_up_time != ALARM_POISON_TIME); + assert(!waiter->on_tchain); + + spin_lock_irqsave(&tchain->lock); + if (waiter->no_rearm) { + /* no_rearm exists to prevent alarm handlers from perpetually rearming + * when another thread is trying to unset the alarm. We could return an + * error / false, but I don't have a use for that yet. */ spin_unlock_irqsave(&tchain->lock); + return; } + if (__insert_awaiter(tchain, waiter)) + reset_tchain_interrupt(tchain); + spin_unlock_irqsave(&tchain->lock); } /* Helper, rips the waiter from the tchain, knowing that it is on the list. @@ -248,6 +243,7 @@ static bool __remove_awaiter(struct timer_chain *tchain, { struct alarm_waiter *temp; bool reset_int = FALSE; /* whether or not to reset the interrupt */ + /* 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) { @@ -265,63 +261,44 @@ static bool __remove_awaiter(struct timer_chain *tchain, } /* Removes waiter from the tchain before it goes off. Returns TRUE if we - * disarmed before the alarm went off, FALSE if it already fired. */ + * disarmed before the alarm went off, FALSE if it already fired. May block, + * since the handler may be running asynchronously. */ bool unset_alarm(struct timer_chain *tchain, struct alarm_waiter *waiter) { - assert(!waiter->holds_tchain_lock); /* Don't call from within a handler */ + int8_t irq_state = 0; + spin_lock_irqsave(&tchain->lock); - bool ret = waiter->on_tchain; - if (ret && __remove_awaiter(tchain, waiter)) - reset_tchain_interrupt(tchain); + if (waiter->on_tchain) { + if (__remove_awaiter(tchain, waiter)) + reset_tchain_interrupt(tchain); + spin_unlock_irqsave(&tchain->lock); + return true; + } - /* if alarm had already gone off then its not on this tchain's list, though - * the concurrent change to on_tchain (specifically, the setting of it to - * FALSE), happens under the tchain's lock. */ + /* no_rearm is set and checked under the tchain lock. It is cleared when + * unset completes, outside the lock. That is safe since we know the alarm + * service is no longer aware of waiter (either the handler ran or we + * stopped it). */ + waiter->no_rearm = true; spin_unlock_irqsave(&tchain->lock); - return ret; -} -/* 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() */ -static bool __reset_alarm_abs(struct timer_chain *tchain, - struct alarm_waiter *waiter, uint64_t abs_time) -{ - /* The tchain's lock is held */ - bool ret = waiter->on_tchain; - /* We only need to remove/unset when the alarm has not fired yet (is still - * on the tchain). If it has fired, it's like a fresh insert. We must also - * check if we need to reset the interrupt. */ - bool reset_int = ret && __remove_awaiter(tchain, waiter); - set_awaiter_abs(waiter, abs_time); - /* regardless, we need to be reinserted */ - if (__insert_awaiter(tchain, waiter) || reset_int) - reset_tchain_interrupt(tchain); - return ret; -} + cv_lock_irqsave(&waiter->done_cv, &irq_state); + while (waiter->is_running) + cv_wait(&waiter->done_cv); + cv_unlock_irqsave(&waiter->done_cv, &irq_state); -static bool __reset_alarm_rel(struct timer_chain *tchain, - struct alarm_waiter *waiter, uint64_t usleep) -{ - uint64_t now, then; - now = read_tsc(); - then = now + usec2tsc(usleep); - assert(now <= then); - return __reset_alarm_abs(tchain, waiter, then); + waiter->no_rearm = false; + return false; } bool reset_alarm_abs(struct timer_chain *tchain, struct alarm_waiter *waiter, uint64_t abs_time) { bool ret; - if (waiter->holds_tchain_lock) { - ret = __reset_alarm_abs(tchain, waiter, abs_time); - } else { - spin_lock_irqsave(&tchain->lock); - ret = __reset_alarm_abs(tchain, waiter, abs_time); - spin_unlock_irqsave(&tchain->lock); - } + + ret = unset_alarm(tchain, waiter); + set_awaiter_abs(waiter, abs_time); + set_alarm(tchain, waiter); return ret; } @@ -329,13 +306,10 @@ bool reset_alarm_rel(struct timer_chain *tchain, struct alarm_waiter *waiter, uint64_t usleep) { bool ret; - if (waiter->holds_tchain_lock) { - ret =__reset_alarm_rel(tchain, waiter, usleep); - } else { - spin_lock_irqsave(&tchain->lock); - ret =__reset_alarm_rel(tchain, waiter, usleep); - spin_unlock_irqsave(&tchain->lock); - } + + ret = unset_alarm(tchain, waiter); + set_awaiter_rel(waiter, usleep); + set_alarm(tchain, waiter); return ret; } @@ -407,17 +381,10 @@ void print_chain(struct timer_chain *tchain) tchain->earliest_time, tchain->latest_time); TAILQ_FOREACH(i, &tchain->waiters, next) { - uintptr_t f; - char *f_name; + uintptr_t f = (uintptr_t)i->func; - if (i->irq_ok) - f = (uintptr_t)i->func_irq; - else - f = (uintptr_t)i->func; - f_name = get_fn_name(f); printk("\tWaiter %p, time %llu, func %p (%s)\n", i, - i->wake_up_time, f, f_name); - kfree(f_name); + i->wake_up_time, f, get_fn_name(f)); } spin_unlock_irqsave(&tchain->lock); }