From patchwork Sat Feb 25 19:42:40 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Anthony Liguori X-Patchwork-Id: 143072 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [140.186.70.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id D11F1B6FA2 for ; Sun, 26 Feb 2012 07:08:01 +1100 (EST) Received: from localhost ([::1]:33982 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1S1NXN-0006rN-W2 for incoming@patchwork.ozlabs.org; Sat, 25 Feb 2012 14:44:09 -0500 Received: from eggs.gnu.org ([208.118.235.92]:46341) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1S1NWS-00055G-Cp for qemu-devel@nongnu.org; Sat, 25 Feb 2012 14:43:21 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1S1NWG-0007MQ-Gk for qemu-devel@nongnu.org; Sat, 25 Feb 2012 14:43:12 -0500 Received: from e37.co.us.ibm.com ([32.97.110.158]:51148) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1S1NWG-0007LT-6x for qemu-devel@nongnu.org; Sat, 25 Feb 2012 14:43:00 -0500 Received: from /spool/local by e37.co.us.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted for from ; Sat, 25 Feb 2012 12:42:55 -0700 Received: from d03dlp01.boulder.ibm.com (9.17.202.177) by e37.co.us.ibm.com (192.168.1.137) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted; Sat, 25 Feb 2012 12:42:54 -0700 Received: from d03relay03.boulder.ibm.com (d03relay03.boulder.ibm.com [9.17.195.228]) by d03dlp01.boulder.ibm.com (Postfix) with ESMTP id 967A1C4000B for ; Sat, 25 Feb 2012 12:42:53 -0700 (MST) Received: from d03av01.boulder.ibm.com (d03av01.boulder.ibm.com [9.17.195.167]) by d03relay03.boulder.ibm.com (8.13.8/8.13.8/NCO v10.0) with ESMTP id q1PJgrT2157314 for ; Sat, 25 Feb 2012 12:42:53 -0700 Received: from d03av01.boulder.ibm.com (loopback [127.0.0.1]) by d03av01.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVout) with ESMTP id q1PJgruZ010325 for ; Sat, 25 Feb 2012 12:42:53 -0700 Received: from titi.austin.rr.com (sig-9-65-148-108.mts.ibm.com [9.65.148.108]) by d03av01.boulder.ibm.com (8.14.4/8.13.1/NCO v10.0 AVin) with ESMTP id q1PJgqHv010314; Sat, 25 Feb 2012 12:42:52 -0700 From: Anthony Liguori To: qemu-devel@nongnu.org Date: Sat, 25 Feb 2012 13:42:40 -0600 Message-Id: <1330198969-27364-2-git-send-email-aliguori@us.ibm.com> X-Mailer: git-send-email 1.7.4.1 In-Reply-To: <1330198969-27364-1-git-send-email-aliguori@us.ibm.com> References: <1330198969-27364-1-git-send-email-aliguori@us.ibm.com> X-Content-Scanned: Fidelis XPS MAILER x-cbid: 12022519-7408-0000-0000-000002F45DCE X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 32.97.110.158 Cc: Kevin Wolf , Paolo Bonzini , Anthony Liguori Subject: [Qemu-devel] [PATCH 01/10] qtest: add test framework (v2) X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org The idea behind qtest is pretty simple. Instead of executing a CPU via TCG or KVM, rely on an external process to send events to the device model that the CPU would normally generate. qtest presents itself as an accelerator. In addition, a new option is added to establish a qtest server (-qtest) that takes a character device. This is what allows the external process to send CPU events to the device model. This is currently modelled after Xen since the Xen device model does something very similar. Instead of hooking cpu_exec, Xen sticks the CPU in the halted state making sure it never gets to execute. In addition, Xen replaces the LAPIC with a dummy interrupt controller that forwards interrupt requests. qtest does the exact same thing and uses a simple line based protocol to send the events. Documentation of that protocol is in qtest.c. I considered reusing the monitor for this job. Adding interrupts would be a bit difficult. In addition, logging would also be difficult. qtest has extensive logging support. All protocol commands are logged with time stamps using a new command line option (-qtest-log). Logging is important since ultimately, this is a feature for debugging. Signed-off-by: Anthony Liguori Signed-off-by: Paolo Bonzini --- v1 -> v2 - always send a response (Paolo) - enable echo (Paolo) - do not use TCG CPU threads (Paolo) --- Makefile.objs | 2 + cpu-exec.c | 1 + cpus.c | 62 +++++++++- qemu-options.hx | 8 ++ qtest.c | 359 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ qtest.h | 37 ++++++ vl.c | 8 ++ 7 files changed, 474 insertions(+), 3 deletions(-) create mode 100644 qtest.c create mode 100644 qtest.h diff --git a/Makefile.objs b/Makefile.objs index 808de6a..3372d9b 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -302,6 +302,8 @@ hw-obj-$(CONFIG_DP8393X) += dp8393x.o hw-obj-$(CONFIG_DS1225Y) += ds1225y.o hw-obj-$(CONFIG_MIPSNET) += mipsnet.o +hw-obj-y += qtest.o + # Sound sound-obj-y = sound-obj-$(CONFIG_SB16) += sb16.o diff --git a/cpu-exec.c b/cpu-exec.c index 2c2d24e..d476616 100644 --- a/cpu-exec.c +++ b/cpu-exec.c @@ -21,6 +21,7 @@ #include "disas.h" #include "tcg.h" #include "qemu-barrier.h" +#include "qtest.h" int tb_invalidated_flag; diff --git a/cpus.c b/cpus.c index f45a438..c77e649 100644 --- a/cpus.c +++ b/cpus.c @@ -740,6 +740,48 @@ static void *qemu_kvm_cpu_thread_fn(void *arg) return NULL; } +static void *qemu_dummy_cpu_thread_fn(void *arg) +{ +#ifdef _WIN32 + fprintf(stderr, "qtest is not supported under Windows\n"); + exit(1); +#else + CPUState *env = arg; + sigset_t waitset; + int r; + + qemu_mutex_lock_iothread(); + qemu_thread_get_self(env->thread); + env->thread_id = qemu_get_thread_id(); + + sigemptyset(&waitset); + sigaddset(&waitset, SIG_IPI); + + /* signal CPU creation */ + env->created = 1; + qemu_cond_signal(&qemu_cpu_cond); + + cpu_single_env = env; + while (1) { + cpu_single_env = NULL; + qemu_mutex_unlock_iothread(); + do { + int sig; + r = sigwait(&waitset, &sig); + } while (r == -1 && (errno == EAGAIN || errno == EINTR)); + if (r == -1) { + perror("sigwait"); + exit(1); + } + qemu_mutex_lock_iothread(); + cpu_single_env = env; + qemu_wait_io_event_common(env); + } + + return NULL; +#endif +} + static void tcg_exec_all(void); static void *qemu_tcg_cpu_thread_fn(void *arg) @@ -797,7 +839,7 @@ void qemu_cpu_kick(void *_env) CPUState *env = _env; qemu_cond_broadcast(env->halt_cond); - if (kvm_enabled() && !env->thread_kicked) { + if (!tcg_enabled() && !env->thread_kicked) { qemu_cpu_kick_thread(env); env->thread_kicked = true; } @@ -826,7 +868,7 @@ int qemu_cpu_is_self(void *_env) void qemu_mutex_lock_iothread(void) { - if (kvm_enabled()) { + if (!tcg_enabled()) { qemu_mutex_lock(&qemu_global_mutex); } else { iothread_requesting_mutex = true; @@ -929,6 +971,18 @@ static void qemu_kvm_start_vcpu(CPUState *env) } } +static void qemu_dummy_start_vcpu(CPUState *env) +{ + env->thread = g_malloc0(sizeof(QemuThread)); + env->halt_cond = g_malloc0(sizeof(QemuCond)); + qemu_cond_init(env->halt_cond); + qemu_thread_create(env->thread, qemu_dummy_cpu_thread_fn, env, + QEMU_THREAD_JOINABLE); + while (env->created == 0) { + qemu_cond_wait(&qemu_cpu_cond, &qemu_global_mutex); + } +} + void qemu_init_vcpu(void *_env) { CPUState *env = _env; @@ -938,8 +992,10 @@ void qemu_init_vcpu(void *_env) env->stopped = 1; if (kvm_enabled()) { qemu_kvm_start_vcpu(env); - } else { + } else if (tcg_enabled()) { qemu_tcg_init_vcpu(env); + } else { + qemu_dummy_start_vcpu(env); } } diff --git a/qemu-options.hx b/qemu-options.hx index b129996..3dca2dc 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -2698,6 +2698,14 @@ the @var{simple} tracing backend. @end table ETEXI +DEF("qtest", HAS_ARG, QEMU_OPTION_qtest, + "-qtest CHR specify tracing options\n", + QEMU_ARCH_ALL) + +DEF("qtest-log", HAS_ARG, QEMU_OPTION_qtest_log, + "-qtest-log LOG specify tracing options\n", + QEMU_ARCH_ALL) + HXCOMM This is the last statement. Insert new options before this line! STEXI @end table diff --git a/qtest.c b/qtest.c new file mode 100644 index 0000000..c2fbf50 --- /dev/null +++ b/qtest.c @@ -0,0 +1,359 @@ +/* + * Test Server + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#include "qtest.h" +#include "qemu-char.h" +#include "ioport.h" +#include "memory.h" +#include "hw/irq.h" +#include "sysemu.h" + +#define MAX_IRQ 256 + +const char *qtest_chrdev; +const char *qtest_log; +int qtest_allowed = 0; + +static FILE *qtest_log_fp; +static CharDriverState *qtest_chr; +static GString *inbuf; +static int irq_levels[MAX_IRQ]; +static struct timeval start_time; +static bool qtest_opened; + +#define FMT_timeval "%" PRId64 ".%06" PRId64 + +/** + * QTest Protocol + * + * Line based protocol, request/response based. Server can send async messages + * so clients should always handle many async messages before the response + * comes in. + * + * Valid requests + * + * > outb ADDR VALUE + * < OK + * + * > outw ADDR VALUE + * < OK + * + * > outl ADDR VALUE + * < OK + * + * > inb ADDR + * < OK VALUE + * + * > inw ADDR + * < OK VALUE + * + * > inl ADDR + * < OK VALUE + * + * > read ADDR SIZE + * < OK DATA + * + * > write ADDR SIZE DATA + * < OK + * + * Valid async messages: + * + * IRQ raise NUM + * IRQ lower NUM + * + * ADDR, SIZE, VALUE are all integers parsed with strtoul() with a base of 0. + * + * DATA is an arbitrarily long hex number prefixed with '0x'. If it's smaller + * than the expected size, the value will be zero filled at the end of the data + * sequence. + * + * NUM is an IRQ number. + */ + +static int hex2nib(char ch) +{ + if (ch >= '0' && ch <= '9') { + return ch - '0'; + } else if (ch >= 'a' && ch <= 'f') { + return 10 + (ch - 'a'); + } else if (ch >= 'A' && ch <= 'F') { + return 10 + (ch - 'a'); + } else { + return -1; + } +} + +static void qtest_get_time(struct timeval *tv) +{ + gettimeofday(tv, NULL); + tv->tv_sec -= start_time.tv_sec; + tv->tv_usec -= start_time.tv_usec; + if (tv->tv_usec < 0) { + tv->tv_usec += 1000000; + tv->tv_sec -= 1; + } +} + +static void qtest_send_prefix(CharDriverState *chr) +{ + struct timeval tv; + + if (!qtest_log_fp || !qtest_opened) { + return; + } + + qtest_get_time(&tv); + fprintf(qtest_log_fp, "[S +" FMT_timeval "] ", + tv.tv_sec, tv.tv_usec); +} + +static void qtest_send(CharDriverState *chr, const char *fmt, ...) +{ + va_list ap; + char buffer[1024]; + size_t len; + + va_start(ap, fmt); + len = vsnprintf(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + qemu_chr_fe_write(chr, (uint8_t *)buffer, len); + if (qtest_log_fp && qtest_opened) { + fprintf(qtest_log_fp, "%s", buffer); + } +} + +static void qtest_process_command(CharDriverState *chr, gchar **words) +{ + const gchar *command; + + g_assert(words); + + command = words[0]; + + if (qtest_log_fp) { + struct timeval tv; + int i; + + qtest_get_time(&tv); + fprintf(qtest_log_fp, "[R +" FMT_timeval "]", + tv.tv_sec, tv.tv_usec); + for (i = 0; words[i]; i++) { + fprintf(qtest_log_fp, " %s", words[i]); + } + fprintf(qtest_log_fp, "\n"); + } + + g_assert(command); + if (strcmp(words[0], "outb") == 0 || + strcmp(words[0], "outw") == 0 || + strcmp(words[0], "outl") == 0) { + uint16_t addr; + uint32_t value; + + g_assert(words[1] && words[2]); + addr = strtol(words[1], NULL, 0); + value = strtol(words[2], NULL, 0); + + if (words[0][3] == 'b') { + cpu_outb(addr, value); + } else if (words[0][3] == 'w') { + cpu_outw(addr, value); + } else if (words[0][3] == 'l') { + cpu_outl(addr, value); + } + qtest_send_prefix(chr); + qtest_send(chr, "OK\n"); + } else if (strcmp(words[0], "inb") == 0 || + strcmp(words[0], "inw") == 0 || + strcmp(words[0], "inl") == 0) { + uint16_t addr; + uint32_t value = -1U; + + g_assert(words[1]); + addr = strtol(words[1], NULL, 0); + + if (words[0][2] == 'b') { + value = cpu_inb(addr); + } else if (words[0][2] == 'w') { + value = cpu_inw(addr); + } else if (words[0][2] == 'l') { + value = cpu_inl(addr); + } + qtest_send_prefix(chr); + qtest_send(chr, "OK 0x%04x\n", value); + } else if (strcmp(words[0], "read") == 0) { + uint64_t addr, len, i; + uint8_t *data; + + g_assert(words[1] && words[2]); + addr = strtoul(words[1], NULL, 0); + len = strtoul(words[2], NULL, 0); + + data = g_malloc(len); + cpu_physical_memory_read(addr, data, len); + + qtest_send_prefix(chr); + qtest_send(chr, "OK 0x"); + for (i = 0; i < len; i++) { + qtest_send(chr, "%02x", data[i]); + } + qtest_send(chr, "\n"); + + g_free(data); + } else if (strcmp(words[0], "write") == 0) { + uint64_t addr, len, i; + uint8_t *data; + size_t data_len; + + g_assert(words[1] && words[2] && words[3]); + addr = strtoul(words[1], NULL, 0); + len = strtoul(words[2], NULL, 0); + + data_len = strlen(words[3]); + if (data_len < 3) { + qtest_send(chr, "ERR invalid argument size\n"); + return; + } + + data = g_malloc(len); + for (i = 0; i < len; i++) { + if ((i * 2 + 4) <= data_len) { + data[i] = hex2nib(words[3][i * 2 + 2]) << 4; + data[i] |= hex2nib(words[3][i * 2 + 3]); + } else { + data[i] = 0; + } + } + cpu_physical_memory_write(addr, data, len); + g_free(data); + + qtest_send_prefix(chr); + qtest_send(chr, "OK\n"); + } else { + qtest_send_prefix(chr); + qtest_send(chr, "FAIL Unknown command `%s'\n", words[0]); + } +} + +static void qtest_process_inbuf(CharDriverState *chr, GString *inbuf) +{ + char *end; + + while ((end = strchr(inbuf->str, '\n')) != NULL) { + size_t offset; + GString *cmd; + gchar **words; + + offset = end - inbuf->str; + + cmd = g_string_new_len(inbuf->str, offset); + g_string_erase(inbuf, 0, offset + 1); + + words = g_strsplit(cmd->str, " ", 0); + qtest_process_command(chr, words); + g_strfreev(words); + + g_string_free(cmd, TRUE); + } +} + +static void qtest_read(void *opaque, const uint8_t *buf, int size) +{ + CharDriverState *chr = opaque; + + g_string_append_len(inbuf, (const gchar *)buf, size); + qtest_process_inbuf(chr, inbuf); +} + +static int qtest_can_read(void *opaque) +{ + return 1024; +} + +static void qtest_event(void *opaque, int event) +{ + int i; + + switch (event) { + case CHR_EVENT_OPENED: + qemu_system_reset(false); + for (i = 0; i < ARRAY_SIZE(irq_levels); i++) { + irq_levels[i] = 0; + } + gettimeofday(&start_time, NULL); + qtest_opened = true; + if (qtest_log_fp) { + fprintf(qtest_log_fp, "[I " FMT_timeval "] OPENED\n", + start_time.tv_sec, start_time.tv_usec); + } + break; + case CHR_EVENT_CLOSED: + qtest_opened = false; + if (qtest_log_fp) { + struct timeval tv; + qtest_get_time(&tv); + fprintf(qtest_log_fp, "[I +" FMT_timeval "] CLOSED\n", + tv.tv_sec, tv.tv_usec); + } + break; + default: + break; + } +} + +static void qtest_set_irq(void *opaque, int irq, int level) +{ + CharDriverState *chr = qtest_chr; + bool changed; + + changed = (irq_levels[irq] != level); + irq_levels[irq] = level; + + if (changed) { + qtest_send_prefix(chr); + qtest_send(chr, "IRQ %s %d\n", + level ? "raise" : "lower", irq); + } +} + +qemu_irq *qtest_interrupt_controller_init(void) +{ + return qemu_allocate_irqs(qtest_set_irq, NULL, MAX_IRQ); +} + +int qtest_init(void) +{ + CharDriverState *chr; + + g_assert(qtest_chrdev != NULL); + + chr = qemu_chr_new("qtest", qtest_chrdev, NULL); + + qemu_chr_add_handlers(chr, qtest_can_read, qtest_read, qtest_event, chr); + qemu_chr_fe_set_echo(chr, true); + + inbuf = g_string_new(""); + + if (qtest_log) { + if (strcmp(qtest_log, "none") != 0) { + qtest_log_fp = fopen(qtest_log, "w+"); + } + } else { + qtest_log_fp = stderr; + } + + qtest_chr = chr; + + return 0; +} diff --git a/qtest.h b/qtest.h new file mode 100644 index 0000000..f0e1377 --- /dev/null +++ b/qtest.h @@ -0,0 +1,37 @@ +/* + * Test Server + * + * Copyright IBM, Corp. 2011 + * + * Authors: + * Anthony Liguori + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#ifndef QTEST_H +#define QTEST_H + +#include "qemu-common.h" + +extern int qtest_allowed; +extern const char *qtest_chrdev; +extern const char *qtest_log; + +static inline bool qtest_enabled(void) +{ + return qtest_allowed; +} + +static inline int qtest_available(void) +{ + return 1; +} + +int qtest_init(void); + +qemu_irq *qtest_interrupt_controller_init(void); + +#endif diff --git a/vl.c b/vl.c index 1d4c350..1473016 100644 --- a/vl.c +++ b/vl.c @@ -152,6 +152,7 @@ int main(int argc, char **argv) #ifdef CONFIG_VIRTFS #include "fsdev/qemu-fsdev.h" #endif +#include "qtest.h" #include "disas.h" @@ -2097,6 +2098,7 @@ static struct { { "tcg", "tcg", tcg_available, tcg_init, &tcg_allowed }, { "xen", "Xen", xen_available, xen_init, &xen_allowed }, { "kvm", "KVM", kvm_available, kvm_init, &kvm_allowed }, + { "qtest", "QTest", qtest_available, qtest_init, &qtest_allowed }, }; static int configure_accelerator(void) @@ -3180,6 +3182,12 @@ int main(int argc, char **argv, char **envp) fclose(fp); break; } + case QEMU_OPTION_qtest: + qtest_chrdev = optarg; + break; + case QEMU_OPTION_qtest_log: + qtest_log = optarg; + break; default: os_parse_cmd_args(popt->index, optarg); }