From: Anthony Liguori <aliguori@us.ibm.com>
To: qemu-devel@nongnu.org
Cc: Paolo Bonzini <pbonzini@redhat.com>,
Anthony Liguori <aliguori@us.ibm.com>
Subject: [Qemu-devel] [PATCH 2/9] qtest: add test framework (v3)
Date: Thu, 15 Mar 2012 08:37:39 -0500 [thread overview]
Message-ID: <1331818666-31718-3-git-send-email-aliguori@us.ibm.com> (raw)
In-Reply-To: <1331818666-31718-1-git-send-email-aliguori@us.ibm.com>
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.
qtest 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 <aliguori@us.ibm.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
v1 -> v2
- always send a response (Paolo)
- enable echo (Paolo)
- do not use TCG CPU threads (Paolo)
v2 -> v3
- no need for synthetic interupt controller (Paolo)
---
Makefile.objs | 2 +
cpu-exec.c | 1 +
cpus.c | 62 +++++++++-
qemu-options.hx | 8 ++
qtest.c | 354 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
qtest.h | 35 ++++++
vl.c | 8 ++
7 files changed, 467 insertions(+), 3 deletions(-)
create mode 100644 qtest.c
create mode 100644 qtest.h
diff --git a/Makefile.objs b/Makefile.objs
index 226b01d..e9842b0 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -322,6 +322,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 bd5791f..d8354b8 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 25ba621..010047e 100644
--- a/cpus.c
+++ b/cpus.c
@@ -741,6 +741,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
+ CPUArchState *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)
@@ -803,7 +845,7 @@ void qemu_cpu_kick(void *_env)
CPUArchState *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;
}
@@ -832,7 +874,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;
@@ -947,6 +989,18 @@ static void qemu_kvm_start_vcpu(CPUArchState *env)
}
}
+static void qemu_dummy_start_vcpu(CPUArchState *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)
{
CPUArchState *env = _env;
@@ -956,8 +1010,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 daefce3..dae3407 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -2707,6 +2707,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..46ebda1
--- /dev/null
+++ b/qtest.c
@@ -0,0 +1,354 @@
+/*
+ * Test Server
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * 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);
+ }
+}
+
+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..1478343
--- /dev/null
+++ b/qtest.h
@@ -0,0 +1,35 @@
+/*
+ * Test Server
+ *
+ * Copyright IBM, Corp. 2011
+ *
+ * Authors:
+ * Anthony Liguori <aliguori@us.ibm.com>
+ *
+ * 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);
+
+#endif
diff --git a/vl.c b/vl.c
index bd95539..30e6c3a 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"
@@ -2098,6 +2099,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);
}
--
1.7.5.4
next prev parent reply other threads:[~2012-03-15 13:38 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-03-15 13:37 [Qemu-devel] [PATCH 0/9] qtest: a testing framework for devices (v3) Anthony Liguori
2012-03-15 13:37 ` [Qemu-devel] [PATCH 1/9] w32: Support tests (make check) Anthony Liguori
2012-03-15 13:37 ` Anthony Liguori [this message]
2012-03-15 13:37 ` [Qemu-devel] [PATCH 3/9] qtest: add C version of test infrastructure Anthony Liguori
2012-03-15 14:42 ` Stefan Hajnoczi
2012-03-15 14:58 ` Kevin Wolf
2012-03-15 15:04 ` Paolo Bonzini
2012-03-15 15:03 ` Anthony Liguori
2012-03-15 13:37 ` [Qemu-devel] [PATCH 4/9] make: add check targets based on gtester (v2) Anthony Liguori
2012-03-15 15:08 ` Paolo Bonzini
2012-03-15 13:37 ` [Qemu-devel] [PATCH 5/9] qtest: IRQ interception infrastructure (v2) Anthony Liguori
2012-03-15 13:37 ` [Qemu-devel] [PATCH 6/9] libqtest: add IRQ intercept commands Anthony Liguori
2012-03-15 13:37 ` [Qemu-devel] [PATCH 7/9] rtc: split out macros into a header file and use in test case Anthony Liguori
2012-03-15 13:37 ` [Qemu-devel] [PATCH 8/9] qtest: add rtc-test test-case (v3) Anthony Liguori
2012-03-15 13:37 ` [Qemu-devel] [PATCH 9/9] qtest: add clock management Anthony Liguori
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1331818666-31718-3-git-send-email-aliguori@us.ibm.com \
--to=aliguori@us.ibm.com \
--cc=pbonzini@redhat.com \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).