This patch implements record and replay of serial ports. In record mode serial port can be connected to the source of the data: -serial tcp:127.0.0.1:23,nowait,server In replay mode it should not be connected to any data source, but should be initialized with null: -serial null
Signed-off-by: Pavel Dovgalyuk <pavel.dovga...@ispras.ru> --- include/sysemu/char.h | 25 ++++++++++++ qemu-char.c | 55 +++++++++++++++++++++++--- replay/Makefile.objs | 1 replay/replay-char.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++ replay/replay-events.c | 18 ++++++++ replay/replay-internal.h | 14 ++++++- replay/replay.c | 14 +++++++ replay/replay.h | 13 ++++++ vl.c | 4 +- 9 files changed, 236 insertions(+), 7 deletions(-) create mode 100755 replay/replay-char.c diff --git a/include/sysemu/char.h b/include/sysemu/char.h index 0bbd631..a7eb578 100644 --- a/include/sysemu/char.h +++ b/include/sysemu/char.h @@ -85,6 +85,7 @@ struct CharDriverState { int is_mux; guint fd_in_tag; QemuOpts *opts; + bool replay; QTAILQ_ENTRY(CharDriverState) next; }; @@ -126,6 +127,21 @@ CharDriverState *qemu_chr_new(const char *label, const char *filename, void (*init)(struct CharDriverState *s)); /** + * @qemu_chr_new_replay: + * + * Create a new character backend from a URI. + * All recieved data will be logged by replay module. + * + * @label the name of the backend + * @filename the URI + * @init not sure.. + * + * Returns: a new character backend + */ +CharDriverState *qemu_chr_new_replay(const char *label, const char *filename, + void (*init)(struct CharDriverState *s)); + +/** * @qemu_chr_delete: * * Destroy a character backend. @@ -320,6 +336,15 @@ int qemu_chr_be_can_write(CharDriverState *s); */ void qemu_chr_be_write(CharDriverState *s, uint8_t *buf, int len); +/** + * @qemu_chr_be_write_impl: + * + * Implementation of back end writing. Used by replay module. + * + * @buf a buffer to receive data from the front end + * @len the number of bytes to receive from the front end + */ +void qemu_chr_be_write_impl(CharDriverState *s, uint8_t *buf, int len); /** * @qemu_chr_be_event: diff --git a/qemu-char.c b/qemu-char.c index 55e372c..80d4809 100644 --- a/qemu-char.c +++ b/qemu-char.c @@ -28,6 +28,7 @@ #include "sysemu/char.h" #include "hw/usb.h" #include "qmp-commands.h" +#include "replay/replay.h" #include <unistd.h> #include <fcntl.h> @@ -126,6 +127,9 @@ int qemu_chr_fe_write(CharDriverState *s, const uint8_t *buf, int len) qemu_mutex_lock(&s->chr_write_lock); ret = s->chr_write(s, buf, len); + if (s->replay) { + replay_data_int(&ret); + } qemu_mutex_unlock(&s->chr_write_lock); return ret; } @@ -195,9 +199,18 @@ int qemu_chr_fe_read_all(CharDriverState *s, uint8_t *buf, int len) int qemu_chr_fe_ioctl(CharDriverState *s, int cmd, void *arg) { - if (!s->chr_ioctl) - return -ENOTSUP; - return s->chr_ioctl(s, cmd, arg); + int res; + if (!s->chr_ioctl) { + res = -ENOTSUP; + } else { + res = s->chr_ioctl(s, cmd, arg); + if (s->replay) { + fprintf(stderr, "Replay: ioctl is not supported for serial devices yet\n"); + exit(1); + } + } + + return res; } int qemu_chr_be_can_write(CharDriverState *s) @@ -207,17 +220,34 @@ int qemu_chr_be_can_write(CharDriverState *s) return s->chr_can_read(s->handler_opaque); } -void qemu_chr_be_write(CharDriverState *s, uint8_t *buf, int len) +void qemu_chr_be_write_impl(CharDriverState *s, uint8_t *buf, int len) { if (s->chr_read) { s->chr_read(s->handler_opaque, buf, len); } } +void qemu_chr_be_write(CharDriverState *s, uint8_t *buf, int len) +{ + if (s->replay) { + if (replay_mode == REPLAY_PLAY) { + fprintf(stderr, "Replay: calling qemu_chr_be_write in play mode\n"); + exit(1); + } + replay_chr_be_write(s, buf, len); + } else { + qemu_chr_be_write_impl(s, buf, len); + } +} + int qemu_chr_fe_get_msgfd(CharDriverState *s) { int fd; - return (qemu_chr_fe_get_msgfds(s, &fd, 1) == 1) ? fd : -1; + int res = (qemu_chr_fe_get_msgfds(s, &fd, 1) == 1) ? fd : -1; + if (s->replay) { + replay_data_int(&res); + } + return res; } int qemu_chr_fe_get_msgfds(CharDriverState *s, int *fds, int len) @@ -3609,6 +3639,21 @@ CharDriverState *qemu_chr_new(const char *label, const char *filename, void (*in return chr; } +CharDriverState *qemu_chr_new_replay(const char *label, const char *filename, + void (*init)(struct CharDriverState *s)) +{ + if (replay_mode == REPLAY_PLAY && (strcmp(filename, "null") + && strcmp(filename, "vc:80Cx24C"))) { + fprintf(stderr, "Only \"-serial null\" can be used with replay\n"); + exit(1); + } + CharDriverState *chr = qemu_chr_new(label, filename, init); + if (strcmp(filename, "vc:80Cx24C")) { + replay_register_char_driver(chr); + } + return chr; +} + void qemu_chr_fe_set_echo(struct CharDriverState *chr, bool echo) { if (chr->chr_set_echo) { diff --git a/replay/Makefile.objs b/replay/Makefile.objs index 1d57e71..c54e550 100755 --- a/replay/Makefile.objs +++ b/replay/Makefile.objs @@ -5,3 +5,4 @@ obj-y += replay-time.o obj-y += replay-input.o obj-y += replay-net.o obj-y += replay-audio.o +obj-y += replay-char.o diff --git a/replay/replay-char.c b/replay/replay-char.c new file mode 100755 index 0000000..ae4d9ed --- /dev/null +++ b/replay/replay-char.c @@ -0,0 +1,99 @@ +/* + * replay-char.c + * + * Copyright (c) 2010-2014 Institute for System Programming + * of the Russian Academy of Sciences. + * + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "replay.h" +#include "replay-internal.h" +#include "sysemu/sysemu.h" +#include "sysemu/char.h" + +#define MAX_CHAR_DRIVERS MAX_SERIAL_PORTS +/* Char drivers that generate qemu_chr_be_write events + that should be saved into the log. */ +static CharDriverState *char_drivers[MAX_CHAR_DRIVERS]; + +/* Char event attributes. */ +typedef struct CharEvent { + int id; + uint8_t *buf; + size_t len; +} CharEvent; + +static int find_char_driver(CharDriverState *chr) +{ + int i = 0; + while (i < MAX_CHAR_DRIVERS && char_drivers[i] != chr) { + ++i; + } + + return i >= MAX_CHAR_DRIVERS ? -1 : i; +} + + +void replay_register_char_driver(CharDriverState *chr) +{ + chr->replay = true; + int i = find_char_driver(NULL); + + if (i < 0) { + fprintf(stderr, "Replay: cannot register char driver\n"); + exit(1); + } else { + char_drivers[i] = chr; + } +} + +void replay_chr_be_write(CharDriverState *s, uint8_t *buf, int len) +{ + CharEvent *event = g_malloc0(sizeof(CharEvent)); + + event->id = find_char_driver(s); + if (event->id < 0) { + fprintf(stderr, "Replay: cannot find char driver\n"); + exit(1); + } + event->buf = g_malloc(len); + memcpy(event->buf, buf, len); + event->len = len; + + replay_add_event(REPLAY_ASYNC_EVENT_CHAR, event); +} + +void replay_event_char_run(void *opaque) +{ + CharEvent *event = (CharEvent*)opaque; + + qemu_chr_be_write_impl(char_drivers[event->id], event->buf, (int)event->len); + + g_free(event->buf); + g_free(event); +} + +void replay_event_char_save(void *opaque) +{ + CharEvent *event = (CharEvent*)opaque; + + replay_put_byte(event->id); + replay_put_array(event->buf, event->len); +} + +void *replay_event_char_read(void) +{ + CharEvent *event = g_malloc0(sizeof(CharEvent)); + + event->id = replay_get_byte(); + replay_get_array_alloc(&event->buf, &event->len); + + return event; +} diff --git a/replay/replay-events.c b/replay/replay-events.c index 077b012..6e0120e 100755 --- a/replay/replay-events.c +++ b/replay/replay-events.c @@ -56,6 +56,9 @@ static void replay_run_event(Event *event) case REPLAY_ASYNC_EVENT_NETWORK: replay_net_send_packet(event->opaque); break; + case REPLAY_ASYNC_EVENT_CHAR: + replay_event_char_run(event->opaque); + break; default: fprintf(stderr, "Replay: invalid async event ID (%d) in the queue\n", event->event_kind); @@ -185,6 +188,9 @@ void replay_save_events(int opt) case REPLAY_ASYNC_EVENT_NETWORK: replay_net_save_packet(event->opaque); break; + case REPLAY_ASYNC_EVENT_CHAR: + replay_event_char_save(event->opaque); + break; } } @@ -255,6 +261,18 @@ void replay_read_events(int opt) replay_fetch_data_kind(); /* continue with the next event */ continue; + case REPLAY_ASYNC_EVENT_CHAR: + e.event_kind = read_event_kind; + e.opaque = replay_event_char_read(); + + replay_has_unread_data = 0; + read_event_kind = -1; + read_opt = -1; + replay_fetch_data_kind(); + + replay_run_event(&e); + /* continue with the next event */ + continue; default: fprintf(stderr, "Unknown ID %d of replay event\n", read_event_kind); exit(1); diff --git a/replay/replay-internal.h b/replay/replay-internal.h index 0f001f4..dfcd5fd 100755 --- a/replay/replay-internal.h +++ b/replay/replay-internal.h @@ -38,6 +38,8 @@ /* for async events */ #define EVENT_ASYNC 24 #define EVENT_ASYNC_OPT 25 +/* for int data */ +#define EVENT_DATA_INT 26 /* for instruction event */ #define EVENT_INSTRUCTION 32 /* for clock read/writes */ @@ -56,7 +58,8 @@ #define REPLAY_ASYNC_EVENT_INPUT 2 #define REPLAY_ASYNC_EVENT_INPUT_SYNC 3 #define REPLAY_ASYNC_EVENT_NETWORK 4 -#define REPLAY_ASYNC_COUNT 5 +#define REPLAY_ASYNC_EVENT_CHAR 5 +#define REPLAY_ASYNC_COUNT 6 typedef struct ReplayState { /*! Cached clock values. */ @@ -183,4 +186,13 @@ void replay_read_sound_out(void); void replay_read_sound_in(void); void replay_sound_flush_queue(void); +/* Character devices */ + +/*! Called to run char device event. */ +void replay_event_char_run(void *opaque); +/*! Writes char event to the file. */ +void replay_event_char_save(void *opaque); +/*! Reads char event from the file. */ +void *replay_event_char_read(void); + #endif diff --git a/replay/replay.c b/replay/replay.c index 47a5809..8e15b04 100755 --- a/replay/replay.c +++ b/replay/replay.c @@ -595,3 +595,17 @@ void replay_finish(void) replay_net_free(); replay_finish_events(); } + +void replay_data_int(int *data) +{ + if (replay_file && replay_mode == REPLAY_PLAY) { + skip_async_events_until(EVENT_DATA_INT); + *data = replay_get_dword(); + replay_check_error(); + replay_has_unread_data = 0; + } else if (replay_file && replay_mode == REPLAY_SAVE) { + replay_save_instructions(); + replay_put_event(EVENT_DATA_INT); + replay_put_dword(*data); + } +} diff --git a/replay/replay.h b/replay/replay.h index 700b19a..533fa0a 100755 --- a/replay/replay.h +++ b/replay/replay.h @@ -23,6 +23,7 @@ struct QemuOpts; struct InputEvent; struct NetClientState; +struct CharDriverState; /* replay modes */ #define REPLAY_NONE 0 @@ -149,4 +150,16 @@ void replay_init_sound_in(void *instance, WAVEHDR *hdrs, int sz); void replay_init_sound_out(void *instance, WAVEHDR *hdrs, int sz); #endif +/* Character device */ + +/*! Registers char driver to save it's events */ +void replay_register_char_driver(struct CharDriverState *chr); +/*! Saves write to char device event to the log */ +void replay_chr_be_write(struct CharDriverState *s, uint8_t *buf, int len); + +/* Other data */ + +/*! Writes or reads integer value to/from replay log. */ +void replay_data_int(int *data); + #endif diff --git a/vl.c b/vl.c index f8c1e9e..9957bfe 100644 --- a/vl.c +++ b/vl.c @@ -2559,7 +2559,9 @@ static int serial_parse(const char *devname) exit(1); } snprintf(label, sizeof(label), "serial%d", index); - serial_hds[index] = qemu_chr_new(label, devname, NULL); + serial_hds[index] = (replay_mode == REPLAY_NONE ? qemu_chr_new + : qemu_chr_new_replay) + (label, devname, NULL); if (!serial_hds[index]) { fprintf(stderr, "qemu: could not connect serial device" " to character backend '%s'\n", devname);