diff options
Diffstat (limited to 'arch/um/kernel/time.c')
-rw-r--r-- | arch/um/kernel/time.c | 97 |
1 files changed, 86 insertions, 11 deletions
diff --git a/arch/um/kernel/time.c b/arch/um/kernel/time.c index 3d109ff3309b..f4db89b5b5a6 100644 --- a/arch/um/kernel/time.c +++ b/arch/um/kernel/time.c @@ -31,6 +31,7 @@ static bool time_travel_start_set; static unsigned long long time_travel_start; static unsigned long long time_travel_time; static LIST_HEAD(time_travel_events); +static LIST_HEAD(time_travel_irqs); static unsigned long long time_travel_timer_interval; static unsigned long long time_travel_next_event; static struct time_travel_event time_travel_timer_event; @@ -46,6 +47,9 @@ static void time_travel_set_time(unsigned long long ns) if (unlikely(ns < time_travel_time)) panic("time-travel: time goes backwards %lld -> %lld\n", time_travel_time, ns); + else if (unlikely(ns >= S64_MAX)) + panic("The system was going to sleep forever, aborting"); + time_travel_time = ns; } @@ -180,6 +184,14 @@ static void time_travel_ext_update_request(unsigned long long time) time == time_travel_ext_prev_request) return; + /* + * if we're running and are allowed to run past the request + * then we don't need to update it either + */ + if (!time_travel_ext_waiting && time_travel_ext_free_until_valid && + time < time_travel_ext_free_until) + return; + time_travel_ext_prev_request = time; time_travel_ext_prev_request_valid = true; time_travel_ext_req(UM_TIMETRAVEL_REQUEST, time); @@ -187,7 +199,13 @@ static void time_travel_ext_update_request(unsigned long long time) void __time_travel_propagate_time(void) { + static unsigned long long last_propagated; + + if (last_propagated == time_travel_time) + return; + time_travel_ext_req(UM_TIMETRAVEL_UPDATE, time_travel_time); + last_propagated = time_travel_time; } EXPORT_SYMBOL_GPL(__time_travel_propagate_time); @@ -214,6 +232,7 @@ static void time_travel_ext_wait(bool idle) }; time_travel_ext_prev_request_valid = false; + time_travel_ext_free_until_valid = false; time_travel_ext_waiting++; time_travel_ext_req(UM_TIMETRAVEL_WAIT, -1); @@ -260,11 +279,6 @@ static void __time_travel_add_event(struct time_travel_event *e, struct time_travel_event *tmp; bool inserted = false; - if (WARN(time_travel_mode == TT_MODE_BASIC && - e != &time_travel_timer_event, - "only timer events can be handled in basic mode")) - return; - if (e->pending) return; @@ -311,6 +325,35 @@ void time_travel_periodic_timer(struct time_travel_event *e) deliver_alarm(); } +void deliver_time_travel_irqs(void) +{ + struct time_travel_event *e; + unsigned long flags; + + /* + * Don't do anything for most cases. Note that because here we have + * to disable IRQs (and re-enable later) we'll actually recurse at + * the end of the function, so this is strictly necessary. + */ + if (likely(list_empty(&time_travel_irqs))) + return; + + local_irq_save(flags); + irq_enter(); + while ((e = list_first_entry_or_null(&time_travel_irqs, + struct time_travel_event, + list))) { + WARN(e->time != time_travel_time, + "time moved from %lld to %lld before IRQ delivery\n", + time_travel_time, e->time); + list_del(&e->list); + e->pending = false; + e->fn(e); + } + irq_exit(); + local_irq_restore(flags); +} + static void time_travel_deliver_event(struct time_travel_event *e) { if (e == &time_travel_timer_event) { @@ -319,6 +362,14 @@ static void time_travel_deliver_event(struct time_travel_event *e) * by itself, so must handle it specially here */ e->fn(e); + } else if (irqs_disabled()) { + list_add_tail(&e->list, &time_travel_irqs); + /* + * set pending again, it was set to false when the + * event was deleted from the original list, but + * now it's still pending until we deliver the IRQ. + */ + e->pending = true; } else { unsigned long flags; @@ -404,9 +455,14 @@ static void time_travel_oneshot_timer(struct time_travel_event *e) deliver_alarm(); } -void time_travel_sleep(unsigned long long duration) +void time_travel_sleep(void) { - unsigned long long next = time_travel_time + duration; + /* + * Wait "forever" (using S64_MAX because there are some potential + * wrapping issues, especially with the current TT_MODE_EXTERNAL + * controller application. + */ + unsigned long long next = S64_MAX; if (time_travel_mode == TT_MODE_BASIC) os_timer_disable(); @@ -483,6 +539,7 @@ invalid_number: #define time_travel_start_set 0 #define time_travel_start 0 #define time_travel_time 0 +#define time_travel_ext_waiting 0 static inline void time_travel_update_time(unsigned long long ns, bool retearly) { @@ -628,7 +685,8 @@ static u64 timer_read(struct clocksource *cs) * "what do I do next" and onstack event we use to know when * to return from time_travel_update_time(). */ - if (!irqs_disabled() && !in_interrupt() && !in_softirq()) + if (!irqs_disabled() && !in_interrupt() && !in_softirq() && + !time_travel_ext_waiting) time_travel_update_time(time_travel_time + TIMER_MULTIPLIER, false); @@ -673,10 +731,8 @@ void read_persistent_clock64(struct timespec64 *ts) { long long nsecs; - if (time_travel_start_set) + if (time_travel_mode != TT_MODE_OFF) nsecs = time_travel_start + time_travel_time; - else if (time_travel_mode == TT_MODE_EXTERNAL) - nsecs = time_travel_ext_req(UM_TIMETRAVEL_GET_TOD, -1); else nsecs = os_persistent_clock_emulation(); @@ -686,6 +742,25 @@ void read_persistent_clock64(struct timespec64 *ts) void __init time_init(void) { +#ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT + switch (time_travel_mode) { + case TT_MODE_EXTERNAL: + time_travel_start = time_travel_ext_req(UM_TIMETRAVEL_GET_TOD, -1); + /* controller gave us the *current* time, so adjust by that */ + time_travel_ext_get_time(); + time_travel_start -= time_travel_time; + break; + case TT_MODE_INFCPU: + case TT_MODE_BASIC: + if (!time_travel_start_set) + time_travel_start = os_persistent_clock_emulation(); + break; + case TT_MODE_OFF: + /* we just read the host clock with os_persistent_clock_emulation() */ + break; + } +#endif + timer_set_signal_handler(); late_time_init = um_timer_setup; } |