From nobody Sun Apr 28 09:03:11 2024 Delivered-To: importer@patchew.org Received-SPF: pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) client-ip=208.118.235.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Authentication-Results: mx.zoho.com; spf=pass (zoho.com: domain of gnu.org designates 208.118.235.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; Return-Path: Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) by mx.zohomail.com with SMTPS id 1487097570575625.01088004893; Tue, 14 Feb 2017 10:39:30 -0800 (PST) Received: from localhost ([::1]:36550 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cdi0d-0001Dr-Q5 for importer@patchew.org; Tue, 14 Feb 2017 13:39:27 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:55278) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1cdhzV-0000ph-BZ for qemu-devel@nongnu.org; Tue, 14 Feb 2017 13:38:20 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1cdhzS-0004Ra-6e for qemu-devel@nongnu.org; Tue, 14 Feb 2017 13:38:17 -0500 Received: from mta02.ornl.gov ([128.219.177.136]:9628) by eggs.gnu.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:32) (Exim 4.71) (envelope-from ) id 1cdhzR-0004Qt-RN for qemu-devel@nongnu.org; Tue, 14 Feb 2017 13:38:14 -0500 Received: from emgwy1.ornl.gov ([160.91.254.9]) by iron2.ornl.gov with ESMTP/TLS/DHE-RSA-AES256-GCM-SHA384; 14 Feb 2017 13:38:11 -0500 Received: from stygia.ornl.gov (stygia.ornl.gov [160.91.240.222]) by emgwy1.ornl.gov (Postfix) with ESMTP id 3vNB4g1hbSz7tJg; Tue, 14 Feb 2017 13:38:11 -0500 (EST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=ornl.gov; i=@ornl.gov; q=dns/txt; s=p20151116; t=1487097493; x=1518633493; h=from:to:cc:subject:date:message-id; bh=IwGAdVM+02vrv2VqSyCE/lKS4Bcv8wGb7QIsYGkrAcs=; b=nA99IRPAW7kCpao+vdntuNTGWxvuFhpeOpVifYUs2L/uGZ+hcFDi//vr j4qPH6wE1WCTJrf8Bc6cXs0osqXoITrYu+FjcFiSY6brCKz6VzJH4h+6V cudKwb8c8wj20osYPCmmZhFz+ByPy1eHwx7PQt0YYFd7kTawCdXfYyH/S PoTWvhe/T4oNex37qiqY+oWeVV9D9toFPKTVPwvgX3pM6ZUFRhS8Kjtl7 sGBhxVfmpW3A3O0gjAFKxu4lXbKvKHKflo/yGtKcD0HdV5WD5VgUQlQVd xHEwl635e1X8sQzkF1rrO9xKUzoQFBDe7gdVym0A1aRNULnRZgoH1HBpS Q==; X-SG: RELAYLIST X-IronPort-AV: E=Sophos;i="5.35,162,1484024400"; d="scan'208";a="5013090" From: "James J. Nutaro" To: qemu-devel@nongnu.org Date: Tue, 14 Feb 2017 13:37:56 -0500 Message-Id: <1487097476-14259-1-git-send-email-nutarojj@ornl.gov> X-Mailer: git-send-email 2.7.4 X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 128.219.177.136 Subject: [Qemu-devel] [PATCH V8] qqq: module for synchronizing with a simulation X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: pbonzini@redhat.com, nutarojj@ornl.gov Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: "Qemu-devel" X-ZohoMail: RSF_0 Z_629925259 SPT_0 Content-Transfer-Encoding: quoted-printable MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" This patch adds an interface for pacing the execution of QEMU to match an e= xternal simulation clock. Its aim is to permit QEMU to be used as a module within a larger simulation system. v6 -> v7 Added a check for command line arguments that are consistent with -qqq and print an error message if incompatible arguments are provide. [Paolo] v7 -> v8 Updated patch to track changes in the main branch of qemu development. Signed-off-by: James J. Nutaro --- Makefile.target | 3 + cpus.c | 8 +++ docs/simulation-sync.txt | 59 ++++++++++++++++ include/sysemu/cpus.h | 1 + kvm-all.c | 10 +++ qemu-options.hx | 15 ++++ qqq.c | 177 +++++++++++++++++++++++++++++++++++++++++++= ++++ qqq.h | 37 ++++++++++ vl.c | 35 ++++++++++ 9 files changed, 345 insertions(+) create mode 100644 docs/simulation-sync.txt create mode 100644 qqq.c create mode 100644 qqq.h diff --git a/Makefile.target b/Makefile.target index 924304c..19703ca 100644 --- a/Makefile.target +++ b/Makefile.target @@ -149,6 +149,9 @@ obj-y +=3D dump.o obj-y +=3D migration/ram.o migration/savevm.o LIBS :=3D $(libs_softmmu) $(LIBS) =20 +# qqq support +obj-y +=3D qqq.o + # xen support obj-$(CONFIG_XEN) +=3D xen-common.o obj-$(CONFIG_XEN_I386) +=3D xen-hvm.o xen-mapcache.o diff --git a/cpus.c b/cpus.c index 71a82e5..dd659f1 100644 --- a/cpus.c +++ b/cpus.c @@ -1765,3 +1765,11 @@ void dump_drift_info(FILE *f, fprintf_function cpu_f= printf) cpu_fprintf(f, "Max guest advance NA\n"); } } + +void kick_all_vcpus(void) +{ + CPUState *cpu; + CPU_FOREACH(cpu) { + qemu_cpu_kick(cpu); + } +} diff --git a/docs/simulation-sync.txt b/docs/simulation-sync.txt new file mode 100644 index 0000000..de4dd34 --- /dev/null +++ b/docs/simulation-sync.txt @@ -0,0 +1,59 @@ +=3D Synchronizing the virtual clock with an external source =3D + +QEMU has a protocol for synchronizing its virtual clock +with the clock of a simulator in which QEMU is embedded +as a component. This options is enabled with the -qqq +argument, and it should generally be accompanied by the +following additional command line arguments: + +-icount 1,sleep=3Doff -rtc clock=3Dvm + or +-enable-kvm -rtc clock=3Dvm + +The -qqq argument is used to supply a file descriptor +for a Unix socket, which is used for synchronization. +The procedure for launching QEMU in is synchronization +mode has three steps: + +(1) Create a socket pair with the Linux socketpair function. + The code segment that does this might look like + + int socks[2]; + socketpair(AF_UNIX,SOCK_STREAM,0,socks); + +(2) Fork QEMU with the appropriate command line arguments. + The -qqq part of the argument will look something like + + -qqq sock=3Dsocks[1] + +(3) After forking QEMU, close sock[1] and retain the + sock[0] for communicating with QEMU. + +The synchronization protocol is very simple. To start, the +external simulator writes an integer to its socket with +the amount of time in microseconds that QEMU is allowed to +advance. The code segment that does this might look like: + + uint32_t ta =3D htonl(1000); // Advance by 1 millisecond + write(sock[0],&ta,sizeof(uint32_t)); + +The external simulator can then advance its clock by this +same amount. During this time, QEMU and the external simulator +will be executing in parallel. When the external simulator +completes its time advance, it waits for QEMU by reading from +its socket. The value read will be the actual number of +virtual microseconds by which QEMU has advanced its virtual clock. +This will be greater than or equal to the requested advance. +The code that does this might look like: + + uint32_t ta; + read(fd,&ta,sizeof(uint32_t)); + ta =3D ntohl(ta); + +These steps are repeated until either (1) the external simulator +closes its socket thereby causing QEMU to terminate or (2) QEMU +stops executing (e.g., if the emulated computer is shutdown) and +causes a read or write error on the simulator's socket. + +You can find an example of a simulator using this protocol in +the adevs simulation package at http://sourceforge.net/projects/adevs/ diff --git a/include/sysemu/cpus.h b/include/sysemu/cpus.h index 3728a1e..bdf22c9 100644 --- a/include/sysemu/cpus.h +++ b/include/sysemu/cpus.h @@ -2,6 +2,7 @@ #define QEMU_CPUS_H =20 /* cpus.c */ +void kick_all_vcpus(void); bool qemu_in_vcpu_thread(void); void qemu_init_cpu_loop(void); void resume_all_vcpus(void); diff --git a/kvm-all.c b/kvm-all.c index a27c880..b361cb5 100644 --- a/kvm-all.c +++ b/kvm-all.c @@ -18,6 +18,7 @@ =20 #include =20 +#include "qqq.h" #include "qemu-common.h" #include "qemu/atomic.h" #include "qemu/option.h" @@ -1926,6 +1927,15 @@ int kvm_cpu_exec(CPUState *cpu) qemu_cpu_kick_self(); } =20 + if (qqq_enabled()) { + /* Pause here while qqq is synchronizing with a simulation clo= ck. + * We do not want to execute instructions past the synchroniza= tion + * deadline, but its ok to update the states of other equipment + * like timers, i/o devices, etc. + */ + qqq_sync(); + } + run_ret =3D kvm_vcpu_ioctl(cpu, KVM_RUN, 0); =20 attrs =3D kvm_arch_post_run(cpu, run); diff --git a/qemu-options.hx b/qemu-options.hx index ac036b4..6a7abf7 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -4100,6 +4100,21 @@ contents of @code{iv.b64} to the second secret =20 ETEXI =20 +DEF("qqq", HAS_ARG, QEMU_OPTION_qqq, \ + "-qqq sock=3Dfd\n" \ + " enable synchronization of the virtual clock \n" \ + " with an external simulation clock\n", QEMU_ARCH_ALL) +STEXI +@item -qqq sock=3D@var{fd0} +@findex -qqq +Qemu will use the supplied socket to synchronize its virtual clock with +an external simulation clock. Qemu will wait until a time slice size in +microseconds is supplied on the socket. Then it will execute for at +least that number of virtual microseconds before writing the actual +virtual time that has elapsed in microseconds to the socket. This +cycle will repeat until a zero time advance is requested, which +will cause qemu to exit. +ETEXI =20 HXCOMM This is the last statement. Insert new options before this line! STEXI diff --git a/qqq.c b/qqq.c new file mode 100644 index 0000000..b186c37 --- /dev/null +++ b/qqq.c @@ -0,0 +1,177 @@ +#include "qemu/osdep.h" +#include "qemu/timer.h" +#include "qemu/main-loop.h" +#include "sysemu/cpus.h" +#include "sysemu/kvm.h" +#include "qqq.h" +/* This is a Linux only feature */ + +#ifndef _WIN32 + +#include +#include +#include +#include + +static bool enabled =3D false, syncing =3D true; +static unsigned elapsed; /* initialized to zero */ +static int time_advance =3D -1; +static int fd =3D -1; +static int64_t t; +static QEMUTimer *sync_timer; +static QemuMutex qqq_mutex; +static QemuCond qqq_cond; + +bool qqq_enabled(void) +{ + return enabled; +} + +void qqq_sync(void) +{ + /* kvm-all.c will call this function before running + * instructions with kvm. Because syncing will be + * true while qqq is waiting for a new time advance + * from the simulation, no instructions will execute + * while the machine is supposed to be suspended in + * simulation time. + */ + qemu_mutex_lock(&qqq_mutex); + while (syncing) { + qemu_cond_wait(&qqq_cond, &qqq_mutex); + } + qemu_mutex_unlock(&qqq_mutex); +} + +static void cleanup_and_exit(void) +{ + /* Close the socket and quit */ + close(fd); + exit(0); +} + +static void start_emulator(void) +{ + if (kvm_enabled()) { + /* Setting syncing to false tells kvm-all that + * it can execute guest instructions. + */ + qemu_mutex_lock(&qqq_mutex); + syncing =3D false; + qemu_mutex_unlock(&qqq_mutex); + qemu_cond_signal(&qqq_cond); + /* Restart the emulator clock */ + cpu_enable_ticks(); + } +} + +static void stop_emulator(void) +{ + if (kvm_enabled()) { + /* Tell the emulator that it is not allowed to + * execute guest instructions. + */ + qemu_mutex_lock(&qqq_mutex); + syncing =3D true; + qemu_mutex_unlock(&qqq_mutex); + /* Kick KVM off of the CPU and stop the emulator clock. */ + cpu_disable_ticks(); + kick_all_vcpus(); + } +} + +static void write_mem_value(unsigned val) +{ + uint32_t msg =3D htonl(val); + if (write(fd, &msg, sizeof(uint32_t)) !=3D sizeof(uint32_t)) { + /* If the socket is no good, then assume this is an + * indication that we should exit. + */ + cleanup_and_exit(); + } +} + +static unsigned read_mem_value(void) +{ + uint32_t msg; + if (read(fd, &msg, sizeof(uint32_t)) !=3D sizeof(uint32_t)) { + /* If the socket is no good, then assume this is an + * indication that we should exit. + */ + cleanup_and_exit(); + } + return ntohl(msg); +} + +static void schedule_next_event(void) +{ + /* If we got the time advance in fd_read, then don't do it + * again here. */ + if (time_advance < 0) { + /* Otherwise read the value from the socket */ + time_advance =3D read_mem_value(); + } + assert(t =3D=3D 0 || + abs(t - qemu_clock_get_us(QEMU_CLOCK_VIRTUAL)) <=3D time_advance); + /* Schedule the next synchronization point */ + timer_mod(sync_timer, t + time_advance); + /* Note that we need to read the time advance again on the next pass */ + time_advance =3D -1; + /* Start advancing cpu ticks and the wall clock */ + start_emulator(); +} + +static void sync_func(void *data) +{ + /* Stop advancing cpu ticks and the wall clock */ + stop_emulator(); + /* Report the actual elapsed time to the external simulator. */ + int64_t tnow =3D qemu_clock_get_us(QEMU_CLOCK_VIRTUAL); + elapsed =3D tnow - t; + write_mem_value(elapsed); + /* Update our time of last event */ + t =3D tnow; + /* Schedule the next event */ + schedule_next_event(); +} + +static void fd_read(void *opaque) +{ + /* Read the time advance if its becomes available + * before our timer expires */ + time_advance =3D read_mem_value(); +} + +void setup_qqq(QemuOpts *opts) +{ + /* The module has been enabled */ + enabled =3D true; + if (kvm_enabled()) { + qemu_mutex_init(&qqq_mutex); + qemu_cond_init(&qqq_cond); + } + /* Stop the clock while the simulation is initialized */ + stop_emulator(); + /* Initialize the simulation clock */ + t =3D 0; + /* Get the communication socket */ + fd =3D qemu_opt_get_number(opts, "sock", 0); + /* Start the timer to ensure time warps advance the clock */ + sync_timer =3D timer_new_us(QEMU_CLOCK_VIRTUAL, sync_func, NULL); + /* Get the time advance that is requested by the simulation */ + schedule_next_event(); + /* Register the file descriptor with qemu. This should ensure + * the emulator doesn't pause for lack of I/O and thereby + * cause the attached simulator to pause with it. */ + qemu_set_fd_handler(fd, fd_read, NULL, NULL); +} + +#else + +void setup_qqq(QemuOpts *opts) +{ + fprintf(stderr, "-qqq is not supported on Windows, exiting\n"); + exit(0); +} + +#endif diff --git a/qqq.h b/qqq.h new file mode 100644 index 0000000..e106d3c --- /dev/null +++ b/qqq.h @@ -0,0 +1,37 @@ +/* + * This work is licensed under the terms of the GNU GPL + * version 2. Seethe COPYING file in the top-level directory. + * + * A module for pacing the rate of advance of the computer + * clock in reference to an external simulation clock. The + * basic approach used here is adapted from QBox from Green + * Socs. The mode of operation is as follows: + * + * The simulator uses pipes to exchange time advance data. + * The external simulator starts the exchange by forking a + * QEMU process and passing is descriptors for a read and + * write pipe. Then the external simulator writes an integer + * (native endian) to the pipe to indicate the number of + * microseconds that QEMU should advance. QEMU advances its + * virtual clock by this amount and writes to its write pipe + * the actual number of microseconds that have advanced. + * This process continues until a pipe on either side is + * closed, which will either cause QEMU to exit (if the + * external simulator closes a pipe) or raise SIGPIPE in the + * external simulator (if QEMU closes a pipe). + * + * Authors: + * James Nutaro + * + */ +#ifndef QQQ_H +#define QQQ_H + +#include "qemu/osdep.h" +#include "qemu-options.h" + +void qqq_sync(void); +bool qqq_enabled(void); +void setup_qqq(QemuOpts *opts); + +#endif diff --git a/vl.c b/vl.c index b4eaf03..63a4d92 100644 --- a/vl.c +++ b/vl.c @@ -126,6 +126,8 @@ int main(int argc, char **argv) #include "qapi/qmp/qerror.h" #include "sysemu/iothread.h" =20 +#include "qqq.h" + #define MAX_VIRTIO_CONSOLES 1 #define MAX_SCLP_CONSOLES 1 =20 @@ -237,6 +239,20 @@ static struct { { .driver =3D "virtio-vga", .flag =3D &default_vga }, }; =20 +static QemuOptsList qemu_qqq_opts =3D { + .name =3D "qqq", + .implied_opt_name =3D "", + .merge_lists =3D true, + .head =3D QTAILQ_HEAD_INITIALIZER(qemu_qqq_opts.head), + .desc =3D { + { + .name =3D "sock", + .type =3D QEMU_OPT_NUMBER, + }, + { /* end of list */ } + }, +}; + static QemuOptsList qemu_rtc_opts =3D { .name =3D "rtc", .head =3D QTAILQ_HEAD_INITIALIZER(qemu_rtc_opts.head), @@ -2940,6 +2956,7 @@ int main(int argc, char **argv, char **envp) DisplayState *ds; int cyls, heads, secs, translation; QemuOpts *hda_opts =3D NULL, *opts, *machine_opts, *icount_opts =3D NU= LL; + QemuOpts *qqq_opts =3D NULL; QemuOptsList *olist; int optind; const char *optarg; @@ -2979,6 +2996,7 @@ int main(int argc, char **argv, char **envp) module_call_init(MODULE_INIT_QOM); module_call_init(MODULE_INIT_QAPI); =20 + qemu_add_opts(&qemu_qqq_opts); qemu_add_opts(&qemu_drive_opts); qemu_add_drive_opts(&qemu_legacy_drive_opts); qemu_add_drive_opts(&qemu_common_drive_opts); @@ -3842,6 +3860,13 @@ int main(int argc, char **argv, char **envp) exit(1); } break; + case QEMU_OPTION_qqq: + qqq_opts =3D qemu_opts_parse_noisily(qemu_find_opts("qqq"), + optarg, true); + if (!qqq_opts) { + exit(1); + } + break; case QEMU_OPTION_incoming: if (!incoming) { runstate_set(RUN_STATE_INMIGRATE); @@ -4355,6 +4380,16 @@ int main(int argc, char **argv, char **envp) /* spice needs the timers to be initialized by this point */ qemu_spice_init(); =20 + if (qqq_opts) { + if (!(rtc_clock =3D=3D QEMU_CLOCK_VIRTUAL && ( + (icount_opts && !qemu_opt_get_bool(icount_opts, "sleep", tru= e)) || + kvm_enabled()))) { + error_report("-qqq requires options -rtc clock=3Dvm and either= " + "icount -1,sleep=3Doff or -enable-kvm"); + exit(1); + } + setup_qqq(qqq_opts); + } cpu_ticks_init(); if (icount_opts) { if (!tcg_enabled()) { --=20 2.7.4