Johannes Bergum: add a pseudo RTCdde8b585 years agoHistory
871 lines · 22.7 KB · C
RawDownload
// 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.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t