From 99a0db9b8dd72ea20a2d4cd99fe91b08903ae857 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Mon, 13 Dec 2010 17:30:12 +0100 Subject: [PATCH 1/8] add migration state change notifiers This patch adds functions to register and unregister notifiers for migration state changes and a function to query the migration state. The notifier is called on every state change. Once after establishing a new migration object (which is in active state then) and once when the state changes from active to completed, canceled or error. Signed-off-by: Gerd Hoffmann --- migration.c | 28 ++++++++++++++++++++++++++++ migration.h | 5 +++++ 2 files changed, 33 insertions(+) diff --git a/migration.c b/migration.c index d593b1df5d..36125720a4 100644 --- a/migration.c +++ b/migration.c @@ -36,6 +36,9 @@ static int64_t max_throttle = (32 << 20); static MigrationState *current_migration; +static NotifierList migration_state_notifiers = + NOTIFIER_LIST_INITIALIZER(migration_state_notifiers); + int qemu_start_incoming_migration(const char *uri) { const char *p; @@ -121,6 +124,7 @@ int do_migrate(Monitor *mon, const QDict *qdict, QObject **ret_data) } current_migration = s; + notifier_list_notify(&migration_state_notifiers); return 0; } @@ -272,6 +276,7 @@ void migrate_fd_error(FdMigrationState *s) { DPRINTF("setting error state\n"); s->state = MIG_STATE_ERROR; + notifier_list_notify(&migration_state_notifiers); migrate_fd_cleanup(s); } @@ -329,6 +334,7 @@ ssize_t migrate_fd_put_buffer(void *opaque, const void *data, size_t size) monitor_resume(s->mon); } s->state = MIG_STATE_ERROR; + notifier_list_notify(&migration_state_notifiers); } return ret; @@ -389,6 +395,7 @@ void migrate_fd_put_ready(void *opaque) state = MIG_STATE_ERROR; } s->state = state; + notifier_list_notify(&migration_state_notifiers); } } @@ -408,6 +415,7 @@ void migrate_fd_cancel(MigrationState *mig_state) DPRINTF("cancelling migration\n"); s->state = MIG_STATE_CANCELLED; + notifier_list_notify(&migration_state_notifiers); qemu_savevm_state_cancel(s->mon, s->file); migrate_fd_cleanup(s); @@ -421,6 +429,7 @@ void migrate_fd_release(MigrationState *mig_state) if (s->state == MIG_STATE_ACTIVE) { s->state = MIG_STATE_CANCELLED; + notifier_list_notify(&migration_state_notifiers); migrate_fd_cleanup(s); } qemu_free(s); @@ -452,3 +461,22 @@ int migrate_fd_close(void *opaque) qemu_set_fd_handler2(s->fd, NULL, NULL, NULL, NULL); return s->close(s); } + +void add_migration_state_change_notifier(Notifier *notify) +{ + notifier_list_add(&migration_state_notifiers, notify); +} + +void remove_migration_state_change_notifier(Notifier *notify) +{ + notifier_list_remove(&migration_state_notifiers, notify); +} + +int get_migration_state(void) +{ + if (current_migration) { + return migrate_fd_get_status(current_migration); + } else { + return MIG_STATE_ERROR; + } +} diff --git a/migration.h b/migration.h index d13ed4fca9..21707922ef 100644 --- a/migration.h +++ b/migration.h @@ -16,6 +16,7 @@ #include "qdict.h" #include "qemu-common.h" +#include "notify.h" #define MIG_STATE_ERROR -1 #define MIG_STATE_COMPLETED 0 @@ -134,4 +135,8 @@ static inline FdMigrationState *migrate_to_fms(MigrationState *mig_state) return container_of(mig_state, FdMigrationState, mig_state); } +void add_migration_state_change_notifier(Notifier *notify); +void remove_migration_state_change_notifier(Notifier *notify); +int get_migration_state(void); + #endif From e866e23959b45325414ac84a21f31dea79867ee6 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 23 Apr 2010 13:28:21 +0200 Subject: [PATCH 2/8] spice/vnc: client migration. Handle spice client migration, i.e. inform a spice client connected about the new host and connection parameters, so it can move over the connection automatically. The monitor command has a not-yet used protocol argument simliar to set_password and expire_password commands. This allows to add a simliar feature to vnc in the future. Daniel Berrange plans to work on this. Signed-off-by: Gerd Hoffmann --- hmp-commands.hx | 17 +++++++++++++++++ monitor.c | 27 +++++++++++++++++++++++++++ qmp-commands.hx | 35 +++++++++++++++++++++++++++++++++++ ui/qemu-spice.h | 4 ++++ ui/spice-core.c | 25 +++++++++++++++++++++++++ 5 files changed, 108 insertions(+) diff --git a/hmp-commands.hx b/hmp-commands.hx index 1cea572b15..4906d892cf 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -814,6 +814,23 @@ ETEXI .mhandler.cmd_new = do_snapshot_blkdev, }, +STEXI +@item client_migrate_info @var{protocol} @var{hostname} @var{port} @var{tls-port} @var{cert-subject} +@findex client_migrate_info +Set the spice/vnc connection info for the migration target. The spice/vnc +server will ask the spice/vnc client to automatically reconnect using the +new parameters (if specified) once the vm migration finished successfully. +ETEXI + + { + .name = "client_migrate_info", + .args_type = "protocol:s,hostname:s,port:i?,tls-port:i?,cert-subject:s?", + .params = "protocol hostname port tls-port cert-subject", + .help = "send migration info to spice/vnc client", + .user_print = monitor_user_noop, + .mhandler.cmd_new = client_migrate_info, + }, + STEXI @item snapshot_blkdev @findex snapshot_blkdev diff --git a/monitor.c b/monitor.c index d291158c2f..4c92d38d9e 100644 --- a/monitor.c +++ b/monitor.c @@ -1173,6 +1173,33 @@ static int expire_password(Monitor *mon, const QDict *qdict, QObject **ret_data) return -1; } +static int client_migrate_info(Monitor *mon, const QDict *qdict, QObject **ret_data) +{ + const char *protocol = qdict_get_str(qdict, "protocol"); + const char *hostname = qdict_get_str(qdict, "hostname"); + const char *subject = qdict_get_try_str(qdict, "cert-subject"); + int port = qdict_get_try_int(qdict, "port", -1); + int tls_port = qdict_get_try_int(qdict, "tls-port", -1); + int ret; + + if (strcmp(protocol, "spice") == 0) { + if (!using_spice) { + qerror_report(QERR_DEVICE_NOT_ACTIVE, "spice"); + return -1; + } + + ret = qemu_spice_migrate_info(hostname, port, tls_port, subject); + if (ret != 0) { + qerror_report(QERR_UNDEFINED_ERROR); + return -1; + } + return 0; + } + + qerror_report(QERR_INVALID_PARAMETER, "protocol"); + return -1; +} + static int do_screen_dump(Monitor *mon, const QDict *qdict, QObject **ret_data) { vga_hw_screen_dump(qdict_get_str(qdict, "filename")); diff --git a/qmp-commands.hx b/qmp-commands.hx index 56c4d8bc47..2ed8f44227 100644 --- a/qmp-commands.hx +++ b/qmp-commands.hx @@ -502,6 +502,41 @@ EQMP .mhandler.cmd_new = do_migrate_set_speed, }, +SQMP +client_migrate_info +------------------ + +Set the spice/vnc connection info for the migration target. The spice/vnc +server will ask the spice/vnc client to automatically reconnect using the +new parameters (if specified) once the vm migration finished successfully. + +Arguments: + +- "protocol": protocol: "spice" or "vnc" (json-string) +- "hostname": migration target hostname (json-string) +- "port": spice/vnc tcp port for plaintext channels (json-int, optional) +- "tls-port": spice tcp port for tls-secured channels (json-int, optional) +- "cert-subject": server certificate subject (json-string, optional) + +Example: + +-> { "execute": "client_migrate_info", + "arguments": { "protocol": "spice", + "hostname": "virt42.lab.kraxel.org", + "port": 1234 } } +<- { "return": {} } + +EQMP + + { + .name = "client_migrate_info", + .args_type = "protocol:s,hostname:s,port:i?,tls-port:i?,cert-subject:s?", + .params = "protocol hostname port tls-port cert-subject", + .help = "send migration info to spice/vnc client", + .user_print = monitor_user_noop, + .mhandler.cmd_new = client_migrate_info, + }, + SQMP migrate_set_speed ----------------- diff --git a/ui/qemu-spice.h b/ui/qemu-spice.h index 48239c3dbd..05dc50a69d 100644 --- a/ui/qemu-spice.h +++ b/ui/qemu-spice.h @@ -35,6 +35,8 @@ int qemu_spice_add_interface(SpiceBaseInstance *sin); int qemu_spice_set_passwd(const char *passwd, bool fail_if_connected, bool disconnect_if_connected); int qemu_spice_set_pw_expire(time_t expires); +int qemu_spice_migrate_info(const char *hostname, int port, int tls_port, + const char *subject); void do_info_spice_print(Monitor *mon, const QObject *data); void do_info_spice(Monitor *mon, QObject **ret_data); @@ -44,6 +46,8 @@ void do_info_spice(Monitor *mon, QObject **ret_data); #define using_spice 0 #define qemu_spice_set_passwd(_p, _f1, _f2) (-1) #define qemu_spice_set_pw_expire(_e) (-1) +static inline int qemu_spice_migrate_info(const char *h, int p, int t, const char *s) +{ return -1; } #endif /* CONFIG_SPICE */ diff --git a/ui/spice-core.c b/ui/spice-core.c index 27a1ced430..1aa1a5ed18 100644 --- a/ui/spice-core.c +++ b/ui/spice-core.c @@ -30,11 +30,15 @@ #include "qbool.h" #include "qstring.h" #include "qjson.h" +#include "notify.h" +#include "migration.h" #include "monitor.h" +#include "hw/hw.h" /* core bits */ static SpiceServer *spice_server; +static Notifier migration_state; static const char *auth = "spice"; static char *auth_passwd; static time_t auth_expires = TIME_MAX; @@ -416,6 +420,24 @@ void do_info_spice(Monitor *mon, QObject **ret_data) *ret_data = QOBJECT(server); } +static void migration_state_notifier(Notifier *notifier) +{ + int state = get_migration_state(); + + if (state == MIG_STATE_COMPLETED) { +#if SPICE_SERVER_VERSION >= 0x000701 /* 0.7.1 */ + spice_server_migrate_switch(spice_server); +#endif + } +} + +int qemu_spice_migrate_info(const char *hostname, int port, int tls_port, + const char *subject) +{ + return spice_server_migrate_info(spice_server, hostname, + port, tls_port, subject); +} + static int add_channel(const char *name, const char *value, void *opaque) { int security = 0; @@ -573,6 +595,9 @@ void qemu_spice_init(void) spice_server_init(spice_server, &core_interface); using_spice = 1; + migration_state.notify = migration_state_notifier; + add_migration_state_change_notifier(&migration_state); + qemu_spice_input_init(); qemu_spice_audio_init(); From 7ee3bf03984ada1ac7c7a4f27d8583a5718adadb Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Mon, 13 Dec 2010 21:30:37 +0100 Subject: [PATCH 3/8] spice: MAINTAINERS update --- MAINTAINERS | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index f20d390d0c..ab48380058 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -383,6 +383,14 @@ S: Odd Fixes F: gdbstub* F: gdb-xml/ +SPICE +M: Gerd Hoffmann +S: Supported +F: ui/qemu-spice.h +F: ui/spice-*.c +F: audio/spiceaudio.c +F: hw/qxl* + Graphics M: Anthony Liguori S: Maintained From 8d86e2bfe36ae8e03bf2ba0c8206b91e29ad292f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Thu, 6 Jan 2011 11:43:17 +0100 Subject: [PATCH 4/8] vnc/spice: fix "never" and "now" expire_time --- monitor.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/monitor.c b/monitor.c index 4c92d38d9e..6aa1bb99ef 100644 --- a/monitor.c +++ b/monitor.c @@ -1136,9 +1136,9 @@ static int expire_password(Monitor *mon, const QDict *qdict, QObject **ret_data) time_t when; int rc; - if (strcmp(whenstr, "now")) { + if (strcmp(whenstr, "now") == 0) { when = 0; - } else if (strcmp(whenstr, "never")) { + } else if (strcmp(whenstr, "never") == 0) { when = TIME_MAX; } else if (whenstr[0] == '+') { when = time(NULL) + strtoull(whenstr+1, NULL, 10); From b67737a6cf71d1f029a692e5dc2dc26579299b97 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Wed, 5 Jan 2011 18:05:52 +0100 Subject: [PATCH 5/8] spice/qxl: zap spice 0.4 migration compatibility bits Live migration from and to spice 0.4 qxl devices isn't going to work. Rip out the bits which attempt to support that. Zap the subsection logic which is obsolete now. Bumb the version to make a clean cut. This should obviously go in before 0.14 is released. Signed-off-by: Gerd Hoffmann --- hw/qxl.c | 79 +++++++------------------------------------------------- hw/qxl.h | 4 --- 2 files changed, 10 insertions(+), 73 deletions(-) diff --git a/hw/qxl.c b/hw/qxl.c index bd71e5810f..dcea65d982 100644 --- a/hw/qxl.c +++ b/hw/qxl.c @@ -1418,43 +1418,10 @@ static int qxl_post_load(void *opaque, int version) } dprint(d, 1, "%s: done\n", __FUNCTION__); - /* spice 0.4 compatibility -- accept but ignore */ - qemu_free(d->worker_data); - d->worker_data = NULL; - d->worker_data_size = 0; - return 0; } -#define QXL_SAVE_VERSION 20 - -static bool qxl_test_worker_data(void *opaque, int version_id) -{ - PCIQXLDevice* d = opaque; - - if (d->revision != 1) { - return false; - } - if (!d->worker_data_size) { - return false; - } - if (!d->worker_data) { - d->worker_data = qemu_malloc(d->worker_data_size); - } - return true; -} - -static bool qxl_test_spice04(void *opaque, int version_id) -{ - PCIQXLDevice* d = opaque; - return d->revision == 1; -} - -static bool qxl_test_spice06(void *opaque) -{ - PCIQXLDevice* d = opaque; - return d->revision > 1; -} +#define QXL_SAVE_VERSION 21 static VMStateDescription qxl_memslot = { .name = "qxl-memslot", @@ -1486,24 +1453,6 @@ static VMStateDescription qxl_surface = { } }; -static VMStateDescription qxl_vmstate_spice06 = { - .name = "qxl/spice06", - .version_id = QXL_SAVE_VERSION, - .minimum_version_id = QXL_SAVE_VERSION, - .fields = (VMStateField []) { - VMSTATE_INT32_EQUAL(num_memslots, PCIQXLDevice), - VMSTATE_STRUCT_ARRAY(guest_slots, PCIQXLDevice, NUM_MEMSLOTS, 0, - qxl_memslot, struct guest_slots), - VMSTATE_STRUCT(guest_primary.surface, PCIQXLDevice, 0, - qxl_surface, QXLSurfaceCreate), - VMSTATE_INT32_EQUAL(num_surfaces, PCIQXLDevice), - VMSTATE_ARRAY(guest_surfaces.cmds, PCIQXLDevice, NUM_SURFACES, 0, - vmstate_info_uint64, uint64_t), - VMSTATE_UINT64(guest_cursor, PCIQXLDevice), - VMSTATE_END_OF_LIST() - }, -}; - static VMStateDescription qxl_vmstate = { .name = "qxl", .version_id = QXL_SAVE_VERSION, @@ -1519,25 +1468,17 @@ static VMStateDescription qxl_vmstate = { VMSTATE_UINT32(last_release_offset, PCIQXLDevice), VMSTATE_UINT32(mode, PCIQXLDevice), VMSTATE_UINT32(ssd.unique, PCIQXLDevice), - - /* spice 0.4 sends/expects them */ - VMSTATE_VBUFFER_UINT32(vga.vram_ptr, PCIQXLDevice, 0, qxl_test_spice04, 0, - vga.vram_size), - VMSTATE_UINT32_TEST(worker_data_size, PCIQXLDevice, qxl_test_spice04), - VMSTATE_VBUFFER_UINT32(worker_data, PCIQXLDevice, 0, qxl_test_worker_data, 0, - worker_data_size), - + VMSTATE_INT32_EQUAL(num_memslots, PCIQXLDevice), + VMSTATE_STRUCT_ARRAY(guest_slots, PCIQXLDevice, NUM_MEMSLOTS, 0, + qxl_memslot, struct guest_slots), + VMSTATE_STRUCT(guest_primary.surface, PCIQXLDevice, 0, + qxl_surface, QXLSurfaceCreate), + VMSTATE_INT32_EQUAL(num_surfaces, PCIQXLDevice), + VMSTATE_ARRAY(guest_surfaces.cmds, PCIQXLDevice, NUM_SURFACES, 0, + vmstate_info_uint64, uint64_t), + VMSTATE_UINT64(guest_cursor, PCIQXLDevice), VMSTATE_END_OF_LIST() }, - .subsections = (VMStateSubsection[]) { - { - /* additional spice 0.6 state */ - .vmsd = &qxl_vmstate_spice06, - .needed = qxl_test_spice06, - },{ - /* end of list */ - }, - }, }; static PCIDeviceInfo qxl_info_primary = { diff --git a/hw/qxl.h b/hw/qxl.h index 98e11ce8d1..f6c450d32d 100644 --- a/hw/qxl.h +++ b/hw/qxl.h @@ -80,10 +80,6 @@ typedef struct PCIQXLDevice { /* io bar */ uint32_t io_base; - - /* spice 0.4 loadvm compatibility */ - void *worker_data; - uint32_t worker_data_size; } PCIQXLDevice; #define PANIC_ON(x) if ((x)) { \ From 17268d54be2e15b0d8ccfc4102bc48c84299b027 Mon Sep 17 00:00:00 2001 From: Gerd Hoffmann Date: Fri, 21 Jan 2011 10:32:03 +0100 Subject: [PATCH 6/8] qxl: locking fix One spice worker call lacks the unlock/relock calls, which may lead to deadlocks, add them. Signed-off-by: Gerd Hoffmann --- hw/qxl.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hw/qxl.c b/hw/qxl.c index dcea65d982..fe4212bff0 100644 --- a/hw/qxl.c +++ b/hw/qxl.c @@ -866,7 +866,9 @@ static void qxl_destroy_primary(PCIQXLDevice *d) dprint(d, 1, "%s\n", __FUNCTION__); d->mode = QXL_MODE_UNDEFINED; + qemu_mutex_unlock_iothread(); d->ssd.worker->destroy_primary_surface(d->ssd.worker, 0); + qemu_mutex_lock_iothread(); } static void qxl_set_mode(PCIQXLDevice *d, int modenr, int loadvm) From 710fc4f5f1cbb6d2cebdc575def9f9dba4b0263f Mon Sep 17 00:00:00 2001 From: Jiri Denemark Date: Mon, 24 Jan 2011 13:20:29 +0100 Subject: [PATCH 7/8] configure: Fix spice probe Non-existent $pkgconfig instead of $pkg_config was used when configure probes for spice availability. Signed-off-by: Jiri Denemark --- configure | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/configure b/configure index 210670c16f..dc469b2001 100755 --- a/configure +++ b/configure @@ -2207,9 +2207,9 @@ if test "$spice" != "no" ; then #include int main(void) { spice_server_new(); return 0; } EOF - spice_cflags=$($pkgconfig --cflags spice-protocol spice-server 2>/dev/null) - spice_libs=$($pkgconfig --libs spice-protocol spice-server 2>/dev/null) - if $pkgconfig --atleast-version=0.5.3 spice-server >/dev/null 2>&1 && \ + spice_cflags=$($pkg_config --cflags spice-protocol spice-server 2>/dev/null) + spice_libs=$($pkg_config --libs spice-protocol spice-server 2>/dev/null) + if $pkg_config --atleast-version=0.5.3 spice-server >/dev/null 2>&1 && \ compile_prog "$spice_cflags" "$spice_libs" ; then spice="yes" libs_softmmu="$libs_softmmu $spice_libs" From cbcc6336ce9e5c048821b136649712e078c4d05f Mon Sep 17 00:00:00 2001 From: Alon Levy Date: Wed, 19 Jan 2011 10:49:50 +0200 Subject: [PATCH 8/8] spice: add chardev (v5) Adding a chardev backend for spice, where spice determines what to do with it based on the name attribute given during chardev creation. For usage by spice vdagent in conjunction with a properly named virtio-serial device, and future smartcard channel usage. Example usage: qemu -device virtio-serial -chardev spicevmc,name=vdagent,id=vdagent \ -device virtserialport,chardev=vdagent,name=com.redhat.spice.0 v4->v5: * add tracing events * fix missing comma * fix help string to show debug is optional v3->v4: * updated commit message v1->v3 changes: (v2 had a wrong commit message) * removed spice-qemu-char.h, folded into ui/qemu-spice.h * removed dead IOCTL code * removed comment * removed ifdef CONFIG_SPICE from qemu-config.c and qemu-options.hx help. Signed-off-by: Gerd Hoffmann --- Makefile.objs | 2 +- qemu-char.c | 4 + qemu-config.c | 6 ++ qemu-options.hx | 16 +++- spice-qemu-char.c | 190 ++++++++++++++++++++++++++++++++++++++++++++++ trace-events | 6 ++ ui/qemu-spice.h | 3 + 7 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 spice-qemu-char.c diff --git a/Makefile.objs b/Makefile.objs index fda366d11c..13f6b38e46 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -105,7 +105,7 @@ common-obj-$(CONFIG_BRLAPI) += baum.o common-obj-$(CONFIG_POSIX) += migration-exec.o migration-unix.o migration-fd.o common-obj-$(CONFIG_WIN32) += version.o -common-obj-$(CONFIG_SPICE) += ui/spice-core.o ui/spice-input.o ui/spice-display.o +common-obj-$(CONFIG_SPICE) += ui/spice-core.o ui/spice-input.o ui/spice-display.o spice-qemu-char.o audio-obj-y = audio.o noaudio.o wavaudio.o mixeng.o audio-obj-$(CONFIG_SDL) += sdlaudio.o diff --git a/qemu-char.c b/qemu-char.c index edc9ad6851..acc7130a0a 100644 --- a/qemu-char.c +++ b/qemu-char.c @@ -97,6 +97,7 @@ #endif #include "qemu_socket.h" +#include "ui/qemu-spice.h" #define READ_BUF_LEN 4096 @@ -2495,6 +2496,9 @@ static const struct { || defined(__FreeBSD_kernel__) { .name = "parport", .open = qemu_chr_open_pp }, #endif +#ifdef CONFIG_SPICE + { .name = "spicevmc", .open = qemu_chr_open_spice }, +#endif }; CharDriverState *qemu_chr_open_opts(QemuOpts *opts, diff --git a/qemu-config.c b/qemu-config.c index 965fa46fdf..323d3c2c29 100644 --- a/qemu-config.c +++ b/qemu-config.c @@ -146,6 +146,12 @@ static QemuOptsList qemu_chardev_opts = { },{ .name = "signal", .type = QEMU_OPT_BOOL, + },{ + .name = "name", + .type = QEMU_OPT_STRING, + },{ + .name = "debug", + .type = QEMU_OPT_NUMBER, }, { /* end of list */ } }, diff --git a/qemu-options.hx b/qemu-options.hx index 898561d08f..939297a1b0 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -1367,6 +1367,9 @@ DEF("chardev", HAS_ARG, QEMU_OPTION_chardev, #endif #if defined(__linux__) || defined(__FreeBSD__) || defined(__DragonFly__) "-chardev parport,id=id,path=path[,mux=on|off]\n" +#endif +#if defined(CONFIG_SPICE) + "-chardev spicevmc,id=id,name=name[,debug=debug]\n" #endif , QEMU_ARCH_ALL ) @@ -1392,7 +1395,8 @@ Backend is one of: @option{stdio}, @option{braille}, @option{tty}, -@option{parport}. +@option{parport}, +@option{spicevmc}. The specific backend will determine the applicable options. All devices must have an id, which can be any string up to 127 characters long. @@ -1568,6 +1572,16 @@ Connect to a local parallel port. @option{path} specifies the path to the parallel port device. @option{path} is required. +#if defined(CONFIG_SPICE) +@item -chardev spicevmc ,id=@var{id} ,debug=@var{debug}, name=@var{name} + +@option{debug} debug level for spicevmc + +@option{name} name of spice channel to connect to + +Connect to a spice virtual machine channel, such as vdiport. +#endif + @end table ETEXI diff --git a/spice-qemu-char.c b/spice-qemu-char.c new file mode 100644 index 0000000000..517f337c43 --- /dev/null +++ b/spice-qemu-char.c @@ -0,0 +1,190 @@ +#include "config-host.h" +#include "trace.h" +#include "ui/qemu-spice.h" +#include +#include + +#include "osdep.h" + +#define dprintf(_scd, _level, _fmt, ...) \ + do { \ + static unsigned __dprintf_counter = 0; \ + if (_scd->debug >= _level) { \ + fprintf(stderr, "scd: %3d: " _fmt, ++__dprintf_counter, ## __VA_ARGS__);\ + } \ + } while (0) + +#define VMC_MAX_HOST_WRITE 2048 + +typedef struct SpiceCharDriver { + CharDriverState* chr; + SpiceCharDeviceInstance sin; + char *subtype; + bool active; + uint8_t *buffer; + uint8_t *datapos; + ssize_t bufsize, datalen; + uint32_t debug; +} SpiceCharDriver; + +static int vmc_write(SpiceCharDeviceInstance *sin, const uint8_t *buf, int len) +{ + SpiceCharDriver *scd = container_of(sin, SpiceCharDriver, sin); + ssize_t out = 0; + ssize_t last_out; + uint8_t* p = (uint8_t*)buf; + + while (len > 0) { + last_out = MIN(len, VMC_MAX_HOST_WRITE); + qemu_chr_read(scd->chr, p, last_out); + if (last_out > 0) { + out += last_out; + len -= last_out; + p += last_out; + } else { + break; + } + } + + dprintf(scd, 3, "%s: %lu/%zd\n", __func__, out, len + out); + trace_spice_vmc_write(out, len + out); + return out; +} + +static int vmc_read(SpiceCharDeviceInstance *sin, uint8_t *buf, int len) +{ + SpiceCharDriver *scd = container_of(sin, SpiceCharDriver, sin); + int bytes = MIN(len, scd->datalen); + + dprintf(scd, 2, "%s: %p %d/%d/%zd\n", __func__, scd->datapos, len, bytes, scd->datalen); + if (bytes > 0) { + memcpy(buf, scd->datapos, bytes); + scd->datapos += bytes; + scd->datalen -= bytes; + assert(scd->datalen >= 0); + if (scd->datalen == 0) { + scd->datapos = 0; + } + } + trace_spice_vmc_read(bytes, len); + return bytes; +} + +static SpiceCharDeviceInterface vmc_interface = { + .base.type = SPICE_INTERFACE_CHAR_DEVICE, + .base.description = "spice virtual channel char device", + .base.major_version = SPICE_INTERFACE_CHAR_DEVICE_MAJOR, + .base.minor_version = SPICE_INTERFACE_CHAR_DEVICE_MINOR, + .write = vmc_write, + .read = vmc_read, +}; + + +static void vmc_register_interface(SpiceCharDriver *scd) +{ + if (scd->active) { + return; + } + dprintf(scd, 1, "%s\n", __func__); + scd->sin.base.sif = &vmc_interface.base; + qemu_spice_add_interface(&scd->sin.base); + scd->active = true; + trace_spice_vmc_register_interface(scd); +} + +static void vmc_unregister_interface(SpiceCharDriver *scd) +{ + if (!scd->active) { + return; + } + dprintf(scd, 1, "%s\n", __func__); + spice_server_remove_interface(&scd->sin.base); + scd->active = false; + trace_spice_vmc_unregister_interface(scd); +} + + +static int spice_chr_write(CharDriverState *chr, const uint8_t *buf, int len) +{ + SpiceCharDriver *s = chr->opaque; + + dprintf(s, 2, "%s: %d\n", __func__, len); + vmc_register_interface(s); + assert(s->datalen == 0); + if (s->bufsize < len) { + s->bufsize = len; + s->buffer = qemu_realloc(s->buffer, s->bufsize); + } + memcpy(s->buffer, buf, len); + s->datapos = s->buffer; + s->datalen = len; + spice_server_char_device_wakeup(&s->sin); + return len; +} + +static void spice_chr_close(struct CharDriverState *chr) +{ + SpiceCharDriver *s = chr->opaque; + + printf("%s\n", __func__); + vmc_unregister_interface(s); + qemu_free(s); +} + +static void print_allowed_subtypes(void) +{ + const char** psubtype; + int i; + + fprintf(stderr, "allowed names: "); + for(i=0, psubtype = spice_server_char_device_recognized_subtypes(); + *psubtype != NULL; ++psubtype, ++i) { + if (i == 0) { + fprintf(stderr, "%s", *psubtype); + } else { + fprintf(stderr, ", %s", *psubtype); + } + } + fprintf(stderr, "\n"); +} + +CharDriverState *qemu_chr_open_spice(QemuOpts *opts) +{ + CharDriverState *chr; + SpiceCharDriver *s; + const char* name = qemu_opt_get(opts, "name"); + uint32_t debug = qemu_opt_get_number(opts, "debug", 0); + const char** psubtype = spice_server_char_device_recognized_subtypes(); + const char *subtype = NULL; + + if (name == NULL) { + fprintf(stderr, "spice-qemu-char: missing name parameter\n"); + print_allowed_subtypes(); + return NULL; + } + for(;*psubtype != NULL; ++psubtype) { + if (strcmp(name, *psubtype) == 0) { + subtype = *psubtype; + break; + } + } + if (subtype == NULL) { + fprintf(stderr, "spice-qemu-char: unsupported name\n"); + print_allowed_subtypes(); + return NULL; + } + + chr = qemu_mallocz(sizeof(CharDriverState)); + s = qemu_mallocz(sizeof(SpiceCharDriver)); + s->chr = chr; + s->debug = debug; + s->active = false; + s->sin.subtype = subtype; + chr->opaque = s; + chr->chr_write = spice_chr_write; + chr->chr_close = spice_chr_close; + + qemu_chr_generic_open(chr); + + return chr; +} diff --git a/trace-events b/trace-events index 19cee6a1d8..ed754f56e1 100644 --- a/trace-events +++ b/trace-events @@ -224,3 +224,9 @@ disable qed_aio_write_data(void *s, void *acb, int ret, uint64_t offset, size_t disable qed_aio_write_prefill(void *s, void *acb, uint64_t start, size_t len, uint64_t offset) "s %p acb %p start %"PRIu64" len %zu offset %"PRIu64"" disable qed_aio_write_postfill(void *s, void *acb, uint64_t start, size_t len, uint64_t offset) "s %p acb %p start %"PRIu64" len %zu offset %"PRIu64"" disable qed_aio_write_main(void *s, void *acb, int ret, uint64_t offset, size_t len) "s %p acb %p ret %d offset %"PRIu64" len %zu" + +# spice-qemu-char.c +disable spice_vmc_write(ssize_t out, int len) "spice wrottn %lu of requested %zd" +disable spice_vmc_read(int bytes, int len) "spice read %lu of requested %zd" +disable spice_vmc_register_interface(void *scd) "spice vmc registered interface %p" +disable spice_vmc_unregister_interface(void *scd) "spice vmc unregistered interface %p" diff --git a/ui/qemu-spice.h b/ui/qemu-spice.h index 05dc50a69d..916e5dce21 100644 --- a/ui/qemu-spice.h +++ b/ui/qemu-spice.h @@ -24,6 +24,7 @@ #include "qemu-option.h" #include "qemu-config.h" +#include "qemu-char.h" extern int using_spice; @@ -41,6 +42,8 @@ int qemu_spice_migrate_info(const char *hostname, int port, int tls_port, void do_info_spice_print(Monitor *mon, const QObject *data); void do_info_spice(Monitor *mon, QObject **ret_data); +CharDriverState *qemu_chr_open_spice(QemuOpts *opts); + #else /* CONFIG_SPICE */ #define using_spice 0