@@ -67,6 +67,7 @@
/vhost-user-blk
/vhost-user-gpu
/vhost-user-input
+/vhost-user-sim
/fsdev/virtfs-proxy-helper
*.tmp
*.[1-9]
@@ -418,6 +418,7 @@ dummy := $(call unnest-vars,, \
vhost-user-blk-obj-y \
vhost-user-input-obj-y \
vhost-user-gpu-obj-y \
+ vhost-user-sim-obj-y \
qga-vss-dll-obj-y \
block-obj-y \
block-obj-m \
@@ -638,6 +639,8 @@ vhost-user-scsi$(EXESUF): $(vhost-user-scsi-obj-y) libvhost-user.a
$(call LINK, $^)
vhost-user-blk$(EXESUF): $(vhost-user-blk-obj-y) libvhost-user.a
$(call LINK, $^)
+vhost-user-sim$(EXESUF): $(vhost-user-sim-obj-y) libvhost-user.a
+ $(call LINK, $^)
rdmacm-mux$(EXESUF): LIBS += "-libumad"
rdmacm-mux$(EXESUF): $(rdmacm-mux-obj-y) $(COMMON_LDADDS)
@@ -122,6 +122,7 @@ vhost-user-scsi.o-cflags := $(LIBISCSI_CFLAGS)
vhost-user-scsi.o-libs := $(LIBISCSI_LIBS)
vhost-user-scsi-obj-y = contrib/vhost-user-scsi/
vhost-user-blk-obj-y = contrib/vhost-user-blk/
+vhost-user-sim-obj-y = contrib/vhost-user-sim/
rdmacm-mux-obj-y = contrib/rdmacm-mux/
vhost-user-input-obj-y = contrib/vhost-user-input/
vhost-user-gpu-obj-y = contrib/vhost-user-gpu/
new file mode 100644
@@ -0,0 +1 @@
+vhost-user-sim-obj-y = cal.o simtime.o net.o main.o
new file mode 100644
@@ -0,0 +1,288 @@
+/*
+ * vhost-user-sim calendar
+ *
+ * Copyright (c) 2019 Intel Corporation. All rights reserved.
+ *
+ * Author:
+ * Johannes Berg <johannes.berg@intel.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 only.
+ * See the COPYING file in the top-level directory.
+ */
+#include <stdbool.h>
+#include <stdio.h>
+#include "cal.h"
+
+#define CAL_DEBUG 1
+
+static unsigned long long simtime;
+static G_LOCK_DEFINE(calendar);
+static GSequence *calendar;
+static GAsyncQueue *queue;
+static bool scheduling;
+static unsigned int required_clients;
+static unsigned int running_clients;
+static SimCalendarEntry *running_entry;
+
+#define DPRINT(...) do { \
+ if (CAL_DEBUG) { \
+ fprintf(stderr, __VA_ARGS__); \
+ fflush(stderr); \
+ } \
+} while (0)
+
+typedef enum {
+ CAL_OP_INVALID,
+ CAL_OP_RUN_DONE,
+ CAL_OP_QUIT,
+} CalMessage;
+
+static void dump_scheduler(gpointer data, gpointer user)
+{
+ SimCalendarEntry *entry = data;
+
+ DPRINT("<CAL>| %.20lld | %s\n", entry->time, entry->name);
+}
+
+static void dump_calendar(void)
+{
+ DPRINT("<CAL>|----------- calendar state at %lld\n", simtime);
+ g_sequence_foreach(calendar, dump_scheduler, NULL);
+}
+
+unsigned long long calendar_get_time(void)
+{
+ return simtime;
+}
+
+static void count_clients(gpointer data, gpointer user)
+{
+ SimCalendarEntry *entry = data;
+ unsigned int *count = user;
+
+ if (entry->client) {
+ (*count)++;
+ }
+}
+
+static unsigned long long calendar_get_next_time(void)
+{
+ GSequenceIter *first;
+
+ first = g_sequence_get_iter_at_pos(calendar, 0);
+ if (!g_sequence_iter_is_end(first)) {
+ SimCalendarEntry *next = g_sequence_get(first);
+ return next->time;
+ }
+
+ return (unsigned long long)-1;
+}
+
+void calendar_set_time(unsigned long long time)
+{
+ g_assert(time >= simtime);
+ G_LOCK(calendar);
+ if (time > calendar_get_next_time()) {
+ dump_calendar();
+ DPRINT("CAL: setting time to %lld which is > %lld\n",
+ time, calendar_get_next_time());
+ g_assert(0);
+ }
+ simtime = time;
+ G_UNLOCK(calendar);
+}
+
+static void calendar_schedule(void)
+{
+ GSequenceIter *first;
+ SimCalendarEntry *entry;
+ unsigned long long nexttime;
+
+ G_LOCK(calendar);
+ if (CAL_DEBUG) {
+ dump_calendar();
+ }
+
+ if (!scheduling) {
+ unsigned int count = 0;
+
+ g_sequence_foreach(calendar, count_clients, &count);
+
+ if (count < required_clients) {
+ G_UNLOCK(calendar);
+ return;
+ }
+ scheduling = true;
+ }
+
+ first = g_sequence_get_iter_at_pos(calendar, 0);
+ if (g_sequence_iter_is_end(first)) {
+ /* Everything disappeared, stop the simulation. */
+ g_async_queue_push(queue, (void *)CAL_OP_QUIT);
+ G_UNLOCK(calendar);
+ return;
+ }
+
+ entry = g_sequence_get(first);
+ g_sequence_remove(first);
+ entry->iter = NULL;
+ simtime = entry->time;
+ entry->running = true;
+ running_clients++;
+
+ nexttime = calendar_get_next_time();
+ running_entry = entry;
+ G_UNLOCK(calendar);
+
+ if (entry->update_until) {
+ DPRINT("update %s to be free until %lld\n", entry->name, nexttime);
+ entry->update_until(entry, nexttime);
+ }
+ entry->callback(entry);
+}
+
+static int entry_cmp_func(gconstpointer _a, gconstpointer _b, gpointer data)
+{
+ const SimCalendarEntry *a = _a;
+ const SimCalendarEntry *b = _b;
+
+ if (a->time == b->time) {
+ return 0;
+ }
+
+ if (a->time < b->time) {
+ return -1;
+ }
+
+ return 1;
+}
+
+void calendar_entry_add_unless_present(SimCalendarEntry *entry,
+ unsigned long long time)
+{
+ G_LOCK(calendar);
+ if (!entry->iter) {
+ entry->time = time;
+ entry->iter = g_sequence_insert_sorted(calendar, entry,
+ entry_cmp_func, NULL);
+ g_assert(entry->iter);
+ dump_calendar();
+ if (running_entry && entry != running_entry && running_entry->update_until) {
+ unsigned long long nexttime = calendar_get_next_time();
+
+ DPRINT("update %s to be free until %lld (due to add unless of %s)\n", running_entry->name, nexttime, entry->name);
+ running_entry->update_until(running_entry, nexttime);
+ } else {
+ DPRINT("no update for running entry %s\n", running_entry ? running_entry->name : "<none>");
+ }
+ }
+ G_UNLOCK(calendar);
+}
+
+void calendar_entry_add(SimCalendarEntry *entry)
+{
+ G_LOCK(calendar);
+ g_assert(!entry->iter);
+ entry->iter = g_sequence_insert_sorted(calendar, entry,
+ entry_cmp_func, NULL);
+ g_assert(entry->iter);
+ dump_calendar();
+ if (running_entry && entry != running_entry &&
+ running_entry->update_until) {
+ unsigned long long nexttime = calendar_get_next_time();
+
+ DPRINT("update %s to be free until %lld (due to add of %s)\n", running_entry->name, nexttime, entry->name);
+ running_entry->update_until(running_entry, nexttime);
+ } else {
+ DPRINT("no update for running entry %s\n", running_entry ? running_entry->name : "<none>");
+ }
+ G_UNLOCK(calendar);
+}
+
+static void _calendar_run_done(SimCalendarEntry *entry)
+{
+ /*
+ * This will happen while new clients join the
+ * simulation and go into wait - their events
+ * will be marked as "done" even though they
+ * never started ...
+ * Currently we don't let any clients join the
+ * simulation after it has started, but that's
+ * not really controllable anyway, so better.
+ */
+ if (scheduling) {
+ g_assert(entry->running);
+ entry->running = false;
+ running_clients--;
+ g_assert(running_clients == 0);
+ }
+ g_async_queue_push(queue, (void *)CAL_OP_RUN_DONE);
+}
+
+static bool _calendar_entry_remove(SimCalendarEntry *entry)
+{
+ if (entry->iter) {
+ g_sequence_remove(entry->iter);
+ entry->iter = NULL;
+ return true;
+ }
+
+ return false;
+}
+
+bool calendar_entry_remove(SimCalendarEntry *entry)
+{
+ bool scheduled;
+
+ G_LOCK(calendar);
+ scheduled = _calendar_entry_remove(entry);
+ G_UNLOCK(calendar);
+
+ return scheduled;
+}
+
+void calendar_run_done(SimCalendarEntry *entry)
+{
+ G_LOCK(calendar);
+ _calendar_run_done(entry);
+ G_UNLOCK(calendar);
+}
+
+void calendar_entry_destroy(SimCalendarEntry *entry)
+{
+ G_LOCK(calendar);
+ if (running_entry == entry) {
+ running_entry = NULL;
+ }
+ if (entry->running) {
+ DPRINT("destroying running client %s\n", entry->name);
+ _calendar_run_done(entry);
+ }
+ _calendar_entry_remove(entry);
+ G_UNLOCK(calendar);
+}
+
+void calendar_init(unsigned int required)
+{
+ calendar = g_sequence_new(NULL);
+ queue = g_async_queue_new();
+ required_clients = required;
+}
+
+void calendar_run(void)
+{
+ while (1) {
+ CalMessage msg = (CalMessage)g_async_queue_pop(queue);
+
+ switch (msg) {
+ case CAL_OP_INVALID:
+ g_assert(0);
+ break;
+ case CAL_OP_RUN_DONE:
+ calendar_schedule();
+ break;
+ case CAL_OP_QUIT:
+ return;
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,45 @@
+/*
+ * vhost-user-sim calendar (header file)
+ *
+ * Copyright (c) 2019 Intel Corporation. All rights reserved.
+ *
+ * Author:
+ * Johannes Berg <johannes.berg@intel.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 only.
+ * See the COPYING file in the top-level directory.
+ */
+#ifndef _SIM_CAL_H
+#define _SIM_CAL_H
+#include <stdbool.h>
+#include <gmodule.h>
+
+typedef struct SimCalendarEntry SimCalendarEntry;
+typedef void (*start_callback_t)(SimCalendarEntry *entry);
+typedef void (*update_until_callback_t)(SimCalendarEntry *entry,
+ unsigned long long until);
+
+struct SimCalendarEntry {
+ unsigned long long time;
+ start_callback_t callback;
+ update_until_callback_t update_until;
+ gchar *name;
+ GSequenceIter *iter;
+ bool running;
+ bool client;
+};
+
+void calendar_init(unsigned int required_clients);
+void calendar_run(void);
+
+unsigned long long calendar_get_time(void);
+void calendar_set_time(unsigned long long time);
+void calendar_entry_add(SimCalendarEntry *entry);
+void calendar_entry_add_unless_present(SimCalendarEntry *entry,
+ unsigned long long time);
+bool calendar_entry_remove(SimCalendarEntry *entry);
+void calendar_entry_destroy(SimCalendarEntry *entry);
+
+void calendar_run_done(SimCalendarEntry *entry);
+
+#endif /* _SIM_CAL_H */
new file mode 100644
@@ -0,0 +1,122 @@
+/*
+ * vhost-user sim main application
+ *
+ * Copyright (c) 2019 Intel Corporation. All rights reserved.
+ *
+ * Author:
+ * Johannes Berg <johannes.berg@intel.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 only.
+ * See the COPYING file in the top-level directory.
+ */
+#include <gmodule.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include "main.h"
+#include "cal.h"
+
+static int unix_sock_new(const char *unix_fn)
+{
+ int sock;
+ struct sockaddr_un un;
+ size_t len;
+
+ g_assert(unix_fn);
+
+ sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sock <= 0) {
+ perror("socket");
+ g_assert(0);
+ return -1;
+ }
+
+ un.sun_family = AF_UNIX;
+ (void)snprintf(un.sun_path, sizeof(un.sun_path), "%s", unix_fn);
+ len = sizeof(un.sun_family) + strlen(un.sun_path);
+
+ (void)unlink(unix_fn);
+ if (bind(sock, (struct sockaddr *)&un, len) < 0) {
+ perror("bind");
+ goto fail;
+ }
+
+ if (listen(sock, 1) < 0) {
+ perror("listen");
+ goto fail;
+ }
+
+ return sock;
+
+fail:
+ (void)close(sock);
+ g_assert(0);
+ return -1;
+}
+
+static gpointer thread_func(gpointer data)
+{
+ g_main_context_push_thread_default(g_main_loop_get_context(data));
+ g_main_loop_run(data);
+ return NULL;
+}
+
+static GThread *new_device_thread(GIOFunc cb, const char *socket,
+ const char *name)
+{
+ GMainContext *ctx = g_main_context_new();
+ GMainLoop *loop = g_main_loop_new(ctx, FALSE);
+ int lsock = unix_sock_new(socket);
+ GIOChannel *chan = g_io_channel_unix_new(lsock);
+ GSource *src = g_io_create_watch(chan, G_IO_IN);
+
+ g_source_set_callback(src, G_SOURCE_FUNC(cb), NULL, NULL);
+ g_source_attach(src, ctx);
+
+ return g_thread_new(name, thread_func, loop);
+}
+
+int main(int argc, char **argv)
+{
+ char *time_socket = NULL, *net_socket = NULL;
+ int opt;
+
+ while ((opt = getopt(argc, argv, "s:n:h")) != -1) {
+ switch (opt) {
+ case 's':
+ time_socket = g_strdup(optarg);
+ break;
+ case 'n':
+ net_socket = g_strdup(optarg);
+ break;
+ case 'h':
+ default:
+ printf("Usage: %s -s time-device-socket [-n net-device-socket] | [ -h ]\n",
+ argv[0]);
+ return 0;
+ }
+ }
+
+ g_assert(time_socket);
+#define N_CLIENTS 2
+ fprintf(stderr,
+ "============ starting up simulation, requires %d clients ============\n",
+ N_CLIENTS);
+
+ calendar_init(N_CLIENTS);
+
+ new_device_thread(simtime_client_connected, time_socket, "time");
+ if (net_socket) {
+ new_device_thread(vu_net_client_connected, net_socket, "net");
+ }
+
+ calendar_run();
+
+ unlink(time_socket);
+ if (net_socket)
+ unlink(net_socket);
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,22 @@
+/*
+ * vhost-user sim main application header file
+ *
+ * Copyright (c) 2019 Intel Corporation. All rights reserved.
+ *
+ * Author:
+ * Johannes Berg <johannes.berg@intel.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 only.
+ * See the COPYING file in the top-level directory.
+ */
+#ifndef _SIM_MAIN_H
+#define _SIM_MAIN_H
+
+gboolean simtime_client_connected(GIOChannel *src,
+ GIOCondition cond,
+ gpointer data);
+gboolean vu_net_client_connected(GIOChannel *src,
+ GIOCondition cond,
+ gpointer data);
+
+#endif /* _SIM_MAIN_H */
new file mode 100644
@@ -0,0 +1,246 @@
+/*
+ * vhost-user sim network device
+ *
+ * Copyright (c) 2017 Intel Corporation. All rights reserved.
+ * Copyright (c) 2019 Intel Corporation. All rights reserved.
+ *
+ * Author:
+ * Johannes Berg <johannes.berg@intel.com>
+ *
+ * This work is based on the "vhost-user-blk" sample code by
+ * Changpeng Liu <changpeng.liu@intel.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 only.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "contrib/libvhost-user/libvhost-user-glib.h"
+#include "contrib/libvhost-user/libvhost-user.h"
+#include "qemu/iov.h"
+#include <gmodule.h>
+#include "main.h"
+#include "cal.h"
+
+static unsigned int clients;
+static GList *netdevs;
+G_LOCK_DEFINE(net);
+
+typedef struct VuNetDev {
+ SimCalendarEntry entry;
+ VugDev parent;
+ VuVirtq *rxq, *txq;
+ GSequenceIter *iter;
+ int idx;
+} VuNetDev;
+
+typedef struct VuNetPacket {
+ SimCalendarEntry entry;
+ void *transmitter;
+ int txidx;
+ unsigned int len;
+ char buf[];
+} VuNetPacket;
+
+static void vu_net_panic_cb(VuDev *vu_dev, const char *buf)
+{
+ if (buf) {
+ g_warning("vu_net_panic_cb: %s", buf);
+ }
+}
+
+static void send_to_one(VuNetDev *ndev, VuNetPacket *pkt)
+{
+ VuVirtqElement *elem;
+ VuDev *vu_dev = &ndev->parent.parent;
+
+ elem = vu_queue_pop(vu_dev, ndev->rxq, sizeof(VuVirtqElement));
+ if (!elem) {
+ /* no space on this device, drop the frame for it */
+ fprintf(stderr, "dropped packet to net %d!\n", ndev->idx);
+ return;
+ }
+ fprintf(stderr, "forwarding packet to net %d\n", ndev->idx);
+ g_assert(elem->in_num && !elem->out_num);
+ iov_from_buf(elem->in_sg, elem->in_num, 0, pkt->buf, pkt->len);
+
+ vu_queue_push(vu_dev, ndev->rxq, elem, pkt->len);
+ /* this _sync is key so the recipient can request scheduler time */
+ vu_queue_notify_sync(vu_dev, ndev->rxq);
+
+ free(elem);
+}
+
+static void vu_netpkt_calendar_cb(SimCalendarEntry *entry)
+{
+ VuNetPacket *pkt = container_of(entry, VuNetPacket, entry);
+ /* send to all devices */
+ GList *l;
+
+ G_LOCK(net);
+ for (l = netdevs; l; l = l->next) {
+ if (l->data != pkt->transmitter) {
+ send_to_one(l->data, pkt);
+ }
+ }
+ G_UNLOCK(net);
+
+ calendar_run_done(&pkt->entry);
+ g_free((void *)pkt->entry.name);
+ g_free(pkt);
+}
+
+static int vu_net_virtio_process_pkt(VuNetDev *ndev)
+{
+ VuVirtq *vq = ndev->txq;
+ VugDev *gdev = &ndev->parent;
+ VuDev *vu_dev = &gdev->parent;
+ VuVirtqElement *elem;
+ VuNetPacket *pkt;
+ ssize_t sz;
+
+ elem = vu_queue_pop(vu_dev, vq, sizeof(VuVirtqElement));
+ if (!elem) {
+ return -1;
+ }
+
+ g_assert(elem->out_num);
+
+ sz = iov_size(elem->out_sg, elem->out_num);
+ pkt = g_malloc(sizeof(*pkt) + sz);
+ pkt->entry.time = calendar_get_time() + 50 * 1000 * 1000; /* 50ms */
+ pkt->entry.callback = vu_netpkt_calendar_cb;
+ pkt->entry.name = g_strdup_printf("packet from %d", ndev->idx);
+ pkt->len = sz;
+ pkt->transmitter = ndev;
+ pkt->txidx = ndev->idx;
+ iov_to_buf(elem->out_sg, elem->out_num, 0, pkt->buf, sz);
+ calendar_entry_add(&pkt->entry);
+
+ vu_queue_push(vu_dev, vq, elem, 0);
+ /* the reclaim interrupt should also be predictable, so _sync() */
+ vu_queue_notify_sync(vu_dev, vq);
+
+ return 0;
+}
+
+static void vu_netdev_calendar_cb(SimCalendarEntry *entry)
+{
+ VuNetDev *ndev = container_of(entry, VuNetDev, entry);
+ int ret;
+
+ G_LOCK(net);
+ fprintf(stderr, "NET: handle TX IRQ from net %d\n", ndev->idx);
+ /* here we handle TX from the VQ */
+ do {
+ ret = vu_net_virtio_process_pkt(ndev);
+ } while (!ret);
+ fprintf(stderr, "NET: handle TX from net %d completed\n", ndev->idx);
+ G_UNLOCK(net);
+
+ calendar_run_done(entry);
+}
+
+static uint64_t vu_net_get_protocol_features(VuDev *dev)
+{
+ return 1ULL << VHOST_USER_PROTOCOL_F_INBAND_NOTIFICATIONS;
+}
+
+static void vu_net_process_vq(VuDev *vu_dev, int idx)
+{
+ VugDev *gdev = container_of(vu_dev, VugDev, parent);
+ VuNetDev *ndev = container_of(gdev, VuNetDev, parent);
+
+ assert(vu_dev && ndev);
+
+ fprintf(stderr, "insert IRQ from %s at %lld\n", ndev->entry.name, calendar_get_time());
+
+ /* insert the calendar entry to handle the interrupt */
+ calendar_entry_add_unless_present(&ndev->entry, calendar_get_time());
+}
+
+static void vu_net_queue_set_started(VuDev *vu_dev, int idx, bool started)
+{
+ VugDev *gdev = container_of(vu_dev, VugDev, parent);
+ VuNetDev *ndev = container_of(gdev, VuNetDev, parent);
+ VuVirtq *vq;
+
+ assert(vu_dev);
+
+ vq = vu_get_queue(vu_dev, idx);
+ /* set up the read fd */
+ switch (idx) {
+ case 0:
+ ndev->rxq = vq;
+ break;
+ case 1:
+ ndev->txq = vq;
+ vu_set_queue_handler(vu_dev, vq, started ? vu_net_process_vq : NULL);
+ break;
+ }
+}
+
+static int
+vu_net_process_msg(VuDev *vu_dev, VhostUserMsg *msg, int *do_reply)
+{
+ VugDev *gdev = container_of(vu_dev, VugDev, parent);
+ VuNetDev *ndev = container_of(gdev, VuNetDev, parent);
+
+ if (msg->request != VHOST_USER_NONE) {
+ return 0;
+ }
+
+ *do_reply = 0;
+ fprintf(stderr, "net client %d disconnected\n", ndev->idx);
+
+ G_LOCK(net);
+ netdevs = g_list_remove(netdevs, ndev);
+ clients--;
+
+ vug_deinit(gdev);
+ G_UNLOCK(net);
+ calendar_entry_destroy(&ndev->entry);
+ g_free((void *)ndev->entry.name);
+ g_free(ndev);
+
+ return 1;
+}
+
+static const VuDevIface vu_net_iface = {
+ .queue_set_started = vu_net_queue_set_started,
+ .get_protocol_features = vu_net_get_protocol_features,
+ .process_msg = vu_net_process_msg,
+};
+
+gboolean vu_net_client_connected(GIOChannel *src,
+ GIOCondition cond,
+ gpointer data)
+{
+ int lsock = g_io_channel_unix_get_fd(src);
+ int csock = accept(lsock, NULL, NULL);
+ VuNetDev *ndev;
+
+ if (csock < 0) {
+ fprintf(stderr, "Accept error %s\n", strerror(errno));
+ return TRUE;
+ }
+
+ ndev = g_new0(VuNetDev, 1);
+ if (!ndev) {
+ return TRUE;
+ }
+
+ clients++;
+ ndev->idx = clients;
+ ndev->entry.name = g_strdup_printf("net-irq %d", clients);
+ ndev->entry.callback = vu_netdev_calendar_cb;
+ vug_init(&ndev->parent, 2, csock, vu_net_panic_cb,
+ &vu_net_iface);
+ fprintf(stderr, "net client %d connected\n", clients);
+
+ G_LOCK(net);
+ netdevs = g_list_prepend(netdevs, ndev);
+ G_UNLOCK(net);
+
+ return TRUE;
+}
new file mode 100644
@@ -0,0 +1,267 @@
+/*
+ * vhost-user simtime device application
+ *
+ * Copyright (c) 2017 Intel Corporation. All rights reserved.
+ * Copyright (c) 2019 Intel Corporation. All rights reserved.
+ *
+ * Author:
+ * Johannes Berg <johannes.berg@intel.com>
+ *
+ * This work is based on the "vhost-user-blk" sample code by
+ * Changpeng Liu <changpeng.liu@intel.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 only.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "standard-headers/linux/um_timetravel.h"
+#include "cal.h"
+#include "main.h"
+
+#define DEBUG 1
+#define DPRINT(...) do { \
+ if (DEBUG) { \
+ fprintf(stderr, __VA_ARGS__); \
+ fflush(stderr); \
+ } \
+} while (0)
+
+typedef struct SimTimeConnection {
+ GMutex lock;
+ __u64 offset;
+ GIOChannel *chan;
+ GMainLoop *loop;
+ int idx;
+ SimCalendarEntry entry;
+ unsigned long long num_requests, num_waits, num_updates;
+ bool waiting;
+} SimTimeConnection;
+
+static int clients;
+
+static const char *simtime_op_str(uint64_t op)
+{
+#define OPSTR(x) case UM_TIMETRAVEL_##x: return "UM_TIMETRAVEL_" #x
+ switch (op) {
+ OPSTR(ACK);
+ OPSTR(REQUEST);
+ OPSTR(WAIT);
+ OPSTR(GET);
+ OPSTR(UPDATE);
+ OPSTR(RUN);
+ OPSTR(FREE_UNTIL);
+ default:
+ return "unknown";
+ }
+}
+
+static int full_read(int fd, void *_buf, size_t len)
+{
+ unsigned char *buf = _buf;
+ ssize_t ret;
+
+ do {
+ ret = read(fd, buf, len);
+ if (ret > 0) {
+ buf += ret;
+ len -= ret;
+ } else if (ret == 0) {
+ return 0;
+ } else {
+ return -errno;
+ }
+ } while (len > 0);
+
+ return buf - (unsigned char *)_buf;
+}
+
+static int full_write(int fd, const void *_buf, size_t len)
+{
+ const unsigned char *buf = _buf;
+ ssize_t ret;
+
+ do {
+ ret = write(fd, buf, len);
+ if (ret > 0) {
+ buf += ret;
+ len -= ret;
+ } else if (ret == 0) {
+ return 0;
+ } else {
+ return -errno;
+ }
+ } while (len > 0);
+
+ return buf - (const unsigned char *)_buf;
+}
+
+static void simtime_handle_message(SimTimeConnection *conn,
+ struct um_timetravel_msg *msg)
+{
+ struct um_timetravel_msg resp = {
+ .op = UM_TIMETRAVEL_ACK,
+ };
+
+ DPRINT(" %d | message %s (%lld, time=%lld)\n",
+ conn->idx, simtime_op_str(msg->op), msg->op, msg->time);
+
+ switch (msg->op) {
+ case UM_TIMETRAVEL_REQUEST:
+ if (calendar_entry_remove(&conn->entry)) {
+ conn->entry.time = conn->offset + msg->time;
+ calendar_entry_add(&conn->entry);
+ DPRINT(" %d | calendar entry added for %lld\n", conn->idx, msg->time);
+ } else {
+ conn->entry.time = conn->offset + msg->time;
+ DPRINT(" %d | calendar entry time updated for %lld\n", conn->idx, msg->time);
+ }
+ conn->num_requests++;
+ break;
+ case UM_TIMETRAVEL_WAIT:
+ conn->num_waits++;
+ calendar_entry_add(&conn->entry);
+ calendar_run_done(&conn->entry);
+ break;
+ case UM_TIMETRAVEL_GET:
+ resp.time = calendar_get_time() - conn->offset;
+ DPRINT(" %d | returning time %lld\n", conn->idx, resp.time);
+ break;
+ case UM_TIMETRAVEL_UPDATE:
+ if (conn->offset + msg->time > conn->entry.time) {
+ calendar_entry_remove(&conn->entry);
+ }
+ calendar_set_time(conn->offset + msg->time);
+ conn->num_updates++;
+ break;
+ default:
+ printf("ignoring invalid message %llu (time %llu)\n",
+ msg->op, msg->time);
+ break;
+ }
+
+ g_assert(full_write(g_io_channel_unix_get_fd(conn->chan), &resp, sizeof(resp)) == sizeof(resp));
+ DPRINT(" %d | sent ACK for message %s (%lld, time %lld)\n", conn->idx, simtime_op_str(msg->op), msg->op, msg->time);
+}
+
+static void simtime_send_message(SimTimeConnection *conn,
+ struct um_timetravel_msg *msg)
+{
+ g_mutex_lock(&conn->lock);
+ DPRINT(" %d | send %s (%lld, time=%lld)\n",
+ conn->idx, simtime_op_str(msg->op), msg->op, msg->time);
+ g_io_channel_set_flags(conn->chan, 0, NULL);
+ g_assert(full_write(g_io_channel_unix_get_fd(conn->chan), msg, sizeof(*msg)) == sizeof(*msg));
+ do {
+ g_assert(full_read(g_io_channel_unix_get_fd(conn->chan), msg, sizeof(*msg)) == sizeof(*msg));
+ DPRINT(" %d | read %s (%lld, time=%lld), expecting ACK (0)\n",
+ conn->idx, simtime_op_str(msg->op), msg->op, msg->time);
+ if (msg->op == UM_TIMETRAVEL_ACK)
+ break;
+ simtime_handle_message(conn, msg);
+ } while (1);
+ g_io_channel_set_flags(conn->chan, G_IO_FLAG_NONBLOCK, NULL);
+ g_mutex_unlock(&conn->lock);
+}
+
+static void simtime_calendar_cb(SimCalendarEntry *entry)
+{
+ SimTimeConnection *conn = container_of(entry, SimTimeConnection, entry);
+ struct um_timetravel_msg msg = {
+ .op = UM_TIMETRAVEL_RUN,
+ .time = entry->time - conn->offset,
+ };
+
+ simtime_send_message(conn, &msg);
+}
+
+static void __attribute__((used))
+simtime_update_until_cb(SimCalendarEntry *entry, unsigned long long time)
+{
+ SimTimeConnection *conn = container_of(entry, SimTimeConnection, entry);
+ struct um_timetravel_msg msg = {
+ .op = UM_TIMETRAVEL_FREE_UNTIL,
+ .time = time - conn->offset,
+ };
+
+ simtime_send_message(conn, &msg);
+}
+
+static gboolean simtime_read_cb(GIOChannel *src, GIOCondition c, gpointer data)
+{
+ SimTimeConnection *conn = data;
+ int fd = g_io_channel_unix_get_fd(src);
+ struct um_timetravel_msg msg;
+ int bytes;
+
+ g_mutex_lock(&conn->lock);
+ DPRINT(" %d | locked connection for reading\n", conn->idx);
+ bytes = full_read(fd, &msg, sizeof(msg));
+ if (bytes < 0 && bytes == -EAGAIN) {
+ g_mutex_unlock(&conn->lock);
+ return TRUE;
+ }
+ if (bytes <= 0) {
+ clients--;
+ printf("client disconnected, made %lld requests and waited %lld times, sent %lld updates\n",
+ conn->num_requests, conn->num_waits, conn->num_updates);
+ printf("we now have %d clients left\n", clients);
+
+ calendar_entry_destroy(&conn->entry);
+ // leak for now ... source is still attached
+ //g_free(conn);
+ g_mutex_unlock(&conn->lock);
+ return FALSE;
+ }
+ g_assert(bytes == sizeof(msg));
+
+ simtime_handle_message(conn, &msg);
+ g_mutex_unlock(&conn->lock);
+ DPRINT(" %d | unlocked connection\n", conn->idx);
+ return TRUE;
+}
+
+gboolean simtime_client_connected(GIOChannel *listen_src,
+ GIOCondition cond,
+ gpointer data)
+{
+ int lsock = g_io_channel_unix_get_fd(listen_src);
+ int csock = accept(lsock, NULL, NULL);
+ SimTimeConnection *conn;
+ GSource *src;
+
+ if (csock < 0) {
+ fprintf(stderr, "Accept error %s\n", strerror(errno));
+ return TRUE;
+ }
+
+ conn = g_new0(SimTimeConnection, 1);
+ if (!conn) {
+ return TRUE;
+ }
+
+ g_mutex_init(&conn->lock);
+
+ conn->chan = g_io_channel_unix_new(csock);
+ g_io_channel_set_flags(conn->chan, G_IO_FLAG_NONBLOCK, NULL);
+ src = g_io_create_watch(conn->chan, G_IO_IN);
+ g_source_set_callback(src, G_SOURCE_FUNC(simtime_read_cb), conn, NULL);
+ g_source_attach(src, g_main_context_get_thread_default());
+
+ conn->entry.callback = simtime_calendar_cb;
+ conn->entry.update_until = simtime_update_until_cb;
+ /*
+ * Mark this as a real scheduling client for purposes of
+ * tracking the number of them connected to the sim.
+ */
+ conn->entry.client = true;
+
+ clients++;
+ printf("client connected (now have %d)\n", clients);
+
+ conn->offset = calendar_get_time();
+ conn->idx = clients;
+ conn->entry.name = g_strdup_printf("time %d", clients);
+
+ return TRUE;
+}
new file mode 100644
@@ -0,0 +1,107 @@
+/*
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Copyright (C) 2019 Intel Corporation
+ */
+#ifndef _UAPI_LINUX_UM_TIMETRAVEL_H
+#define _UAPI_LINUX_UM_TIMETRAVEL_H
+#include <linux/types.h>
+
+/**
+ * struct um_timetravel_msg - UM time travel message
+ *
+ * This is the basic message type, going in both directions.
+ *
+ * This is the message passed between the host (user-mode Linux instance)
+ * and the calendar (the application on the other side of the socket) in
+ * order to implement common scheduling.
+ *
+ * Whenever UML has an event it will request runtime for it from the
+ * calendar, and then wait for its turn until it can run, etc. Note
+ * that it will only ever request the single next runtime, i.e. multiple
+ * REQUEST messages override each other.
+ */
+struct um_timetravel_msg {
+ /**
+ * @op: operation value from &enum um_timetravel_ops
+ */
+ __u64 op;
+
+ /**
+ * @time: time in nanoseconds
+ */
+ __u64 time;
+};
+
+/**
+ * enum um_timetravel_ops - Operation codes
+ */
+enum um_timetravel_ops {
+ /**
+ * @UM_TIMETRAVEL_ACK: response (ACK) to any previous message,
+ * this usually doesn't carry any data in the 'time' field
+ * unless otherwise specified below
+ */
+ UM_TIMETRAVEL_ACK = 0,
+
+ /**
+ * @UM_TIMETRAVEL_REQUEST: request to run at the given time
+ * (host -> calendar)
+ */
+ UM_TIMETRAVEL_REQUEST = 1,
+
+ /**
+ * @UM_TIMETRAVEL_WAIT: Indicate waiting for the previously requested
+ * runtime, new requests may be made while waiting (e.g. due to
+ * interrupts); the time field is ignored. The calendar must process
+ * this message and later send a %UM_TIMETRAVEL_RUN message when
+ * the host can run again.
+ * (host -> calendar)
+ */
+ UM_TIMETRAVEL_WAIT = 2,
+
+ /**
+ * @UM_TIMETRAVEL_GET: return the current time from the calendar in the
+ * ACK message, the time in the request message is ignored
+ * (host -> calendar)
+ */
+ UM_TIMETRAVEL_GET = 3,
+
+ /**
+ * @UM_TIMETRAVEL_UPDATE: time update to the calendar, must be sent e.g.
+ * before kicking an interrupt to another calendar
+ * (host -> calendar)
+ */
+ UM_TIMETRAVEL_UPDATE = 4,
+
+ /**
+ * @UM_TIMETRAVEL_RUN: run time request granted, current time is in
+ * the time field
+ * (calendar -> host)
+ */
+ UM_TIMETRAVEL_RUN = 5,
+
+ /**
+ * @UM_TIMETRAVEL_FREE_UNTIL: Enable free-running until the given time,
+ * this is a message from the calendar telling the host that it can
+ * freely do its own scheduling for anything before the indicated
+ * time.
+ * Note that if a calendar sends this message once, the host may
+ * assume that it will also do so in the future, if it implements
+ * wraparound semantics for the time field.
+ * (calendar -> host)
+ */
+ UM_TIMETRAVEL_FREE_UNTIL = 6,
+};
+
+#endif /* _UAPI_LINUX_UM_TIMETRAVEL_H */