All of lore.kernel.org
 help / color / mirror / Atom feed
From: Luiz Capitulino <lcapitulino@redhat.com>
To: Anthony Liguori <aliguori@us.ibm.com>
Cc: qemu-devel@nongnu.org
Subject: Re: [Qemu-devel] [RFC v2 1/6] qtest: add test framework
Date: Mon, 5 Dec 2011 12:27:55 -0200	[thread overview]
Message-ID: <20111205122755.333168ce@doriath> (raw)
In-Reply-To: <1322765012-3164-2-git-send-email-aliguori@us.ibm.com>

On Thu,  1 Dec 2011 12:43:27 -0600
Anthony Liguori <aliguori@us.ibm.com> wrote:

> 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 <aliguori@us.ibm.com>
> ---
>  Makefile.objs   |    2 +
>  qemu-options.hx |    8 ++
>  qtest.c         |  357 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  qtest.h         |   37 ++++++
>  vl.c            |    8 ++
>  5 files changed, 412 insertions(+), 0 deletions(-)
>  create mode 100644 qtest.c
>  create mode 100644 qtest.h
> 
> diff --git a/Makefile.objs b/Makefile.objs
> index d7a6539..8364ea2 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -293,6 +293,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/qemu-options.hx b/qemu-options.hx
> index 681eaf1..ca50988 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -2598,6 +2598,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)

Missing good descriptions.

> +
>  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..f41a9c3
> --- /dev/null
> +++ b/qtest.c
> @@ -0,0 +1,357 @@
> +/*
> + * 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 {
> +        fprintf(stderr, "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);
> +
> +    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   <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);
> +
> +qemu_irq *qtest_interrupt_controller_init(void);
> +
> +#endif
> diff --git a/vl.c b/vl.c
> index a50842b..1c299e4 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"
>  
> @@ -2001,6 +2002,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)
> @@ -3057,6 +3059,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);
>              }

  reply	other threads:[~2011-12-05 14:28 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2011-12-01 18:43 [Qemu-devel] [RFC v2 0/6] qtest unit test framework Anthony Liguori
2011-12-01 18:43 ` [Qemu-devel] [RFC v2 1/6] qtest: add " Anthony Liguori
2011-12-05 14:27   ` Luiz Capitulino [this message]
2011-12-01 18:43 ` [Qemu-devel] [RFC v2 2/6] qtest: add support for target-i386 -M pc Anthony Liguori
2011-12-02  7:52   ` Paolo Bonzini
2011-12-29 17:40   ` Peter Maydell
2011-12-29 18:47     ` Anthony Liguori
2011-12-01 18:43 ` [Qemu-devel] [RFC v2 3/6] Add core python test framework Anthony Liguori
2011-12-01 18:43 ` [Qemu-devel] [RFC v2 4/6] Add uart test case Anthony Liguori
2011-12-01 18:43 ` [Qemu-devel] [RFC v2 5/6] Add RTC " Anthony Liguori
2011-12-05 14:32   ` Luiz Capitulino
2011-12-01 18:43 ` [Qemu-devel] [RFC v2 6/6] Add C version of rtc-test Anthony Liguori
2011-12-02 17:25   ` Kevin Wolf
2011-12-02 17:26     ` Anthony Liguori
2011-12-02 17:45       ` Kevin Wolf
2011-12-02 18:20         ` Luiz Capitulino
2011-12-02 18:43         ` Anthony Liguori
2011-12-05  8:51           ` Kevin Wolf
2011-12-04 10:03 ` [Qemu-devel] [RFC v2 0/6] qtest unit test framework Dor Laor
2011-12-05 15:29   ` 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=20111205122755.333168ce@doriath \
    --to=lcapitulino@redhat.com \
    --cc=aliguori@us.ibm.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.