// SPDX-License-Identifier: GPL-2.0/* * Copyright (C) 2015 Anton Ivanov (aivanov@{brocade.com,kot-begemot.co.uk}) * Copyright (C) 2015 Thomas Meyer (thomas@m3y3r.de) * Copyright (C) 2012-2014 Cisco Systems * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) * Copyright (C) 2019 Intel Corporation */ #include <linux/clockchips.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/jiffies.h>#include <linux/mm.h>#include <linux/sched.h>#include <linux/spinlock.h>#include <linux/threads.h>#include <asm/irq.h>#include <asm/param.h>#include <kern_util.h>#include <os.h>#include <linux/time-internal.h>#include <linux/um_timetravel.h>#include <shared/init.h> #ifdef CONFIG_UML_TIME_TRAVEL_SUPPORTenum time_travel_mode time_travel_mode;EXPORT_SYMBOL_GPL(time_travel_mode); 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;static int time_travel_ext_fd = -1;static unsigned int time_travel_ext_waiting;static bool time_travel_ext_prev_request_valid;static unsigned long long time_travel_ext_prev_request;static bool time_travel_ext_free_until_valid;static unsigned long long time_travel_ext_free_until; 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;} enum time_travel_message_handling { TTMH_IDLE, TTMH_POLL, TTMH_READ,}; static void time_travel_handle_message(struct um_timetravel_msg *msg, enum time_travel_message_handling mode){ struct um_timetravel_msg resp = { .op = UM_TIMETRAVEL_ACK, }; int ret; /* * Poll outside the locked section (if we're not called to only read * the response) so we can get interrupts for e.g. virtio while we're * here, but then we need to lock to not get interrupted between the * read of the message and write of the ACK. */ if (mode != TTMH_READ) { bool disabled = irqs_disabled(); BUG_ON(mode == TTMH_IDLE && !disabled); if (disabled) local_irq_enable(); while (os_poll(1, &time_travel_ext_fd) != 0) { /* nothing */ } if (disabled) local_irq_disable(); } ret = os_read_file(time_travel_ext_fd, msg, sizeof(*msg)); if (ret == 0) panic("time-travel external link is broken\n"); if (ret != sizeof(*msg)) panic("invalid time-travel message - %d bytes\n", ret); switch (msg->op) { default: WARN_ONCE(1, "time-travel: unexpected message %lld\n", (unsigned long long)msg->op); break; case UM_TIMETRAVEL_ACK: return; case UM_TIMETRAVEL_RUN: time_travel_set_time(msg->time); break; case UM_TIMETRAVEL_FREE_UNTIL: time_travel_ext_free_until_valid = true; time_travel_ext_free_until = msg->time; break; } resp.seq = msg->seq; os_write_file(time_travel_ext_fd, &resp, sizeof(resp));} static u64 time_travel_ext_req(u32 op, u64 time){ static int seq; int mseq = ++seq; struct um_timetravel_msg msg = { .op = op, .time = time, .seq = mseq, }; unsigned long flags; /* * We need to save interrupts here and only restore when we * got the ACK - otherwise we can get interrupted and send * another request while we're still waiting for an ACK, but * the peer doesn't know we got interrupted and will send * the ACKs in the same order as the message, but we'd need * to see them in the opposite order ... * * This wouldn't matter *too* much, but some ACKs carry the * current time (for UM_TIMETRAVEL_GET) and getting another * ACK without a time would confuse us a lot! * * The sequence number assignment that happens here lets us * debug such message handling issues more easily. */ local_irq_save(flags); os_write_file(time_travel_ext_fd, &msg, sizeof(msg)); while (msg.op != UM_TIMETRAVEL_ACK) time_travel_handle_message(&msg, TTMH_READ); if (msg.seq != mseq) panic("time-travel: ACK message has different seqno! op=%d, seq=%d != %d time=%lld\n", msg.op, msg.seq, mseq, msg.time); if (op == UM_TIMETRAVEL_GET) time_travel_set_time(msg.time); local_irq_restore(flags); return msg.time;} void __time_travel_wait_readable(int fd){ int fds[2] = { fd, time_travel_ext_fd }; int ret; if (time_travel_mode != TT_MODE_EXTERNAL) return; while ((ret = os_poll(2, fds))) { struct um_timetravel_msg msg; if (ret == 1) time_travel_handle_message(&msg, TTMH_READ); }}EXPORT_SYMBOL_GPL(__time_travel_wait_readable); static void time_travel_ext_update_request(unsigned long long time){ if (time_travel_mode != TT_MODE_EXTERNAL) return; /* asked for exactly this time previously */ if (time_travel_ext_prev_request_valid && 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);} 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); /* returns true if we must do a wait to the simtime device */static bool time_travel_ext_request(unsigned long long time){ /* * If we received an external sync point ("free until") then we * don't have to request/wait for anything until then, unless * we're already waiting. */ if (!time_travel_ext_waiting && time_travel_ext_free_until_valid && time < time_travel_ext_free_until) return false; time_travel_ext_update_request(time); return true;} static void time_travel_ext_wait(bool idle){ struct um_timetravel_msg msg = { .op = UM_TIMETRAVEL_ACK, }; 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); /* * Here we are deep in the idle loop, so we have to break out of the * kernel abstraction in a sense and implement this in terms of the * UML system waiting on the VQ interrupt while sleeping, when we get * the signal it'll call time_travel_ext_vq_notify_done() completing the * call. */ while (msg.op != UM_TIMETRAVEL_RUN) time_travel_handle_message(&msg, idle ? TTMH_IDLE : TTMH_POLL); time_travel_ext_waiting--; /* we might request more stuff while polling - reset when we run */ time_travel_ext_prev_request_valid = false;} static void time_travel_ext_get_time(void){ time_travel_ext_req(UM_TIMETRAVEL_GET, -1);} static void __time_travel_update_time(unsigned long long ns, bool idle){ if (time_travel_mode == TT_MODE_EXTERNAL && time_travel_ext_request(ns)) time_travel_ext_wait(idle); else time_travel_set_time(ns);} static struct time_travel_event *time_travel_first_event(void){ return list_first_entry_or_null(&time_travel_events, struct time_travel_event, list);} static void __time_travel_add_event(struct time_travel_event *e, unsigned long long time){ struct time_travel_event *tmp; bool inserted = false; unsigned long flags; if (e->pending) return; e->pending = true; e->time = time; local_irq_save(flags); list_for_each_entry(tmp, &time_travel_events, list) { /* * Add the new entry before one with higher time, * or if they're equal and both on stack, because * in that case we need to unwind the stack in the * right order, and the later event (timer sleep * or such) must be dequeued first. */ if ((tmp->time > e->time) || (tmp->time == e->time && tmp->onstack && e->onstack)) { list_add_tail(&e->list, &tmp->list); inserted = true; break; } } if (!inserted) list_add_tail(&e->list, &time_travel_events); tmp = time_travel_first_event(); time_travel_ext_update_request(tmp->time); time_travel_next_event = tmp->time; local_irq_restore(flags);} static void time_travel_add_event(struct time_travel_event *e, unsigned long long time){ if (WARN_ON(!e->fn)) return; __time_travel_add_event(e, time);} void time_travel_add_event_rel(struct time_travel_event *e, unsigned long long delay_ns){ time_travel_add_event(e, time_travel_time + delay_ns);} void time_travel_periodic_timer(struct time_travel_event *e){ time_travel_add_event(&time_travel_timer_event, time_travel_time + time_travel_timer_interval); 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) { /* * deliver_alarm() does the irq_enter/irq_exit * 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; local_irq_save(flags); irq_enter(); e->fn(e); irq_exit(); local_irq_restore(flags); }} bool time_travel_del_event(struct time_travel_event *e){ unsigned long flags; if (!e->pending) return false; local_irq_save(flags); list_del(&e->list); e->pending = false; local_irq_restore(flags); return true;} static void time_travel_update_time(unsigned long long next, bool idle){ struct time_travel_event ne = { .onstack = true, }; struct time_travel_event *e; bool finished = idle; /* add it without a handler - we deal with that specifically below */ __time_travel_add_event(&ne, next); do { e = time_travel_first_event(); BUG_ON(!e); __time_travel_update_time(e->time, idle); /* new events may have been inserted while we were waiting */ if (e == time_travel_first_event()) { BUG_ON(!time_travel_del_event(e)); BUG_ON(time_travel_time != e->time); if (e == &ne) { finished = true; } else { if (e->onstack) panic("On-stack event dequeued outside of the stack! time=%lld, event time=%lld, event=%pS\n", time_travel_time, e->time, e); time_travel_deliver_event(e); } } e = time_travel_first_event(); if (e) time_travel_ext_update_request(e->time); } while (ne.pending && !finished); time_travel_del_event(&ne);} void time_travel_ndelay(unsigned long nsec){ time_travel_update_time(time_travel_time + nsec, false);}EXPORT_SYMBOL(time_travel_ndelay); void time_travel_add_irq_event(struct time_travel_event *e){ BUG_ON(time_travel_mode != TT_MODE_EXTERNAL); time_travel_ext_get_time(); /* * We could model interrupt latency here, for now just * don't have any latency at all and request the exact * same time (again) to run the interrupt... */ time_travel_add_event(e, time_travel_time);}EXPORT_SYMBOL_GPL(time_travel_add_irq_event); static void time_travel_oneshot_timer(struct time_travel_event *e){ deliver_alarm();} void time_travel_sleep(void){ /* * 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(); time_travel_update_time(next, true); if (time_travel_mode == TT_MODE_BASIC && time_travel_timer_event.pending) { if (time_travel_timer_event.fn == time_travel_periodic_timer) { /* * This is somewhat wrong - we should get the first * one sooner like the os_timer_one_shot() below... */ os_timer_set_interval(time_travel_timer_interval); } else { os_timer_one_shot(time_travel_timer_event.time - next); } }} static void time_travel_handle_real_alarm(void){Lines 1–500 of 871. Load more
// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2015 Anton Ivanov (aivanov@{brocade.com,kot-begemot.co.uk}) * Copyright (C) 2015 Thomas Meyer (thomas@m3y3r.de) * Copyright (C) 2012-2014 Cisco Systems * Copyright (C) 2000 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com) * Copyright (C) 2019 Intel Corporation */ #include <linux/clockchips.h> #include <linux/init.h> #include <linux/interrupt.h> #include <linux/jiffies.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/spinlock.h> #include <linux/threads.h> #include <asm/irq.h> #include <asm/param.h> #include <kern_util.h> #include <os.h> #include <linux/time-internal.h> #include <linux/um_timetravel.h> #include <shared/init.h> #ifdef CONFIG_UML_TIME_TRAVEL_SUPPORT enum time_travel_mode time_travel_mode; EXPORT_SYMBOL_GPL(time_travel_mode); 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; static int time_travel_ext_fd = -1; static unsigned int time_travel_ext_waiting; static bool time_travel_ext_prev_request_valid; static unsigned long long time_travel_ext_prev_request; static bool time_travel_ext_free_until_valid; static unsigned long long time_travel_ext_free_until; 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; } enum time_travel_message_handling { TTMH_IDLE, TTMH_POLL, TTMH_READ, }; static void time_travel_handle_message(struct um_timetravel_msg *msg, enum time_travel_message_handling mode) { struct um_timetravel_msg resp = { .op = UM_TIMETRAVEL_ACK, }; int ret; /* * Poll outside the locked section (if we're not called to only read * the response) so we can get interrupts for e.g. virtio while we're * here, but then we need to lock to not get interrupted between the * read of the message and write of the ACK. */ if (mode != TTMH_READ) { bool disabled = irqs_disabled(); BUG_ON(mode == TTMH_IDLE && !disabled); if (disabled) local_irq_enable(); while (os_poll(1, &time_travel_ext_fd) != 0) { /* nothing */ } if (disabled) local_irq_disable(); } ret = os_read_file(time_travel_ext_fd, msg, sizeof(*msg)); if (ret == 0) panic("time-travel external link is broken\n"); if (ret != sizeof(*msg)) panic("invalid time-travel message - %d bytes\n", ret); switch (msg->op) { default: WARN_ONCE(1, "time-travel: unexpected message %lld\n", (unsigned long long)msg->op); break; case UM_TIMETRAVEL_ACK: return; case UM_TIMETRAVEL_RUN: time_travel_set_time(msg->time); break; case UM_TIMETRAVEL_FREE_UNTIL: time_travel_ext_free_until_valid = true; time_travel_ext_free_until = msg->time; break; } resp.seq = msg->seq; os_write_file(time_travel_ext_fd, &resp, sizeof(resp)); } static u64 time_travel_ext_req(u32 op, u64 time) { static int seq; int mseq = ++seq; struct um_timetravel_msg msg = { .op = op, .time = time, .seq = mseq, }; unsigned long flags; /* * We need to save interrupts here and only restore when we * got the ACK - otherwise we can get interrupted and send * another request while we're still waiting for an ACK, but * the peer doesn't know we got interrupted and will send * the ACKs in the same order as the message, but we'd need * to see them in the opposite order ... * * This wouldn't matter *too* much, but some ACKs carry the * current time (for UM_TIMETRAVEL_GET) and getting another * ACK without a time would confuse us a lot! * * The sequence number assignment that happens here lets us * debug such message handling issues more easily. */ local_irq_save(flags); os_write_file(time_travel_ext_fd, &msg, sizeof(msg)); while (msg.op != UM_TIMETRAVEL_ACK) time_travel_handle_message(&msg, TTMH_READ); if (msg.seq != mseq) panic("time-travel: ACK message has different seqno! op=%d, seq=%d != %d time=%lld\n", msg.op, msg.seq, mseq, msg.time); if (op == UM_TIMETRAVEL_GET) time_travel_set_time(msg.time); local_irq_restore(flags); return msg.time; } void __time_travel_wait_readable(int fd) { int fds[2] = { fd, time_travel_ext_fd }; int ret; if (time_travel_mode != TT_MODE_EXTERNAL) return; while ((ret = os_poll(2, fds))) { struct um_timetravel_msg msg; if (ret == 1) time_travel_handle_message(&msg, TTMH_READ); } } EXPORT_SYMBOL_GPL(__time_travel_wait_readable); static void time_travel_ext_update_request(unsigned long long time) { if (time_travel_mode != TT_MODE_EXTERNAL) return; /* asked for exactly this time previously */ if (time_travel_ext_prev_request_valid && 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); } 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); /* returns true if we must do a wait to the simtime device */ static bool time_travel_ext_request(unsigned long long time) { /* * If we received an external sync point ("free until") then we * don't have to request/wait for anything until then, unless * we're already waiting. */ if (!time_travel_ext_waiting && time_travel_ext_free_until_valid && time < time_travel_ext_free_until) return false; time_travel_ext_update_request(time); return true; } static void time_travel_ext_wait(bool idle) { struct um_timetravel_msg msg = { .op = UM_TIMETRAVEL_ACK, }; 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); /* * Here we are deep in the idle loop, so we have to break out of the * kernel abstraction in a sense and implement this in terms of the * UML system waiting on the VQ interrupt while sleeping, when we get * the signal it'll call time_travel_ext_vq_notify_done() completing the * call. */ while (msg.op != UM_TIMETRAVEL_RUN) time_travel_handle_message(&msg, idle ? TTMH_IDLE : TTMH_POLL); time_travel_ext_waiting--; /* we might request more stuff while polling - reset when we run */ time_travel_ext_prev_request_valid = false; } static void time_travel_ext_get_time(void) { time_travel_ext_req(UM_TIMETRAVEL_GET, -1); } static void __time_travel_update_time(unsigned long long ns, bool idle) { if (time_travel_mode == TT_MODE_EXTERNAL && time_travel_ext_request(ns)) time_travel_ext_wait(idle); else time_travel_set_time(ns); } static struct time_travel_event *time_travel_first_event(void) { return list_first_entry_or_null(&time_travel_events, struct time_travel_event, list); } static void __time_travel_add_event(struct time_travel_event *e, unsigned long long time) { struct time_travel_event *tmp; bool inserted = false; unsigned long flags; if (e->pending) return; e->pending = true; e->time = time; local_irq_save(flags); list_for_each_entry(tmp, &time_travel_events, list) { /* * Add the new entry before one with higher time, * or if they're equal and both on stack, because * in that case we need to unwind the stack in the * right order, and the later event (timer sleep * or such) must be dequeued first. */ if ((tmp->time > e->time) || (tmp->time == e->time && tmp->onstack && e->onstack)) { list_add_tail(&e->list, &tmp->list); inserted = true; break; } } if (!inserted) list_add_tail(&e->list, &time_travel_events); tmp = time_travel_first_event(); time_travel_ext_update_request(tmp->time); time_travel_next_event = tmp->time; local_irq_restore(flags); } static void time_travel_add_event(struct time_travel_event *e, unsigned long long time) { if (WARN_ON(!e->fn)) return; __time_travel_add_event(e, time); } void time_travel_add_event_rel(struct time_travel_event *e, unsigned long long delay_ns) { time_travel_add_event(e, time_travel_time + delay_ns); } void time_travel_periodic_timer(struct time_travel_event *e) { time_travel_add_event(&time_travel_timer_event, time_travel_time + time_travel_timer_interval); 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) { /* * deliver_alarm() does the irq_enter/irq_exit * 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; local_irq_save(flags); irq_enter(); e->fn(e); irq_exit(); local_irq_restore(flags); } } bool time_travel_del_event(struct time_travel_event *e) { unsigned long flags; if (!e->pending) return false; local_irq_save(flags); list_del(&e->list); e->pending = false; local_irq_restore(flags); return true; } static void time_travel_update_time(unsigned long long next, bool idle) { struct time_travel_event ne = { .onstack = true, }; struct time_travel_event *e; bool finished = idle; /* add it without a handler - we deal with that specifically below */ __time_travel_add_event(&ne, next); do { e = time_travel_first_event(); BUG_ON(!e); __time_travel_update_time(e->time, idle); /* new events may have been inserted while we were waiting */ if (e == time_travel_first_event()) { BUG_ON(!time_travel_del_event(e)); BUG_ON(time_travel_time != e->time); if (e == &ne) { finished = true; } else { if (e->onstack) panic("On-stack event dequeued outside of the stack! time=%lld, event time=%lld, event=%pS\n", time_travel_time, e->time, e); time_travel_deliver_event(e); } } e = time_travel_first_event(); if (e) time_travel_ext_update_request(e->time); } while (ne.pending && !finished); time_travel_del_event(&ne); } void time_travel_ndelay(unsigned long nsec) { time_travel_update_time(time_travel_time + nsec, false); } EXPORT_SYMBOL(time_travel_ndelay); void time_travel_add_irq_event(struct time_travel_event *e) { BUG_ON(time_travel_mode != TT_MODE_EXTERNAL); time_travel_ext_get_time(); /* * We could model interrupt latency here, for now just * don't have any latency at all and request the exact * same time (again) to run the interrupt... */ time_travel_add_event(e, time_travel_time); } EXPORT_SYMBOL_GPL(time_travel_add_irq_event); static void time_travel_oneshot_timer(struct time_travel_event *e) { deliver_alarm(); } void time_travel_sleep(void) { /* * 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(); time_travel_update_time(next, true); if (time_travel_mode == TT_MODE_BASIC && time_travel_timer_event.pending) { if (time_travel_timer_event.fn == time_travel_periodic_timer) { /* * This is somewhat wrong - we should get the first * one sooner like the os_timer_one_shot() below... */ os_timer_set_interval(time_travel_timer_interval); } else { os_timer_one_shot(time_travel_timer_event.time - next); } } } static void time_travel_handle_real_alarm(void) {